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.
30static 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
60namespace {
61
62// Current time in milliseconds since Unix epoch.
63double 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.
71template <typename T>
72T* getPixel(T* buffer, int width, int x, int y) {
73    return buffer + (y * width + x);
74}
75
76} // namespace
77
78int 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
116bool 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                            GifColorType bgColorIndex =
278                                    gifIn->SColorMap->Colors[gifIn->SBackGroundColor];
279                            bgColor = gifColorToColorARGB(bgColorIndex);
280                            LOGD("Set background color based on first GCB");
281                        }
282                    }
283
284                    // Record the original disposal mode and then update it.
285                    disposalMode = gcb.DisposalMode;
286                    gcb.DisposalMode = DISPOSE_BACKGROUND;
287                    EGifGCBToExtension(&gcb, ext + 1);
288                }
289                if (EGifPutExtensionLeader(gifOut, extCode) == GIF_ERROR) {
290                    LOGE("Could not write extension leader");
291                    return false;
292                }
293                if (EGifPutExtensionBlock(gifOut, ext[0], ext + 1) == GIF_ERROR) {
294                    LOGE("Could not write extension block");
295                    return false;
296                }
297                LOGD("Wrote extension block");
298                while (ext != NULL) {
299                    if (DGifGetExtensionNext(gifIn, &ext) == GIF_ERROR) {
300                        LOGE("Could not read extension continuation");
301                        return false;
302                    }
303                    if (ext != NULL) {
304                        LOGD("Read extension continuation");
305                        if (EGifPutExtensionBlock(gifOut, ext[0], ext + 1) == GIF_ERROR) {
306                            LOGE("Could not write extension continuation");
307                            return false;
308                        }
309                        LOGD("Wrote extension continuation");
310                    }
311                }
312                if (EGifPutExtensionTrailer(gifOut) == GIF_ERROR) {
313                    LOGE("Could not write extension trailer");
314                    return false;
315                }
316            } break;
317        }
318
319    } while (recordType != TERMINATE_RECORD_TYPE);
320    LOGD("No more records");
321
322    return true;
323}
324
325bool GifTranscoder::readImage(GifFileType* gifIn, GifByteType* rasterBits) {
326    if (gifIn->Image.Interlace) {
327        int interlacedOffset[] = { 0, 4, 2, 1 };
328        int interlacedJumps[] = { 8, 8, 4, 2 };
329
330        // Need to perform 4 passes on the image
331        for (int i = 0; i < 4; i++) {
332            for (int j = interlacedOffset[i]; j < gifIn->Image.Height; j += interlacedJumps[i]) {
333                if (DGifGetLine(gifIn,
334                                rasterBits + j * gifIn->Image.Width,
335                                gifIn->Image.Width) == GIF_ERROR) {
336                    LOGE("Could not read interlaced raster data");
337                    return false;
338                }
339            }
340        }
341    } else {
342        if (DGifGetLine(gifIn, rasterBits, gifIn->Image.Width * gifIn->Image.Height) == GIF_ERROR) {
343            LOGE("Could not read raster data");
344            return false;
345        }
346    }
347    return true;
348}
349
350bool GifTranscoder::renderImage(GifFileType* gifIn,
351                                GifByteType* rasterBits,
352                                int imageIndex,
353                                int transparentColorIndex,
354                                ColorARGB* renderBuffer,
355                                ColorARGB bgColor,
356                                GifImageDesc prevImageDimens,
357                                int prevImageDisposalMode) {
358    ASSERT(imageIndex < gifIn->ImageCount,
359           "Image index %d is out of bounds (count=%d)", imageIndex, gifIn->ImageCount);
360
361    ColorMapObject* colorMap = getColorMap(gifIn);
362    if (colorMap == NULL) {
363        LOGE("No GIF color map found");
364        return false;
365    }
366
367    // Clear all or part of the background, before drawing the first image and maybe before drawing
368    // subsequent images (depending on the DisposalMode).
369    if (imageIndex == 0) {
370        fillRect(renderBuffer, gifIn->SWidth, gifIn->SHeight,
371                 0, 0, gifIn->SWidth, gifIn->SHeight, bgColor);
372    } else if (prevImageDisposalMode == DISPOSE_BACKGROUND) {
373        fillRect(renderBuffer, gifIn->SWidth, gifIn->SHeight,
374                 prevImageDimens.Left, prevImageDimens.Top,
375                 prevImageDimens.Width, prevImageDimens.Height, TRANSPARENT);
376    }
377
378    // Paint this image onto the canvas
379    for (int y = 0; y < gifIn->Image.Height; y++) {
380        for (int x = 0; x < gifIn->Image.Width; x++) {
381            GifByteType colorIndex = *getPixel(rasterBits, gifIn->Image.Width, x, y);
382
383            // This image may be smaller than the GIF's "logical screen"
384            int renderX = x + gifIn->Image.Left;
385            int renderY = y + gifIn->Image.Top;
386
387            // Skip drawing transparent pixels if this image renders on top of the last one
388            if (imageIndex > 0 && prevImageDisposalMode == DISPOSE_DO_NOT &&
389                colorIndex == transparentColorIndex) {
390                continue;
391            }
392
393            ColorARGB* renderPixel = getPixel(renderBuffer, gifIn->SWidth, renderX, renderY);
394            *renderPixel = getColorARGB(colorMap, transparentColorIndex, colorIndex);
395        }
396    }
397    return true;
398}
399
400void GifTranscoder::fillRect(ColorARGB* renderBuffer,
401                             int imageWidth,
402                             int imageHeight,
403                             int left,
404                             int top,
405                             int width,
406                             int height,
407                             ColorARGB color) {
408    ASSERT(left + width <= imageWidth, "Rectangle is outside image bounds");
409    ASSERT(top + height <= imageHeight, "Rectangle is outside image bounds");
410
411    for (int y = 0; y < height; y++) {
412        for (int x = 0; x < width; x++) {
413            ColorARGB* renderPixel = getPixel(renderBuffer, imageWidth, x + left, y + top);
414            *renderPixel = color;
415        }
416    }
417}
418
419GifByteType GifTranscoder::computeNewColorIndex(GifFileType* gifIn,
420                                                int transparentColorIndex,
421                                                ColorARGB* renderBuffer,
422                                                int x,
423                                                int y) {
424    ColorMapObject* colorMap = getColorMap(gifIn);
425
426    // Compute the average color of 4 adjacent pixels from the input image.
427    ColorARGB c1 = *getPixel(renderBuffer, gifIn->SWidth, x * 2, y * 2);
428    ColorARGB c2 = *getPixel(renderBuffer, gifIn->SWidth, x * 2 + 1, y * 2);
429    ColorARGB c3 = *getPixel(renderBuffer, gifIn->SWidth, x * 2, y * 2 + 1);
430    ColorARGB c4 = *getPixel(renderBuffer, gifIn->SWidth, x * 2 + 1, y * 2 + 1);
431    ColorARGB avgColor = computeAverage(c1, c2, c3, c4);
432
433    // Search the color map for the best match.
434    return findBestColor(colorMap, transparentColorIndex, avgColor);
435}
436
437ColorARGB GifTranscoder::computeAverage(ColorARGB c1, ColorARGB c2, ColorARGB c3, ColorARGB c4) {
438    char avgAlpha = (char)(((int) ALPHA(c1) + (int) ALPHA(c2) +
439                            (int) ALPHA(c3) + (int) ALPHA(c4)) / 4);
440    char avgRed =   (char)(((int) RED(c1) + (int) RED(c2) +
441                            (int) RED(c3) + (int) RED(c4)) / 4);
442    char avgGreen = (char)(((int) GREEN(c1) + (int) GREEN(c2) +
443                            (int) GREEN(c3) + (int) GREEN(c4)) / 4);
444    char avgBlue =  (char)(((int) BLUE(c1) + (int) BLUE(c2) +
445                            (int) BLUE(c3) + (int) BLUE(c4)) / 4);
446    return MAKE_COLOR_ARGB(avgAlpha, avgRed, avgGreen, avgBlue);
447}
448
449GifByteType GifTranscoder::findBestColor(ColorMapObject* colorMap, int transparentColorIndex,
450                                         ColorARGB targetColor) {
451    // Return the transparent color if the average alpha is zero.
452    char alpha = ALPHA(targetColor);
453    if (alpha == 0 && transparentColorIndex != NO_TRANSPARENT_COLOR) {
454        return transparentColorIndex;
455    }
456
457    GifByteType closestColorIndex = 0;
458    int closestColorDistance = MAX_COLOR_DISTANCE;
459    for (int i = 0; i < colorMap->ColorCount; i++) {
460        // Skip the transparent color (we've already eliminated that option).
461        if (i == transparentColorIndex) {
462            continue;
463        }
464        ColorARGB indexedColor = gifColorToColorARGB(colorMap->Colors[i]);
465        int distance = computeDistance(targetColor, indexedColor);
466        if (distance < closestColorDistance) {
467            closestColorIndex = i;
468            closestColorDistance = distance;
469        }
470    }
471    return closestColorIndex;
472}
473
474int GifTranscoder::computeDistance(ColorARGB c1, ColorARGB c2) {
475    return SQUARE(RED(c1) - RED(c2)) +
476           SQUARE(GREEN(c1) - GREEN(c2)) +
477           SQUARE(BLUE(c1) - BLUE(c2));
478}
479
480ColorMapObject* GifTranscoder::getColorMap(GifFileType* gifIn) {
481    if (gifIn->Image.ColorMap) {
482        return gifIn->Image.ColorMap;
483    }
484    return gifIn->SColorMap;
485}
486
487ColorARGB GifTranscoder::getColorARGB(ColorMapObject* colorMap, int transparentColorIndex,
488                                      GifByteType colorIndex) {
489    if (colorIndex == transparentColorIndex) {
490        return TRANSPARENT;
491    }
492    return gifColorToColorARGB(colorMap->Colors[colorIndex]);
493}
494
495ColorARGB GifTranscoder::gifColorToColorARGB(const GifColorType& color) {
496    return MAKE_COLOR_ARGB(0xff, color.Red, color.Green, color.Blue);
497}
498
499GifFilesCloser::~GifFilesCloser() {
500    if (mGifIn) {
501        DGifCloseFile(mGifIn, NULL);
502        mGifIn = NULL;
503    }
504    if (mGifOut) {
505        EGifCloseFile(mGifOut, NULL);
506        mGifOut = NULL;
507    }
508}
509
510void GifFilesCloser::setGifIn(GifFileType* gifIn) {
511    ASSERT(mGifIn == NULL, "mGifIn is already set");
512    mGifIn = gifIn;
513}
514
515void GifFilesCloser::releaseGifIn() {
516    ASSERT(mGifIn != NULL, "mGifIn is already NULL");
517    mGifIn = NULL;
518}
519
520void GifFilesCloser::setGifOut(GifFileType* gifOut) {
521    ASSERT(mGifOut == NULL, "mGifOut is already set");
522    mGifOut = gifOut;
523}
524
525void GifFilesCloser::releaseGifOut() {
526    ASSERT(mGifOut != NULL, "mGifOut is already NULL");
527    mGifOut = NULL;
528}
529
530// JNI stuff
531
532jboolean transcode(JNIEnv* env, jobject clazz, jstring filePath, jstring outFilePath) {
533    const char* pathIn = env->GetStringUTFChars(filePath, JNI_FALSE);
534    const char* pathOut = env->GetStringUTFChars(outFilePath, JNI_FALSE);
535
536    GifTranscoder transcoder;
537    int gifCode = transcoder.transcode(pathIn, pathOut);
538
539    env->ReleaseStringUTFChars(filePath, pathIn);
540    env->ReleaseStringUTFChars(outFilePath, pathOut);
541
542    return (gifCode == GIF_OK);
543}
544
545const char *kClassPathName = "com/android/messaging/util/GifTranscoder";
546
547JNINativeMethod kMethods[] = {
548        { "transcodeInternal", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)transcode },
549};
550
551int registerNativeMethods(JNIEnv* env, const char* className,
552                          JNINativeMethod* gMethods, int numMethods) {
553    jclass clazz = env->FindClass(className);
554    if (clazz == NULL) {
555        return JNI_FALSE;
556    }
557    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
558        return JNI_FALSE;
559    }
560    return JNI_TRUE;
561}
562
563jint JNI_OnLoad(JavaVM* vm, void* reserved) {
564    JNIEnv* env;
565    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
566        return -1;
567    }
568    if (!registerNativeMethods(env, kClassPathName,
569                               kMethods, sizeof(kMethods) / sizeof(kMethods[0]))) {
570      return -1;
571    }
572    return JNI_VERSION_1_6;
573}
574