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