blob: db74831d096d912b02e116e7a70914437a8d74b4 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -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
Mathias Agopianb13b9bd2012-02-17 18:27:36 -080011#include <androidfw/ResourceTypes.h>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080012#include <utils/ByteOrder.h>
13
14#include <png.h>
John Recke982b722013-08-26 16:53:40 -070015#include <zlib.h>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080016
17#define NOISY(x) //x
18
19static void
20png_write_aapt_file(png_structp png_ptr, png_bytep data, png_size_t length)
21{
John Recke982b722013-08-26 16:53:40 -070022 AaptFile* aaptfile = (AaptFile*) png_get_io_ptr(png_ptr);
23 status_t err = aaptfile->writeData(data, length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072
Amith Yamasaniec4a5042012-04-04 10:27:15 -070073 // Layout padding, if relevant
74 bool haveLayoutBounds;
75 int32_t layoutBoundsLeft;
76 int32_t layoutBoundsTop;
77 int32_t layoutBoundsRight;
78 int32_t layoutBoundsBottom;
79
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080 png_uint_32 allocHeight;
81 png_bytepp allocRows;
82};
83
84static void read_png(const char* imageName,
85 png_structp read_ptr, png_infop read_info,
86 image_info* outImageInfo)
87{
88 int color_type;
89 int bit_depth, interlace_type, compression_type;
90 int i;
91
92 png_read_info(read_ptr, read_info);
93
94 png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
95 &outImageInfo->height, &bit_depth, &color_type,
96 &interlace_type, &compression_type, NULL);
97
98 //printf("Image %s:\n", imageName);
99 //printf("color_type=%d, bit_depth=%d, interlace_type=%d, compression_type=%d\n",
100 // color_type, bit_depth, interlace_type, compression_type);
101
102 if (color_type == PNG_COLOR_TYPE_PALETTE)
103 png_set_palette_to_rgb(read_ptr);
104
105 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
John Recke982b722013-08-26 16:53:40 -0700106 png_set_expand_gray_1_2_4_to_8(read_ptr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107
108 if (png_get_valid(read_ptr, read_info, PNG_INFO_tRNS)) {
109 //printf("Has PNG_INFO_tRNS!\n");
110 png_set_tRNS_to_alpha(read_ptr);
111 }
112
113 if (bit_depth == 16)
114 png_set_strip_16(read_ptr);
115
116 if ((color_type&PNG_COLOR_MASK_ALPHA) == 0)
117 png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
118
119 if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
120 png_set_gray_to_rgb(read_ptr);
121
122 png_read_update_info(read_ptr, read_info);
123
124 outImageInfo->rows = (png_bytepp)malloc(
John Recke982b722013-08-26 16:53:40 -0700125 outImageInfo->height * sizeof(png_bytep));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126 outImageInfo->allocHeight = outImageInfo->height;
127 outImageInfo->allocRows = outImageInfo->rows;
128
129 png_set_rows(read_ptr, read_info, outImageInfo->rows);
130
131 for (i = 0; i < (int)outImageInfo->height; i++)
132 {
133 outImageInfo->rows[i] = (png_bytep)
134 malloc(png_get_rowbytes(read_ptr, read_info));
135 }
136
137 png_read_image(read_ptr, outImageInfo->rows);
138
139 png_read_end(read_ptr, read_info);
140
141 NOISY(printf("Image %s: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
142 imageName,
143 (int)outImageInfo->width, (int)outImageInfo->height,
144 bit_depth, color_type,
145 interlace_type, compression_type));
146
147 png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
148 &outImageInfo->height, &bit_depth, &color_type,
149 &interlace_type, &compression_type, NULL);
150}
151
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700152#define COLOR_TRANSPARENT 0
153#define COLOR_WHITE 0xFFFFFFFF
154#define COLOR_TICK 0xFF000000
155#define COLOR_LAYOUT_BOUNDS_TICK 0xFF0000FF
156
157enum {
158 TICK_TYPE_NONE,
159 TICK_TYPE_TICK,
160 TICK_TYPE_LAYOUT_BOUNDS,
161 TICK_TYPE_BOTH
162};
163
164static int tick_type(png_bytep p, bool transparent, const char** outError)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165{
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700166 png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
167
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168 if (transparent) {
169 if (p[3] == 0) {
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700170 return TICK_TYPE_NONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 }
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700172 if (color == COLOR_LAYOUT_BOUNDS_TICK) {
173 return TICK_TYPE_LAYOUT_BOUNDS;
174 }
175 if (color == COLOR_TICK) {
176 return TICK_TYPE_TICK;
177 }
178
179 // Error cases
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 if (p[3] != 0xff) {
181 *outError = "Frame pixels must be either solid or transparent (not intermediate alphas)";
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700182 return TICK_TYPE_NONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 }
184 if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700185 *outError = "Ticks in transparent frame must be black or red";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186 }
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700187 return TICK_TYPE_TICK;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800188 }
189
190 if (p[3] != 0xFF) {
191 *outError = "White frame must be a solid color (no alpha)";
192 }
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700193 if (color == COLOR_WHITE) {
194 return TICK_TYPE_NONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800195 }
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700196 if (color == COLOR_TICK) {
197 return TICK_TYPE_TICK;
198 }
199 if (color == COLOR_LAYOUT_BOUNDS_TICK) {
200 return TICK_TYPE_LAYOUT_BOUNDS;
201 }
202
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800203 if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700204 *outError = "Ticks in white frame must be black or red";
205 return TICK_TYPE_NONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800206 }
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700207 return TICK_TYPE_TICK;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208}
209
210enum {
211 TICK_START,
212 TICK_INSIDE_1,
213 TICK_OUTSIDE_1
214};
215
216static status_t get_horizontal_ticks(
217 png_bytep row, int width, bool transparent, bool required,
218 int32_t* outLeft, int32_t* outRight, const char** outError,
219 uint8_t* outDivs, bool multipleAllowed)
220{
221 int i;
222 *outLeft = *outRight = -1;
223 int state = TICK_START;
224 bool found = false;
225
226 for (i=1; i<width-1; i++) {
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700227 if (TICK_TYPE_TICK == tick_type(row+i*4, transparent, outError)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 if (state == TICK_START ||
229 (state == TICK_OUTSIDE_1 && multipleAllowed)) {
230 *outLeft = i-1;
231 *outRight = width-2;
232 found = true;
233 if (outDivs != NULL) {
234 *outDivs += 2;
235 }
236 state = TICK_INSIDE_1;
237 } else if (state == TICK_OUTSIDE_1) {
238 *outError = "Can't have more than one marked region along edge";
239 *outLeft = i;
240 return UNKNOWN_ERROR;
241 }
242 } else if (*outError == NULL) {
243 if (state == TICK_INSIDE_1) {
244 // We're done with this div. Move on to the next.
245 *outRight = i-1;
246 outRight += 2;
247 outLeft += 2;
248 state = TICK_OUTSIDE_1;
249 }
250 } else {
251 *outLeft = i;
252 return UNKNOWN_ERROR;
253 }
254 }
255
256 if (required && !found) {
257 *outError = "No marked region found along edge";
258 *outLeft = -1;
259 return UNKNOWN_ERROR;
260 }
261
262 return NO_ERROR;
263}
264
265static status_t get_vertical_ticks(
266 png_bytepp rows, int offset, int height, bool transparent, bool required,
267 int32_t* outTop, int32_t* outBottom, const char** outError,
268 uint8_t* outDivs, bool multipleAllowed)
269{
270 int i;
271 *outTop = *outBottom = -1;
272 int state = TICK_START;
273 bool found = false;
274
275 for (i=1; i<height-1; i++) {
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700276 if (TICK_TYPE_TICK == tick_type(rows[i]+offset, transparent, outError)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800277 if (state == TICK_START ||
278 (state == TICK_OUTSIDE_1 && multipleAllowed)) {
279 *outTop = i-1;
280 *outBottom = height-2;
281 found = true;
282 if (outDivs != NULL) {
283 *outDivs += 2;
284 }
285 state = TICK_INSIDE_1;
286 } else if (state == TICK_OUTSIDE_1) {
287 *outError = "Can't have more than one marked region along edge";
288 *outTop = i;
289 return UNKNOWN_ERROR;
290 }
291 } else if (*outError == NULL) {
292 if (state == TICK_INSIDE_1) {
293 // We're done with this div. Move on to the next.
294 *outBottom = i-1;
295 outTop += 2;
296 outBottom += 2;
297 state = TICK_OUTSIDE_1;
298 }
299 } else {
300 *outTop = i;
301 return UNKNOWN_ERROR;
302 }
303 }
304
305 if (required && !found) {
306 *outError = "No marked region found along edge";
307 *outTop = -1;
308 return UNKNOWN_ERROR;
309 }
310
311 return NO_ERROR;
312}
313
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700314static status_t get_horizontal_layout_bounds_ticks(
315 png_bytep row, int width, bool transparent, bool required,
316 int32_t* outLeft, int32_t* outRight, const char** outError)
317{
318 int i;
319 *outLeft = *outRight = 0;
320
321 // Look for left tick
322 if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + 4, transparent, outError)) {
323 // Starting with a layout padding tick
324 i = 1;
325 while (i < width - 1) {
326 (*outLeft)++;
327 i++;
328 int tick = tick_type(row + i * 4, transparent, outError);
329 if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
330 break;
331 }
332 }
333 }
334
335 // Look for right tick
336 if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + (width - 2) * 4, transparent, outError)) {
337 // Ending with a layout padding tick
338 i = width - 2;
339 while (i > 1) {
340 (*outRight)++;
341 i--;
342 int tick = tick_type(row+i*4, transparent, outError);
343 if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
344 break;
345 }
346 }
347 }
348
349 return NO_ERROR;
350}
351
352static status_t get_vertical_layout_bounds_ticks(
353 png_bytepp rows, int offset, int height, bool transparent, bool required,
354 int32_t* outTop, int32_t* outBottom, const char** outError)
355{
356 int i;
357 *outTop = *outBottom = 0;
358
359 // Look for top tick
360 if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[1] + offset, transparent, outError)) {
361 // Starting with a layout padding tick
362 i = 1;
363 while (i < height - 1) {
364 (*outTop)++;
365 i++;
366 int tick = tick_type(rows[i] + offset, transparent, outError);
367 if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
368 break;
369 }
370 }
371 }
372
373 // Look for bottom tick
374 if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[height - 2] + offset, transparent, outError)) {
375 // Ending with a layout padding tick
376 i = height - 2;
377 while (i > 1) {
378 (*outBottom)++;
379 i--;
380 int tick = tick_type(rows[i] + offset, transparent, outError);
381 if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
382 break;
383 }
384 }
385 }
386
387 return NO_ERROR;
388}
389
390
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800391static uint32_t get_color(
392 png_bytepp rows, int left, int top, int right, int bottom)
393{
394 png_bytep color = rows[top] + left*4;
395
396 if (left > right || top > bottom) {
397 return Res_png_9patch::TRANSPARENT_COLOR;
398 }
399
400 while (top <= bottom) {
401 for (int i = left; i <= right; i++) {
402 png_bytep p = rows[top]+i*4;
403 if (color[3] == 0) {
404 if (p[3] != 0) {
405 return Res_png_9patch::NO_COLOR;
406 }
407 } else if (p[0] != color[0] || p[1] != color[1]
408 || p[2] != color[2] || p[3] != color[3]) {
409 return Res_png_9patch::NO_COLOR;
410 }
411 }
412 top++;
413 }
414
415 if (color[3] == 0) {
416 return Res_png_9patch::TRANSPARENT_COLOR;
417 }
418 return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
419}
420
421static void select_patch(
422 int which, int front, int back, int size, int* start, int* end)
423{
424 switch (which) {
425 case 0:
426 *start = 0;
427 *end = front-1;
428 break;
429 case 1:
430 *start = front;
431 *end = back-1;
432 break;
433 case 2:
434 *start = back;
435 *end = size-1;
436 break;
437 }
438}
439
440static uint32_t get_color(image_info* image, int hpatch, int vpatch)
441{
442 int left, right, top, bottom;
443 select_patch(
Narayan Kamath6381dd42014-03-03 17:12:03 +0000444 hpatch, image->xDivs[0], image->xDivs[1],
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 image->width, &left, &right);
446 select_patch(
Narayan Kamath6381dd42014-03-03 17:12:03 +0000447 vpatch, image->yDivs[0], image->yDivs[1],
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 image->height, &top, &bottom);
449 //printf("Selecting h=%d v=%d: (%d,%d)-(%d,%d)\n",
450 // hpatch, vpatch, left, top, right, bottom);
451 const uint32_t c = get_color(image->rows, left, top, right, bottom);
452 NOISY(printf("Color in (%d,%d)-(%d,%d): #%08x\n", left, top, right, bottom, c));
453 return c;
454}
455
456static status_t do_9patch(const char* imageName, image_info* image)
457{
458 image->is9Patch = true;
459
460 int W = image->width;
461 int H = image->height;
462 int i, j;
463
The Android Open Source Project4df24232009-03-05 14:34:35 -0800464 int maxSizeXDivs = W * sizeof(int32_t);
465 int maxSizeYDivs = H * sizeof(int32_t);
Narayan Kamath6381dd42014-03-03 17:12:03 +0000466 int32_t* xDivs = image->xDivs = (int32_t*) malloc(maxSizeXDivs);
467 int32_t* yDivs = image->yDivs = (int32_t*) malloc(maxSizeYDivs);
Elliott Hughesc367d482013-10-29 13:12:55 -0700468 uint8_t numXDivs = 0;
469 uint8_t numYDivs = 0;
470
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800471 int8_t numColors;
472 int numRows;
473 int numCols;
474 int top;
475 int left;
476 int right;
477 int bottom;
478 memset(xDivs, -1, maxSizeXDivs);
479 memset(yDivs, -1, maxSizeYDivs);
480 image->info9Patch.paddingLeft = image->info9Patch.paddingRight =
481 image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
482
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700483 image->layoutBoundsLeft = image->layoutBoundsRight =
484 image->layoutBoundsTop = image->layoutBoundsBottom = 0;
485
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800486 png_bytep p = image->rows[0];
487 bool transparent = p[3] == 0;
488 bool hasColor = false;
489
490 const char* errorMsg = NULL;
491 int errorPixel = -1;
Kenny Root9e652a62010-03-12 14:12:14 -0800492 const char* errorEdge = NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493
494 int colorIndex = 0;
495
496 // Validate size...
497 if (W < 3 || H < 3) {
498 errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
499 goto getout;
500 }
501
502 // Validate frame...
503 if (!transparent &&
504 (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
505 errorMsg = "Must have one-pixel frame that is either transparent or white";
506 goto getout;
507 }
508
509 // Find left and right of sizing areas...
510 if (get_horizontal_ticks(p, W, transparent, true, &xDivs[0],
511 &xDivs[1], &errorMsg, &numXDivs, true) != NO_ERROR) {
512 errorPixel = xDivs[0];
513 errorEdge = "top";
514 goto getout;
515 }
516
517 // Find top and bottom of sizing areas...
518 if (get_vertical_ticks(image->rows, 0, H, transparent, true, &yDivs[0],
519 &yDivs[1], &errorMsg, &numYDivs, true) != NO_ERROR) {
520 errorPixel = yDivs[0];
521 errorEdge = "left";
522 goto getout;
523 }
524
Elliott Hughesc367d482013-10-29 13:12:55 -0700525 // Copy patch size data into image...
526 image->info9Patch.numXDivs = numXDivs;
527 image->info9Patch.numYDivs = numYDivs;
528
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 // Find left and right of padding area...
530 if (get_horizontal_ticks(image->rows[H-1], W, transparent, false, &image->info9Patch.paddingLeft,
531 &image->info9Patch.paddingRight, &errorMsg, NULL, false) != NO_ERROR) {
532 errorPixel = image->info9Patch.paddingLeft;
533 errorEdge = "bottom";
534 goto getout;
535 }
536
537 // Find top and bottom of padding area...
538 if (get_vertical_ticks(image->rows, (W-1)*4, H, transparent, false, &image->info9Patch.paddingTop,
539 &image->info9Patch.paddingBottom, &errorMsg, NULL, false) != NO_ERROR) {
540 errorPixel = image->info9Patch.paddingTop;
541 errorEdge = "right";
542 goto getout;
543 }
544
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700545 // Find left and right of layout padding...
546 get_horizontal_layout_bounds_ticks(image->rows[H-1], W, transparent, false,
547 &image->layoutBoundsLeft,
548 &image->layoutBoundsRight, &errorMsg);
549
550 get_vertical_layout_bounds_ticks(image->rows, (W-1)*4, H, transparent, false,
551 &image->layoutBoundsTop,
552 &image->layoutBoundsBottom, &errorMsg);
553
554 image->haveLayoutBounds = image->layoutBoundsLeft != 0
555 || image->layoutBoundsRight != 0
556 || image->layoutBoundsTop != 0
557 || image->layoutBoundsBottom != 0;
558
559 if (image->haveLayoutBounds) {
560 NOISY(printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
561 image->layoutBoundsRight, image->layoutBoundsBottom));
562 }
563
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800564 // If padding is not yet specified, take values from size.
565 if (image->info9Patch.paddingLeft < 0) {
566 image->info9Patch.paddingLeft = xDivs[0];
567 image->info9Patch.paddingRight = W - 2 - xDivs[1];
568 } else {
569 // Adjust value to be correct!
570 image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
571 }
572 if (image->info9Patch.paddingTop < 0) {
573 image->info9Patch.paddingTop = yDivs[0];
574 image->info9Patch.paddingBottom = H - 2 - yDivs[1];
575 } else {
576 // Adjust value to be correct!
577 image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
578 }
579
580 NOISY(printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
581 image->info9Patch.xDivs[0], image->info9Patch.xDivs[1],
582 image->info9Patch.yDivs[0], image->info9Patch.yDivs[1]));
583 NOISY(printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
584 image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
585 image->info9Patch.paddingTop, image->info9Patch.paddingBottom));
586
587 // Remove frame from image.
John Recke982b722013-08-26 16:53:40 -0700588 image->rows = (png_bytepp)malloc((H-2) * sizeof(png_bytep));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800589 for (i=0; i<(H-2); i++) {
590 image->rows[i] = image->allocRows[i+1];
591 memmove(image->rows[i], image->rows[i]+4, (W-2)*4);
592 }
593 image->width -= 2;
594 W = image->width;
595 image->height -= 2;
596 H = image->height;
597
598 // Figure out the number of rows and columns in the N-patch
599 numCols = numXDivs + 1;
600 if (xDivs[0] == 0) { // Column 1 is strechable
601 numCols--;
602 }
603 if (xDivs[numXDivs - 1] == W) {
604 numCols--;
605 }
606 numRows = numYDivs + 1;
607 if (yDivs[0] == 0) { // Row 1 is strechable
608 numRows--;
609 }
610 if (yDivs[numYDivs - 1] == H) {
611 numRows--;
612 }
Kenny Root9e652a62010-03-12 14:12:14 -0800613
614 // Make sure the amount of rows and columns will fit in the number of
615 // colors we can use in the 9-patch format.
616 if (numRows * numCols > 0x7F) {
617 errorMsg = "Too many rows and columns in 9-patch perimeter";
618 goto getout;
619 }
620
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800621 numColors = numRows * numCols;
622 image->info9Patch.numColors = numColors;
Narayan Kamath6381dd42014-03-03 17:12:03 +0000623 image->colors = (uint32_t*)malloc(numColors * sizeof(uint32_t));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800624
625 // Fill in color information for each patch.
626
627 uint32_t c;
628 top = 0;
629
630 // The first row always starts with the top being at y=0 and the bottom
631 // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
632 // the first row is stretchable along the Y axis, otherwise it is fixed.
633 // The last row always ends with the bottom being bitmap.height and the top
634 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
635 // yDivs[numYDivs-1]. In the former case the last row is stretchable along
636 // the Y axis, otherwise it is fixed.
637 //
638 // The first and last columns are similarly treated with respect to the X
639 // axis.
640 //
641 // The above is to help explain some of the special casing that goes on the
642 // code below.
643
644 // The initial yDiv and whether the first row is considered stretchable or
645 // not depends on whether yDiv[0] was zero or not.
646 for (j = (yDivs[0] == 0 ? 1 : 0);
647 j <= numYDivs && top < H;
648 j++) {
649 if (j == numYDivs) {
650 bottom = H;
651 } else {
652 bottom = yDivs[j];
653 }
654 left = 0;
655 // The initial xDiv and whether the first column is considered
656 // stretchable or not depends on whether xDiv[0] was zero or not.
657 for (i = xDivs[0] == 0 ? 1 : 0;
658 i <= numXDivs && left < W;
659 i++) {
660 if (i == numXDivs) {
661 right = W;
662 } else {
663 right = xDivs[i];
664 }
665 c = get_color(image->rows, left, top, right - 1, bottom - 1);
Narayan Kamath6381dd42014-03-03 17:12:03 +0000666 image->colors[colorIndex++] = c;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800667 NOISY(if (c != Res_png_9patch::NO_COLOR) hasColor = true);
668 left = right;
669 }
670 top = bottom;
671 }
672
673 assert(colorIndex == numColors);
674
675 for (i=0; i<numColors; i++) {
676 if (hasColor) {
677 if (i == 0) printf("Colors in %s:\n ", imageName);
Narayan Kamath6381dd42014-03-03 17:12:03 +0000678 printf(" #%08x", image->colors[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800679 if (i == numColors - 1) printf("\n");
680 }
681 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800682getout:
683 if (errorMsg) {
684 fprintf(stderr,
685 "ERROR: 9-patch image %s malformed.\n"
686 " %s.\n", imageName, errorMsg);
Kenny Root9e652a62010-03-12 14:12:14 -0800687 if (errorEdge != NULL) {
688 if (errorPixel >= 0) {
689 fprintf(stderr,
690 " Found at pixel #%d along %s edge.\n", errorPixel, errorEdge);
691 } else {
692 fprintf(stderr,
693 " Found along %s edge.\n", errorEdge);
694 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800695 }
696 return UNKNOWN_ERROR;
697 }
698 return NO_ERROR;
699}
700
Narayan Kamath6381dd42014-03-03 17:12:03 +0000701static void checkNinePatchSerialization(Res_png_9patch* inPatch, void* data)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800702{
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800703 size_t patchSize = inPatch->serializedSize();
Narayan Kamath6381dd42014-03-03 17:12:03 +0000704 void* newData = malloc(patchSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800705 memcpy(newData, data, patchSize);
706 Res_png_9patch* outPatch = inPatch->deserialize(newData);
707 // deserialization is done in place, so outPatch == newData
708 assert(outPatch == newData);
709 assert(outPatch->numXDivs == inPatch->numXDivs);
710 assert(outPatch->numYDivs == inPatch->numYDivs);
711 assert(outPatch->paddingLeft == inPatch->paddingLeft);
712 assert(outPatch->paddingRight == inPatch->paddingRight);
713 assert(outPatch->paddingTop == inPatch->paddingTop);
714 assert(outPatch->paddingBottom == inPatch->paddingBottom);
715 for (int i = 0; i < outPatch->numXDivs; i++) {
716 assert(outPatch->xDivs[i] == inPatch->xDivs[i]);
717 }
718 for (int i = 0; i < outPatch->numYDivs; i++) {
719 assert(outPatch->yDivs[i] == inPatch->yDivs[i]);
720 }
721 for (int i = 0; i < outPatch->numColors; i++) {
722 assert(outPatch->colors[i] == inPatch->colors[i]);
723 }
724 free(newData);
725}
726
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800727static void dump_image(int w, int h, png_bytepp rows, int color_type)
728{
729 int i, j, rr, gg, bb, aa;
730
731 int bpp;
732 if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
733 bpp = 1;
734 } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
735 bpp = 2;
736 } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
Kenny Root9e652a62010-03-12 14:12:14 -0800737 // We use a padding byte even when there is no alpha
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738 bpp = 4;
739 } else {
740 printf("Unknown color type %d.\n", color_type);
741 }
742
743 for (j = 0; j < h; j++) {
744 png_bytep row = rows[j];
745 for (i = 0; i < w; i++) {
746 rr = row[0];
747 gg = row[1];
748 bb = row[2];
749 aa = row[3];
750 row += bpp;
751
752 if (i == 0) {
753 printf("Row %d:", j);
754 }
755 switch (bpp) {
756 case 1:
757 printf(" (%d)", rr);
758 break;
759 case 2:
760 printf(" (%d %d", rr, gg);
761 break;
762 case 3:
763 printf(" (%d %d %d)", rr, gg, bb);
764 break;
765 case 4:
766 printf(" (%d %d %d %d)", rr, gg, bb, aa);
767 break;
768 }
769 if (i == (w - 1)) {
770 NOISY(printf("\n"));
771 }
772 }
773 }
774}
775
776#define MAX(a,b) ((a)>(b)?(a):(b))
777#define ABS(a) ((a)<0?-(a):(a))
778
779static void analyze_image(const char *imageName, image_info &imageInfo, int grayscaleTolerance,
780 png_colorp rgbPalette, png_bytep alphaPalette,
781 int *paletteEntries, bool *hasTransparency, int *colorType,
782 png_bytepp outRows)
783{
784 int w = imageInfo.width;
785 int h = imageInfo.height;
786 int i, j, rr, gg, bb, aa, idx;
787 uint32_t colors[256], col;
788 int num_colors = 0;
789 int maxGrayDeviation = 0;
790
791 bool isOpaque = true;
792 bool isPalette = true;
793 bool isGrayscale = true;
794
795 // Scan the entire image and determine if:
796 // 1. Every pixel has R == G == B (grayscale)
797 // 2. Every pixel has A == 255 (opaque)
798 // 3. There are no more than 256 distinct RGBA colors
799
800 // NOISY(printf("Initial image data:\n"));
801 // dump_image(w, h, imageInfo.rows, PNG_COLOR_TYPE_RGB_ALPHA);
802
803 for (j = 0; j < h; j++) {
804 png_bytep row = imageInfo.rows[j];
805 png_bytep out = outRows[j];
806 for (i = 0; i < w; i++) {
807 rr = *row++;
808 gg = *row++;
809 bb = *row++;
810 aa = *row++;
811
812 int odev = maxGrayDeviation;
813 maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
814 maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
815 maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
816 if (maxGrayDeviation > odev) {
817 NOISY(printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
818 maxGrayDeviation, i, j, rr, gg, bb, aa));
819 }
820
821 // Check if image is really grayscale
822 if (isGrayscale) {
823 if (rr != gg || rr != bb) {
824 NOISY(printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
825 i, j, rr, gg, bb, aa));
826 isGrayscale = false;
827 }
828 }
829
830 // Check if image is really opaque
831 if (isOpaque) {
832 if (aa != 0xff) {
833 NOISY(printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
834 i, j, rr, gg, bb, aa));
835 isOpaque = false;
836 }
837 }
838
839 // Check if image is really <= 256 colors
840 if (isPalette) {
841 col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
842 bool match = false;
843 for (idx = 0; idx < num_colors; idx++) {
844 if (colors[idx] == col) {
845 match = true;
846 break;
847 }
848 }
849
850 // Write the palette index for the pixel to outRows optimistically
851 // We might overwrite it later if we decide to encode as gray or
852 // gray + alpha
853 *out++ = idx;
854 if (!match) {
855 if (num_colors == 256) {
856 NOISY(printf("Found 257th color at %d, %d\n", i, j));
857 isPalette = false;
858 } else {
859 colors[num_colors++] = col;
860 }
861 }
862 }
863 }
864 }
865
866 *paletteEntries = 0;
867 *hasTransparency = !isOpaque;
868 int bpp = isOpaque ? 3 : 4;
869 int paletteSize = w * h + bpp * num_colors;
870
871 NOISY(printf("isGrayscale = %s\n", isGrayscale ? "true" : "false"));
872 NOISY(printf("isOpaque = %s\n", isOpaque ? "true" : "false"));
873 NOISY(printf("isPalette = %s\n", isPalette ? "true" : "false"));
874 NOISY(printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
875 paletteSize, 2 * w * h, bpp * w * h));
876 NOISY(printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance));
877
878 // Choose the best color type for the image.
879 // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
880 // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
881 // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
882 // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
883 // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
884 if (isGrayscale) {
885 if (isOpaque) {
886 *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
887 } else {
888 // Use a simple heuristic to determine whether using a palette will
889 // save space versus using gray + alpha for each pixel.
890 // This doesn't take into account chunk overhead, filtering, LZ
891 // compression, etc.
892 if (isPalette && (paletteSize < 2 * w * h)) {
893 *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
894 } else {
895 *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
896 }
897 }
898 } else if (isPalette && (paletteSize < bpp * w * h)) {
899 *colorType = PNG_COLOR_TYPE_PALETTE;
900 } else {
901 if (maxGrayDeviation <= grayscaleTolerance) {
902 printf("%s: forcing image to gray (max deviation = %d)\n", imageName, maxGrayDeviation);
903 *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
904 } else {
905 *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
906 }
907 }
908
909 // Perform postprocessing of the image or palette data based on the final
910 // color type chosen
911
912 if (*colorType == PNG_COLOR_TYPE_PALETTE) {
913 // Create separate RGB and Alpha palettes and set the number of colors
914 *paletteEntries = num_colors;
915
916 // Create the RGB and alpha palettes
917 for (int idx = 0; idx < num_colors; idx++) {
918 col = colors[idx];
919 rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff);
920 rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
921 rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff);
922 alphaPalette[idx] = (png_byte) (col & 0xff);
923 }
924 } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
925 // If the image is gray or gray + alpha, compact the pixels into outRows
926 for (j = 0; j < h; j++) {
927 png_bytep row = imageInfo.rows[j];
928 png_bytep out = outRows[j];
929 for (i = 0; i < w; i++) {
930 rr = *row++;
931 gg = *row++;
932 bb = *row++;
933 aa = *row++;
Elliott Hughesc367d482013-10-29 13:12:55 -0700934
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800935 if (isGrayscale) {
936 *out++ = rr;
937 } else {
938 *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
939 }
940 if (!isOpaque) {
941 *out++ = aa;
942 }
943 }
944 }
945 }
946}
947
948
949static void write_png(const char* imageName,
950 png_structp write_ptr, png_infop write_info,
951 image_info& imageInfo, int grayscaleTolerance)
952{
953 bool optimize = true;
954 png_uint_32 width, height;
955 int color_type;
956 int bit_depth, interlace_type, compression_type;
957 int i;
958
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700959 png_unknown_chunk unknowns[2];
Marco Nelissen6a1fade2009-04-20 16:16:01 -0700960 unknowns[0].data = NULL;
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700961 unknowns[1].data = NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800962
John Recke982b722013-08-26 16:53:40 -0700963 png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * sizeof(png_bytep));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800964 if (outRows == (png_bytepp) 0) {
965 printf("Can't allocate output buffer!\n");
966 exit(1);
967 }
968 for (i = 0; i < (int) imageInfo.height; i++) {
969 outRows[i] = (png_bytep) malloc(2 * (int) imageInfo.width);
970 if (outRows[i] == (png_bytep) 0) {
971 printf("Can't allocate output buffer!\n");
972 exit(1);
973 }
974 }
975
976 png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
977
978 NOISY(printf("Writing image %s: w = %d, h = %d\n", imageName,
979 (int) imageInfo.width, (int) imageInfo.height));
980
981 png_color rgbPalette[256];
982 png_byte alphaPalette[256];
983 bool hasTransparency;
984 int paletteEntries;
985
986 analyze_image(imageName, imageInfo, grayscaleTolerance, rgbPalette, alphaPalette,
987 &paletteEntries, &hasTransparency, &color_type, outRows);
988
989 // If the image is a 9-patch, we need to preserve it as a ARGB file to make
990 // sure the pixels will not be pre-dithered/clamped until we decide they are
991 if (imageInfo.is9Patch && (color_type == PNG_COLOR_TYPE_RGB ||
992 color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)) {
993 color_type = PNG_COLOR_TYPE_RGB_ALPHA;
994 }
995
996 switch (color_type) {
997 case PNG_COLOR_TYPE_PALETTE:
998 NOISY(printf("Image %s has %d colors%s, using PNG_COLOR_TYPE_PALETTE\n",
999 imageName, paletteEntries,
1000 hasTransparency ? " (with alpha)" : ""));
1001 break;
1002 case PNG_COLOR_TYPE_GRAY:
1003 NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName));
1004 break;
1005 case PNG_COLOR_TYPE_GRAY_ALPHA:
1006 NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName));
1007 break;
1008 case PNG_COLOR_TYPE_RGB:
1009 NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName));
1010 break;
1011 case PNG_COLOR_TYPE_RGB_ALPHA:
1012 NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName));
1013 break;
1014 }
1015
1016 png_set_IHDR(write_ptr, write_info, imageInfo.width, imageInfo.height,
1017 8, color_type, PNG_INTERLACE_NONE,
1018 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
1019
1020 if (color_type == PNG_COLOR_TYPE_PALETTE) {
1021 png_set_PLTE(write_ptr, write_info, rgbPalette, paletteEntries);
1022 if (hasTransparency) {
1023 png_set_tRNS(write_ptr, write_info, alphaPalette, paletteEntries, (png_color_16p) 0);
1024 }
1025 png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
1026 } else {
1027 png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
1028 }
1029
1030 if (imageInfo.is9Patch) {
Amith Yamasaniec4a5042012-04-04 10:27:15 -07001031 int chunk_count = 1 + (imageInfo.haveLayoutBounds ? 1 : 0);
1032 int p_index = imageInfo.haveLayoutBounds ? 1 : 0;
1033 int b_index = 0;
1034 png_byte *chunk_names = imageInfo.haveLayoutBounds
1035 ? (png_byte*)"npLb\0npTc\0"
1036 : (png_byte*)"npTc";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001037 NOISY(printf("Adding 9-patch info...\n"));
Amith Yamasaniec4a5042012-04-04 10:27:15 -07001038 strcpy((char*)unknowns[p_index].name, "npTc");
Narayan Kamath6381dd42014-03-03 17:12:03 +00001039 unknowns[p_index].data = (png_byte*)imageInfo.serialize9patch();
Amith Yamasaniec4a5042012-04-04 10:27:15 -07001040 unknowns[p_index].size = imageInfo.info9Patch.serializedSize();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001041 // TODO: remove the check below when everything works
Amith Yamasaniec4a5042012-04-04 10:27:15 -07001042 checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[p_index].data);
1043
1044 if (imageInfo.haveLayoutBounds) {
1045 int chunk_size = sizeof(png_uint_32) * 4;
1046 strcpy((char*)unknowns[b_index].name, "npLb");
1047 unknowns[b_index].data = (png_byte*) calloc(chunk_size, 1);
1048 memcpy(unknowns[b_index].data, &imageInfo.layoutBoundsLeft, chunk_size);
1049 unknowns[b_index].size = chunk_size;
1050 }
1051
John Recke982b722013-08-26 16:53:40 -07001052 for (int i = 0; i < chunk_count; i++) {
1053 unknowns[i].location = PNG_HAVE_PLTE;
1054 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001055 png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS,
Amith Yamasaniec4a5042012-04-04 10:27:15 -07001056 chunk_names, chunk_count);
1057 png_set_unknown_chunks(write_ptr, write_info, unknowns, chunk_count);
John Recke982b722013-08-26 16:53:40 -07001058#if PNG_LIBPNG_VER < 10600
1059 /* Deal with unknown chunk location bug in 1.5.x and earlier */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001060 png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE);
Amith Yamasaniec4a5042012-04-04 10:27:15 -07001061 if (imageInfo.haveLayoutBounds) {
1062 png_set_unknown_chunk_location(write_ptr, write_info, 1, PNG_HAVE_PLTE);
1063 }
John Recke982b722013-08-26 16:53:40 -07001064#endif
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001065 }
1066
Amith Yamasaniec4a5042012-04-04 10:27:15 -07001067
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001068 png_write_info(write_ptr, write_info);
1069
1070 png_bytepp rows;
1071 if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
John Recke982b722013-08-26 16:53:40 -07001072 if (color_type == PNG_COLOR_TYPE_RGB) {
1073 png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
1074 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001075 rows = imageInfo.rows;
1076 } else {
1077 rows = outRows;
1078 }
1079 png_write_image(write_ptr, rows);
1080
1081// NOISY(printf("Final image data:\n"));
1082// dump_image(imageInfo.width, imageInfo.height, rows, color_type);
1083
1084 png_write_end(write_ptr, write_info);
1085
1086 for (i = 0; i < (int) imageInfo.height; i++) {
1087 free(outRows[i]);
1088 }
1089 free(outRows);
Marco Nelissen6a1fade2009-04-20 16:16:01 -07001090 free(unknowns[0].data);
Amith Yamasaniec4a5042012-04-04 10:27:15 -07001091 free(unknowns[1].data);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001092
1093 png_get_IHDR(write_ptr, write_info, &width, &height,
1094 &bit_depth, &color_type, &interlace_type,
1095 &compression_type, NULL);
1096
1097 NOISY(printf("Image written: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
1098 (int)width, (int)height, bit_depth, color_type, interlace_type,
1099 compression_type));
1100}
1101
Jeff Brownc0f73662012-03-16 22:17:41 -07001102status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001103 const sp<AaptFile>& file, String8* outNewLeafName)
1104{
1105 String8 ext(file->getPath().getPathExtension());
1106
1107 // We currently only process PNG images.
1108 if (strcmp(ext.string(), ".png") != 0) {
1109 return NO_ERROR;
1110 }
1111
1112 // Example of renaming a file:
1113 //*outNewLeafName = file->getPath().getBasePath().getFileName();
1114 //outNewLeafName->append(".nupng");
1115
1116 String8 printableName(file->getPrintableSource());
1117
Dianne Hackborne6b68032011-10-13 16:26:02 -07001118 if (bundle->getVerbose()) {
1119 printf("Processing image: %s\n", printableName.string());
1120 }
1121
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001122 png_structp read_ptr = NULL;
1123 png_infop read_info = NULL;
1124 FILE* fp;
1125
1126 image_info imageInfo;
1127
1128 png_structp write_ptr = NULL;
1129 png_infop write_info = NULL;
1130
1131 status_t error = UNKNOWN_ERROR;
1132
1133 const size_t nameLen = file->getPath().length();
1134
1135 fp = fopen(file->getSourceFile().string(), "rb");
1136 if (fp == NULL) {
1137 fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string());
1138 goto bail;
1139 }
1140
1141 read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
1142 (png_error_ptr)NULL);
1143 if (!read_ptr) {
1144 goto bail;
1145 }
1146
1147 read_info = png_create_info_struct(read_ptr);
1148 if (!read_info) {
1149 goto bail;
1150 }
1151
1152 if (setjmp(png_jmpbuf(read_ptr))) {
1153 goto bail;
1154 }
1155
1156 png_init_io(read_ptr, fp);
1157
1158 read_png(printableName.string(), read_ptr, read_info, &imageInfo);
1159
1160 if (nameLen > 6) {
1161 const char* name = file->getPath().string();
1162 if (name[nameLen-5] == '9' && name[nameLen-6] == '.') {
1163 if (do_9patch(printableName.string(), &imageInfo) != NO_ERROR) {
1164 goto bail;
1165 }
1166 }
1167 }
1168
1169 write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
1170 (png_error_ptr)NULL);
1171 if (!write_ptr)
1172 {
1173 goto bail;
1174 }
1175
1176 write_info = png_create_info_struct(write_ptr);
1177 if (!write_info)
1178 {
1179 goto bail;
1180 }
1181
1182 png_set_write_fn(write_ptr, (void*)file.get(),
1183 png_write_aapt_file, png_flush_aapt_file);
1184
1185 if (setjmp(png_jmpbuf(write_ptr)))
1186 {
1187 goto bail;
1188 }
1189
1190 write_png(printableName.string(), write_ptr, write_info, imageInfo,
1191 bundle->getGrayscaleTolerance());
1192
1193 error = NO_ERROR;
1194
1195 if (bundle->getVerbose()) {
1196 fseek(fp, 0, SEEK_END);
1197 size_t oldSize = (size_t)ftell(fp);
1198 size_t newSize = file->getSize();
1199 float factor = ((float)newSize)/oldSize;
1200 int percent = (int)(factor*100);
1201 printf(" (processed image %s: %d%% size of source)\n", printableName.string(), percent);
1202 }
1203
1204bail:
1205 if (read_ptr) {
1206 png_destroy_read_struct(&read_ptr, &read_info, (png_infopp)NULL);
1207 }
1208 if (fp) {
1209 fclose(fp);
1210 }
1211 if (write_ptr) {
1212 png_destroy_write_struct(&write_ptr, &write_info);
1213 }
1214
1215 if (error != NO_ERROR) {
1216 fprintf(stderr, "ERROR: Failure processing PNG image %s\n",
1217 file->getPrintableSource().string());
1218 }
1219 return error;
1220}
1221
Jeff Brownc0f73662012-03-16 22:17:41 -07001222status_t preProcessImageToCache(const Bundle* bundle, const String8& source, const String8& dest)
Josiah Gaskin8a39da82011-06-06 17:00:35 -07001223{
1224 png_structp read_ptr = NULL;
1225 png_infop read_info = NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001226
Josiah Gaskin8a39da82011-06-06 17:00:35 -07001227 FILE* fp;
1228
1229 image_info imageInfo;
1230
1231 png_structp write_ptr = NULL;
1232 png_infop write_info = NULL;
1233
1234 status_t error = UNKNOWN_ERROR;
1235
Dianne Hackborne6b68032011-10-13 16:26:02 -07001236 if (bundle->getVerbose()) {
1237 printf("Processing image to cache: %s => %s\n", source.string(), dest.string());
1238 }
1239
Josiah Gaskin8a39da82011-06-06 17:00:35 -07001240 // Get a file handler to read from
1241 fp = fopen(source.string(),"rb");
1242 if (fp == NULL) {
1243 fprintf(stderr, "%s ERROR: Unable to open PNG file\n", source.string());
1244 return error;
1245 }
1246
1247 // Call libpng to get a struct to read image data into
1248 read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
1249 if (!read_ptr) {
1250 fclose(fp);
1251 png_destroy_read_struct(&read_ptr, &read_info,NULL);
1252 return error;
1253 }
1254
1255 // Call libpng to get a struct to read image info into
1256 read_info = png_create_info_struct(read_ptr);
1257 if (!read_info) {
1258 fclose(fp);
1259 png_destroy_read_struct(&read_ptr, &read_info,NULL);
1260 return error;
1261 }
1262
1263 // Set a jump point for libpng to long jump back to on error
1264 if (setjmp(png_jmpbuf(read_ptr))) {
1265 fclose(fp);
1266 png_destroy_read_struct(&read_ptr, &read_info,NULL);
1267 return error;
1268 }
1269
1270 // Set up libpng to read from our file.
1271 png_init_io(read_ptr,fp);
1272
1273 // Actually read data from the file
1274 read_png(source.string(), read_ptr, read_info, &imageInfo);
1275
1276 // We're done reading so we can clean up
1277 // Find old file size before releasing handle
1278 fseek(fp, 0, SEEK_END);
1279 size_t oldSize = (size_t)ftell(fp);
1280 fclose(fp);
1281 png_destroy_read_struct(&read_ptr, &read_info,NULL);
1282
1283 // Check to see if we're dealing with a 9-patch
1284 // If we are, process appropriately
1285 if (source.getBasePath().getPathExtension() == ".9") {
1286 if (do_9patch(source.string(), &imageInfo) != NO_ERROR) {
1287 return error;
1288 }
1289 }
1290
1291 // Call libpng to create a structure to hold the processed image data
1292 // that can be written to disk
1293 write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
1294 if (!write_ptr) {
1295 png_destroy_write_struct(&write_ptr, &write_info);
1296 return error;
1297 }
1298
1299 // Call libpng to create a structure to hold processed image info that can
1300 // be written to disk
1301 write_info = png_create_info_struct(write_ptr);
1302 if (!write_info) {
1303 png_destroy_write_struct(&write_ptr, &write_info);
1304 return error;
1305 }
1306
1307 // Open up our destination file for writing
1308 fp = fopen(dest.string(), "wb");
1309 if (!fp) {
1310 fprintf(stderr, "%s ERROR: Unable to open PNG file\n", dest.string());
1311 png_destroy_write_struct(&write_ptr, &write_info);
1312 return error;
1313 }
1314
1315 // Set up libpng to write to our file
1316 png_init_io(write_ptr, fp);
1317
1318 // Set up a jump for libpng to long jump back on on errors
1319 if (setjmp(png_jmpbuf(write_ptr))) {
1320 fclose(fp);
1321 png_destroy_write_struct(&write_ptr, &write_info);
1322 return error;
1323 }
1324
1325 // Actually write out to the new png
1326 write_png(dest.string(), write_ptr, write_info, imageInfo,
1327 bundle->getGrayscaleTolerance());
1328
1329 if (bundle->getVerbose()) {
1330 // Find the size of our new file
1331 FILE* reader = fopen(dest.string(), "rb");
1332 fseek(reader, 0, SEEK_END);
1333 size_t newSize = (size_t)ftell(reader);
1334 fclose(reader);
1335
1336 float factor = ((float)newSize)/oldSize;
1337 int percent = (int)(factor*100);
1338 printf(" (processed image to cache entry %s: %d%% size of source)\n",
1339 dest.string(), percent);
1340 }
1341
1342 //Clean up
1343 fclose(fp);
1344 png_destroy_write_struct(&write_ptr, &write_info);
1345
1346 return NO_ERROR;
1347}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001348
1349status_t postProcessImage(const sp<AaptAssets>& assets,
1350 ResourceTable* table, const sp<AaptFile>& file)
1351{
1352 String8 ext(file->getPath().getPathExtension());
1353
1354 // At this point, now that we have all the resource data, all we need to
1355 // do is compile XML files.
1356 if (strcmp(ext.string(), ".xml") == 0) {
1357 return compileXmlFile(assets, file, table);
1358 }
1359
1360 return NO_ERROR;
1361}