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