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