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