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.
now(void)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>
getPixel(T * buffer,int width,int x,int y)72 T* getPixel(T* buffer, int width, int x, int y) {
73     return buffer + (y * width + x);
74 }
75 
76 } // namespace
77 
transcode(const char * pathIn,const char * pathOut)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 
resizeBoxFilter(GifFileType * gifIn,GifFileType * gifOut)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 
readImage(GifFileType * gifIn,GifByteType * rasterBits)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 
renderImage(GifFileType * gifIn,GifByteType * rasterBits,int imageIndex,int transparentColorIndex,ColorARGB * renderBuffer,ColorARGB bgColor,GifImageDesc prevImageDimens,int prevImageDisposalMode)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 
fillRect(ColorARGB * renderBuffer,int imageWidth,int imageHeight,int left,int top,int width,int height,ColorARGB color)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 
computeNewColorIndex(GifFileType * gifIn,int transparentColorIndex,ColorARGB * renderBuffer,int x,int y)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 
computeAverage(ColorARGB c1,ColorARGB c2,ColorARGB c3,ColorARGB c4)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 
findBestColor(ColorMapObject * colorMap,int transparentColorIndex,ColorARGB targetColor)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 
computeDistance(ColorARGB c1,ColorARGB c2)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 
getColorMap(GifFileType * gifIn)490 ColorMapObject* GifTranscoder::getColorMap(GifFileType* gifIn) {
491     if (gifIn->Image.ColorMap) {
492         return gifIn->Image.ColorMap;
493     }
494     return gifIn->SColorMap;
495 }
496 
getColorARGB(ColorMapObject * colorMap,int transparentColorIndex,GifByteType colorIndex)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 
gifColorToColorARGB(const GifColorType & color)505 ColorARGB GifTranscoder::gifColorToColorARGB(const GifColorType& color) {
506     return MAKE_COLOR_ARGB(0xff, color.Red, color.Green, color.Blue);
507 }
508 
~GifFilesCloser()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 
setGifIn(GifFileType * gifIn)520 void GifFilesCloser::setGifIn(GifFileType* gifIn) {
521     ASSERT(mGifIn == NULL, "mGifIn is already set");
522     mGifIn = gifIn;
523 }
524 
releaseGifIn()525 void GifFilesCloser::releaseGifIn() {
526     ASSERT(mGifIn != NULL, "mGifIn is already NULL");
527     mGifIn = NULL;
528 }
529 
setGifOut(GifFileType * gifOut)530 void GifFilesCloser::setGifOut(GifFileType* gifOut) {
531     ASSERT(mGifOut == NULL, "mGifOut is already set");
532     mGifOut = gifOut;
533 }
534 
releaseGifOut()535 void GifFilesCloser::releaseGifOut() {
536     ASSERT(mGifOut != NULL, "mGifOut is already NULL");
537     mGifOut = NULL;
538 }
539 
540 // JNI stuff
541 
transcode(JNIEnv * env,jobject clazz,jstring filePath,jstring outFilePath)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 
registerNativeMethods(JNIEnv * env,const char * className,JNINativeMethod * gMethods,int numMethods)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 
JNI_OnLoad(JavaVM * vm,void * reserved)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