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