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