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