summaryrefslogtreecommitdiffstats
path: root/jni/GifTranscoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'jni/GifTranscoder.cpp')
-rw-r--r--jni/GifTranscoder.cpp573
1 files changed, 0 insertions, 573 deletions
diff --git a/jni/GifTranscoder.cpp b/jni/GifTranscoder.cpp
deleted file mode 100644
index 44fa30c..0000000
--- a/jni/GifTranscoder.cpp
+++ /dev/null
@@ -1,573 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <jni.h>
-#include <time.h>
-#include <stdio.h>
-#include <memory>
-#include <vector>
-
-#include <android/log.h>
-
-#include "GifTranscoder.h"
-
-#define SQUARE(a) (a)*(a)
-
-// GIF does not support partial transparency, so our alpha channels are always 0x0 or 0xff.
-static const ColorARGB TRANSPARENT = 0x0;
-
-#define ALPHA(color) (((color) >> 24) & 0xff)
-#define RED(color) (((color) >> 16) & 0xff)
-#define GREEN(color) (((color) >> 8) & 0xff)
-#define BLUE(color) (((color) >> 0) & 0xff)
-
-#define MAKE_COLOR_ARGB(a, r, g, b) \
- ((a) << 24 | (r) << 16 | (g) << 8 | (b))
-
-#define MAX_COLOR_DISTANCE 255 * 255 * 255
-
-#define TAG "GifTranscoder.cpp"
-#define LOGD_ENABLED 0
-#if LOGD_ENABLED
-#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__))
-#else
-#define LOGD(...) ((void)0)
-#endif
-#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__))
-#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__))
-#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__))
-
-// This macro expects the assertion to pass, but logs a FATAL if not.
-#define ASSERT(cond, ...) \
- ( (__builtin_expect((cond) == 0, 0)) \
- ? ((void)__android_log_assert(#cond, TAG, ## __VA_ARGS__)) \
- : (void) 0 )
-#define ASSERT_ENABLED 1
-
-namespace {
-
-// Current time in milliseconds since Unix epoch.
-double now(void) {
- struct timespec res;
- clock_gettime(CLOCK_REALTIME, &res);
- return 1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6;
-}
-
-// Gets the pixel at position (x,y) from a buffer that uses row-major order to store an image with
-// the specified width.
-template <typename T>
-T* getPixel(T* buffer, int width, int x, int y) {
- return buffer + (y * width + x);
-}
-
-} // namespace
-
-int GifTranscoder::transcode(const char* pathIn, const char* pathOut) {
- int error;
- double t0;
- GifFileType* gifIn;
- GifFileType* gifOut;
-
- // Automatically closes the GIF files when this method returns
- GifFilesCloser closer;
-
- gifIn = DGifOpenFileName(pathIn, &error);
- if (gifIn) {
- closer.setGifIn(gifIn);
- LOGD("Opened input GIF: %s", pathIn);
- } else {
- LOGE("Could not open input GIF: %s, error = %d", pathIn, error);
- return GIF_ERROR;
- }
-
- gifOut = EGifOpenFileName(pathOut, false, &error);
- if (gifOut) {
- closer.setGifOut(gifOut);
- LOGD("Opened output GIF: %s", pathOut);
- } else {
- LOGE("Could not open output GIF: %s, error = %d", pathOut, error);
- return GIF_ERROR;
- }
-
- t0 = now();
- if (resizeBoxFilter(gifIn, gifOut)) {
- LOGD("Resized GIF in %.2f ms", now() - t0);
- } else {
- LOGE("Could not resize GIF");
- return GIF_ERROR;
- }
-
- return GIF_OK;
-}
-
-bool GifTranscoder::resizeBoxFilter(GifFileType* gifIn, GifFileType* gifOut) {
- ASSERT(gifIn != NULL, "gifIn cannot be NULL");
- ASSERT(gifOut != NULL, "gifOut cannot be NULL");
-
- if (gifIn->SWidth < 0 || gifIn->SHeight < 0) {
- LOGE("Input GIF has invalid size: %d x %d", gifIn->SWidth, gifIn->SHeight);
- return false;
- }
-
- // Output GIF will be 50% the size of the original.
- if (EGifPutScreenDesc(gifOut,
- gifIn->SWidth / 2,
- gifIn->SHeight / 2,
- gifIn->SColorResolution,
- gifIn->SBackGroundColor,
- gifIn->SColorMap) == GIF_ERROR) {
- LOGE("Could not write screen descriptor");
- return false;
- }
- LOGD("Wrote screen descriptor");
-
- // Index of the current image.
- int imageIndex = 0;
-
- // Transparent color of the current image.
- int transparentColor = NO_TRANSPARENT_COLOR;
-
- // Buffer for reading raw images from the input GIF.
- std::vector<GifByteType> srcBuffer(gifIn->SWidth * gifIn->SHeight);
-
- // Buffer for rendering images from the input GIF.
- std::unique_ptr<ColorARGB> renderBuffer(new ColorARGB[gifIn->SWidth * gifIn->SHeight]);
-
- // Buffer for writing new images to output GIF (one row at a time).
- std::unique_ptr<GifByteType> dstRowBuffer(new GifByteType[gifOut->SWidth]);
-
- // Many GIFs use DISPOSE_DO_NOT to make images draw on top of previous images. They can also
- // use DISPOSE_BACKGROUND to clear the last image region before drawing the next one. We need
- // to keep track of the disposal mode as we go along to properly render the GIF.
- int disposalMode = DISPOSAL_UNSPECIFIED;
- int prevImageDisposalMode = DISPOSAL_UNSPECIFIED;
- GifImageDesc prevImageDimens;
-
- // Background color (applies to entire GIF).
- ColorARGB bgColor = TRANSPARENT;
-
- GifRecordType recordType;
- do {
- if (DGifGetRecordType(gifIn, &recordType) == GIF_ERROR) {
- LOGE("Could not get record type");
- return false;
- }
- LOGD("Read record type: %d", recordType);
- switch (recordType) {
- case IMAGE_DESC_RECORD_TYPE: {
- if (DGifGetImageDesc(gifIn) == GIF_ERROR) {
- LOGE("Could not read image descriptor (%d)", imageIndex);
- return false;
- }
-
- // Sanity-check the current image position.
- if (gifIn->Image.Left < 0 ||
- gifIn->Image.Top < 0 ||
- gifIn->Image.Left + gifIn->Image.Width > gifIn->SWidth ||
- gifIn->Image.Top + gifIn->Image.Height > gifIn->SHeight) {
- LOGE("GIF image extends beyond logical screen");
- return false;
- }
-
- // Write the new image descriptor.
- if (EGifPutImageDesc(gifOut,
- 0, // Left
- 0, // Top
- gifOut->SWidth,
- gifOut->SHeight,
- false, // Interlace
- gifIn->Image.ColorMap) == GIF_ERROR) {
- LOGE("Could not write image descriptor (%d)", imageIndex);
- return false;
- }
-
- // Read the image from the input GIF. The buffer is already initialized to the
- // size of the GIF, which is usually equal to the size of all the images inside it.
- // If not, the call to resize below ensures that the buffer is the right size.
- srcBuffer.resize(gifIn->Image.Width * gifIn->Image.Height);
- if (readImage(gifIn, srcBuffer.data()) == false) {
- LOGE("Could not read image data (%d)", imageIndex);
- return false;
- }
- LOGD("Read image data (%d)", imageIndex);
- // Render the image from the input GIF.
- if (renderImage(gifIn,
- srcBuffer.data(),
- imageIndex,
- transparentColor,
- renderBuffer.get(),
- bgColor,
- prevImageDimens,
- prevImageDisposalMode) == false) {
- LOGE("Could not render %d", imageIndex);
- return false;
- }
- LOGD("Rendered image (%d)", imageIndex);
-
- // Generate the image in the output GIF.
- for (int y = 0; y < gifOut->SHeight; y++) {
- for (int x = 0; x < gifOut->SWidth; x++) {
- const GifByteType dstColorIndex = computeNewColorIndex(
- gifIn, transparentColor, renderBuffer.get(), x, y);
- *(dstRowBuffer.get() + x) = dstColorIndex;
- }
- if (EGifPutLine(gifOut, dstRowBuffer.get(), gifOut->SWidth) == GIF_ERROR) {
- LOGE("Could not write raster data (%d)", imageIndex);
- return false;
- }
- }
- LOGD("Wrote raster data (%d)", imageIndex);
-
- // Save the disposal mode for rendering the next image.
- // We only support DISPOSE_DO_NOT and DISPOSE_BACKGROUND.
- prevImageDisposalMode = disposalMode;
- if (prevImageDisposalMode == DISPOSAL_UNSPECIFIED) {
- prevImageDisposalMode = DISPOSE_DO_NOT;
- } else if (prevImageDisposalMode == DISPOSE_PREVIOUS) {
- prevImageDisposalMode = DISPOSE_BACKGROUND;
- }
- if (prevImageDisposalMode == DISPOSE_BACKGROUND) {
- prevImageDimens.Left = gifIn->Image.Left;
- prevImageDimens.Top = gifIn->Image.Top;
- prevImageDimens.Width = gifIn->Image.Width;
- prevImageDimens.Height = gifIn->Image.Height;
- }
-
- if (gifOut->Image.ColorMap) {
- GifFreeMapObject(gifOut->Image.ColorMap);
- gifOut->Image.ColorMap = NULL;
- }
-
- imageIndex++;
- } break;
- case EXTENSION_RECORD_TYPE: {
- int extCode;
- GifByteType* ext;
- if (DGifGetExtension(gifIn, &extCode, &ext) == GIF_ERROR) {
- LOGE("Could not read extension block");
- return false;
- }
- LOGD("Read extension block, code: %d", extCode);
- if (extCode == GRAPHICS_EXT_FUNC_CODE) {
- GraphicsControlBlock gcb;
- if (DGifExtensionToGCB(ext[0], ext + 1, &gcb) == GIF_ERROR) {
- LOGE("Could not interpret GCB extension");
- return false;
- }
- transparentColor = gcb.TransparentColor;
-
- // This logic for setting the background color based on the first GCB
- // doesn't quite match the GIF spec, but empirically it seems to work and it
- // matches what libframesequence (Rastermill) does.
- if (imageIndex == 0 && gifIn->SColorMap) {
- if (gcb.TransparentColor == NO_TRANSPARENT_COLOR) {
- GifColorType bgColorIndex =
- gifIn->SColorMap->Colors[gifIn->SBackGroundColor];
- bgColor = gifColorToColorARGB(bgColorIndex);
- LOGD("Set background color based on first GCB");
- }
- }
-
- // Record the original disposal mode and then update it.
- disposalMode = gcb.DisposalMode;
- gcb.DisposalMode = DISPOSE_BACKGROUND;
- EGifGCBToExtension(&gcb, ext + 1);
- }
- if (EGifPutExtensionLeader(gifOut, extCode) == GIF_ERROR) {
- LOGE("Could not write extension leader");
- return false;
- }
- if (EGifPutExtensionBlock(gifOut, ext[0], ext + 1) == GIF_ERROR) {
- LOGE("Could not write extension block");
- return false;
- }
- LOGD("Wrote extension block");
- while (ext != NULL) {
- if (DGifGetExtensionNext(gifIn, &ext) == GIF_ERROR) {
- LOGE("Could not read extension continuation");
- return false;
- }
- if (ext != NULL) {
- LOGD("Read extension continuation");
- if (EGifPutExtensionBlock(gifOut, ext[0], ext + 1) == GIF_ERROR) {
- LOGE("Could not write extension continuation");
- return false;
- }
- LOGD("Wrote extension continuation");
- }
- }
- if (EGifPutExtensionTrailer(gifOut) == GIF_ERROR) {
- LOGE("Could not write extension trailer");
- return false;
- }
- } break;
- }
-
- } while (recordType != TERMINATE_RECORD_TYPE);
- LOGD("No more records");
-
- return true;
-}
-
-bool GifTranscoder::readImage(GifFileType* gifIn, GifByteType* rasterBits) {
- if (gifIn->Image.Interlace) {
- int interlacedOffset[] = { 0, 4, 2, 1 };
- int interlacedJumps[] = { 8, 8, 4, 2 };
-
- // Need to perform 4 passes on the image
- for (int i = 0; i < 4; i++) {
- for (int j = interlacedOffset[i]; j < gifIn->Image.Height; j += interlacedJumps[i]) {
- if (DGifGetLine(gifIn,
- rasterBits + j * gifIn->Image.Width,
- gifIn->Image.Width) == GIF_ERROR) {
- LOGE("Could not read interlaced raster data");
- return false;
- }
- }
- }
- } else {
- if (DGifGetLine(gifIn, rasterBits, gifIn->Image.Width * gifIn->Image.Height) == GIF_ERROR) {
- LOGE("Could not read raster data");
- return false;
- }
- }
- return true;
-}
-
-bool GifTranscoder::renderImage(GifFileType* gifIn,
- GifByteType* rasterBits,
- int imageIndex,
- int transparentColorIndex,
- ColorARGB* renderBuffer,
- ColorARGB bgColor,
- GifImageDesc prevImageDimens,
- int prevImageDisposalMode) {
- ASSERT(imageIndex < gifIn->ImageCount,
- "Image index %d is out of bounds (count=%d)", imageIndex, gifIn->ImageCount);
-
- ColorMapObject* colorMap = getColorMap(gifIn);
- if (colorMap == NULL) {
- LOGE("No GIF color map found");
- return false;
- }
-
- // Clear all or part of the background, before drawing the first image and maybe before drawing
- // subsequent images (depending on the DisposalMode).
- if (imageIndex == 0) {
- fillRect(renderBuffer, gifIn->SWidth, gifIn->SHeight,
- 0, 0, gifIn->SWidth, gifIn->SHeight, bgColor);
- } else if (prevImageDisposalMode == DISPOSE_BACKGROUND) {
- fillRect(renderBuffer, gifIn->SWidth, gifIn->SHeight,
- prevImageDimens.Left, prevImageDimens.Top,
- prevImageDimens.Width, prevImageDimens.Height, TRANSPARENT);
- }
-
- // Paint this image onto the canvas
- for (int y = 0; y < gifIn->Image.Height; y++) {
- for (int x = 0; x < gifIn->Image.Width; x++) {
- GifByteType colorIndex = *getPixel(rasterBits, gifIn->Image.Width, x, y);
-
- // This image may be smaller than the GIF's "logical screen"
- int renderX = x + gifIn->Image.Left;
- int renderY = y + gifIn->Image.Top;
-
- // Skip drawing transparent pixels if this image renders on top of the last one
- if (imageIndex > 0 && prevImageDisposalMode == DISPOSE_DO_NOT &&
- colorIndex == transparentColorIndex) {
- continue;
- }
-
- ColorARGB* renderPixel = getPixel(renderBuffer, gifIn->SWidth, renderX, renderY);
- *renderPixel = getColorARGB(colorMap, transparentColorIndex, colorIndex);
- }
- }
- return true;
-}
-
-void GifTranscoder::fillRect(ColorARGB* renderBuffer,
- int imageWidth,
- int imageHeight,
- int left,
- int top,
- int width,
- int height,
- ColorARGB color) {
- ASSERT(left + width <= imageWidth, "Rectangle is outside image bounds");
- ASSERT(top + height <= imageHeight, "Rectangle is outside image bounds");
-
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- ColorARGB* renderPixel = getPixel(renderBuffer, imageWidth, x + left, y + top);
- *renderPixel = color;
- }
- }
-}
-
-GifByteType GifTranscoder::computeNewColorIndex(GifFileType* gifIn,
- int transparentColorIndex,
- ColorARGB* renderBuffer,
- int x,
- int y) {
- ColorMapObject* colorMap = getColorMap(gifIn);
-
- // Compute the average color of 4 adjacent pixels from the input image.
- ColorARGB c1 = *getPixel(renderBuffer, gifIn->SWidth, x * 2, y * 2);
- ColorARGB c2 = *getPixel(renderBuffer, gifIn->SWidth, x * 2 + 1, y * 2);
- ColorARGB c3 = *getPixel(renderBuffer, gifIn->SWidth, x * 2, y * 2 + 1);
- ColorARGB c4 = *getPixel(renderBuffer, gifIn->SWidth, x * 2 + 1, y * 2 + 1);
- ColorARGB avgColor = computeAverage(c1, c2, c3, c4);
-
- // Search the color map for the best match.
- return findBestColor(colorMap, transparentColorIndex, avgColor);
-}
-
-ColorARGB GifTranscoder::computeAverage(ColorARGB c1, ColorARGB c2, ColorARGB c3, ColorARGB c4) {
- char avgAlpha = (char)(((int) ALPHA(c1) + (int) ALPHA(c2) +
- (int) ALPHA(c3) + (int) ALPHA(c4)) / 4);
- char avgRed = (char)(((int) RED(c1) + (int) RED(c2) +
- (int) RED(c3) + (int) RED(c4)) / 4);
- char avgGreen = (char)(((int) GREEN(c1) + (int) GREEN(c2) +
- (int) GREEN(c3) + (int) GREEN(c4)) / 4);
- char avgBlue = (char)(((int) BLUE(c1) + (int) BLUE(c2) +
- (int) BLUE(c3) + (int) BLUE(c4)) / 4);
- return MAKE_COLOR_ARGB(avgAlpha, avgRed, avgGreen, avgBlue);
-}
-
-GifByteType GifTranscoder::findBestColor(ColorMapObject* colorMap, int transparentColorIndex,
- ColorARGB targetColor) {
- // Return the transparent color if the average alpha is zero.
- char alpha = ALPHA(targetColor);
- if (alpha == 0 && transparentColorIndex != NO_TRANSPARENT_COLOR) {
- return transparentColorIndex;
- }
-
- GifByteType closestColorIndex = 0;
- int closestColorDistance = MAX_COLOR_DISTANCE;
- for (int i = 0; i < colorMap->ColorCount; i++) {
- // Skip the transparent color (we've already eliminated that option).
- if (i == transparentColorIndex) {
- continue;
- }
- ColorARGB indexedColor = gifColorToColorARGB(colorMap->Colors[i]);
- int distance = computeDistance(targetColor, indexedColor);
- if (distance < closestColorDistance) {
- closestColorIndex = i;
- closestColorDistance = distance;
- }
- }
- return closestColorIndex;
-}
-
-int GifTranscoder::computeDistance(ColorARGB c1, ColorARGB c2) {
- return SQUARE(RED(c1) - RED(c2)) +
- SQUARE(GREEN(c1) - GREEN(c2)) +
- SQUARE(BLUE(c1) - BLUE(c2));
-}
-
-ColorMapObject* GifTranscoder::getColorMap(GifFileType* gifIn) {
- if (gifIn->Image.ColorMap) {
- return gifIn->Image.ColorMap;
- }
- return gifIn->SColorMap;
-}
-
-ColorARGB GifTranscoder::getColorARGB(ColorMapObject* colorMap, int transparentColorIndex,
- GifByteType colorIndex) {
- if (colorIndex == transparentColorIndex) {
- return TRANSPARENT;
- }
- return gifColorToColorARGB(colorMap->Colors[colorIndex]);
-}
-
-ColorARGB GifTranscoder::gifColorToColorARGB(const GifColorType& color) {
- return MAKE_COLOR_ARGB(0xff, color.Red, color.Green, color.Blue);
-}
-
-GifFilesCloser::~GifFilesCloser() {
- if (mGifIn) {
- DGifCloseFile(mGifIn);
- mGifIn = NULL;
- }
- if (mGifOut) {
- EGifCloseFile(mGifOut);
- mGifOut = NULL;
- }
-}
-
-void GifFilesCloser::setGifIn(GifFileType* gifIn) {
- ASSERT(mGifIn == NULL, "mGifIn is already set");
- mGifIn = gifIn;
-}
-
-void GifFilesCloser::releaseGifIn() {
- ASSERT(mGifIn != NULL, "mGifIn is already NULL");
- mGifIn = NULL;
-}
-
-void GifFilesCloser::setGifOut(GifFileType* gifOut) {
- ASSERT(mGifOut == NULL, "mGifOut is already set");
- mGifOut = gifOut;
-}
-
-void GifFilesCloser::releaseGifOut() {
- ASSERT(mGifOut != NULL, "mGifOut is already NULL");
- mGifOut = NULL;
-}
-
-// JNI stuff
-
-jboolean transcode(JNIEnv* env, jobject clazz, jstring filePath, jstring outFilePath) {
- const char* pathIn = env->GetStringUTFChars(filePath, JNI_FALSE);
- const char* pathOut = env->GetStringUTFChars(outFilePath, JNI_FALSE);
-
- GifTranscoder transcoder;
- int gifCode = transcoder.transcode(pathIn, pathOut);
-
- env->ReleaseStringUTFChars(filePath, pathIn);
- env->ReleaseStringUTFChars(outFilePath, pathOut);
-
- return (gifCode == GIF_OK);
-}
-
-const char *kClassPathName = "com/android/messaging/util/GifTranscoder";
-
-JNINativeMethod kMethods[] = {
- { "transcodeInternal", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)transcode },
-};
-
-int registerNativeMethods(JNIEnv* env, const char* className,
- JNINativeMethod* gMethods, int numMethods) {
- jclass clazz = env->FindClass(className);
- if (clazz == NULL) {
- return JNI_FALSE;
- }
- if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
- return JNI_FALSE;
- }
- return JNI_TRUE;
-}
-
-jint JNI_OnLoad(JavaVM* vm, void* reserved) {
- JNIEnv* env;
- if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
- return -1;
- }
- if (!registerNativeMethods(env, kClassPathName,
- kMethods, sizeof(kMethods) / sizeof(kMethods[0]))) {
- return -1;
- }
- return JNI_VERSION_1_6;
-}