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
33namespace aapt {
34
35constexpr bool kDebug = false;
36
37struct 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
82static 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
91static 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
98static void flushDataToStream(png_structp /*writePtr*/) {}
99
100static 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
106static 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
159static 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
187color_type) {
188    int i, j, rr, gg, bb, aa;
189
190    int bpp;
191    if (color_type == PNG_COLOR_TYPE_PALETTE || color_type ==
192PNG_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 ==
197PNG_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
247static 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
433static 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
625constexpr uint32_t kColorWhite = 0xffffffffu;
626constexpr uint32_t kColorTick = 0xff000000u;
627constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu;
628
629enum class TickType { kNone, kTick, kLayoutBounds, kBoth };
630
631static 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
679enum class TickState { kStart, kInside1, kOutside1 };
680
681static 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
727static 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
773static 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
811static 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
850static 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
867static 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
876static 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
886static 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
947static 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
976static 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  }
1215getout:
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
1234bool 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;
1312bail:
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