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