diff options
Diffstat (limited to 'jni/GifTranscoder.cpp')
-rw-r--r-- | jni/GifTranscoder.cpp | 573 |
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; -} |