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 "Png.h" 18 19 #include <png.h> 20 #include <zlib.h> 21 22 #include <iostream> 23 #include <sstream> 24 #include <string> 25 #include <vector> 26 27 #include "androidfw/ResourceTypes.h" 28 29 #include "Source.h" 30 #include "trace/TraceBuffer.h" 31 #include "util/BigBuffer.h" 32 #include "util/Util.h" 33 34 namespace aapt { 35 36 constexpr bool kDebug = false; 37 38 struct PngInfo { 39 ~PngInfo() { 40 for (png_bytep row : rows) { 41 if (row != nullptr) { 42 delete[] row; 43 } 44 } 45 46 delete[] xDivs; 47 delete[] yDivs; 48 } 49 50 void* serialize9Patch() { 51 void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs, 52 yDivs, colors.data()); 53 reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile(); 54 return serialized; 55 } 56 57 uint32_t width = 0; 58 uint32_t height = 0; 59 std::vector<png_bytep> rows; 60 61 bool is9Patch = false; 62 android::Res_png_9patch info9Patch; 63 int32_t* xDivs = nullptr; 64 int32_t* yDivs = nullptr; 65 std::vector<uint32_t> colors; 66 67 // Layout padding. 68 bool haveLayoutBounds = false; 69 int32_t layoutBoundsLeft; 70 int32_t layoutBoundsTop; 71 int32_t layoutBoundsRight; 72 int32_t layoutBoundsBottom; 73 74 // Round rect outline description. 75 int32_t outlineInsetsLeft; 76 int32_t outlineInsetsTop; 77 int32_t outlineInsetsRight; 78 int32_t outlineInsetsBottom; 79 float outlineRadius; 80 uint8_t outlineAlpha; 81 }; 82 83 static void readDataFromStream(png_structp readPtr, png_bytep data, 84 png_size_t length) { 85 std::istream* input = 86 reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr)); 87 if (!input->read(reinterpret_cast<char*>(data), length)) { 88 png_error(readPtr, strerror(errno)); 89 } 90 } 91 92 static void writeDataToStream(png_structp writePtr, png_bytep data, 93 png_size_t length) { 94 BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr)); 95 png_bytep buf = outBuffer->NextBlock<png_byte>(length); 96 memcpy(buf, data, length); 97 } 98 99 static void flushDataToStream(png_structp /*writePtr*/) {} 100 101 static void logWarning(png_structp readPtr, png_const_charp warningMessage) { 102 IDiagnostics* diag = 103 reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr)); 104 diag->Warn(DiagMessage() << warningMessage); 105 } 106 107 static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, 108 PngInfo* outInfo) { 109 if (setjmp(png_jmpbuf(readPtr))) { 110 diag->Error(DiagMessage() << "failed reading png"); 111 return false; 112 } 113 114 png_set_sig_bytes(readPtr, kPngSignatureSize); 115 png_read_info(readPtr, infoPtr); 116 117 int colorType, bitDepth, interlaceType, compressionType; 118 png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, 119 &colorType, &interlaceType, &compressionType, nullptr); 120 121 if (colorType == PNG_COLOR_TYPE_PALETTE) { 122 png_set_palette_to_rgb(readPtr); 123 } 124 125 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) { 126 png_set_expand_gray_1_2_4_to_8(readPtr); 127 } 128 129 if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) { 130 png_set_tRNS_to_alpha(readPtr); 131 } 132 133 if (bitDepth == 16) { 134 png_set_strip_16(readPtr); 135 } 136 137 if (!(colorType & PNG_COLOR_MASK_ALPHA)) { 138 png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER); 139 } 140 141 if (colorType == PNG_COLOR_TYPE_GRAY || 142 colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { 143 png_set_gray_to_rgb(readPtr); 144 } 145 146 png_set_interlace_handling(readPtr); 147 png_read_update_info(readPtr, infoPtr); 148 149 const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr); 150 outInfo->rows.resize(outInfo->height); 151 for (size_t i = 0; i < outInfo->height; i++) { 152 outInfo->rows[i] = new png_byte[rowBytes]; 153 } 154 155 png_read_image(readPtr, outInfo->rows.data()); 156 png_read_end(readPtr, infoPtr); 157 return true; 158 } 159 160 static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, 161 void* data) { 162 size_t patchSize = inPatch->serializedSize(); 163 void* newData = malloc(patchSize); 164 memcpy(newData, data, patchSize); 165 android::Res_png_9patch* outPatch = inPatch->deserialize(newData); 166 outPatch->fileToDevice(); 167 // deserialization is done in place, so outPatch == newData 168 assert(outPatch == newData); 169 assert(outPatch->numXDivs == inPatch->numXDivs); 170 assert(outPatch->numYDivs == inPatch->numYDivs); 171 assert(outPatch->paddingLeft == inPatch->paddingLeft); 172 assert(outPatch->paddingRight == inPatch->paddingRight); 173 assert(outPatch->paddingTop == inPatch->paddingTop); 174 assert(outPatch->paddingBottom == inPatch->paddingBottom); 175 /* for (int i = 0; i < outPatch->numXDivs; i++) { 176 assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]); 177 } 178 for (int i = 0; i < outPatch->numYDivs; i++) { 179 assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]); 180 } 181 for (int i = 0; i < outPatch->numColors; i++) { 182 assert(outPatch->getColors()[i] == inPatch->getColors()[i]); 183 }*/ 184 free(newData); 185 } 186 187 /*static void dump_image(int w, int h, const png_byte* const* rows, int 188 color_type) { 189 int i, j, rr, gg, bb, aa; 190 191 int bpp; 192 if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == 193 PNG_COLOR_TYPE_GRAY) { 194 bpp = 1; 195 } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { 196 bpp = 2; 197 } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == 198 PNG_COLOR_TYPE_RGB_ALPHA) { 199 // We use a padding byte even when there is no alpha 200 bpp = 4; 201 } else { 202 printf("Unknown color type %d.\n", color_type); 203 } 204 205 for (j = 0; j < h; j++) { 206 const png_byte* row = rows[j]; 207 for (i = 0; i < w; i++) { 208 rr = row[0]; 209 gg = row[1]; 210 bb = row[2]; 211 aa = row[3]; 212 row += bpp; 213 214 if (i == 0) { 215 printf("Row %d:", j); 216 } 217 switch (bpp) { 218 case 1: 219 printf(" (%d)", rr); 220 break; 221 case 2: 222 printf(" (%d %d", rr, gg); 223 break; 224 case 3: 225 printf(" (%d %d %d)", rr, gg, bb); 226 break; 227 case 4: 228 printf(" (%d %d %d %d)", rr, gg, bb, aa); 229 break; 230 } 231 if (i == (w - 1)) { 232 printf("\n"); 233 } 234 } 235 } 236 }*/ 237 238 #ifdef MAX 239 #undef MAX 240 #endif 241 #ifdef ABS 242 #undef ABS 243 #endif 244 245 #define MAX(a, b) ((a) > (b) ? (a) : (b)) 246 #define ABS(a) ((a) < 0 ? -(a) : (a)) 247 248 static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, 249 int grayscaleTolerance, png_colorp rgbPalette, 250 png_bytep alphaPalette, int* paletteEntries, 251 bool* hasTransparency, int* colorType, 252 png_bytepp outRows) { 253 int w = imageInfo.width; 254 int h = imageInfo.height; 255 int i, j, rr, gg, bb, aa, idx; 256 uint32_t colors[256], col; 257 int num_colors = 0; 258 int maxGrayDeviation = 0; 259 260 bool isOpaque = true; 261 bool isPalette = true; 262 bool isGrayscale = true; 263 264 // Scan the entire image and determine if: 265 // 1. Every pixel has R == G == B (grayscale) 266 // 2. Every pixel has A == 255 (opaque) 267 // 3. There are no more than 256 distinct RGBA colors 268 269 if (kDebug) { 270 printf("Initial image data:\n"); 271 // dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA); 272 } 273 274 for (j = 0; j < h; j++) { 275 const png_byte* row = imageInfo.rows[j]; 276 png_bytep out = outRows[j]; 277 for (i = 0; i < w; i++) { 278 rr = *row++; 279 gg = *row++; 280 bb = *row++; 281 aa = *row++; 282 283 int odev = maxGrayDeviation; 284 maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation); 285 maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation); 286 maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation); 287 if (maxGrayDeviation > odev) { 288 if (kDebug) { 289 printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n", 290 maxGrayDeviation, i, j, rr, gg, bb, aa); 291 } 292 } 293 294 // Check if image is really grayscale 295 if (isGrayscale) { 296 if (rr != gg || rr != bb) { 297 if (kDebug) { 298 printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", i, j, 299 rr, gg, bb, aa); 300 } 301 isGrayscale = false; 302 } 303 } 304 305 // Check if image is really opaque 306 if (isOpaque) { 307 if (aa != 0xff) { 308 if (kDebug) { 309 printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", i, j, 310 rr, gg, bb, aa); 311 } 312 isOpaque = false; 313 } 314 } 315 316 // Check if image is really <= 256 colors 317 if (isPalette) { 318 col = (uint32_t)((rr << 24) | (gg << 16) | (bb << 8) | aa); 319 bool match = false; 320 for (idx = 0; idx < num_colors; idx++) { 321 if (colors[idx] == col) { 322 match = true; 323 break; 324 } 325 } 326 327 // Write the palette index for the pixel to outRows optimistically 328 // We might overwrite it later if we decide to encode as gray or 329 // gray + alpha 330 *out++ = idx; 331 if (!match) { 332 if (num_colors == 256) { 333 if (kDebug) { 334 printf("Found 257th color at %d, %d\n", i, j); 335 } 336 isPalette = false; 337 } else { 338 colors[num_colors++] = col; 339 } 340 } 341 } 342 } 343 } 344 345 *paletteEntries = 0; 346 *hasTransparency = !isOpaque; 347 int bpp = isOpaque ? 3 : 4; 348 int paletteSize = w * h + bpp * num_colors; 349 350 if (kDebug) { 351 printf("isGrayscale = %s\n", isGrayscale ? "true" : "false"); 352 printf("isOpaque = %s\n", isOpaque ? "true" : "false"); 353 printf("isPalette = %s\n", isPalette ? "true" : "false"); 354 printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", paletteSize, 355 2 * w * h, bpp * w * h); 356 printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, 357 grayscaleTolerance); 358 } 359 360 // Choose the best color type for the image. 361 // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel 362 // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct 363 // combinations 364 // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA 365 // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is 366 // sufficiently 367 // small, otherwise use COLOR_TYPE_RGB{_ALPHA} 368 if (isGrayscale) { 369 if (isOpaque) { 370 *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel 371 } else { 372 // Use a simple heuristic to determine whether using a palette will 373 // save space versus using gray + alpha for each pixel. 374 // This doesn't take into account chunk overhead, filtering, LZ 375 // compression, etc. 376 if (isPalette && (paletteSize < 2 * w * h)) { 377 *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color 378 } else { 379 *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel 380 } 381 } 382 } else if (isPalette && (paletteSize < bpp * w * h)) { 383 *colorType = PNG_COLOR_TYPE_PALETTE; 384 } else { 385 if (maxGrayDeviation <= grayscaleTolerance) { 386 diag->Note(DiagMessage() << "forcing image to gray (max deviation = " 387 << maxGrayDeviation << ")"); 388 *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA; 389 } else { 390 *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; 391 } 392 } 393 394 // Perform postprocessing of the image or palette data based on the final 395 // color type chosen 396 397 if (*colorType == PNG_COLOR_TYPE_PALETTE) { 398 // Create separate RGB and Alpha palettes and set the number of colors 399 *paletteEntries = num_colors; 400 401 // Create the RGB and alpha palettes 402 for (int idx = 0; idx < num_colors; idx++) { 403 col = colors[idx]; 404 rgbPalette[idx].red = (png_byte)((col >> 24) & 0xff); 405 rgbPalette[idx].green = (png_byte)((col >> 16) & 0xff); 406 rgbPalette[idx].blue = (png_byte)((col >> 8) & 0xff); 407 alphaPalette[idx] = (png_byte)(col & 0xff); 408 } 409 } else if (*colorType == PNG_COLOR_TYPE_GRAY || 410 *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { 411 // If the image is gray or gray + alpha, compact the pixels into outRows 412 for (j = 0; j < h; j++) { 413 const png_byte* row = imageInfo.rows[j]; 414 png_bytep out = outRows[j]; 415 for (i = 0; i < w; i++) { 416 rr = *row++; 417 gg = *row++; 418 bb = *row++; 419 aa = *row++; 420 421 if (isGrayscale) { 422 *out++ = rr; 423 } else { 424 *out++ = (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f); 425 } 426 if (!isOpaque) { 427 *out++ = aa; 428 } 429 } 430 } 431 } 432 } 433 434 static bool writePng(IDiagnostics* diag, png_structp writePtr, 435 png_infop infoPtr, PngInfo* info, int grayScaleTolerance) { 436 if (setjmp(png_jmpbuf(writePtr))) { 437 diag->Error(DiagMessage() << "failed to write png"); 438 return false; 439 } 440 441 uint32_t width, height; 442 int colorType, bitDepth, interlaceType, compressionType; 443 444 png_unknown_chunk unknowns[3]; 445 unknowns[0].data = nullptr; 446 unknowns[1].data = nullptr; 447 unknowns[2].data = nullptr; 448 449 png_bytepp outRows = 450 (png_bytepp)malloc((int)info->height * sizeof(png_bytep)); 451 if (outRows == (png_bytepp)0) { 452 printf("Can't allocate output buffer!\n"); 453 exit(1); 454 } 455 for (uint32_t i = 0; i < info->height; i++) { 456 outRows[i] = (png_bytep)malloc(2 * (int)info->width); 457 if (outRows[i] == (png_bytep)0) { 458 printf("Can't allocate output buffer!\n"); 459 exit(1); 460 } 461 } 462 463 png_set_compression_level(writePtr, Z_BEST_COMPRESSION); 464 465 if (kDebug) { 466 diag->Note(DiagMessage() << "writing image: w = " << info->width 467 << ", h = " << info->height); 468 } 469 470 png_color rgbPalette[256]; 471 png_byte alphaPalette[256]; 472 bool hasTransparency; 473 int paletteEntries; 474 475 analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette, 476 &paletteEntries, &hasTransparency, &colorType, outRows); 477 478 // If the image is a 9-patch, we need to preserve it as a ARGB file to make 479 // sure the pixels will not be pre-dithered/clamped until we decide they are 480 if (info->is9Patch && 481 (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY || 482 colorType == PNG_COLOR_TYPE_PALETTE)) { 483 colorType = PNG_COLOR_TYPE_RGB_ALPHA; 484 } 485 486 if (kDebug) { 487 switch (colorType) { 488 case PNG_COLOR_TYPE_PALETTE: 489 diag->Note(DiagMessage() << "has " << paletteEntries << " colors" 490 << (hasTransparency ? " (with alpha)" : "") 491 << ", using PNG_COLOR_TYPE_PALLETTE"); 492 break; 493 case PNG_COLOR_TYPE_GRAY: 494 diag->Note(DiagMessage() 495 << "is opaque gray, using PNG_COLOR_TYPE_GRAY"); 496 break; 497 case PNG_COLOR_TYPE_GRAY_ALPHA: 498 diag->Note(DiagMessage() 499 << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA"); 500 break; 501 case PNG_COLOR_TYPE_RGB: 502 diag->Note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB"); 503 break; 504 case PNG_COLOR_TYPE_RGB_ALPHA: 505 diag->Note(DiagMessage() 506 << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA"); 507 break; 508 } 509 } 510 511 png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType, 512 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, 513 PNG_FILTER_TYPE_DEFAULT); 514 515 if (colorType == PNG_COLOR_TYPE_PALETTE) { 516 png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries); 517 if (hasTransparency) { 518 png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, 519 (png_color_16p)0); 520 } 521 png_set_filter(writePtr, 0, PNG_NO_FILTERS); 522 } else { 523 png_set_filter(writePtr, 0, PNG_ALL_FILTERS); 524 } 525 526 if (info->is9Patch) { 527 int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0); 528 int pIndex = info->haveLayoutBounds ? 2 : 1; 529 int bIndex = 1; 530 int oIndex = 0; 531 532 // Chunks ordered thusly because older platforms depend on the base 9 patch 533 // data being last 534 png_bytep chunkNames = info->haveLayoutBounds 535 ? (png_bytep) "npOl\0npLb\0npTc\0" 536 : (png_bytep) "npOl\0npTc"; 537 538 // base 9 patch data 539 if (kDebug) { 540 diag->Note(DiagMessage() << "adding 9-patch info.."); 541 } 542 memcpy((char*)unknowns[pIndex].name, "npTc", 5); 543 unknowns[pIndex].data = (png_byte*)info->serialize9Patch(); 544 unknowns[pIndex].size = info->info9Patch.serializedSize(); 545 // TODO: remove the check below when everything works 546 checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data); 547 548 // automatically generated 9 patch outline data 549 int chunkSize = sizeof(png_uint_32) * 6; 550 memcpy((char*)unknowns[oIndex].name, "npOl", 5); 551 unknowns[oIndex].data = (png_byte*)calloc(chunkSize, 1); 552 png_byte outputData[chunkSize]; 553 memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32)); 554 ((float*)outputData)[4] = info->outlineRadius; 555 ((png_uint_32*)outputData)[5] = info->outlineAlpha; 556 memcpy(unknowns[oIndex].data, &outputData, chunkSize); 557 unknowns[oIndex].size = chunkSize; 558 559 // optional optical inset / layout bounds data 560 if (info->haveLayoutBounds) { 561 int chunkSize = sizeof(png_uint_32) * 4; 562 memcpy((char*)unknowns[bIndex].name, "npLb", 5); 563 unknowns[bIndex].data = (png_byte*)calloc(chunkSize, 1); 564 memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize); 565 unknowns[bIndex].size = chunkSize; 566 } 567 568 for (int i = 0; i < chunkCount; i++) { 569 unknowns[i].location = PNG_HAVE_PLTE; 570 } 571 png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, chunkNames, 572 chunkCount); 573 png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount); 574 575 #if PNG_LIBPNG_VER < 10600 576 // Deal with unknown chunk location bug in 1.5.x and earlier. 577 png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE); 578 if (info->haveLayoutBounds) { 579 png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE); 580 } 581 #endif 582 } 583 584 png_write_info(writePtr, infoPtr); 585 586 png_bytepp rows; 587 if (colorType == PNG_COLOR_TYPE_RGB || 588 colorType == PNG_COLOR_TYPE_RGB_ALPHA) { 589 if (colorType == PNG_COLOR_TYPE_RGB) { 590 png_set_filler(writePtr, 0, PNG_FILLER_AFTER); 591 } 592 rows = info->rows.data(); 593 } else { 594 rows = outRows; 595 } 596 png_write_image(writePtr, rows); 597 598 if (kDebug) { 599 printf("Final image data:\n"); 600 // dump_image(info->width, info->height, rows, colorType); 601 } 602 603 png_write_end(writePtr, infoPtr); 604 605 for (uint32_t i = 0; i < info->height; i++) { 606 free(outRows[i]); 607 } 608 free(outRows); 609 free(unknowns[0].data); 610 free(unknowns[1].data); 611 free(unknowns[2].data); 612 613 png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, 614 &interlaceType, &compressionType, nullptr); 615 616 if (kDebug) { 617 diag->Note(DiagMessage() << "image written: w = " << width 618 << ", h = " << height << ", d = " << bitDepth 619 << ", colors = " << colorType 620 << ", inter = " << interlaceType 621 << ", comp = " << compressionType); 622 } 623 return true; 624 } 625 626 constexpr uint32_t kColorWhite = 0xffffffffu; 627 constexpr uint32_t kColorTick = 0xff000000u; 628 constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu; 629 630 enum class TickType { kNone, kTick, kLayoutBounds, kBoth }; 631 632 static TickType tickType(png_bytep p, bool transparent, const char** outError) { 633 png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); 634 635 if (transparent) { 636 if (p[3] == 0) { 637 return TickType::kNone; 638 } 639 if (color == kColorLayoutBoundsTick) { 640 return TickType::kLayoutBounds; 641 } 642 if (color == kColorTick) { 643 return TickType::kTick; 644 } 645 646 // Error cases 647 if (p[3] != 0xff) { 648 *outError = 649 "Frame pixels must be either solid or transparent " 650 "(not intermediate alphas)"; 651 return TickType::kNone; 652 } 653 654 if (p[0] != 0 || p[1] != 0 || p[2] != 0) { 655 *outError = "Ticks in transparent frame must be black or red"; 656 } 657 return TickType::kTick; 658 } 659 660 if (p[3] != 0xFF) { 661 *outError = "White frame must be a solid color (no alpha)"; 662 } 663 if (color == kColorWhite) { 664 return TickType::kNone; 665 } 666 if (color == kColorTick) { 667 return TickType::kTick; 668 } 669 if (color == kColorLayoutBoundsTick) { 670 return TickType::kLayoutBounds; 671 } 672 673 if (p[0] != 0 || p[1] != 0 || p[2] != 0) { 674 *outError = "Ticks in white frame must be black or red"; 675 return TickType::kNone; 676 } 677 return TickType::kTick; 678 } 679 680 enum class TickState { kStart, kInside1, kOutside1 }; 681 682 static bool getHorizontalTicks(png_bytep row, int width, bool transparent, 683 bool required, int32_t* outLeft, 684 int32_t* outRight, const char** outError, 685 uint8_t* outDivs, bool multipleAllowed) { 686 *outLeft = *outRight = -1; 687 TickState state = TickState::kStart; 688 bool found = false; 689 690 for (int i = 1; i < width - 1; i++) { 691 if (tickType(row + i * 4, transparent, outError) == TickType::kTick) { 692 if (state == TickState::kStart || 693 (state == TickState::kOutside1 && multipleAllowed)) { 694 *outLeft = i - 1; 695 *outRight = width - 2; 696 found = true; 697 if (outDivs != NULL) { 698 *outDivs += 2; 699 } 700 state = TickState::kInside1; 701 } else if (state == TickState::kOutside1) { 702 *outError = "Can't have more than one marked region along edge"; 703 *outLeft = i; 704 return false; 705 } 706 } else if (!*outError) { 707 if (state == TickState::kInside1) { 708 // We're done with this div. Move on to the next. 709 *outRight = i - 1; 710 outRight += 2; 711 outLeft += 2; 712 state = TickState::kOutside1; 713 } 714 } else { 715 *outLeft = i; 716 return false; 717 } 718 } 719 720 if (required && !found) { 721 *outError = "No marked region found along edge"; 722 *outLeft = -1; 723 return false; 724 } 725 return true; 726 } 727 728 static bool getVerticalTicks(png_bytepp rows, int offset, int height, 729 bool transparent, bool required, int32_t* outTop, 730 int32_t* outBottom, const char** outError, 731 uint8_t* outDivs, bool multipleAllowed) { 732 *outTop = *outBottom = -1; 733 TickState state = TickState::kStart; 734 bool found = false; 735 736 for (int i = 1; i < height - 1; i++) { 737 if (tickType(rows[i] + offset, transparent, outError) == TickType::kTick) { 738 if (state == TickState::kStart || 739 (state == TickState::kOutside1 && multipleAllowed)) { 740 *outTop = i - 1; 741 *outBottom = height - 2; 742 found = true; 743 if (outDivs != NULL) { 744 *outDivs += 2; 745 } 746 state = TickState::kInside1; 747 } else if (state == TickState::kOutside1) { 748 *outError = "Can't have more than one marked region along edge"; 749 *outTop = i; 750 return false; 751 } 752 } else if (!*outError) { 753 if (state == TickState::kInside1) { 754 // We're done with this div. Move on to the next. 755 *outBottom = i - 1; 756 outTop += 2; 757 outBottom += 2; 758 state = TickState::kOutside1; 759 } 760 } else { 761 *outTop = i; 762 return false; 763 } 764 } 765 766 if (required && !found) { 767 *outError = "No marked region found along edge"; 768 *outTop = -1; 769 return false; 770 } 771 return true; 772 } 773 774 static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, 775 bool transparent, 776 bool /* required */, 777 int32_t* outLeft, int32_t* outRight, 778 const char** outError) { 779 *outLeft = *outRight = 0; 780 781 // Look for left tick 782 if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) { 783 // Starting with a layout padding tick 784 int i = 1; 785 while (i < width - 1) { 786 (*outLeft)++; 787 i++; 788 if (tickType(row + i * 4, transparent, outError) != 789 TickType::kLayoutBounds) { 790 break; 791 } 792 } 793 } 794 795 // Look for right tick 796 if (tickType(row + (width - 2) * 4, transparent, outError) == 797 TickType::kLayoutBounds) { 798 // Ending with a layout padding tick 799 int i = width - 2; 800 while (i > 1) { 801 (*outRight)++; 802 i--; 803 if (tickType(row + i * 4, transparent, outError) != 804 TickType::kLayoutBounds) { 805 break; 806 } 807 } 808 } 809 return true; 810 } 811 812 static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, 813 int height, bool transparent, 814 bool /* required */, int32_t* outTop, 815 int32_t* outBottom, 816 const char** outError) { 817 *outTop = *outBottom = 0; 818 819 // Look for top tick 820 if (tickType(rows[1] + offset, transparent, outError) == 821 TickType::kLayoutBounds) { 822 // Starting with a layout padding tick 823 int i = 1; 824 while (i < height - 1) { 825 (*outTop)++; 826 i++; 827 if (tickType(rows[i] + offset, transparent, outError) != 828 TickType::kLayoutBounds) { 829 break; 830 } 831 } 832 } 833 834 // Look for bottom tick 835 if (tickType(rows[height - 2] + offset, transparent, outError) == 836 TickType::kLayoutBounds) { 837 // Ending with a layout padding tick 838 int i = height - 2; 839 while (i > 1) { 840 (*outBottom)++; 841 i--; 842 if (tickType(rows[i] + offset, transparent, outError) != 843 TickType::kLayoutBounds) { 844 break; 845 } 846 } 847 } 848 return true; 849 } 850 851 static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, 852 int endY, int dX, int dY, int* outInset) { 853 uint8_t maxOpacity = 0; 854 int inset = 0; 855 *outInset = 0; 856 for (int x = startX, y = startY; x != endX && y != endY; 857 x += dX, y += dY, inset++) { 858 png_byte* color = rows[y] + x * 4; 859 uint8_t opacity = color[3]; 860 if (opacity > maxOpacity) { 861 maxOpacity = opacity; 862 *outInset = inset; 863 } 864 if (opacity == 0xff) return; 865 } 866 } 867 868 static uint8_t maxAlphaOverRow(png_bytep row, int startX, int endX) { 869 uint8_t maxAlpha = 0; 870 for (int x = startX; x < endX; x++) { 871 uint8_t alpha = (row + x * 4)[3]; 872 if (alpha > maxAlpha) maxAlpha = alpha; 873 } 874 return maxAlpha; 875 } 876 877 static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, 878 int endY) { 879 uint8_t maxAlpha = 0; 880 for (int y = startY; y < endY; y++) { 881 uint8_t alpha = (rows[y] + offsetX * 4)[3]; 882 if (alpha > maxAlpha) maxAlpha = alpha; 883 } 884 return maxAlpha; 885 } 886 887 static void getOutline(PngInfo* image) { 888 int midX = image->width / 2; 889 int midY = image->height / 2; 890 int endX = image->width - 2; 891 int endY = image->height - 2; 892 893 // find left and right extent of nine patch content on center row 894 if (image->width > 4) { 895 findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, 896 &image->outlineInsetsLeft); 897 findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0, 898 &image->outlineInsetsRight); 899 } else { 900 image->outlineInsetsLeft = 0; 901 image->outlineInsetsRight = 0; 902 } 903 904 // find top and bottom extent of nine patch content on center column 905 if (image->height > 4) { 906 findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, 907 &image->outlineInsetsTop); 908 findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1, 909 &image->outlineInsetsBottom); 910 } else { 911 image->outlineInsetsTop = 0; 912 image->outlineInsetsBottom = 0; 913 } 914 915 int innerStartX = 1 + image->outlineInsetsLeft; 916 int innerStartY = 1 + image->outlineInsetsTop; 917 int innerEndX = endX - image->outlineInsetsRight; 918 int innerEndY = endY - image->outlineInsetsBottom; 919 int innerMidX = (innerEndX + innerStartX) / 2; 920 int innerMidY = (innerEndY + innerStartY) / 2; 921 922 // assuming the image is a round rect, compute the radius by marching 923 // diagonally from the top left corner towards the center 924 image->outlineAlpha = std::max( 925 maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX), 926 maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY)); 927 928 int diagonalInset = 0; 929 findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, 930 innerMidY, 1, 1, &diagonalInset); 931 932 /* Determine source radius based upon inset: 933 * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r 934 * sqrt(2) * r = sqrt(2) * i + r 935 * (sqrt(2) - 1) * r = sqrt(2) * i 936 * r = sqrt(2) / (sqrt(2) - 1) * i 937 */ 938 image->outlineRadius = 3.4142f * diagonalInset; 939 940 if (kDebug) { 941 printf("outline insets %d %d %d %d, rad %f, alpha %x\n", 942 image->outlineInsetsLeft, image->outlineInsetsTop, 943 image->outlineInsetsRight, image->outlineInsetsBottom, 944 image->outlineRadius, image->outlineAlpha); 945 } 946 } 947 948 static uint32_t getColor(png_bytepp rows, int left, int top, int right, 949 int bottom) { 950 png_bytep color = rows[top] + left * 4; 951 952 if (left > right || top > bottom) { 953 return android::Res_png_9patch::TRANSPARENT_COLOR; 954 } 955 956 while (top <= bottom) { 957 for (int i = left; i <= right; i++) { 958 png_bytep p = rows[top] + i * 4; 959 if (color[3] == 0) { 960 if (p[3] != 0) { 961 return android::Res_png_9patch::NO_COLOR; 962 } 963 } else if (p[0] != color[0] || p[1] != color[1] || p[2] != color[2] || 964 p[3] != color[3]) { 965 return android::Res_png_9patch::NO_COLOR; 966 } 967 } 968 top++; 969 } 970 971 if (color[3] == 0) { 972 return android::Res_png_9patch::TRANSPARENT_COLOR; 973 } 974 return (color[3] << 24) | (color[0] << 16) | (color[1] << 8) | color[2]; 975 } 976 977 static bool do9Patch(PngInfo* image, std::string* outError) { 978 image->is9Patch = true; 979 980 int W = image->width; 981 int H = image->height; 982 int i, j; 983 984 const int maxSizeXDivs = W * sizeof(int32_t); 985 const int maxSizeYDivs = H * sizeof(int32_t); 986 int32_t* xDivs = image->xDivs = new int32_t[W]; 987 int32_t* yDivs = image->yDivs = new int32_t[H]; 988 uint8_t numXDivs = 0; 989 uint8_t numYDivs = 0; 990 991 int8_t numColors; 992 int numRows; 993 int numCols; 994 int top; 995 int left; 996 int right; 997 int bottom; 998 memset(xDivs, -1, maxSizeXDivs); 999 memset(yDivs, -1, maxSizeYDivs); 1000 image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1; 1001 image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1; 1002 image->layoutBoundsLeft = image->layoutBoundsRight = 0; 1003 image->layoutBoundsTop = image->layoutBoundsBottom = 0; 1004 1005 png_bytep p = image->rows[0]; 1006 bool transparent = p[3] == 0; 1007 bool hasColor = false; 1008 1009 const char* errorMsg = nullptr; 1010 int errorPixel = -1; 1011 const char* errorEdge = nullptr; 1012 1013 int colorIndex = 0; 1014 std::vector<png_bytep> newRows; 1015 1016 // Validate size... 1017 if (W < 3 || H < 3) { 1018 errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels"; 1019 goto getout; 1020 } 1021 1022 // Validate frame... 1023 if (!transparent && 1024 (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) { 1025 errorMsg = "Must have one-pixel frame that is either transparent or white"; 1026 goto getout; 1027 } 1028 1029 // Find left and right of sizing areas... 1030 if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], 1031 &errorMsg, &numXDivs, true)) { 1032 errorPixel = xDivs[0]; 1033 errorEdge = "top"; 1034 goto getout; 1035 } 1036 1037 // Find top and bottom of sizing areas... 1038 if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], 1039 &yDivs[1], &errorMsg, &numYDivs, true)) { 1040 errorPixel = yDivs[0]; 1041 errorEdge = "left"; 1042 goto getout; 1043 } 1044 1045 // Copy patch size data into image... 1046 image->info9Patch.numXDivs = numXDivs; 1047 image->info9Patch.numYDivs = numYDivs; 1048 1049 // Find left and right of padding area... 1050 if (!getHorizontalTicks(image->rows[H - 1], W, transparent, false, 1051 &image->info9Patch.paddingLeft, 1052 &image->info9Patch.paddingRight, &errorMsg, nullptr, 1053 false)) { 1054 errorPixel = image->info9Patch.paddingLeft; 1055 errorEdge = "bottom"; 1056 goto getout; 1057 } 1058 1059 // Find top and bottom of padding area... 1060 if (!getVerticalTicks(image->rows.data(), (W - 1) * 4, H, transparent, false, 1061 &image->info9Patch.paddingTop, 1062 &image->info9Patch.paddingBottom, &errorMsg, nullptr, 1063 false)) { 1064 errorPixel = image->info9Patch.paddingTop; 1065 errorEdge = "right"; 1066 goto getout; 1067 } 1068 1069 // Find left and right of layout padding... 1070 getHorizontalLayoutBoundsTicks(image->rows[H - 1], W, transparent, false, 1071 &image->layoutBoundsLeft, 1072 &image->layoutBoundsRight, &errorMsg); 1073 1074 getVerticalLayoutBoundsTicks(image->rows.data(), (W - 1) * 4, H, transparent, 1075 false, &image->layoutBoundsTop, 1076 &image->layoutBoundsBottom, &errorMsg); 1077 1078 image->haveLayoutBounds = 1079 image->layoutBoundsLeft != 0 || image->layoutBoundsRight != 0 || 1080 image->layoutBoundsTop != 0 || image->layoutBoundsBottom != 0; 1081 1082 if (image->haveLayoutBounds) { 1083 if (kDebug) { 1084 printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, 1085 image->layoutBoundsTop, image->layoutBoundsRight, 1086 image->layoutBoundsBottom); 1087 } 1088 } 1089 1090 // use opacity of pixels to estimate the round rect outline 1091 getOutline(image); 1092 1093 // If padding is not yet specified, take values from size. 1094 if (image->info9Patch.paddingLeft < 0) { 1095 image->info9Patch.paddingLeft = xDivs[0]; 1096 image->info9Patch.paddingRight = W - 2 - xDivs[1]; 1097 } else { 1098 // Adjust value to be correct! 1099 image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight; 1100 } 1101 if (image->info9Patch.paddingTop < 0) { 1102 image->info9Patch.paddingTop = yDivs[0]; 1103 image->info9Patch.paddingBottom = H - 2 - yDivs[1]; 1104 } else { 1105 // Adjust value to be correct! 1106 image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom; 1107 } 1108 1109 /* if (kDebug) { 1110 printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName, 1111 xDivs[0], xDivs[1], 1112 yDivs[0], yDivs[1]); 1113 printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName, 1114 image->info9Patch.paddingLeft, image->info9Patch.paddingRight, 1115 image->info9Patch.paddingTop, 1116 image->info9Patch.paddingBottom); 1117 }*/ 1118 1119 // Remove frame from image. 1120 newRows.resize(H - 2); 1121 for (i = 0; i < H - 2; i++) { 1122 newRows[i] = image->rows[i + 1]; 1123 memmove(newRows[i], newRows[i] + 4, (W - 2) * 4); 1124 } 1125 image->rows.swap(newRows); 1126 1127 image->width -= 2; 1128 W = image->width; 1129 image->height -= 2; 1130 H = image->height; 1131 1132 // Figure out the number of rows and columns in the N-patch 1133 numCols = numXDivs + 1; 1134 if (xDivs[0] == 0) { // Column 1 is strechable 1135 numCols--; 1136 } 1137 if (xDivs[numXDivs - 1] == W) { 1138 numCols--; 1139 } 1140 numRows = numYDivs + 1; 1141 if (yDivs[0] == 0) { // Row 1 is strechable 1142 numRows--; 1143 } 1144 if (yDivs[numYDivs - 1] == H) { 1145 numRows--; 1146 } 1147 1148 // Make sure the amount of rows and columns will fit in the number of 1149 // colors we can use in the 9-patch format. 1150 if (numRows * numCols > 0x7F) { 1151 errorMsg = "Too many rows and columns in 9-patch perimeter"; 1152 goto getout; 1153 } 1154 1155 numColors = numRows * numCols; 1156 image->info9Patch.numColors = numColors; 1157 image->colors.resize(numColors); 1158 1159 // Fill in color information for each patch. 1160 1161 uint32_t c; 1162 top = 0; 1163 1164 // The first row always starts with the top being at y=0 and the bottom 1165 // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case 1166 // the first row is stretchable along the Y axis, otherwise it is fixed. 1167 // The last row always ends with the bottom being bitmap.height and the top 1168 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or 1169 // yDivs[numYDivs-1]. In the former case the last row is stretchable along 1170 // the Y axis, otherwise it is fixed. 1171 // 1172 // The first and last columns are similarly treated with respect to the X 1173 // axis. 1174 // 1175 // The above is to help explain some of the special casing that goes on the 1176 // code below. 1177 1178 // The initial yDiv and whether the first row is considered stretchable or 1179 // not depends on whether yDiv[0] was zero or not. 1180 for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) { 1181 if (j == numYDivs) { 1182 bottom = H; 1183 } else { 1184 bottom = yDivs[j]; 1185 } 1186 left = 0; 1187 // The initial xDiv and whether the first column is considered 1188 // stretchable or not depends on whether xDiv[0] was zero or not. 1189 for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) { 1190 if (i == numXDivs) { 1191 right = W; 1192 } else { 1193 right = xDivs[i]; 1194 } 1195 c = getColor(image->rows.data(), left, top, right - 1, bottom - 1); 1196 image->colors[colorIndex++] = c; 1197 if (kDebug) { 1198 if (c != android::Res_png_9patch::NO_COLOR) { 1199 hasColor = true; 1200 } 1201 } 1202 left = right; 1203 } 1204 top = bottom; 1205 } 1206 1207 assert(colorIndex == numColors); 1208 1209 if (kDebug && hasColor) { 1210 for (i = 0; i < numColors; i++) { 1211 if (i == 0) printf("Colors:\n"); 1212 printf(" #%08x", image->colors[i]); 1213 if (i == numColors - 1) printf("\n"); 1214 } 1215 } 1216 getout: 1217 if (errorMsg) { 1218 std::stringstream err; 1219 err << "9-patch malformed: " << errorMsg; 1220 if (errorEdge) { 1221 err << "." << std::endl; 1222 if (errorPixel >= 0) { 1223 err << "Found at pixel #" << errorPixel << " along " << errorEdge 1224 << " edge"; 1225 } else { 1226 err << "Found along " << errorEdge << " edge"; 1227 } 1228 } 1229 *outError = err.str(); 1230 return false; 1231 } 1232 return true; 1233 } 1234 1235 bool Png::process(const Source& source, std::istream* input, 1236 BigBuffer* outBuffer, const PngOptions& options) { 1237 TRACE_CALL(); 1238 png_byte signature[kPngSignatureSize]; 1239 1240 // Read the PNG signature first. 1241 if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) { 1242 mDiag->Error(DiagMessage() << strerror(errno)); 1243 return false; 1244 } 1245 1246 // If the PNG signature doesn't match, bail early. 1247 if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) { 1248 mDiag->Error(DiagMessage() << "not a valid png file"); 1249 return false; 1250 } 1251 1252 bool result = false; 1253 png_structp readPtr = nullptr; 1254 png_infop infoPtr = nullptr; 1255 png_structp writePtr = nullptr; 1256 png_infop writeInfoPtr = nullptr; 1257 PngInfo pngInfo = {}; 1258 1259 readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); 1260 if (!readPtr) { 1261 mDiag->Error(DiagMessage() << "failed to allocate read ptr"); 1262 goto bail; 1263 } 1264 1265 infoPtr = png_create_info_struct(readPtr); 1266 if (!infoPtr) { 1267 mDiag->Error(DiagMessage() << "failed to allocate info ptr"); 1268 goto bail; 1269 } 1270 1271 png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, 1272 logWarning); 1273 1274 // Set the read function to read from std::istream. 1275 png_set_read_fn(readPtr, (png_voidp)input, readDataFromStream); 1276 1277 if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) { 1278 goto bail; 1279 } 1280 1281 if (util::EndsWith(source.path, ".9.png")) { 1282 std::string errorMsg; 1283 if (!do9Patch(&pngInfo, &errorMsg)) { 1284 mDiag->Error(DiagMessage() << errorMsg); 1285 goto bail; 1286 } 1287 } 1288 1289 writePtr = 1290 png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); 1291 if (!writePtr) { 1292 mDiag->Error(DiagMessage() << "failed to allocate write ptr"); 1293 goto bail; 1294 } 1295 1296 writeInfoPtr = png_create_info_struct(writePtr); 1297 if (!writeInfoPtr) { 1298 mDiag->Error(DiagMessage() << "failed to allocate write info ptr"); 1299 goto bail; 1300 } 1301 1302 png_set_error_fn(writePtr, nullptr, nullptr, logWarning); 1303 1304 // Set the write function to write to std::ostream. 1305 png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, 1306 flushDataToStream); 1307 1308 if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, 1309 options.grayscale_tolerance)) { 1310 goto bail; 1311 } 1312 1313 result = true; 1314 bail: 1315 if (readPtr) { 1316 png_destroy_read_struct(&readPtr, &infoPtr, nullptr); 1317 } 1318 1319 if (writePtr) { 1320 png_destroy_write_struct(&writePtr, &writeInfoPtr); 1321 } 1322 return result; 1323 } 1324 1325 } // namespace aapt 1326