Home | History | Annotate | Download | only in jni
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include <jni.h>
     18 #include <time.h>
     19 #include <stdio.h>
     20 #include <memory>
     21 #include <vector>
     22 
     23 #include <android/log.h>
     24 
     25 #include "GifTranscoder.h"
     26 
     27 #define SQUARE(a) ((a)*(a))
     28 
     29 // GIF does not support partial transparency, so our alpha channels are always 0x0 or 0xff.
     30 static const ColorARGB TRANSPARENT = 0x0;
     31 
     32 #define ALPHA(color) (((color) >> 24) & 0xff)
     33 #define RED(color)   (((color) >> 16) & 0xff)
     34 #define GREEN(color) (((color) >>  8) & 0xff)
     35 #define BLUE(color)  (((color) >>  0) & 0xff)
     36 
     37 #define MAKE_COLOR_ARGB(a, r, g, b) \
     38     ((a) << 24 | (r) << 16 | (g) << 8 | (b))
     39 
     40 #define MAX_COLOR_DISTANCE (255 * 255 * 255)
     41 
     42 #define TAG "GifTranscoder.cpp"
     43 #define LOGD_ENABLED 0
     44 #if LOGD_ENABLED
     45 #define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__))
     46 #else
     47 #define LOGD(...) ((void)0)
     48 #endif
     49 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__))
     50 #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__))
     51 #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__))
     52 
     53 // This macro expects the assertion to pass, but logs a FATAL if not.
     54 #define ASSERT(cond, ...) \
     55     ( (__builtin_expect((cond) == 0, 0)) \
     56     ? ((void)__android_log_assert(#cond, TAG, ## __VA_ARGS__)) \
     57     : (void) 0 )
     58 #define ASSERT_ENABLED 1
     59 
     60 namespace {
     61 
     62 // Current time in milliseconds since Unix epoch.
     63 double now(void) {
     64     struct timespec res;
     65     clock_gettime(CLOCK_REALTIME, &res);
     66     return 1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6;
     67 }
     68 
     69 // Gets the pixel at position (x,y) from a buffer that uses row-major order to store an image with
     70 // the specified width.
     71 template <typename T>
     72 T* getPixel(T* buffer, int width, int x, int y) {
     73     return buffer + (y * width + x);
     74 }
     75 
     76 } // namespace
     77 
     78 int GifTranscoder::transcode(const char* pathIn, const char* pathOut) {
     79     int error;
     80     double t0;
     81     GifFileType* gifIn;
     82     GifFileType* gifOut;
     83 
     84     // Automatically closes the GIF files when this method returns
     85     GifFilesCloser closer;
     86 
     87     gifIn = DGifOpenFileName(pathIn, &error);
     88     if (gifIn) {
     89         closer.setGifIn(gifIn);
     90         LOGD("Opened input GIF: %s", pathIn);
     91     } else {
     92         LOGE("Could not open input GIF: %s, error = %d", pathIn, error);
     93         return GIF_ERROR;
     94     }
     95 
     96     gifOut = EGifOpenFileName(pathOut, false, &error);
     97     if (gifOut) {
     98         closer.setGifOut(gifOut);
     99         LOGD("Opened output GIF: %s", pathOut);
    100     } else {
    101         LOGE("Could not open output GIF: %s, error = %d", pathOut, error);
    102         return GIF_ERROR;
    103     }
    104 
    105     t0 = now();
    106     if (resizeBoxFilter(gifIn, gifOut)) {
    107         LOGD("Resized GIF in %.2f ms", now() - t0);
    108     } else {
    109         LOGE("Could not resize GIF");
    110         return GIF_ERROR;
    111     }
    112 
    113     return GIF_OK;
    114 }
    115 
    116 bool GifTranscoder::resizeBoxFilter(GifFileType* gifIn, GifFileType* gifOut) {
    117     ASSERT(gifIn != NULL, "gifIn cannot be NULL");
    118     ASSERT(gifOut != NULL, "gifOut cannot be NULL");
    119 
    120     if (gifIn->SWidth < 0 || gifIn->SHeight < 0) {
    121         LOGE("Input GIF has invalid size: %d x %d", gifIn->SWidth, gifIn->SHeight);
    122         return false;
    123     }
    124 
    125     // Output GIF will be 50% the size of the original.
    126     if (EGifPutScreenDesc(gifOut,
    127                           gifIn->SWidth / 2,
    128                           gifIn->SHeight / 2,
    129                           gifIn->SColorResolution,
    130                           gifIn->SBackGroundColor,
    131                           gifIn->SColorMap) == GIF_ERROR) {
    132         LOGE("Could not write screen descriptor");
    133         return false;
    134     }
    135     LOGD("Wrote screen descriptor");
    136 
    137     // Index of the current image.
    138     int imageIndex = 0;
    139 
    140     // Transparent color of the current image.
    141     int transparentColor = NO_TRANSPARENT_COLOR;
    142 
    143     // Buffer for reading raw images from the input GIF.
    144     std::vector<GifByteType> srcBuffer(gifIn->SWidth * gifIn->SHeight);
    145 
    146     // Buffer for rendering images from the input GIF.
    147     std::unique_ptr<ColorARGB[]> renderBuffer(new ColorARGB[gifIn->SWidth * gifIn->SHeight]);
    148 
    149     // Buffer for writing new images to output GIF (one row at a time).
    150     std::unique_ptr<GifByteType[]> dstRowBuffer(new GifByteType[gifOut->SWidth]);
    151 
    152     // Many GIFs use DISPOSE_DO_NOT to make images draw on top of previous images. They can also
    153     // use DISPOSE_BACKGROUND to clear the last image region before drawing the next one. We need
    154     // to keep track of the disposal mode as we go along to properly render the GIF.
    155     int disposalMode = DISPOSAL_UNSPECIFIED;
    156     int prevImageDisposalMode = DISPOSAL_UNSPECIFIED;
    157     GifImageDesc prevImageDimens;
    158 
    159     // Background color (applies to entire GIF).
    160     ColorARGB bgColor = TRANSPARENT;
    161 
    162     GifRecordType recordType;
    163     do {
    164         if (DGifGetRecordType(gifIn, &recordType) == GIF_ERROR) {
    165             LOGE("Could not get record type");
    166             return false;
    167         }
    168         LOGD("Read record type: %d", recordType);
    169         switch (recordType) {
    170             case IMAGE_DESC_RECORD_TYPE: {
    171                 if (DGifGetImageDesc(gifIn) == GIF_ERROR) {
    172                     LOGE("Could not read image descriptor (%d)", imageIndex);
    173                     return false;
    174                 }
    175 
    176                 // Sanity-check the current image position.
    177                 if (gifIn->Image.Left < 0 ||
    178                         gifIn->Image.Top < 0 ||
    179                         gifIn->Image.Left + gifIn->Image.Width > gifIn->SWidth ||
    180                         gifIn->Image.Top + gifIn->Image.Height > gifIn->SHeight) {
    181                     LOGE("GIF image extends beyond logical screen");
    182                     return false;
    183                 }
    184 
    185                 // Write the new image descriptor.
    186                 if (EGifPutImageDesc(gifOut,
    187                                      0, // Left
    188                                      0, // Top
    189                                      gifOut->SWidth,
    190                                      gifOut->SHeight,
    191                                      false, // Interlace
    192                                      gifIn->Image.ColorMap) == GIF_ERROR) {
    193                     LOGE("Could not write image descriptor (%d)", imageIndex);
    194                     return false;
    195                 }
    196 
    197                 // Read the image from the input GIF. The buffer is already initialized to the
    198                 // size of the GIF, which is usually equal to the size of all the images inside it.
    199                 // If not, the call to resize below ensures that the buffer is the right size.
    200                 srcBuffer.resize(gifIn->Image.Width * gifIn->Image.Height);
    201                 if (readImage(gifIn, srcBuffer.data()) == false) {
    202                     LOGE("Could not read image data (%d)", imageIndex);
    203                     return false;
    204                 }
    205                 LOGD("Read image data (%d)", imageIndex);
    206                 // Render the image from the input GIF.
    207                 if (renderImage(gifIn,
    208                                 srcBuffer.data(),
    209                                 imageIndex,
    210                                 transparentColor,
    211                                 renderBuffer.get(),
    212                                 bgColor,
    213                                 prevImageDimens,
    214                                 prevImageDisposalMode) == false) {
    215                     LOGE("Could not render %d", imageIndex);
    216                     return false;
    217                 }
    218                 LOGD("Rendered image (%d)", imageIndex);
    219 
    220                 // Generate the image in the output GIF.
    221                 for (int y = 0; y < gifOut->SHeight; y++) {
    222                     for (int x = 0; x < gifOut->SWidth; x++) {
    223                       const GifByteType dstColorIndex = computeNewColorIndex(
    224                           gifIn, transparentColor, renderBuffer.get(), x, y);
    225                       *(dstRowBuffer.get() + x) = dstColorIndex;
    226                     }
    227                     if (EGifPutLine(gifOut, dstRowBuffer.get(), gifOut->SWidth) == GIF_ERROR) {
    228                         LOGE("Could not write raster data (%d)", imageIndex);
    229                         return false;
    230                     }
    231                 }
    232                 LOGD("Wrote raster data (%d)", imageIndex);
    233 
    234                 // Save the disposal mode for rendering the next image.
    235                 // We only support DISPOSE_DO_NOT and DISPOSE_BACKGROUND.
    236                 prevImageDisposalMode = disposalMode;
    237                 if (prevImageDisposalMode == DISPOSAL_UNSPECIFIED) {
    238                     prevImageDisposalMode = DISPOSE_DO_NOT;
    239                 } else if (prevImageDisposalMode == DISPOSE_PREVIOUS) {
    240                     prevImageDisposalMode = DISPOSE_BACKGROUND;
    241                 }
    242                 if (prevImageDisposalMode == DISPOSE_BACKGROUND) {
    243                     prevImageDimens.Left = gifIn->Image.Left;
    244                     prevImageDimens.Top = gifIn->Image.Top;
    245                     prevImageDimens.Width = gifIn->Image.Width;
    246                     prevImageDimens.Height = gifIn->Image.Height;
    247                 }
    248 
    249                 if (gifOut->Image.ColorMap) {
    250                     GifFreeMapObject(gifOut->Image.ColorMap);
    251                     gifOut->Image.ColorMap = NULL;
    252                 }
    253 
    254                 imageIndex++;
    255             } break;
    256             case EXTENSION_RECORD_TYPE: {
    257                 int extCode;
    258                 GifByteType* ext;
    259                 if (DGifGetExtension(gifIn, &extCode, &ext) == GIF_ERROR) {
    260                     LOGE("Could not read extension block");
    261                     return false;
    262                 }
    263                 LOGD("Read extension block, code: %d", extCode);
    264                 if (extCode == GRAPHICS_EXT_FUNC_CODE) {
    265                     GraphicsControlBlock gcb;
    266                     if (DGifExtensionToGCB(ext[0], ext + 1, &gcb) == GIF_ERROR) {
    267                         LOGE("Could not interpret GCB extension");
    268                         return false;
    269                     }
    270                     transparentColor = gcb.TransparentColor;
    271 
    272                     // This logic for setting the background color based on the first GCB
    273                     // doesn't quite match the GIF spec, but empirically it seems to work and it
    274                     // matches what libframesequence (Rastermill) does.
    275                     if (imageIndex == 0 && gifIn->SColorMap) {
    276                         if (gcb.TransparentColor == NO_TRANSPARENT_COLOR) {
    277                             if (gifIn->SBackGroundColor < 0 ||
    278                                 gifIn->SBackGroundColor >= gifIn->SColorMap->ColorCount) {
    279                                 LOGE("SBackGroundColor overflow");
    280                                 return false;
    281                             }
    282                             GifColorType bgColorIndex =
    283                                     gifIn->SColorMap->Colors[gifIn->SBackGroundColor];
    284                             bgColor = gifColorToColorARGB(bgColorIndex);
    285                             LOGD("Set background color based on first GCB");
    286                         }
    287                     }
    288 
    289                     // Record the original disposal mode and then update it.
    290                     disposalMode = gcb.DisposalMode;
    291                     gcb.DisposalMode = DISPOSE_BACKGROUND;
    292                     EGifGCBToExtension(&gcb, ext + 1);
    293                 }
    294                 if (EGifPutExtensionLeader(gifOut, extCode) == GIF_ERROR) {
    295                     LOGE("Could not write extension leader");
    296                     return false;
    297                 }
    298                 if (EGifPutExtensionBlock(gifOut, ext[0], ext + 1) == GIF_ERROR) {
    299                     LOGE("Could not write extension block");
    300                     return false;
    301                 }
    302                 LOGD("Wrote extension block");
    303                 while (ext != NULL) {
    304                     if (DGifGetExtensionNext(gifIn, &ext) == GIF_ERROR) {
    305                         LOGE("Could not read extension continuation");
    306                         return false;
    307                     }
    308                     if (ext != NULL) {
    309                         LOGD("Read extension continuation");
    310                         if (EGifPutExtensionBlock(gifOut, ext[0], ext + 1) == GIF_ERROR) {
    311                             LOGE("Could not write extension continuation");
    312                             return false;
    313                         }
    314                         LOGD("Wrote extension continuation");
    315                     }
    316                 }
    317                 if (EGifPutExtensionTrailer(gifOut) == GIF_ERROR) {
    318                     LOGE("Could not write extension trailer");
    319                     return false;
    320                 }
    321             } break;
    322         }
    323 
    324     } while (recordType != TERMINATE_RECORD_TYPE);
    325     LOGD("No more records");
    326 
    327     return true;
    328 }
    329 
    330 bool GifTranscoder::readImage(GifFileType* gifIn, GifByteType* rasterBits) {
    331     if (gifIn->Image.Interlace) {
    332         int interlacedOffset[] = { 0, 4, 2, 1 };
    333         int interlacedJumps[] = { 8, 8, 4, 2 };
    334 
    335         // Need to perform 4 passes on the image
    336         for (int i = 0; i < 4; i++) {
    337             for (int j = interlacedOffset[i]; j < gifIn->Image.Height; j += interlacedJumps[i]) {
    338                 if (DGifGetLine(gifIn,
    339                                 rasterBits + j * gifIn->Image.Width,
    340                                 gifIn->Image.Width) == GIF_ERROR) {
    341                     LOGE("Could not read interlaced raster data");
    342                     return false;
    343                 }
    344             }
    345         }
    346     } else {
    347         if (DGifGetLine(gifIn, rasterBits, gifIn->Image.Width * gifIn->Image.Height) == GIF_ERROR) {
    348             LOGE("Could not read raster data");
    349             return false;
    350         }
    351     }
    352     return true;
    353 }
    354 
    355 bool GifTranscoder::renderImage(GifFileType* gifIn,
    356                                 GifByteType* rasterBits,
    357                                 int imageIndex,
    358                                 int transparentColorIndex,
    359                                 ColorARGB* renderBuffer,
    360                                 ColorARGB bgColor,
    361                                 GifImageDesc prevImageDimens,
    362                                 int prevImageDisposalMode) {
    363     ASSERT(imageIndex < gifIn->ImageCount,
    364            "Image index %d is out of bounds (count=%d)", imageIndex, gifIn->ImageCount);
    365 
    366     ColorMapObject* colorMap = getColorMap(gifIn);
    367     if (colorMap == NULL) {
    368         LOGE("No GIF color map found");
    369         return false;
    370     }
    371 
    372     // Clear all or part of the background, before drawing the first image and maybe before drawing
    373     // subsequent images (depending on the DisposalMode).
    374     if (imageIndex == 0) {
    375         fillRect(renderBuffer, gifIn->SWidth, gifIn->SHeight,
    376                  0, 0, gifIn->SWidth, gifIn->SHeight, bgColor);
    377     } else if (prevImageDisposalMode == DISPOSE_BACKGROUND) {
    378         fillRect(renderBuffer, gifIn->SWidth, gifIn->SHeight,
    379                  prevImageDimens.Left, prevImageDimens.Top,
    380                  prevImageDimens.Width, prevImageDimens.Height, TRANSPARENT);
    381     }
    382 
    383     // Paint this image onto the canvas
    384     for (int y = 0; y < gifIn->Image.Height; y++) {
    385         for (int x = 0; x < gifIn->Image.Width; x++) {
    386             GifByteType colorIndex = *getPixel(rasterBits, gifIn->Image.Width, x, y);
    387             if (colorIndex >= colorMap->ColorCount) {
    388                 LOGE("Color Index %d is out of bounds (count=%d)", colorIndex,
    389                     colorMap->ColorCount);
    390                 return false;
    391             }
    392 
    393             // This image may be smaller than the GIF's "logical screen"
    394             int renderX = x + gifIn->Image.Left;
    395             int renderY = y + gifIn->Image.Top;
    396 
    397             // Skip drawing transparent pixels if this image renders on top of the last one
    398             if (imageIndex > 0 && prevImageDisposalMode == DISPOSE_DO_NOT &&
    399                 colorIndex == transparentColorIndex) {
    400                 continue;
    401             }
    402 
    403             ColorARGB* renderPixel = getPixel(renderBuffer, gifIn->SWidth, renderX, renderY);
    404             *renderPixel = getColorARGB(colorMap, transparentColorIndex, colorIndex);
    405         }
    406     }
    407     return true;
    408 }
    409 
    410 void GifTranscoder::fillRect(ColorARGB* renderBuffer,
    411                              int imageWidth,
    412                              int imageHeight,
    413                              int left,
    414                              int top,
    415                              int width,
    416                              int height,
    417                              ColorARGB color) {
    418     ASSERT(left + width <= imageWidth, "Rectangle is outside image bounds");
    419     ASSERT(top + height <= imageHeight, "Rectangle is outside image bounds");
    420 
    421     for (int y = 0; y < height; y++) {
    422         for (int x = 0; x < width; x++) {
    423             ColorARGB* renderPixel = getPixel(renderBuffer, imageWidth, x + left, y + top);
    424             *renderPixel = color;
    425         }
    426     }
    427 }
    428 
    429 GifByteType GifTranscoder::computeNewColorIndex(GifFileType* gifIn,
    430                                                 int transparentColorIndex,
    431                                                 ColorARGB* renderBuffer,
    432                                                 int x,
    433                                                 int y) {
    434     ColorMapObject* colorMap = getColorMap(gifIn);
    435 
    436     // Compute the average color of 4 adjacent pixels from the input image.
    437     ColorARGB c1 = *getPixel(renderBuffer, gifIn->SWidth, x * 2, y * 2);
    438     ColorARGB c2 = *getPixel(renderBuffer, gifIn->SWidth, x * 2 + 1, y * 2);
    439     ColorARGB c3 = *getPixel(renderBuffer, gifIn->SWidth, x * 2, y * 2 + 1);
    440     ColorARGB c4 = *getPixel(renderBuffer, gifIn->SWidth, x * 2 + 1, y * 2 + 1);
    441     ColorARGB avgColor = computeAverage(c1, c2, c3, c4);
    442 
    443     // Search the color map for the best match.
    444     return findBestColor(colorMap, transparentColorIndex, avgColor);
    445 }
    446 
    447 ColorARGB GifTranscoder::computeAverage(ColorARGB c1, ColorARGB c2, ColorARGB c3, ColorARGB c4) {
    448     char avgAlpha = (char)(((int) ALPHA(c1) + (int) ALPHA(c2) +
    449                             (int) ALPHA(c3) + (int) ALPHA(c4)) / 4);
    450     char avgRed =   (char)(((int) RED(c1) + (int) RED(c2) +
    451                             (int) RED(c3) + (int) RED(c4)) / 4);
    452     char avgGreen = (char)(((int) GREEN(c1) + (int) GREEN(c2) +
    453                             (int) GREEN(c3) + (int) GREEN(c4)) / 4);
    454     char avgBlue =  (char)(((int) BLUE(c1) + (int) BLUE(c2) +
    455                             (int) BLUE(c3) + (int) BLUE(c4)) / 4);
    456     return MAKE_COLOR_ARGB(avgAlpha, avgRed, avgGreen, avgBlue);
    457 }
    458 
    459 GifByteType GifTranscoder::findBestColor(ColorMapObject* colorMap, int transparentColorIndex,
    460                                          ColorARGB targetColor) {
    461     // Return the transparent color if the average alpha is zero.
    462     char alpha = ALPHA(targetColor);
    463     if (alpha == 0 && transparentColorIndex != NO_TRANSPARENT_COLOR) {
    464         return transparentColorIndex;
    465     }
    466 
    467     GifByteType closestColorIndex = 0;
    468     int closestColorDistance = MAX_COLOR_DISTANCE;
    469     for (int i = 0; i < colorMap->ColorCount; i++) {
    470         // Skip the transparent color (we've already eliminated that option).
    471         if (i == transparentColorIndex) {
    472             continue;
    473         }
    474         ColorARGB indexedColor = gifColorToColorARGB(colorMap->Colors[i]);
    475         int distance = computeDistance(targetColor, indexedColor);
    476         if (distance < closestColorDistance) {
    477             closestColorIndex = i;
    478             closestColorDistance = distance;
    479         }
    480     }
    481     return closestColorIndex;
    482 }
    483 
    484 int GifTranscoder::computeDistance(ColorARGB c1, ColorARGB c2) {
    485     return SQUARE(RED(c1) - RED(c2)) +
    486            SQUARE(GREEN(c1) - GREEN(c2)) +
    487            SQUARE(BLUE(c1) - BLUE(c2));
    488 }
    489 
    490 ColorMapObject* GifTranscoder::getColorMap(GifFileType* gifIn) {
    491     if (gifIn->Image.ColorMap) {
    492         return gifIn->Image.ColorMap;
    493     }
    494     return gifIn->SColorMap;
    495 }
    496 
    497 ColorARGB GifTranscoder::getColorARGB(ColorMapObject* colorMap, int transparentColorIndex,
    498                                       GifByteType colorIndex) {
    499     if (colorIndex == transparentColorIndex) {
    500         return TRANSPARENT;
    501     }
    502     return gifColorToColorARGB(colorMap->Colors[colorIndex]);
    503 }
    504 
    505 ColorARGB GifTranscoder::gifColorToColorARGB(const GifColorType& color) {
    506     return MAKE_COLOR_ARGB(0xff, color.Red, color.Green, color.Blue);
    507 }
    508 
    509 GifFilesCloser::~GifFilesCloser() {
    510     if (mGifIn) {
    511         DGifCloseFile(mGifIn, NULL);
    512         mGifIn = NULL;
    513     }
    514     if (mGifOut) {
    515         EGifCloseFile(mGifOut, NULL);
    516         mGifOut = NULL;
    517     }
    518 }
    519 
    520 void GifFilesCloser::setGifIn(GifFileType* gifIn) {
    521     ASSERT(mGifIn == NULL, "mGifIn is already set");
    522     mGifIn = gifIn;
    523 }
    524 
    525 void GifFilesCloser::releaseGifIn() {
    526     ASSERT(mGifIn != NULL, "mGifIn is already NULL");
    527     mGifIn = NULL;
    528 }
    529 
    530 void GifFilesCloser::setGifOut(GifFileType* gifOut) {
    531     ASSERT(mGifOut == NULL, "mGifOut is already set");
    532     mGifOut = gifOut;
    533 }
    534 
    535 void GifFilesCloser::releaseGifOut() {
    536     ASSERT(mGifOut != NULL, "mGifOut is already NULL");
    537     mGifOut = NULL;
    538 }
    539 
    540 // JNI stuff
    541 
    542 jboolean transcode(JNIEnv* env, jobject clazz, jstring filePath, jstring outFilePath) {
    543     const char* pathIn = env->GetStringUTFChars(filePath, JNI_FALSE);
    544     const char* pathOut = env->GetStringUTFChars(outFilePath, JNI_FALSE);
    545 
    546     GifTranscoder transcoder;
    547     int gifCode = transcoder.transcode(pathIn, pathOut);
    548 
    549     env->ReleaseStringUTFChars(filePath, pathIn);
    550     env->ReleaseStringUTFChars(outFilePath, pathOut);
    551 
    552     return (gifCode == GIF_OK);
    553 }
    554 
    555 const char *kClassPathName = "com/android/messaging/util/GifTranscoder";
    556 
    557 JNINativeMethod kMethods[] = {
    558         { "transcodeInternal", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)transcode },
    559 };
    560 
    561 int registerNativeMethods(JNIEnv* env, const char* className,
    562                           JNINativeMethod* gMethods, int numMethods) {
    563     jclass clazz = env->FindClass(className);
    564     if (clazz == NULL) {
    565         return JNI_FALSE;
    566     }
    567     if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
    568         return JNI_FALSE;
    569     }
    570     return JNI_TRUE;
    571 }
    572 
    573 jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    574     JNIEnv* env;
    575     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
    576         return -1;
    577     }
    578     if (!registerNativeMethods(env, kClassPathName,
    579                                kMethods, sizeof(kMethods) / sizeof(kMethods[0]))) {
    580       return -1;
    581     }
    582     return JNI_VERSION_1_6;
    583 }
    584