blob: 5d4a6ac8e0c61bac9a76ba5a9c84416cb6e332b7 [file] [log] [blame]
Adam Lesinski282e1812014-01-23 18:17:42 -08001//
2// Copyright 2006 The Android Open Source Project
3//
4// Build resource files from raw assets.
5//
6
7#define PNG_INTERNAL
8
9#include "Images.h"
10
11#include <androidfw/ResourceTypes.h>
12#include <utils/ByteOrder.h>
13
14#include <png.h>
15#include <zlib.h>
16
17#define NOISY(x) //x
18
19static void
20png_write_aapt_file(png_structp png_ptr, png_bytep data, png_size_t length)
21{
22 AaptFile* aaptfile = (AaptFile*) png_get_io_ptr(png_ptr);
23 status_t err = aaptfile->writeData(data, length);
24 if (err != NO_ERROR) {
25 png_error(png_ptr, "Write Error");
26 }
27}
28
29
30static void
31png_flush_aapt_file(png_structp png_ptr)
32{
33}
34
35// This holds an image as 8bpp RGBA.
36struct image_info
37{
Narayan Kamath6381dd42014-03-03 17:12:03 +000038 image_info() : rows(NULL), is9Patch(false),
39 xDivs(NULL), yDivs(NULL), colors(NULL), allocRows(NULL) { }
40
Adam Lesinski282e1812014-01-23 18:17:42 -080041 ~image_info() {
42 if (rows && rows != allocRows) {
43 free(rows);
44 }
45 if (allocRows) {
46 for (int i=0; i<(int)allocHeight; i++) {
47 free(allocRows[i]);
48 }
49 free(allocRows);
50 }
Narayan Kamath6381dd42014-03-03 17:12:03 +000051 free(xDivs);
52 free(yDivs);
53 free(colors);
54 }
55
56 void* serialize9patch() {
57 void* serialized = Res_png_9patch::serialize(info9Patch, xDivs, yDivs, colors);
58 reinterpret_cast<Res_png_9patch*>(serialized)->deviceToFile();
59 return serialized;
Adam Lesinski282e1812014-01-23 18:17:42 -080060 }
61
62 png_uint_32 width;
63 png_uint_32 height;
64 png_bytepp rows;
65
66 // 9-patch info.
67 bool is9Patch;
68 Res_png_9patch info9Patch;
Narayan Kamath6381dd42014-03-03 17:12:03 +000069 int32_t* xDivs;
70 int32_t* yDivs;
71 uint32_t* colors;
Adam Lesinski282e1812014-01-23 18:17:42 -080072
73 // Layout padding, if relevant
74 bool haveLayoutBounds;
75 int32_t layoutBoundsLeft;
76 int32_t layoutBoundsTop;
77 int32_t layoutBoundsRight;
78 int32_t layoutBoundsBottom;
79
Chris Craik47cd8e92014-07-08 17:13:08 -070080 // Round rect outline description
81 int32_t outlineInsetsLeft;
82 int32_t outlineInsetsTop;
83 int32_t outlineInsetsRight;
84 int32_t outlineInsetsBottom;
85 float outlineRadius;
Chris Craik77b5cad2014-07-30 18:23:07 -070086 uint8_t outlineAlpha;
Chris Craik47cd8e92014-07-08 17:13:08 -070087
Adam Lesinski282e1812014-01-23 18:17:42 -080088 png_uint_32 allocHeight;
89 png_bytepp allocRows;
90};
91
John Reck859e19f2013-09-05 16:26:04 -070092static void log_warning(png_structp png_ptr, png_const_charp warning_message)
93{
94 const char* imageName = (const char*) png_get_error_ptr(png_ptr);
95 fprintf(stderr, "%s: libpng warning: %s\n", imageName, warning_message);
96}
97
Adam Lesinski282e1812014-01-23 18:17:42 -080098static void read_png(const char* imageName,
99 png_structp read_ptr, png_infop read_info,
100 image_info* outImageInfo)
101{
102 int color_type;
103 int bit_depth, interlace_type, compression_type;
104 int i;
105
John Reck859e19f2013-09-05 16:26:04 -0700106 png_set_error_fn(read_ptr, const_cast<char*>(imageName),
107 NULL /* use default errorfn */, log_warning);
Adam Lesinski282e1812014-01-23 18:17:42 -0800108 png_read_info(read_ptr, read_info);
109
110 png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
111 &outImageInfo->height, &bit_depth, &color_type,
112 &interlace_type, &compression_type, NULL);
113
114 //printf("Image %s:\n", imageName);
115 //printf("color_type=%d, bit_depth=%d, interlace_type=%d, compression_type=%d\n",
116 // color_type, bit_depth, interlace_type, compression_type);
117
118 if (color_type == PNG_COLOR_TYPE_PALETTE)
119 png_set_palette_to_rgb(read_ptr);
120
121 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
122 png_set_expand_gray_1_2_4_to_8(read_ptr);
123
124 if (png_get_valid(read_ptr, read_info, PNG_INFO_tRNS)) {
125 //printf("Has PNG_INFO_tRNS!\n");
126 png_set_tRNS_to_alpha(read_ptr);
127 }
128
129 if (bit_depth == 16)
130 png_set_strip_16(read_ptr);
131
132 if ((color_type&PNG_COLOR_MASK_ALPHA) == 0)
133 png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
134
135 if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
136 png_set_gray_to_rgb(read_ptr);
137
John Reck859e19f2013-09-05 16:26:04 -0700138 png_set_interlace_handling(read_ptr);
139
Adam Lesinski282e1812014-01-23 18:17:42 -0800140 png_read_update_info(read_ptr, read_info);
141
142 outImageInfo->rows = (png_bytepp)malloc(
143 outImageInfo->height * sizeof(png_bytep));
144 outImageInfo->allocHeight = outImageInfo->height;
145 outImageInfo->allocRows = outImageInfo->rows;
146
147 png_set_rows(read_ptr, read_info, outImageInfo->rows);
148
149 for (i = 0; i < (int)outImageInfo->height; i++)
150 {
151 outImageInfo->rows[i] = (png_bytep)
152 malloc(png_get_rowbytes(read_ptr, read_info));
153 }
154
155 png_read_image(read_ptr, outImageInfo->rows);
156
157 png_read_end(read_ptr, read_info);
158
159 NOISY(printf("Image %s: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
160 imageName,
161 (int)outImageInfo->width, (int)outImageInfo->height,
162 bit_depth, color_type,
163 interlace_type, compression_type));
164
165 png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
166 &outImageInfo->height, &bit_depth, &color_type,
167 &interlace_type, &compression_type, NULL);
168}
169
170#define COLOR_TRANSPARENT 0
171#define COLOR_WHITE 0xFFFFFFFF
172#define COLOR_TICK 0xFF000000
173#define COLOR_LAYOUT_BOUNDS_TICK 0xFF0000FF
174
175enum {
176 TICK_TYPE_NONE,
177 TICK_TYPE_TICK,
178 TICK_TYPE_LAYOUT_BOUNDS,
179 TICK_TYPE_BOTH
180};
181
182static int tick_type(png_bytep p, bool transparent, const char** outError)
183{
184 png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
185
186 if (transparent) {
187 if (p[3] == 0) {
188 return TICK_TYPE_NONE;
189 }
190 if (color == COLOR_LAYOUT_BOUNDS_TICK) {
191 return TICK_TYPE_LAYOUT_BOUNDS;
192 }
193 if (color == COLOR_TICK) {
194 return TICK_TYPE_TICK;
195 }
196
197 // Error cases
198 if (p[3] != 0xff) {
199 *outError = "Frame pixels must be either solid or transparent (not intermediate alphas)";
200 return TICK_TYPE_NONE;
201 }
202 if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
203 *outError = "Ticks in transparent frame must be black or red";
204 }
205 return TICK_TYPE_TICK;
206 }
207
208 if (p[3] != 0xFF) {
209 *outError = "White frame must be a solid color (no alpha)";
210 }
211 if (color == COLOR_WHITE) {
212 return TICK_TYPE_NONE;
213 }
214 if (color == COLOR_TICK) {
215 return TICK_TYPE_TICK;
216 }
217 if (color == COLOR_LAYOUT_BOUNDS_TICK) {
218 return TICK_TYPE_LAYOUT_BOUNDS;
219 }
220
221 if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
222 *outError = "Ticks in white frame must be black or red";
223 return TICK_TYPE_NONE;
224 }
225 return TICK_TYPE_TICK;
226}
227
228enum {
229 TICK_START,
230 TICK_INSIDE_1,
231 TICK_OUTSIDE_1
232};
233
234static status_t get_horizontal_ticks(
235 png_bytep row, int width, bool transparent, bool required,
236 int32_t* outLeft, int32_t* outRight, const char** outError,
237 uint8_t* outDivs, bool multipleAllowed)
238{
239 int i;
240 *outLeft = *outRight = -1;
241 int state = TICK_START;
242 bool found = false;
243
244 for (i=1; i<width-1; i++) {
245 if (TICK_TYPE_TICK == tick_type(row+i*4, transparent, outError)) {
246 if (state == TICK_START ||
247 (state == TICK_OUTSIDE_1 && multipleAllowed)) {
248 *outLeft = i-1;
249 *outRight = width-2;
250 found = true;
251 if (outDivs != NULL) {
252 *outDivs += 2;
253 }
254 state = TICK_INSIDE_1;
255 } else if (state == TICK_OUTSIDE_1) {
256 *outError = "Can't have more than one marked region along edge";
257 *outLeft = i;
258 return UNKNOWN_ERROR;
259 }
260 } else if (*outError == NULL) {
261 if (state == TICK_INSIDE_1) {
262 // We're done with this div. Move on to the next.
263 *outRight = i-1;
264 outRight += 2;
265 outLeft += 2;
266 state = TICK_OUTSIDE_1;
267 }
268 } else {
269 *outLeft = i;
270 return UNKNOWN_ERROR;
271 }
272 }
273
274 if (required && !found) {
275 *outError = "No marked region found along edge";
276 *outLeft = -1;
277 return UNKNOWN_ERROR;
278 }
279
280 return NO_ERROR;
281}
282
283static status_t get_vertical_ticks(
284 png_bytepp rows, int offset, int height, bool transparent, bool required,
285 int32_t* outTop, int32_t* outBottom, const char** outError,
286 uint8_t* outDivs, bool multipleAllowed)
287{
288 int i;
289 *outTop = *outBottom = -1;
290 int state = TICK_START;
291 bool found = false;
292
293 for (i=1; i<height-1; i++) {
294 if (TICK_TYPE_TICK == tick_type(rows[i]+offset, transparent, outError)) {
295 if (state == TICK_START ||
296 (state == TICK_OUTSIDE_1 && multipleAllowed)) {
297 *outTop = i-1;
298 *outBottom = height-2;
299 found = true;
300 if (outDivs != NULL) {
301 *outDivs += 2;
302 }
303 state = TICK_INSIDE_1;
304 } else if (state == TICK_OUTSIDE_1) {
305 *outError = "Can't have more than one marked region along edge";
306 *outTop = i;
307 return UNKNOWN_ERROR;
308 }
309 } else if (*outError == NULL) {
310 if (state == TICK_INSIDE_1) {
311 // We're done with this div. Move on to the next.
312 *outBottom = i-1;
313 outTop += 2;
314 outBottom += 2;
315 state = TICK_OUTSIDE_1;
316 }
317 } else {
318 *outTop = i;
319 return UNKNOWN_ERROR;
320 }
321 }
322
323 if (required && !found) {
324 *outError = "No marked region found along edge";
325 *outTop = -1;
326 return UNKNOWN_ERROR;
327 }
328
329 return NO_ERROR;
330}
331
332static status_t get_horizontal_layout_bounds_ticks(
333 png_bytep row, int width, bool transparent, bool required,
334 int32_t* outLeft, int32_t* outRight, const char** outError)
335{
336 int i;
337 *outLeft = *outRight = 0;
338
339 // Look for left tick
340 if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + 4, transparent, outError)) {
341 // Starting with a layout padding tick
342 i = 1;
343 while (i < width - 1) {
344 (*outLeft)++;
345 i++;
346 int tick = tick_type(row + i * 4, transparent, outError);
347 if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
348 break;
349 }
350 }
351 }
352
353 // Look for right tick
354 if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + (width - 2) * 4, transparent, outError)) {
355 // Ending with a layout padding tick
356 i = width - 2;
357 while (i > 1) {
358 (*outRight)++;
359 i--;
360 int tick = tick_type(row+i*4, transparent, outError);
361 if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
362 break;
363 }
364 }
365 }
366
367 return NO_ERROR;
368}
369
370static status_t get_vertical_layout_bounds_ticks(
371 png_bytepp rows, int offset, int height, bool transparent, bool required,
372 int32_t* outTop, int32_t* outBottom, const char** outError)
373{
374 int i;
375 *outTop = *outBottom = 0;
376
377 // Look for top tick
378 if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[1] + offset, transparent, outError)) {
379 // Starting with a layout padding tick
380 i = 1;
381 while (i < height - 1) {
382 (*outTop)++;
383 i++;
384 int tick = tick_type(rows[i] + offset, transparent, outError);
385 if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
386 break;
387 }
388 }
389 }
390
391 // Look for bottom tick
392 if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[height - 2] + offset, transparent, outError)) {
393 // Ending with a layout padding tick
394 i = height - 2;
395 while (i > 1) {
396 (*outBottom)++;
397 i--;
398 int tick = tick_type(rows[i] + offset, transparent, outError);
399 if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
400 break;
401 }
402 }
403 }
404
405 return NO_ERROR;
406}
407
Chris Craik47cd8e92014-07-08 17:13:08 -0700408static void find_max_opacity(png_byte** rows,
409 int startX, int startY, int endX, int endY, int dX, int dY,
410 int* out_inset)
411{
412 bool opaque_within_inset = true;
Chris Craik77b5cad2014-07-30 18:23:07 -0700413 uint8_t max_opacity = 0;
Chris Craik47cd8e92014-07-08 17:13:08 -0700414 int inset = 0;
415 *out_inset = 0;
416 for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
417 png_byte* color = rows[y] + x * 4;
Chris Craik77b5cad2014-07-30 18:23:07 -0700418 uint8_t opacity = color[3];
Chris Craik47cd8e92014-07-08 17:13:08 -0700419 if (opacity > max_opacity) {
420 max_opacity = opacity;
421 *out_inset = inset;
422 }
423 if (opacity == 0xff) return;
424 }
425}
426
Chris Craik77b5cad2014-07-30 18:23:07 -0700427static uint8_t max_alpha_over_row(png_byte* row, int startX, int endX)
Chris Craik47cd8e92014-07-08 17:13:08 -0700428{
Chris Craik77b5cad2014-07-30 18:23:07 -0700429 uint8_t max_alpha = 0;
Chris Craik47cd8e92014-07-08 17:13:08 -0700430 for (int x = startX; x < endX; x++) {
Chris Craik77b5cad2014-07-30 18:23:07 -0700431 uint8_t alpha = (row + x * 4)[3];
432 if (alpha > max_alpha) max_alpha = alpha;
Chris Craik47cd8e92014-07-08 17:13:08 -0700433 }
Chris Craik77b5cad2014-07-30 18:23:07 -0700434 return max_alpha;
Chris Craik47cd8e92014-07-08 17:13:08 -0700435}
436
Chris Craik77b5cad2014-07-30 18:23:07 -0700437static uint8_t max_alpha_over_col(png_byte** rows, int offsetX, int startY, int endY)
Chris Craik47cd8e92014-07-08 17:13:08 -0700438{
Chris Craik77b5cad2014-07-30 18:23:07 -0700439 uint8_t max_alpha = 0;
Chris Craik47cd8e92014-07-08 17:13:08 -0700440 for (int y = startY; y < endY; y++) {
Chris Craik77b5cad2014-07-30 18:23:07 -0700441 uint8_t alpha = (rows[y] + offsetX * 4)[3];
442 if (alpha > max_alpha) max_alpha = alpha;
Chris Craik47cd8e92014-07-08 17:13:08 -0700443 }
Chris Craik77b5cad2014-07-30 18:23:07 -0700444 return max_alpha;
Chris Craik47cd8e92014-07-08 17:13:08 -0700445}
446
447static void get_outline(image_info* image)
448{
449 int midX = image->width / 2;
450 int midY = image->height / 2;
451 int endX = image->width - 2;
452 int endY = image->height - 2;
453
454 // find left and right extent of nine patch content on center row
455 if (image->width > 4) {
456 find_max_opacity(image->rows, 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
457 find_max_opacity(image->rows, endX, midY, midX, -1, -1, 0, &image->outlineInsetsRight);
458 } else {
459 image->outlineInsetsLeft = 0;
460 image->outlineInsetsRight = 0;
461 }
462
463 // find top and bottom extent of nine patch content on center column
464 if (image->height > 4) {
465 find_max_opacity(image->rows, midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
466 find_max_opacity(image->rows, midX, endY, -1, midY, 0, -1, &image->outlineInsetsBottom);
467 } else {
468 image->outlineInsetsTop = 0;
469 image->outlineInsetsBottom = 0;
470 }
471
472 int innerStartX = 1 + image->outlineInsetsLeft;
473 int innerStartY = 1 + image->outlineInsetsTop;
474 int innerEndX = endX - image->outlineInsetsRight;
475 int innerEndY = endY - image->outlineInsetsBottom;
476 int innerMidX = (innerEndX + innerStartX) / 2;
477 int innerMidY = (innerEndY + innerStartY) / 2;
478
479 // assuming the image is a round rect, compute the radius by marching
480 // diagonally from the top left corner towards the center
Chris Craik77b5cad2014-07-30 18:23:07 -0700481 image->outlineAlpha = max(max_alpha_over_row(image->rows[innerMidY], innerStartX, innerEndX),
482 max_alpha_over_col(image->rows, innerMidX, innerStartY, innerStartY));
Chris Craik47cd8e92014-07-08 17:13:08 -0700483
484 int diagonalInset = 0;
485 find_max_opacity(image->rows, innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
486 &diagonalInset);
487
Chris Craik47d86232014-08-14 17:26:21 -0700488 /* Determine source radius based upon inset:
489 * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
490 * sqrt(2) * r = sqrt(2) * i + r
491 * (sqrt(2) - 1) * r = sqrt(2) * i
492 * r = sqrt(2) / (sqrt(2) - 1) * i
493 */
494 image->outlineRadius = 3.4142f * diagonalInset;
Chris Craik47cd8e92014-07-08 17:13:08 -0700495
Chris Craik77b5cad2014-07-30 18:23:07 -0700496 NOISY(printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
Chris Craik47cd8e92014-07-08 17:13:08 -0700497 image->outlineInsetsLeft,
498 image->outlineInsetsTop,
499 image->outlineInsetsRight,
500 image->outlineInsetsBottom,
501 image->outlineRadius,
Chris Craik77b5cad2014-07-30 18:23:07 -0700502 image->outlineAlpha));
Chris Craik47cd8e92014-07-08 17:13:08 -0700503}
504
Adam Lesinski282e1812014-01-23 18:17:42 -0800505
506static uint32_t get_color(
507 png_bytepp rows, int left, int top, int right, int bottom)
508{
509 png_bytep color = rows[top] + left*4;
510
511 if (left > right || top > bottom) {
512 return Res_png_9patch::TRANSPARENT_COLOR;
513 }
514
515 while (top <= bottom) {
516 for (int i = left; i <= right; i++) {
517 png_bytep p = rows[top]+i*4;
518 if (color[3] == 0) {
519 if (p[3] != 0) {
520 return Res_png_9patch::NO_COLOR;
521 }
522 } else if (p[0] != color[0] || p[1] != color[1]
523 || p[2] != color[2] || p[3] != color[3]) {
524 return Res_png_9patch::NO_COLOR;
525 }
526 }
527 top++;
528 }
529
530 if (color[3] == 0) {
531 return Res_png_9patch::TRANSPARENT_COLOR;
532 }
533 return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
534}
535
536static void select_patch(
537 int which, int front, int back, int size, int* start, int* end)
538{
539 switch (which) {
540 case 0:
541 *start = 0;
542 *end = front-1;
543 break;
544 case 1:
545 *start = front;
546 *end = back-1;
547 break;
548 case 2:
549 *start = back;
550 *end = size-1;
551 break;
552 }
553}
554
555static uint32_t get_color(image_info* image, int hpatch, int vpatch)
556{
557 int left, right, top, bottom;
558 select_patch(
Narayan Kamath6381dd42014-03-03 17:12:03 +0000559 hpatch, image->xDivs[0], image->xDivs[1],
Adam Lesinski282e1812014-01-23 18:17:42 -0800560 image->width, &left, &right);
561 select_patch(
Narayan Kamath6381dd42014-03-03 17:12:03 +0000562 vpatch, image->yDivs[0], image->yDivs[1],
Adam Lesinski282e1812014-01-23 18:17:42 -0800563 image->height, &top, &bottom);
564 //printf("Selecting h=%d v=%d: (%d,%d)-(%d,%d)\n",
565 // hpatch, vpatch, left, top, right, bottom);
566 const uint32_t c = get_color(image->rows, left, top, right, bottom);
567 NOISY(printf("Color in (%d,%d)-(%d,%d): #%08x\n", left, top, right, bottom, c));
568 return c;
569}
570
571static status_t do_9patch(const char* imageName, image_info* image)
572{
573 image->is9Patch = true;
574
575 int W = image->width;
576 int H = image->height;
577 int i, j;
578
579 int maxSizeXDivs = W * sizeof(int32_t);
580 int maxSizeYDivs = H * sizeof(int32_t);
Narayan Kamath6381dd42014-03-03 17:12:03 +0000581 int32_t* xDivs = image->xDivs = (int32_t*) malloc(maxSizeXDivs);
582 int32_t* yDivs = image->yDivs = (int32_t*) malloc(maxSizeYDivs);
Elliott Hughesc367d482013-10-29 13:12:55 -0700583 uint8_t numXDivs = 0;
584 uint8_t numYDivs = 0;
585
Adam Lesinski282e1812014-01-23 18:17:42 -0800586 int8_t numColors;
587 int numRows;
588 int numCols;
589 int top;
590 int left;
591 int right;
592 int bottom;
593 memset(xDivs, -1, maxSizeXDivs);
594 memset(yDivs, -1, maxSizeYDivs);
595 image->info9Patch.paddingLeft = image->info9Patch.paddingRight =
596 image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
597
598 image->layoutBoundsLeft = image->layoutBoundsRight =
599 image->layoutBoundsTop = image->layoutBoundsBottom = 0;
600
601 png_bytep p = image->rows[0];
602 bool transparent = p[3] == 0;
603 bool hasColor = false;
604
605 const char* errorMsg = NULL;
606 int errorPixel = -1;
607 const char* errorEdge = NULL;
608
609 int colorIndex = 0;
610
611 // Validate size...
612 if (W < 3 || H < 3) {
613 errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
614 goto getout;
615 }
616
617 // Validate frame...
618 if (!transparent &&
619 (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
620 errorMsg = "Must have one-pixel frame that is either transparent or white";
621 goto getout;
622 }
623
624 // Find left and right of sizing areas...
625 if (get_horizontal_ticks(p, W, transparent, true, &xDivs[0],
626 &xDivs[1], &errorMsg, &numXDivs, true) != NO_ERROR) {
627 errorPixel = xDivs[0];
628 errorEdge = "top";
629 goto getout;
630 }
631
632 // Find top and bottom of sizing areas...
633 if (get_vertical_ticks(image->rows, 0, H, transparent, true, &yDivs[0],
634 &yDivs[1], &errorMsg, &numYDivs, true) != NO_ERROR) {
635 errorPixel = yDivs[0];
636 errorEdge = "left";
637 goto getout;
638 }
639
Elliott Hughesb30296b2013-10-29 15:25:52 -0700640 // Copy patch size data into image...
641 image->info9Patch.numXDivs = numXDivs;
642 image->info9Patch.numYDivs = numYDivs;
643
Adam Lesinski282e1812014-01-23 18:17:42 -0800644 // Find left and right of padding area...
645 if (get_horizontal_ticks(image->rows[H-1], W, transparent, false, &image->info9Patch.paddingLeft,
646 &image->info9Patch.paddingRight, &errorMsg, NULL, false) != NO_ERROR) {
647 errorPixel = image->info9Patch.paddingLeft;
648 errorEdge = "bottom";
649 goto getout;
650 }
651
652 // Find top and bottom of padding area...
653 if (get_vertical_ticks(image->rows, (W-1)*4, H, transparent, false, &image->info9Patch.paddingTop,
654 &image->info9Patch.paddingBottom, &errorMsg, NULL, false) != NO_ERROR) {
655 errorPixel = image->info9Patch.paddingTop;
656 errorEdge = "right";
657 goto getout;
658 }
659
660 // Find left and right of layout padding...
661 get_horizontal_layout_bounds_ticks(image->rows[H-1], W, transparent, false,
662 &image->layoutBoundsLeft,
663 &image->layoutBoundsRight, &errorMsg);
664
665 get_vertical_layout_bounds_ticks(image->rows, (W-1)*4, H, transparent, false,
666 &image->layoutBoundsTop,
667 &image->layoutBoundsBottom, &errorMsg);
668
669 image->haveLayoutBounds = image->layoutBoundsLeft != 0
670 || image->layoutBoundsRight != 0
671 || image->layoutBoundsTop != 0
672 || image->layoutBoundsBottom != 0;
673
674 if (image->haveLayoutBounds) {
675 NOISY(printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
676 image->layoutBoundsRight, image->layoutBoundsBottom));
677 }
678
Chris Craik47cd8e92014-07-08 17:13:08 -0700679 // use opacity of pixels to estimate the round rect outline
680 get_outline(image);
681
Adam Lesinski282e1812014-01-23 18:17:42 -0800682 // If padding is not yet specified, take values from size.
683 if (image->info9Patch.paddingLeft < 0) {
684 image->info9Patch.paddingLeft = xDivs[0];
685 image->info9Patch.paddingRight = W - 2 - xDivs[1];
686 } else {
687 // Adjust value to be correct!
688 image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
689 }
690 if (image->info9Patch.paddingTop < 0) {
691 image->info9Patch.paddingTop = yDivs[0];
692 image->info9Patch.paddingBottom = H - 2 - yDivs[1];
693 } else {
694 // Adjust value to be correct!
695 image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
696 }
697
698 NOISY(printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
Chris Craik77b5cad2014-07-30 18:23:07 -0700699 xDivs[0], xDivs[1],
700 yDivs[0], yDivs[1]));
Adam Lesinski282e1812014-01-23 18:17:42 -0800701 NOISY(printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
702 image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
703 image->info9Patch.paddingTop, image->info9Patch.paddingBottom));
704
705 // Remove frame from image.
706 image->rows = (png_bytepp)malloc((H-2) * sizeof(png_bytep));
707 for (i=0; i<(H-2); i++) {
708 image->rows[i] = image->allocRows[i+1];
709 memmove(image->rows[i], image->rows[i]+4, (W-2)*4);
710 }
711 image->width -= 2;
712 W = image->width;
713 image->height -= 2;
714 H = image->height;
715
716 // Figure out the number of rows and columns in the N-patch
717 numCols = numXDivs + 1;
718 if (xDivs[0] == 0) { // Column 1 is strechable
719 numCols--;
720 }
721 if (xDivs[numXDivs - 1] == W) {
722 numCols--;
723 }
724 numRows = numYDivs + 1;
725 if (yDivs[0] == 0) { // Row 1 is strechable
726 numRows--;
727 }
728 if (yDivs[numYDivs - 1] == H) {
729 numRows--;
730 }
731
732 // Make sure the amount of rows and columns will fit in the number of
733 // colors we can use in the 9-patch format.
734 if (numRows * numCols > 0x7F) {
735 errorMsg = "Too many rows and columns in 9-patch perimeter";
736 goto getout;
737 }
738
739 numColors = numRows * numCols;
740 image->info9Patch.numColors = numColors;
Narayan Kamath6381dd42014-03-03 17:12:03 +0000741 image->colors = (uint32_t*)malloc(numColors * sizeof(uint32_t));
Adam Lesinski282e1812014-01-23 18:17:42 -0800742
743 // Fill in color information for each patch.
744
745 uint32_t c;
746 top = 0;
747
748 // The first row always starts with the top being at y=0 and the bottom
749 // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
750 // the first row is stretchable along the Y axis, otherwise it is fixed.
751 // The last row always ends with the bottom being bitmap.height and the top
752 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
753 // yDivs[numYDivs-1]. In the former case the last row is stretchable along
754 // the Y axis, otherwise it is fixed.
755 //
756 // The first and last columns are similarly treated with respect to the X
757 // axis.
758 //
759 // The above is to help explain some of the special casing that goes on the
760 // code below.
761
762 // The initial yDiv and whether the first row is considered stretchable or
763 // not depends on whether yDiv[0] was zero or not.
764 for (j = (yDivs[0] == 0 ? 1 : 0);
765 j <= numYDivs && top < H;
766 j++) {
767 if (j == numYDivs) {
768 bottom = H;
769 } else {
770 bottom = yDivs[j];
771 }
772 left = 0;
773 // The initial xDiv and whether the first column is considered
774 // stretchable or not depends on whether xDiv[0] was zero or not.
775 for (i = xDivs[0] == 0 ? 1 : 0;
776 i <= numXDivs && left < W;
777 i++) {
778 if (i == numXDivs) {
779 right = W;
780 } else {
781 right = xDivs[i];
782 }
783 c = get_color(image->rows, left, top, right - 1, bottom - 1);
Narayan Kamath6381dd42014-03-03 17:12:03 +0000784 image->colors[colorIndex++] = c;
Adam Lesinski282e1812014-01-23 18:17:42 -0800785 NOISY(if (c != Res_png_9patch::NO_COLOR) hasColor = true);
786 left = right;
787 }
788 top = bottom;
789 }
790
791 assert(colorIndex == numColors);
792
793 for (i=0; i<numColors; i++) {
794 if (hasColor) {
795 if (i == 0) printf("Colors in %s:\n ", imageName);
Narayan Kamath6381dd42014-03-03 17:12:03 +0000796 printf(" #%08x", image->colors[i]);
Adam Lesinski282e1812014-01-23 18:17:42 -0800797 if (i == numColors - 1) printf("\n");
798 }
799 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800800getout:
801 if (errorMsg) {
802 fprintf(stderr,
803 "ERROR: 9-patch image %s malformed.\n"
804 " %s.\n", imageName, errorMsg);
805 if (errorEdge != NULL) {
806 if (errorPixel >= 0) {
807 fprintf(stderr,
808 " Found at pixel #%d along %s edge.\n", errorPixel, errorEdge);
809 } else {
810 fprintf(stderr,
811 " Found along %s edge.\n", errorEdge);
812 }
813 }
814 return UNKNOWN_ERROR;
815 }
816 return NO_ERROR;
817}
818
Narayan Kamath6381dd42014-03-03 17:12:03 +0000819static void checkNinePatchSerialization(Res_png_9patch* inPatch, void* data)
Adam Lesinski282e1812014-01-23 18:17:42 -0800820{
Adam Lesinski282e1812014-01-23 18:17:42 -0800821 size_t patchSize = inPatch->serializedSize();
Narayan Kamath6381dd42014-03-03 17:12:03 +0000822 void* newData = malloc(patchSize);
Adam Lesinski282e1812014-01-23 18:17:42 -0800823 memcpy(newData, data, patchSize);
824 Res_png_9patch* outPatch = inPatch->deserialize(newData);
825 // deserialization is done in place, so outPatch == newData
826 assert(outPatch == newData);
827 assert(outPatch->numXDivs == inPatch->numXDivs);
828 assert(outPatch->numYDivs == inPatch->numYDivs);
829 assert(outPatch->paddingLeft == inPatch->paddingLeft);
830 assert(outPatch->paddingRight == inPatch->paddingRight);
831 assert(outPatch->paddingTop == inPatch->paddingTop);
832 assert(outPatch->paddingBottom == inPatch->paddingBottom);
833 for (int i = 0; i < outPatch->numXDivs; i++) {
834 assert(outPatch->xDivs[i] == inPatch->xDivs[i]);
835 }
836 for (int i = 0; i < outPatch->numYDivs; i++) {
837 assert(outPatch->yDivs[i] == inPatch->yDivs[i]);
838 }
839 for (int i = 0; i < outPatch->numColors; i++) {
840 assert(outPatch->colors[i] == inPatch->colors[i]);
841 }
842 free(newData);
843}
844
Adam Lesinski282e1812014-01-23 18:17:42 -0800845static void dump_image(int w, int h, png_bytepp rows, int color_type)
846{
847 int i, j, rr, gg, bb, aa;
848
849 int bpp;
850 if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
851 bpp = 1;
852 } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
853 bpp = 2;
854 } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
855 // We use a padding byte even when there is no alpha
856 bpp = 4;
857 } else {
858 printf("Unknown color type %d.\n", color_type);
859 }
860
861 for (j = 0; j < h; j++) {
862 png_bytep row = rows[j];
863 for (i = 0; i < w; i++) {
864 rr = row[0];
865 gg = row[1];
866 bb = row[2];
867 aa = row[3];
868 row += bpp;
869
870 if (i == 0) {
871 printf("Row %d:", j);
872 }
873 switch (bpp) {
874 case 1:
875 printf(" (%d)", rr);
876 break;
877 case 2:
878 printf(" (%d %d", rr, gg);
879 break;
880 case 3:
881 printf(" (%d %d %d)", rr, gg, bb);
882 break;
883 case 4:
884 printf(" (%d %d %d %d)", rr, gg, bb, aa);
885 break;
886 }
887 if (i == (w - 1)) {
888 NOISY(printf("\n"));
889 }
890 }
891 }
892}
893
894#define MAX(a,b) ((a)>(b)?(a):(b))
895#define ABS(a) ((a)<0?-(a):(a))
896
897static void analyze_image(const char *imageName, image_info &imageInfo, int grayscaleTolerance,
898 png_colorp rgbPalette, png_bytep alphaPalette,
899 int *paletteEntries, bool *hasTransparency, int *colorType,
900 png_bytepp outRows)
901{
902 int w = imageInfo.width;
903 int h = imageInfo.height;
904 int i, j, rr, gg, bb, aa, idx;
905 uint32_t colors[256], col;
906 int num_colors = 0;
907 int maxGrayDeviation = 0;
908
909 bool isOpaque = true;
910 bool isPalette = true;
911 bool isGrayscale = true;
912
913 // Scan the entire image and determine if:
914 // 1. Every pixel has R == G == B (grayscale)
915 // 2. Every pixel has A == 255 (opaque)
916 // 3. There are no more than 256 distinct RGBA colors
917
918 // NOISY(printf("Initial image data:\n"));
919 // dump_image(w, h, imageInfo.rows, PNG_COLOR_TYPE_RGB_ALPHA);
920
921 for (j = 0; j < h; j++) {
922 png_bytep row = imageInfo.rows[j];
923 png_bytep out = outRows[j];
924 for (i = 0; i < w; i++) {
925 rr = *row++;
926 gg = *row++;
927 bb = *row++;
928 aa = *row++;
929
930 int odev = maxGrayDeviation;
931 maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
932 maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
933 maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
934 if (maxGrayDeviation > odev) {
935 NOISY(printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
936 maxGrayDeviation, i, j, rr, gg, bb, aa));
937 }
938
939 // Check if image is really grayscale
940 if (isGrayscale) {
941 if (rr != gg || rr != bb) {
942 NOISY(printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
943 i, j, rr, gg, bb, aa));
944 isGrayscale = false;
945 }
946 }
947
948 // Check if image is really opaque
949 if (isOpaque) {
950 if (aa != 0xff) {
951 NOISY(printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
952 i, j, rr, gg, bb, aa));
953 isOpaque = false;
954 }
955 }
956
957 // Check if image is really <= 256 colors
958 if (isPalette) {
959 col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
960 bool match = false;
961 for (idx = 0; idx < num_colors; idx++) {
962 if (colors[idx] == col) {
963 match = true;
964 break;
965 }
966 }
967
968 // Write the palette index for the pixel to outRows optimistically
969 // We might overwrite it later if we decide to encode as gray or
970 // gray + alpha
971 *out++ = idx;
972 if (!match) {
973 if (num_colors == 256) {
974 NOISY(printf("Found 257th color at %d, %d\n", i, j));
975 isPalette = false;
976 } else {
977 colors[num_colors++] = col;
978 }
979 }
980 }
981 }
982 }
983
984 *paletteEntries = 0;
985 *hasTransparency = !isOpaque;
986 int bpp = isOpaque ? 3 : 4;
987 int paletteSize = w * h + bpp * num_colors;
988
989 NOISY(printf("isGrayscale = %s\n", isGrayscale ? "true" : "false"));
990 NOISY(printf("isOpaque = %s\n", isOpaque ? "true" : "false"));
991 NOISY(printf("isPalette = %s\n", isPalette ? "true" : "false"));
992 NOISY(printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
993 paletteSize, 2 * w * h, bpp * w * h));
994 NOISY(printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance));
995
996 // Choose the best color type for the image.
997 // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
998 // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
999 // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
1000 // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
1001 // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
1002 if (isGrayscale) {
1003 if (isOpaque) {
1004 *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
1005 } else {
1006 // Use a simple heuristic to determine whether using a palette will
1007 // save space versus using gray + alpha for each pixel.
1008 // This doesn't take into account chunk overhead, filtering, LZ
1009 // compression, etc.
1010 if (isPalette && (paletteSize < 2 * w * h)) {
1011 *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
1012 } else {
1013 *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
1014 }
1015 }
1016 } else if (isPalette && (paletteSize < bpp * w * h)) {
1017 *colorType = PNG_COLOR_TYPE_PALETTE;
1018 } else {
1019 if (maxGrayDeviation <= grayscaleTolerance) {
1020 printf("%s: forcing image to gray (max deviation = %d)\n", imageName, maxGrayDeviation);
1021 *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
1022 } else {
1023 *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
1024 }
1025 }
1026
1027 // Perform postprocessing of the image or palette data based on the final
1028 // color type chosen
1029
1030 if (*colorType == PNG_COLOR_TYPE_PALETTE) {
1031 // Create separate RGB and Alpha palettes and set the number of colors
1032 *paletteEntries = num_colors;
1033
1034 // Create the RGB and alpha palettes
1035 for (int idx = 0; idx < num_colors; idx++) {
1036 col = colors[idx];
1037 rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff);
1038 rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
1039 rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff);
1040 alphaPalette[idx] = (png_byte) (col & 0xff);
1041 }
1042 } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
1043 // If the image is gray or gray + alpha, compact the pixels into outRows
1044 for (j = 0; j < h; j++) {
1045 png_bytep row = imageInfo.rows[j];
1046 png_bytep out = outRows[j];
1047 for (i = 0; i < w; i++) {
1048 rr = *row++;
1049 gg = *row++;
1050 bb = *row++;
1051 aa = *row++;
1052
1053 if (isGrayscale) {
1054 *out++ = rr;
1055 } else {
1056 *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
1057 }
1058 if (!isOpaque) {
1059 *out++ = aa;
1060 }
1061 }
1062 }
1063 }
1064}
1065
1066
1067static void write_png(const char* imageName,
1068 png_structp write_ptr, png_infop write_info,
1069 image_info& imageInfo, int grayscaleTolerance)
1070{
1071 bool optimize = true;
1072 png_uint_32 width, height;
1073 int color_type;
1074 int bit_depth, interlace_type, compression_type;
1075 int i;
1076
Chris Craik47cd8e92014-07-08 17:13:08 -07001077 png_unknown_chunk unknowns[3];
Adam Lesinski282e1812014-01-23 18:17:42 -08001078 unknowns[0].data = NULL;
1079 unknowns[1].data = NULL;
Chris Craik47cd8e92014-07-08 17:13:08 -07001080 unknowns[2].data = NULL;
Adam Lesinski282e1812014-01-23 18:17:42 -08001081
1082 png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * sizeof(png_bytep));
1083 if (outRows == (png_bytepp) 0) {
1084 printf("Can't allocate output buffer!\n");
1085 exit(1);
1086 }
1087 for (i = 0; i < (int) imageInfo.height; i++) {
1088 outRows[i] = (png_bytep) malloc(2 * (int) imageInfo.width);
1089 if (outRows[i] == (png_bytep) 0) {
1090 printf("Can't allocate output buffer!\n");
1091 exit(1);
1092 }
1093 }
1094
1095 png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
1096
1097 NOISY(printf("Writing image %s: w = %d, h = %d\n", imageName,
1098 (int) imageInfo.width, (int) imageInfo.height));
1099
1100 png_color rgbPalette[256];
1101 png_byte alphaPalette[256];
1102 bool hasTransparency;
1103 int paletteEntries;
1104
1105 analyze_image(imageName, imageInfo, grayscaleTolerance, rgbPalette, alphaPalette,
1106 &paletteEntries, &hasTransparency, &color_type, outRows);
1107
1108 // If the image is a 9-patch, we need to preserve it as a ARGB file to make
1109 // sure the pixels will not be pre-dithered/clamped until we decide they are
1110 if (imageInfo.is9Patch && (color_type == PNG_COLOR_TYPE_RGB ||
1111 color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)) {
1112 color_type = PNG_COLOR_TYPE_RGB_ALPHA;
1113 }
1114
1115 switch (color_type) {
1116 case PNG_COLOR_TYPE_PALETTE:
1117 NOISY(printf("Image %s has %d colors%s, using PNG_COLOR_TYPE_PALETTE\n",
1118 imageName, paletteEntries,
1119 hasTransparency ? " (with alpha)" : ""));
1120 break;
1121 case PNG_COLOR_TYPE_GRAY:
1122 NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName));
1123 break;
1124 case PNG_COLOR_TYPE_GRAY_ALPHA:
1125 NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName));
1126 break;
1127 case PNG_COLOR_TYPE_RGB:
1128 NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName));
1129 break;
1130 case PNG_COLOR_TYPE_RGB_ALPHA:
1131 NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName));
1132 break;
1133 }
1134
1135 png_set_IHDR(write_ptr, write_info, imageInfo.width, imageInfo.height,
1136 8, color_type, PNG_INTERLACE_NONE,
1137 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
1138
1139 if (color_type == PNG_COLOR_TYPE_PALETTE) {
1140 png_set_PLTE(write_ptr, write_info, rgbPalette, paletteEntries);
1141 if (hasTransparency) {
1142 png_set_tRNS(write_ptr, write_info, alphaPalette, paletteEntries, (png_color_16p) 0);
1143 }
1144 png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
1145 } else {
1146 png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
1147 }
1148
1149 if (imageInfo.is9Patch) {
Chris Craik47cd8e92014-07-08 17:13:08 -07001150 int chunk_count = 2 + (imageInfo.haveLayoutBounds ? 1 : 0);
1151 int p_index = imageInfo.haveLayoutBounds ? 2 : 1;
1152 int b_index = 1;
1153 int o_index = 0;
1154
1155 // Chunks ordered thusly because older platforms depend on the base 9 patch data being last
Adam Lesinski282e1812014-01-23 18:17:42 -08001156 png_byte *chunk_names = imageInfo.haveLayoutBounds
Chris Craik47cd8e92014-07-08 17:13:08 -07001157 ? (png_byte*)"npOl\0npLb\0npTc\0"
1158 : (png_byte*)"npOl\0npTc";
1159
1160 // base 9 patch data
Adam Lesinski282e1812014-01-23 18:17:42 -08001161 NOISY(printf("Adding 9-patch info...\n"));
1162 strcpy((char*)unknowns[p_index].name, "npTc");
Narayan Kamath6381dd42014-03-03 17:12:03 +00001163 unknowns[p_index].data = (png_byte*)imageInfo.serialize9patch();
Adam Lesinski282e1812014-01-23 18:17:42 -08001164 unknowns[p_index].size = imageInfo.info9Patch.serializedSize();
1165 // TODO: remove the check below when everything works
1166 checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[p_index].data);
1167
Chris Craik47cd8e92014-07-08 17:13:08 -07001168 // automatically generated 9 patch outline data
1169 int chunk_size = sizeof(png_uint_32) * 6;
1170 strcpy((char*)unknowns[o_index].name, "npOl");
1171 unknowns[o_index].data = (png_byte*) calloc(chunk_size, 1);
1172 png_byte outputData[chunk_size];
1173 memcpy(&outputData, &imageInfo.outlineInsetsLeft, 4 * sizeof(png_uint_32));
1174 ((float*) outputData)[4] = imageInfo.outlineRadius;
Chris Craik77b5cad2014-07-30 18:23:07 -07001175 ((png_uint_32*) outputData)[5] = imageInfo.outlineAlpha;
Chris Craik47cd8e92014-07-08 17:13:08 -07001176 memcpy(unknowns[o_index].data, &outputData, chunk_size);
1177 unknowns[o_index].size = chunk_size;
1178
1179 // optional optical inset / layout bounds data
Adam Lesinski282e1812014-01-23 18:17:42 -08001180 if (imageInfo.haveLayoutBounds) {
1181 int chunk_size = sizeof(png_uint_32) * 4;
1182 strcpy((char*)unknowns[b_index].name, "npLb");
1183 unknowns[b_index].data = (png_byte*) calloc(chunk_size, 1);
1184 memcpy(unknowns[b_index].data, &imageInfo.layoutBoundsLeft, chunk_size);
1185 unknowns[b_index].size = chunk_size;
1186 }
1187
1188 for (int i = 0; i < chunk_count; i++) {
1189 unknowns[i].location = PNG_HAVE_PLTE;
1190 }
1191 png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS,
1192 chunk_names, chunk_count);
1193 png_set_unknown_chunks(write_ptr, write_info, unknowns, chunk_count);
1194#if PNG_LIBPNG_VER < 10600
1195 /* Deal with unknown chunk location bug in 1.5.x and earlier */
1196 png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE);
1197 if (imageInfo.haveLayoutBounds) {
1198 png_set_unknown_chunk_location(write_ptr, write_info, 1, PNG_HAVE_PLTE);
1199 }
1200#endif
1201 }
1202
1203
1204 png_write_info(write_ptr, write_info);
1205
1206 png_bytepp rows;
1207 if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
1208 if (color_type == PNG_COLOR_TYPE_RGB) {
1209 png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
1210 }
1211 rows = imageInfo.rows;
1212 } else {
1213 rows = outRows;
1214 }
1215 png_write_image(write_ptr, rows);
1216
1217// NOISY(printf("Final image data:\n"));
1218// dump_image(imageInfo.width, imageInfo.height, rows, color_type);
1219
1220 png_write_end(write_ptr, write_info);
1221
1222 for (i = 0; i < (int) imageInfo.height; i++) {
1223 free(outRows[i]);
1224 }
1225 free(outRows);
1226 free(unknowns[0].data);
1227 free(unknowns[1].data);
Chris Craik47cd8e92014-07-08 17:13:08 -07001228 free(unknowns[2].data);
Adam Lesinski282e1812014-01-23 18:17:42 -08001229
1230 png_get_IHDR(write_ptr, write_info, &width, &height,
1231 &bit_depth, &color_type, &interlace_type,
1232 &compression_type, NULL);
1233
1234 NOISY(printf("Image written: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
1235 (int)width, (int)height, bit_depth, color_type, interlace_type,
1236 compression_type));
1237}
1238
1239status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
1240 const sp<AaptFile>& file, String8* outNewLeafName)
1241{
1242 String8 ext(file->getPath().getPathExtension());
1243
1244 // We currently only process PNG images.
1245 if (strcmp(ext.string(), ".png") != 0) {
1246 return NO_ERROR;
1247 }
1248
1249 // Example of renaming a file:
1250 //*outNewLeafName = file->getPath().getBasePath().getFileName();
1251 //outNewLeafName->append(".nupng");
1252
1253 String8 printableName(file->getPrintableSource());
1254
1255 if (bundle->getVerbose()) {
1256 printf("Processing image: %s\n", printableName.string());
1257 }
1258
1259 png_structp read_ptr = NULL;
1260 png_infop read_info = NULL;
1261 FILE* fp;
1262
1263 image_info imageInfo;
1264
1265 png_structp write_ptr = NULL;
1266 png_infop write_info = NULL;
1267
1268 status_t error = UNKNOWN_ERROR;
1269
1270 const size_t nameLen = file->getPath().length();
1271
1272 fp = fopen(file->getSourceFile().string(), "rb");
1273 if (fp == NULL) {
1274 fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string());
1275 goto bail;
1276 }
1277
1278 read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
1279 (png_error_ptr)NULL);
1280 if (!read_ptr) {
1281 goto bail;
1282 }
1283
1284 read_info = png_create_info_struct(read_ptr);
1285 if (!read_info) {
1286 goto bail;
1287 }
1288
1289 if (setjmp(png_jmpbuf(read_ptr))) {
1290 goto bail;
1291 }
1292
1293 png_init_io(read_ptr, fp);
1294
1295 read_png(printableName.string(), read_ptr, read_info, &imageInfo);
1296
1297 if (nameLen > 6) {
1298 const char* name = file->getPath().string();
1299 if (name[nameLen-5] == '9' && name[nameLen-6] == '.') {
1300 if (do_9patch(printableName.string(), &imageInfo) != NO_ERROR) {
1301 goto bail;
1302 }
1303 }
1304 }
1305
1306 write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
1307 (png_error_ptr)NULL);
1308 if (!write_ptr)
1309 {
1310 goto bail;
1311 }
1312
1313 write_info = png_create_info_struct(write_ptr);
1314 if (!write_info)
1315 {
1316 goto bail;
1317 }
1318
1319 png_set_write_fn(write_ptr, (void*)file.get(),
1320 png_write_aapt_file, png_flush_aapt_file);
1321
1322 if (setjmp(png_jmpbuf(write_ptr)))
1323 {
1324 goto bail;
1325 }
1326
1327 write_png(printableName.string(), write_ptr, write_info, imageInfo,
1328 bundle->getGrayscaleTolerance());
1329
1330 error = NO_ERROR;
1331
1332 if (bundle->getVerbose()) {
1333 fseek(fp, 0, SEEK_END);
1334 size_t oldSize = (size_t)ftell(fp);
1335 size_t newSize = file->getSize();
1336 float factor = ((float)newSize)/oldSize;
1337 int percent = (int)(factor*100);
1338 printf(" (processed image %s: %d%% size of source)\n", printableName.string(), percent);
1339 }
1340
1341bail:
1342 if (read_ptr) {
1343 png_destroy_read_struct(&read_ptr, &read_info, (png_infopp)NULL);
1344 }
1345 if (fp) {
1346 fclose(fp);
1347 }
1348 if (write_ptr) {
1349 png_destroy_write_struct(&write_ptr, &write_info);
1350 }
1351
1352 if (error != NO_ERROR) {
1353 fprintf(stderr, "ERROR: Failure processing PNG image %s\n",
1354 file->getPrintableSource().string());
1355 }
1356 return error;
1357}
1358
1359status_t preProcessImageToCache(const Bundle* bundle, const String8& source, const String8& dest)
1360{
1361 png_structp read_ptr = NULL;
1362 png_infop read_info = NULL;
1363
1364 FILE* fp;
1365
1366 image_info imageInfo;
1367
1368 png_structp write_ptr = NULL;
1369 png_infop write_info = NULL;
1370
1371 status_t error = UNKNOWN_ERROR;
1372
1373 if (bundle->getVerbose()) {
1374 printf("Processing image to cache: %s => %s\n", source.string(), dest.string());
1375 }
1376
1377 // Get a file handler to read from
1378 fp = fopen(source.string(),"rb");
1379 if (fp == NULL) {
1380 fprintf(stderr, "%s ERROR: Unable to open PNG file\n", source.string());
1381 return error;
1382 }
1383
1384 // Call libpng to get a struct to read image data into
1385 read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
1386 if (!read_ptr) {
1387 fclose(fp);
1388 png_destroy_read_struct(&read_ptr, &read_info,NULL);
1389 return error;
1390 }
1391
1392 // Call libpng to get a struct to read image info into
1393 read_info = png_create_info_struct(read_ptr);
1394 if (!read_info) {
1395 fclose(fp);
1396 png_destroy_read_struct(&read_ptr, &read_info,NULL);
1397 return error;
1398 }
1399
1400 // Set a jump point for libpng to long jump back to on error
1401 if (setjmp(png_jmpbuf(read_ptr))) {
1402 fclose(fp);
1403 png_destroy_read_struct(&read_ptr, &read_info,NULL);
1404 return error;
1405 }
1406
1407 // Set up libpng to read from our file.
1408 png_init_io(read_ptr,fp);
1409
1410 // Actually read data from the file
1411 read_png(source.string(), read_ptr, read_info, &imageInfo);
1412
1413 // We're done reading so we can clean up
1414 // Find old file size before releasing handle
1415 fseek(fp, 0, SEEK_END);
1416 size_t oldSize = (size_t)ftell(fp);
1417 fclose(fp);
1418 png_destroy_read_struct(&read_ptr, &read_info,NULL);
1419
1420 // Check to see if we're dealing with a 9-patch
1421 // If we are, process appropriately
1422 if (source.getBasePath().getPathExtension() == ".9") {
1423 if (do_9patch(source.string(), &imageInfo) != NO_ERROR) {
1424 return error;
1425 }
1426 }
1427
1428 // Call libpng to create a structure to hold the processed image data
1429 // that can be written to disk
1430 write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
1431 if (!write_ptr) {
1432 png_destroy_write_struct(&write_ptr, &write_info);
1433 return error;
1434 }
1435
1436 // Call libpng to create a structure to hold processed image info that can
1437 // be written to disk
1438 write_info = png_create_info_struct(write_ptr);
1439 if (!write_info) {
1440 png_destroy_write_struct(&write_ptr, &write_info);
1441 return error;
1442 }
1443
1444 // Open up our destination file for writing
1445 fp = fopen(dest.string(), "wb");
1446 if (!fp) {
1447 fprintf(stderr, "%s ERROR: Unable to open PNG file\n", dest.string());
1448 png_destroy_write_struct(&write_ptr, &write_info);
1449 return error;
1450 }
1451
1452 // Set up libpng to write to our file
1453 png_init_io(write_ptr, fp);
1454
1455 // Set up a jump for libpng to long jump back on on errors
1456 if (setjmp(png_jmpbuf(write_ptr))) {
1457 fclose(fp);
1458 png_destroy_write_struct(&write_ptr, &write_info);
1459 return error;
1460 }
1461
1462 // Actually write out to the new png
1463 write_png(dest.string(), write_ptr, write_info, imageInfo,
1464 bundle->getGrayscaleTolerance());
1465
1466 if (bundle->getVerbose()) {
1467 // Find the size of our new file
1468 FILE* reader = fopen(dest.string(), "rb");
1469 fseek(reader, 0, SEEK_END);
1470 size_t newSize = (size_t)ftell(reader);
1471 fclose(reader);
1472
1473 float factor = ((float)newSize)/oldSize;
1474 int percent = (int)(factor*100);
1475 printf(" (processed image to cache entry %s: %d%% size of source)\n",
1476 dest.string(), percent);
1477 }
1478
1479 //Clean up
1480 fclose(fp);
1481 png_destroy_write_struct(&write_ptr, &write_info);
1482
1483 return NO_ERROR;
1484}
1485
Adam Lesinskie572c012014-09-19 15:10:04 -07001486status_t postProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
Adam Lesinski282e1812014-01-23 18:17:42 -08001487 ResourceTable* table, const sp<AaptFile>& file)
1488{
1489 String8 ext(file->getPath().getPathExtension());
1490
1491 // At this point, now that we have all the resource data, all we need to
1492 // do is compile XML files.
1493 if (strcmp(ext.string(), ".xml") == 0) {
Adam Lesinski9306a472014-10-17 14:40:17 -07001494 String16 resourceName(parseResourceName(file->getSourceFile().getPathLeaf()));
Adam Lesinskie572c012014-09-19 15:10:04 -07001495 return compileXmlFile(bundle, assets, resourceName, file, table);
Adam Lesinski282e1812014-01-23 18:17:42 -08001496 }
1497
1498 return NO_ERROR;
1499}