2 // Copyright 2006 The Android Open Source Project
4 // Build resource files from raw assets.
11 #include <androidfw/ResourceTypes.h>
12 #include <utils/ByteOrder.h>
19 png_write_aapt_file(png_structp png_ptr
, png_bytep data
, png_size_t length
)
21 status_t err
= ((AaptFile
*)png_ptr
->io_ptr
)->writeData(data
, length
);
22 if (err
!= NO_ERROR
) {
23 png_error(png_ptr
, "Write Error");
29 png_flush_aapt_file(png_structp png_ptr
)
33 // This holds an image as 8bpp RGBA.
36 image_info() : rows(NULL
), is9Patch(false), allocRows(NULL
) { }
38 if (rows
&& rows
!= allocRows
) {
42 for (int i
=0; i
<(int)allocHeight
; i
++) {
47 free(info9Patch
.xDivs
);
48 free(info9Patch
.yDivs
);
49 free(info9Patch
.colors
);
58 Res_png_9patch info9Patch
;
60 // Layout padding, if relevant
61 bool haveLayoutBounds
;
62 int32_t layoutBoundsLeft
;
63 int32_t layoutBoundsTop
;
64 int32_t layoutBoundsRight
;
65 int32_t layoutBoundsBottom
;
67 png_uint_32 allocHeight
;
71 static void read_png(const char* imageName
,
72 png_structp read_ptr
, png_infop read_info
,
73 image_info
* outImageInfo
)
76 int bit_depth
, interlace_type
, compression_type
;
79 png_read_info(read_ptr
, read_info
);
81 png_get_IHDR(read_ptr
, read_info
, &outImageInfo
->width
,
82 &outImageInfo
->height
, &bit_depth
, &color_type
,
83 &interlace_type
, &compression_type
, NULL
);
85 //printf("Image %s:\n", imageName);
86 //printf("color_type=%d, bit_depth=%d, interlace_type=%d, compression_type=%d\n",
87 // color_type, bit_depth, interlace_type, compression_type);
89 if (color_type
== PNG_COLOR_TYPE_PALETTE
)
90 png_set_palette_to_rgb(read_ptr
);
92 if (color_type
== PNG_COLOR_TYPE_GRAY
&& bit_depth
< 8)
93 png_set_gray_1_2_4_to_8(read_ptr
);
95 if (png_get_valid(read_ptr
, read_info
, PNG_INFO_tRNS
)) {
96 //printf("Has PNG_INFO_tRNS!\n");
97 png_set_tRNS_to_alpha(read_ptr
);
101 png_set_strip_16(read_ptr
);
103 if ((color_type
&PNG_COLOR_MASK_ALPHA
) == 0)
104 png_set_add_alpha(read_ptr
, 0xFF, PNG_FILLER_AFTER
);
106 if (color_type
== PNG_COLOR_TYPE_GRAY
|| color_type
== PNG_COLOR_TYPE_GRAY_ALPHA
)
107 png_set_gray_to_rgb(read_ptr
);
109 png_read_update_info(read_ptr
, read_info
);
111 outImageInfo
->rows
= (png_bytepp
)malloc(
112 outImageInfo
->height
* png_sizeof(png_bytep
));
113 outImageInfo
->allocHeight
= outImageInfo
->height
;
114 outImageInfo
->allocRows
= outImageInfo
->rows
;
116 png_set_rows(read_ptr
, read_info
, outImageInfo
->rows
);
118 for (i
= 0; i
< (int)outImageInfo
->height
; i
++)
120 outImageInfo
->rows
[i
] = (png_bytep
)
121 malloc(png_get_rowbytes(read_ptr
, read_info
));
124 png_read_image(read_ptr
, outImageInfo
->rows
);
126 png_read_end(read_ptr
, read_info
);
128 NOISY(printf("Image %s: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
130 (int)outImageInfo
->width
, (int)outImageInfo
->height
,
131 bit_depth
, color_type
,
132 interlace_type
, compression_type
));
134 png_get_IHDR(read_ptr
, read_info
, &outImageInfo
->width
,
135 &outImageInfo
->height
, &bit_depth
, &color_type
,
136 &interlace_type
, &compression_type
, NULL
);
139 #define COLOR_TRANSPARENT 0
140 #define COLOR_WHITE 0xFFFFFFFF
141 #define COLOR_TICK 0xFF000000
142 #define COLOR_LAYOUT_BOUNDS_TICK 0xFF0000FF
147 TICK_TYPE_LAYOUT_BOUNDS
,
151 static int tick_type(png_bytep p
, bool transparent
, const char** outError
)
153 png_uint_32 color
= p
[0] | (p
[1] << 8) | (p
[2] << 16) | (p
[3] << 24);
157 return TICK_TYPE_NONE
;
159 if (color
== COLOR_LAYOUT_BOUNDS_TICK
) {
160 return TICK_TYPE_LAYOUT_BOUNDS
;
162 if (color
== COLOR_TICK
) {
163 return TICK_TYPE_TICK
;
168 *outError
= "Frame pixels must be either solid or transparent (not intermediate alphas)";
169 return TICK_TYPE_NONE
;
171 if (p
[0] != 0 || p
[1] != 0 || p
[2] != 0) {
172 *outError
= "Ticks in transparent frame must be black or red";
174 return TICK_TYPE_TICK
;
178 *outError
= "White frame must be a solid color (no alpha)";
180 if (color
== COLOR_WHITE
) {
181 return TICK_TYPE_NONE
;
183 if (color
== COLOR_TICK
) {
184 return TICK_TYPE_TICK
;
186 if (color
== COLOR_LAYOUT_BOUNDS_TICK
) {
187 return TICK_TYPE_LAYOUT_BOUNDS
;
190 if (p
[0] != 0 || p
[1] != 0 || p
[2] != 0) {
191 *outError
= "Ticks in white frame must be black or red";
192 return TICK_TYPE_NONE
;
194 return TICK_TYPE_TICK
;
203 static status_t
get_horizontal_ticks(
204 png_bytep row
, int width
, bool transparent
, bool required
,
205 int32_t* outLeft
, int32_t* outRight
, const char** outError
,
206 uint8_t* outDivs
, bool multipleAllowed
)
209 *outLeft
= *outRight
= -1;
210 int state
= TICK_START
;
213 for (i
=1; i
<width
-1; i
++) {
214 if (TICK_TYPE_TICK
== tick_type(row
+i
*4, transparent
, outError
)) {
215 if (state
== TICK_START
||
216 (state
== TICK_OUTSIDE_1
&& multipleAllowed
)) {
220 if (outDivs
!= NULL
) {
223 state
= TICK_INSIDE_1
;
224 } else if (state
== TICK_OUTSIDE_1
) {
225 *outError
= "Can't have more than one marked region along edge";
227 return UNKNOWN_ERROR
;
229 } else if (*outError
== NULL
) {
230 if (state
== TICK_INSIDE_1
) {
231 // We're done with this div. Move on to the next.
235 state
= TICK_OUTSIDE_1
;
239 return UNKNOWN_ERROR
;
243 if (required
&& !found
) {
244 *outError
= "No marked region found along edge";
246 return UNKNOWN_ERROR
;
252 static status_t
get_vertical_ticks(
253 png_bytepp rows
, int offset
, int height
, bool transparent
, bool required
,
254 int32_t* outTop
, int32_t* outBottom
, const char** outError
,
255 uint8_t* outDivs
, bool multipleAllowed
)
258 *outTop
= *outBottom
= -1;
259 int state
= TICK_START
;
262 for (i
=1; i
<height
-1; i
++) {
263 if (TICK_TYPE_TICK
== tick_type(rows
[i
]+offset
, transparent
, outError
)) {
264 if (state
== TICK_START
||
265 (state
== TICK_OUTSIDE_1
&& multipleAllowed
)) {
267 *outBottom
= height
-2;
269 if (outDivs
!= NULL
) {
272 state
= TICK_INSIDE_1
;
273 } else if (state
== TICK_OUTSIDE_1
) {
274 *outError
= "Can't have more than one marked region along edge";
276 return UNKNOWN_ERROR
;
278 } else if (*outError
== NULL
) {
279 if (state
== TICK_INSIDE_1
) {
280 // We're done with this div. Move on to the next.
284 state
= TICK_OUTSIDE_1
;
288 return UNKNOWN_ERROR
;
292 if (required
&& !found
) {
293 *outError
= "No marked region found along edge";
295 return UNKNOWN_ERROR
;
301 static status_t
get_horizontal_layout_bounds_ticks(
302 png_bytep row
, int width
, bool transparent
, bool required
,
303 int32_t* outLeft
, int32_t* outRight
, const char** outError
)
306 *outLeft
= *outRight
= 0;
308 // Look for left tick
309 if (TICK_TYPE_LAYOUT_BOUNDS
== tick_type(row
+ 4, transparent
, outError
)) {
310 // Starting with a layout padding tick
312 while (i
< width
- 1) {
315 int tick
= tick_type(row
+ i
* 4, transparent
, outError
);
316 if (tick
!= TICK_TYPE_LAYOUT_BOUNDS
) {
322 // Look for right tick
323 if (TICK_TYPE_LAYOUT_BOUNDS
== tick_type(row
+ (width
- 2) * 4, transparent
, outError
)) {
324 // Ending with a layout padding tick
329 int tick
= tick_type(row
+i
*4, transparent
, outError
);
330 if (tick
!= TICK_TYPE_LAYOUT_BOUNDS
) {
339 static status_t
get_vertical_layout_bounds_ticks(
340 png_bytepp rows
, int offset
, int height
, bool transparent
, bool required
,
341 int32_t* outTop
, int32_t* outBottom
, const char** outError
)
344 *outTop
= *outBottom
= 0;
347 if (TICK_TYPE_LAYOUT_BOUNDS
== tick_type(rows
[1] + offset
, transparent
, outError
)) {
348 // Starting with a layout padding tick
350 while (i
< height
- 1) {
353 int tick
= tick_type(rows
[i
] + offset
, transparent
, outError
);
354 if (tick
!= TICK_TYPE_LAYOUT_BOUNDS
) {
360 // Look for bottom tick
361 if (TICK_TYPE_LAYOUT_BOUNDS
== tick_type(rows
[height
- 2] + offset
, transparent
, outError
)) {
362 // Ending with a layout padding tick
367 int tick
= tick_type(rows
[i
] + offset
, transparent
, outError
);
368 if (tick
!= TICK_TYPE_LAYOUT_BOUNDS
) {
378 static uint32_t get_color(
379 png_bytepp rows
, int left
, int top
, int right
, int bottom
)
381 png_bytep color
= rows
[top
] + left
*4;
383 if (left
> right
|| top
> bottom
) {
384 return Res_png_9patch::TRANSPARENT_COLOR
;
387 while (top
<= bottom
) {
388 for (int i
= left
; i
<= right
; i
++) {
389 png_bytep p
= rows
[top
]+i
*4;
392 return Res_png_9patch::NO_COLOR
;
394 } else if (p
[0] != color
[0] || p
[1] != color
[1]
395 || p
[2] != color
[2] || p
[3] != color
[3]) {
396 return Res_png_9patch::NO_COLOR
;
403 return Res_png_9patch::TRANSPARENT_COLOR
;
405 return (color
[3]<<24) | (color
[0]<<16) | (color
[1]<<8) | color
[2];
408 static void select_patch(
409 int which
, int front
, int back
, int size
, int* start
, int* end
)
427 static uint32_t get_color(image_info
* image
, int hpatch
, int vpatch
)
429 int left
, right
, top
, bottom
;
431 hpatch
, image
->info9Patch
.xDivs
[0], image
->info9Patch
.xDivs
[1],
432 image
->width
, &left
, &right
);
434 vpatch
, image
->info9Patch
.yDivs
[0], image
->info9Patch
.yDivs
[1],
435 image
->height
, &top
, &bottom
);
436 //printf("Selecting h=%d v=%d: (%d,%d)-(%d,%d)\n",
437 // hpatch, vpatch, left, top, right, bottom);
438 const uint32_t c
= get_color(image
->rows
, left
, top
, right
, bottom
);
439 NOISY(printf("Color in (%d,%d)-(%d,%d): #%08x\n", left
, top
, right
, bottom
, c
));
443 static status_t
do_9patch(const char* imageName
, image_info
* image
)
445 image
->is9Patch
= true;
447 int W
= image
->width
;
448 int H
= image
->height
;
451 int maxSizeXDivs
= W
* sizeof(int32_t);
452 int maxSizeYDivs
= H
* sizeof(int32_t);
453 int32_t* xDivs
= (int32_t*) malloc(maxSizeXDivs
);
454 int32_t* yDivs
= (int32_t*) malloc(maxSizeYDivs
);
455 uint8_t numXDivs
= 0;
456 uint8_t numYDivs
= 0;
464 memset(xDivs
, -1, maxSizeXDivs
);
465 memset(yDivs
, -1, maxSizeYDivs
);
466 image
->info9Patch
.paddingLeft
= image
->info9Patch
.paddingRight
=
467 image
->info9Patch
.paddingTop
= image
->info9Patch
.paddingBottom
= -1;
469 image
->layoutBoundsLeft
= image
->layoutBoundsRight
=
470 image
->layoutBoundsTop
= image
->layoutBoundsBottom
= 0;
472 png_bytep p
= image
->rows
[0];
473 bool transparent
= p
[3] == 0;
474 bool hasColor
= false;
476 const char* errorMsg
= NULL
;
478 const char* errorEdge
= NULL
;
483 if (W
< 3 || H
< 3) {
484 errorMsg
= "Image must be at least 3x3 (1x1 without frame) pixels";
490 (p
[0] != 0xFF || p
[1] != 0xFF || p
[2] != 0xFF || p
[3] != 0xFF)) {
491 errorMsg
= "Must have one-pixel frame that is either transparent or white";
495 // Find left and right of sizing areas...
496 if (get_horizontal_ticks(p
, W
, transparent
, true, &xDivs
[0],
497 &xDivs
[1], &errorMsg
, &numXDivs
, true) != NO_ERROR
) {
498 errorPixel
= xDivs
[0];
503 // Find top and bottom of sizing areas...
504 if (get_vertical_ticks(image
->rows
, 0, H
, transparent
, true, &yDivs
[0],
505 &yDivs
[1], &errorMsg
, &numYDivs
, true) != NO_ERROR
) {
506 errorPixel
= yDivs
[0];
511 // Find left and right of padding area...
512 if (get_horizontal_ticks(image
->rows
[H
-1], W
, transparent
, false, &image
->info9Patch
.paddingLeft
,
513 &image
->info9Patch
.paddingRight
, &errorMsg
, NULL
, false) != NO_ERROR
) {
514 errorPixel
= image
->info9Patch
.paddingLeft
;
515 errorEdge
= "bottom";
519 // Find top and bottom of padding area...
520 if (get_vertical_ticks(image
->rows
, (W
-1)*4, H
, transparent
, false, &image
->info9Patch
.paddingTop
,
521 &image
->info9Patch
.paddingBottom
, &errorMsg
, NULL
, false) != NO_ERROR
) {
522 errorPixel
= image
->info9Patch
.paddingTop
;
527 // Find left and right of layout padding...
528 get_horizontal_layout_bounds_ticks(image
->rows
[H
-1], W
, transparent
, false,
529 &image
->layoutBoundsLeft
,
530 &image
->layoutBoundsRight
, &errorMsg
);
532 get_vertical_layout_bounds_ticks(image
->rows
, (W
-1)*4, H
, transparent
, false,
533 &image
->layoutBoundsTop
,
534 &image
->layoutBoundsBottom
, &errorMsg
);
536 image
->haveLayoutBounds
= image
->layoutBoundsLeft
!= 0
537 || image
->layoutBoundsRight
!= 0
538 || image
->layoutBoundsTop
!= 0
539 || image
->layoutBoundsBottom
!= 0;
541 if (image
->haveLayoutBounds
) {
542 NOISY(printf("layoutBounds=%d %d %d %d\n", image
->layoutBoundsLeft
, image
->layoutBoundsTop
,
543 image
->layoutBoundsRight
, image
->layoutBoundsBottom
));
546 // Copy patch data into image
547 image
->info9Patch
.numXDivs
= numXDivs
;
548 image
->info9Patch
.numYDivs
= numYDivs
;
549 image
->info9Patch
.xDivs
= xDivs
;
550 image
->info9Patch
.yDivs
= yDivs
;
552 // If padding is not yet specified, take values from size.
553 if (image
->info9Patch
.paddingLeft
< 0) {
554 image
->info9Patch
.paddingLeft
= xDivs
[0];
555 image
->info9Patch
.paddingRight
= W
- 2 - xDivs
[1];
557 // Adjust value to be correct!
558 image
->info9Patch
.paddingRight
= W
- 2 - image
->info9Patch
.paddingRight
;
560 if (image
->info9Patch
.paddingTop
< 0) {
561 image
->info9Patch
.paddingTop
= yDivs
[0];
562 image
->info9Patch
.paddingBottom
= H
- 2 - yDivs
[1];
564 // Adjust value to be correct!
565 image
->info9Patch
.paddingBottom
= H
- 2 - image
->info9Patch
.paddingBottom
;
568 NOISY(printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName
,
569 image
->info9Patch
.xDivs
[0], image
->info9Patch
.xDivs
[1],
570 image
->info9Patch
.yDivs
[0], image
->info9Patch
.yDivs
[1]));
571 NOISY(printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName
,
572 image
->info9Patch
.paddingLeft
, image
->info9Patch
.paddingRight
,
573 image
->info9Patch
.paddingTop
, image
->info9Patch
.paddingBottom
));
575 // Remove frame from image.
576 image
->rows
= (png_bytepp
)malloc((H
-2) * png_sizeof(png_bytep
));
577 for (i
=0; i
<(H
-2); i
++) {
578 image
->rows
[i
] = image
->allocRows
[i
+1];
579 memmove(image
->rows
[i
], image
->rows
[i
]+4, (W
-2)*4);
586 // Figure out the number of rows and columns in the N-patch
587 numCols
= numXDivs
+ 1;
588 if (xDivs
[0] == 0) { // Column 1 is strechable
591 if (xDivs
[numXDivs
- 1] == W
) {
594 numRows
= numYDivs
+ 1;
595 if (yDivs
[0] == 0) { // Row 1 is strechable
598 if (yDivs
[numYDivs
- 1] == H
) {
602 // Make sure the amount of rows and columns will fit in the number of
603 // colors we can use in the 9-patch format.
604 if (numRows
* numCols
> 0x7F) {
605 errorMsg
= "Too many rows and columns in 9-patch perimeter";
609 numColors
= numRows
* numCols
;
610 image
->info9Patch
.numColors
= numColors
;
611 image
->info9Patch
.colors
= (uint32_t*)malloc(numColors
* sizeof(uint32_t));
613 // Fill in color information for each patch.
618 // The first row always starts with the top being at y=0 and the bottom
619 // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
620 // the first row is stretchable along the Y axis, otherwise it is fixed.
621 // The last row always ends with the bottom being bitmap.height and the top
622 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
623 // yDivs[numYDivs-1]. In the former case the last row is stretchable along
624 // the Y axis, otherwise it is fixed.
626 // The first and last columns are similarly treated with respect to the X
629 // The above is to help explain some of the special casing that goes on the
632 // The initial yDiv and whether the first row is considered stretchable or
633 // not depends on whether yDiv[0] was zero or not.
634 for (j
= (yDivs
[0] == 0 ? 1 : 0);
635 j
<= numYDivs
&& top
< H
;
643 // The initial xDiv and whether the first column is considered
644 // stretchable or not depends on whether xDiv[0] was zero or not.
645 for (i
= xDivs
[0] == 0 ? 1 : 0;
646 i
<= numXDivs
&& left
< W
;
653 c
= get_color(image
->rows
, left
, top
, right
- 1, bottom
- 1);
654 image
->info9Patch
.colors
[colorIndex
++] = c
;
655 NOISY(if (c
!= Res_png_9patch::NO_COLOR
) hasColor
= true);
661 assert(colorIndex
== numColors
);
663 for (i
=0; i
<numColors
; i
++) {
665 if (i
== 0) printf("Colors in %s:\n ", imageName
);
666 printf(" #%08x", image
->info9Patch
.colors
[i
]);
667 if (i
== numColors
- 1) printf("\n");
671 image
->is9Patch
= true;
672 image
->info9Patch
.deviceToFile();
677 "ERROR: 9-patch image %s malformed.\n"
678 " %s.\n", imageName
, errorMsg
);
679 if (errorEdge
!= NULL
) {
680 if (errorPixel
>= 0) {
682 " Found at pixel #%d along %s edge.\n", errorPixel
, errorEdge
);
685 " Found along %s edge.\n", errorEdge
);
688 return UNKNOWN_ERROR
;
693 static void checkNinePatchSerialization(Res_png_9patch
* inPatch
, void * data
)
695 if (sizeof(void*) != sizeof(int32_t)) {
696 // can't deserialize on a non-32 bit system
699 size_t patchSize
= inPatch
->serializedSize();
700 void * newData
= malloc(patchSize
);
701 memcpy(newData
, data
, patchSize
);
702 Res_png_9patch
* outPatch
= inPatch
->deserialize(newData
);
703 // deserialization is done in place, so outPatch == newData
704 assert(outPatch
== newData
);
705 assert(outPatch
->numXDivs
== inPatch
->numXDivs
);
706 assert(outPatch
->numYDivs
== inPatch
->numYDivs
);
707 assert(outPatch
->paddingLeft
== inPatch
->paddingLeft
);
708 assert(outPatch
->paddingRight
== inPatch
->paddingRight
);
709 assert(outPatch
->paddingTop
== inPatch
->paddingTop
);
710 assert(outPatch
->paddingBottom
== inPatch
->paddingBottom
);
711 for (int i
= 0; i
< outPatch
->numXDivs
; i
++) {
712 assert(outPatch
->xDivs
[i
] == inPatch
->xDivs
[i
]);
714 for (int i
= 0; i
< outPatch
->numYDivs
; i
++) {
715 assert(outPatch
->yDivs
[i
] == inPatch
->yDivs
[i
]);
717 for (int i
= 0; i
< outPatch
->numColors
; i
++) {
718 assert(outPatch
->colors
[i
] == inPatch
->colors
[i
]);
723 static bool patch_equals(Res_png_9patch
& patch1
, Res_png_9patch
& patch2
) {
724 if (!(patch1
.numXDivs
== patch2
.numXDivs
&&
725 patch1
.numYDivs
== patch2
.numYDivs
&&
726 patch1
.numColors
== patch2
.numColors
&&
727 patch1
.paddingLeft
== patch2
.paddingLeft
&&
728 patch1
.paddingRight
== patch2
.paddingRight
&&
729 patch1
.paddingTop
== patch2
.paddingTop
&&
730 patch1
.paddingBottom
== patch2
.paddingBottom
)) {
733 for (int i
= 0; i
< patch1
.numColors
; i
++) {
734 if (patch1
.colors
[i
] != patch2
.colors
[i
]) {
738 for (int i
= 0; i
< patch1
.numXDivs
; i
++) {
739 if (patch1
.xDivs
[i
] != patch2
.xDivs
[i
]) {
743 for (int i
= 0; i
< patch1
.numYDivs
; i
++) {
744 if (patch1
.yDivs
[i
] != patch2
.yDivs
[i
]) {
751 static void dump_image(int w
, int h
, png_bytepp rows
, int color_type
)
753 int i
, j
, rr
, gg
, bb
, aa
;
756 if (color_type
== PNG_COLOR_TYPE_PALETTE
|| color_type
== PNG_COLOR_TYPE_GRAY
) {
758 } else if (color_type
== PNG_COLOR_TYPE_GRAY_ALPHA
) {
760 } else if (color_type
== PNG_COLOR_TYPE_RGB
|| color_type
== PNG_COLOR_TYPE_RGB_ALPHA
) {
761 // We use a padding byte even when there is no alpha
764 printf("Unknown color type %d.\n", color_type
);
767 for (j
= 0; j
< h
; j
++) {
768 png_bytep row
= rows
[j
];
769 for (i
= 0; i
< w
; i
++) {
777 printf("Row %d:", j
);
784 printf(" (%d %d", rr
, gg
);
787 printf(" (%d %d %d)", rr
, gg
, bb
);
790 printf(" (%d %d %d %d)", rr
, gg
, bb
, aa
);
800 #define MAX(a,b) ((a)>(b)?(a):(b))
801 #define ABS(a) ((a)<0?-(a):(a))
803 static void analyze_image(const char *imageName
, image_info
&imageInfo
, int grayscaleTolerance
,
804 png_colorp rgbPalette
, png_bytep alphaPalette
,
805 int *paletteEntries
, bool *hasTransparency
, int *colorType
,
808 int w
= imageInfo
.width
;
809 int h
= imageInfo
.height
;
810 int i
, j
, rr
, gg
, bb
, aa
, idx
;
811 uint32_t colors
[256], col
;
813 int maxGrayDeviation
= 0;
815 bool isOpaque
= true;
816 bool isPalette
= true;
817 bool isGrayscale
= true;
819 // Scan the entire image and determine if:
820 // 1. Every pixel has R == G == B (grayscale)
821 // 2. Every pixel has A == 255 (opaque)
822 // 3. There are no more than 256 distinct RGBA colors
824 // NOISY(printf("Initial image data:\n"));
825 // dump_image(w, h, imageInfo.rows, PNG_COLOR_TYPE_RGB_ALPHA);
827 for (j
= 0; j
< h
; j
++) {
828 png_bytep row
= imageInfo
.rows
[j
];
829 png_bytep out
= outRows
[j
];
830 for (i
= 0; i
< w
; i
++) {
836 int odev
= maxGrayDeviation
;
837 maxGrayDeviation
= MAX(ABS(rr
- gg
), maxGrayDeviation
);
838 maxGrayDeviation
= MAX(ABS(gg
- bb
), maxGrayDeviation
);
839 maxGrayDeviation
= MAX(ABS(bb
- rr
), maxGrayDeviation
);
840 if (maxGrayDeviation
> odev
) {
841 NOISY(printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
842 maxGrayDeviation
, i
, j
, rr
, gg
, bb
, aa
));
845 // Check if image is really grayscale
847 if (rr
!= gg
|| rr
!= bb
) {
848 NOISY(printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
849 i
, j
, rr
, gg
, bb
, aa
));
854 // Check if image is really opaque
857 NOISY(printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
858 i
, j
, rr
, gg
, bb
, aa
));
863 // Check if image is really <= 256 colors
865 col
= (uint32_t) ((rr
<< 24) | (gg
<< 16) | (bb
<< 8) | aa
);
867 for (idx
= 0; idx
< num_colors
; idx
++) {
868 if (colors
[idx
] == col
) {
874 // Write the palette index for the pixel to outRows optimistically
875 // We might overwrite it later if we decide to encode as gray or
879 if (num_colors
== 256) {
880 NOISY(printf("Found 257th color at %d, %d\n", i
, j
));
883 colors
[num_colors
++] = col
;
891 *hasTransparency
= !isOpaque
;
892 int bpp
= isOpaque
? 3 : 4;
893 int paletteSize
= w
* h
+ bpp
* num_colors
;
895 NOISY(printf("isGrayscale = %s\n", isGrayscale
? "true" : "false"));
896 NOISY(printf("isOpaque = %s\n", isOpaque
? "true" : "false"));
897 NOISY(printf("isPalette = %s\n", isPalette
? "true" : "false"));
898 NOISY(printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
899 paletteSize
, 2 * w
* h
, bpp
* w
* h
));
900 NOISY(printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation
, grayscaleTolerance
));
902 // Choose the best color type for the image.
903 // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
904 // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
905 // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
906 // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
907 // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
910 *colorType
= PNG_COLOR_TYPE_GRAY
; // 1 byte/pixel
912 // Use a simple heuristic to determine whether using a palette will
913 // save space versus using gray + alpha for each pixel.
914 // This doesn't take into account chunk overhead, filtering, LZ
916 if (isPalette
&& (paletteSize
< 2 * w
* h
)) {
917 *colorType
= PNG_COLOR_TYPE_PALETTE
; // 1 byte/pixel + 4 bytes/color
919 *colorType
= PNG_COLOR_TYPE_GRAY_ALPHA
; // 2 bytes per pixel
922 } else if (isPalette
&& (paletteSize
< bpp
* w
* h
)) {
923 *colorType
= PNG_COLOR_TYPE_PALETTE
;
925 if (maxGrayDeviation
<= grayscaleTolerance
) {
926 printf("%s: forcing image to gray (max deviation = %d)\n", imageName
, maxGrayDeviation
);
927 *colorType
= isOpaque
? PNG_COLOR_TYPE_GRAY
: PNG_COLOR_TYPE_GRAY_ALPHA
;
929 *colorType
= isOpaque
? PNG_COLOR_TYPE_RGB
: PNG_COLOR_TYPE_RGB_ALPHA
;
933 // Perform postprocessing of the image or palette data based on the final
936 if (*colorType
== PNG_COLOR_TYPE_PALETTE
) {
937 // Create separate RGB and Alpha palettes and set the number of colors
938 *paletteEntries
= num_colors
;
940 // Create the RGB and alpha palettes
941 for (int idx
= 0; idx
< num_colors
; idx
++) {
943 rgbPalette
[idx
].red
= (png_byte
) ((col
>> 24) & 0xff);
944 rgbPalette
[idx
].green
= (png_byte
) ((col
>> 16) & 0xff);
945 rgbPalette
[idx
].blue
= (png_byte
) ((col
>> 8) & 0xff);
946 alphaPalette
[idx
] = (png_byte
) (col
& 0xff);
948 } else if (*colorType
== PNG_COLOR_TYPE_GRAY
|| *colorType
== PNG_COLOR_TYPE_GRAY_ALPHA
) {
949 // If the image is gray or gray + alpha, compact the pixels into outRows
950 for (j
= 0; j
< h
; j
++) {
951 png_bytep row
= imageInfo
.rows
[j
];
952 png_bytep out
= outRows
[j
];
953 for (i
= 0; i
< w
; i
++) {
962 *out
++ = (png_byte
) (rr
* 0.2126f
+ gg
* 0.7152f
+ bb
* 0.0722f
);
973 static void write_png(const char* imageName
,
974 png_structp write_ptr
, png_infop write_info
,
975 image_info
& imageInfo
, int grayscaleTolerance
)
977 bool optimize
= true;
978 png_uint_32 width
, height
;
980 int bit_depth
, interlace_type
, compression_type
;
983 png_unknown_chunk unknowns
[2];
984 unknowns
[0].data
= NULL
;
985 unknowns
[1].data
= NULL
;
987 png_bytepp outRows
= (png_bytepp
) malloc((int) imageInfo
.height
* png_sizeof(png_bytep
));
988 if (outRows
== (png_bytepp
) 0) {
989 printf("Can't allocate output buffer!\n");
992 for (i
= 0; i
< (int) imageInfo
.height
; i
++) {
993 outRows
[i
] = (png_bytep
) malloc(2 * (int) imageInfo
.width
);
994 if (outRows
[i
] == (png_bytep
) 0) {
995 printf("Can't allocate output buffer!\n");
1000 png_set_compression_level(write_ptr
, Z_BEST_COMPRESSION
);
1002 NOISY(printf("Writing image %s: w = %d, h = %d\n", imageName
,
1003 (int) imageInfo
.width
, (int) imageInfo
.height
));
1005 png_color rgbPalette
[256];
1006 png_byte alphaPalette
[256];
1007 bool hasTransparency
;
1010 analyze_image(imageName
, imageInfo
, grayscaleTolerance
, rgbPalette
, alphaPalette
,
1011 &paletteEntries
, &hasTransparency
, &color_type
, outRows
);
1013 // If the image is a 9-patch, we need to preserve it as a ARGB file to make
1014 // sure the pixels will not be pre-dithered/clamped until we decide they are
1015 if (imageInfo
.is9Patch
&& (color_type
== PNG_COLOR_TYPE_RGB
||
1016 color_type
== PNG_COLOR_TYPE_GRAY
|| color_type
== PNG_COLOR_TYPE_PALETTE
)) {
1017 color_type
= PNG_COLOR_TYPE_RGB_ALPHA
;
1020 switch (color_type
) {
1021 case PNG_COLOR_TYPE_PALETTE
:
1022 NOISY(printf("Image %s has %d colors%s, using PNG_COLOR_TYPE_PALETTE\n",
1023 imageName
, paletteEntries
,
1024 hasTransparency
? " (with alpha)" : ""));
1026 case PNG_COLOR_TYPE_GRAY
:
1027 NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName
));
1029 case PNG_COLOR_TYPE_GRAY_ALPHA
:
1030 NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName
));
1032 case PNG_COLOR_TYPE_RGB
:
1033 NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName
));
1035 case PNG_COLOR_TYPE_RGB_ALPHA
:
1036 NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName
));
1040 png_set_IHDR(write_ptr
, write_info
, imageInfo
.width
, imageInfo
.height
,
1041 8, color_type
, PNG_INTERLACE_NONE
,
1042 PNG_COMPRESSION_TYPE_DEFAULT
, PNG_FILTER_TYPE_DEFAULT
);
1044 if (color_type
== PNG_COLOR_TYPE_PALETTE
) {
1045 png_set_PLTE(write_ptr
, write_info
, rgbPalette
, paletteEntries
);
1046 if (hasTransparency
) {
1047 png_set_tRNS(write_ptr
, write_info
, alphaPalette
, paletteEntries
, (png_color_16p
) 0);
1049 png_set_filter(write_ptr
, 0, PNG_NO_FILTERS
);
1051 png_set_filter(write_ptr
, 0, PNG_ALL_FILTERS
);
1054 if (imageInfo
.is9Patch
) {
1055 int chunk_count
= 1 + (imageInfo
.haveLayoutBounds
? 1 : 0);
1056 int p_index
= imageInfo
.haveLayoutBounds
? 1 : 0;
1058 png_byte
*chunk_names
= imageInfo
.haveLayoutBounds
1059 ? (png_byte
*)"npLb\0npTc\0"
1060 : (png_byte
*)"npTc";
1061 NOISY(printf("Adding 9-patch info...\n"));
1062 strcpy((char*)unknowns
[p_index
].name
, "npTc");
1063 unknowns
[p_index
].data
= (png_byte
*)imageInfo
.info9Patch
.serialize();
1064 unknowns
[p_index
].size
= imageInfo
.info9Patch
.serializedSize();
1065 // TODO: remove the check below when everything works
1066 checkNinePatchSerialization(&imageInfo
.info9Patch
, unknowns
[p_index
].data
);
1068 if (imageInfo
.haveLayoutBounds
) {
1069 int chunk_size
= sizeof(png_uint_32
) * 4;
1070 strcpy((char*)unknowns
[b_index
].name
, "npLb");
1071 unknowns
[b_index
].data
= (png_byte
*) calloc(chunk_size
, 1);
1072 memcpy(unknowns
[b_index
].data
, &imageInfo
.layoutBoundsLeft
, chunk_size
);
1073 unknowns
[b_index
].size
= chunk_size
;
1076 png_set_keep_unknown_chunks(write_ptr
, PNG_HANDLE_CHUNK_ALWAYS
,
1077 chunk_names
, chunk_count
);
1078 png_set_unknown_chunks(write_ptr
, write_info
, unknowns
, chunk_count
);
1079 // XXX I can't get this to work without forcibly changing
1080 // the location to what I want... which apparently is supposed
1081 // to be a private API, but everything else I have tried results
1082 // in the location being set to what I -last- wrote so I never
1084 png_set_unknown_chunk_location(write_ptr
, write_info
, 0, PNG_HAVE_PLTE
);
1085 if (imageInfo
.haveLayoutBounds
) {
1086 png_set_unknown_chunk_location(write_ptr
, write_info
, 1, PNG_HAVE_PLTE
);
1091 png_write_info(write_ptr
, write_info
);
1094 if (color_type
== PNG_COLOR_TYPE_RGB
|| color_type
== PNG_COLOR_TYPE_RGB_ALPHA
) {
1095 png_set_filler(write_ptr
, 0, PNG_FILLER_AFTER
);
1096 rows
= imageInfo
.rows
;
1100 png_write_image(write_ptr
, rows
);
1102 // NOISY(printf("Final image data:\n"));
1103 // dump_image(imageInfo.width, imageInfo.height, rows, color_type);
1105 png_write_end(write_ptr
, write_info
);
1107 for (i
= 0; i
< (int) imageInfo
.height
; i
++) {
1111 free(unknowns
[0].data
);
1112 free(unknowns
[1].data
);
1114 png_get_IHDR(write_ptr
, write_info
, &width
, &height
,
1115 &bit_depth
, &color_type
, &interlace_type
,
1116 &compression_type
, NULL
);
1118 NOISY(printf("Image written: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
1119 (int)width
, (int)height
, bit_depth
, color_type
, interlace_type
,
1123 status_t
preProcessImage(const Bundle
* bundle
, const sp
<AaptAssets
>& assets
,
1124 const sp
<AaptFile
>& file
, String8
* outNewLeafName
)
1126 String8
ext(file
->getPath().getPathExtension());
1128 // We currently only process PNG images.
1129 if (strcmp(ext
.string(), ".png") != 0) {
1133 // Example of renaming a file:
1134 //*outNewLeafName = file->getPath().getBasePath().getFileName();
1135 //outNewLeafName->append(".nupng");
1137 String8
printableName(file
->getPrintableSource());
1139 if (bundle
->getVerbose()) {
1140 printf("Processing image: %s\n", printableName
.string());
1143 png_structp read_ptr
= NULL
;
1144 png_infop read_info
= NULL
;
1147 image_info imageInfo
;
1149 png_structp write_ptr
= NULL
;
1150 png_infop write_info
= NULL
;
1152 status_t error
= UNKNOWN_ERROR
;
1154 const size_t nameLen
= file
->getPath().length();
1156 fp
= fopen(file
->getSourceFile().string(), "rb");
1158 fprintf(stderr
, "%s: ERROR: Unable to open PNG file\n", printableName
.string());
1162 read_ptr
= png_create_read_struct(PNG_LIBPNG_VER_STRING
, 0, (png_error_ptr
)NULL
,
1163 (png_error_ptr
)NULL
);
1168 read_info
= png_create_info_struct(read_ptr
);
1173 if (setjmp(png_jmpbuf(read_ptr
))) {
1177 png_init_io(read_ptr
, fp
);
1179 read_png(printableName
.string(), read_ptr
, read_info
, &imageInfo
);
1182 const char* name
= file
->getPath().string();
1183 if (name
[nameLen
-5] == '9' && name
[nameLen
-6] == '.') {
1184 if (do_9patch(printableName
.string(), &imageInfo
) != NO_ERROR
) {
1190 write_ptr
= png_create_write_struct(PNG_LIBPNG_VER_STRING
, 0, (png_error_ptr
)NULL
,
1191 (png_error_ptr
)NULL
);
1197 write_info
= png_create_info_struct(write_ptr
);
1203 png_set_write_fn(write_ptr
, (void*)file
.get(),
1204 png_write_aapt_file
, png_flush_aapt_file
);
1206 if (setjmp(png_jmpbuf(write_ptr
)))
1211 write_png(printableName
.string(), write_ptr
, write_info
, imageInfo
,
1212 bundle
->getGrayscaleTolerance());
1216 if (bundle
->getVerbose()) {
1217 fseek(fp
, 0, SEEK_END
);
1218 size_t oldSize
= (size_t)ftell(fp
);
1219 size_t newSize
= file
->getSize();
1220 float factor
= ((float)newSize
)/oldSize
;
1221 int percent
= (int)(factor
*100);
1222 printf(" (processed image %s: %d%% size of source)\n", printableName
.string(), percent
);
1227 png_destroy_read_struct(&read_ptr
, &read_info
, (png_infopp
)NULL
);
1233 png_destroy_write_struct(&write_ptr
, &write_info
);
1236 if (error
!= NO_ERROR
) {
1237 fprintf(stderr
, "ERROR: Failure processing PNG image %s\n",
1238 file
->getPrintableSource().string());
1243 status_t
preProcessImageToCache(const Bundle
* bundle
, const String8
& source
, const String8
& dest
)
1245 png_structp read_ptr
= NULL
;
1246 png_infop read_info
= NULL
;
1250 image_info imageInfo
;
1252 png_structp write_ptr
= NULL
;
1253 png_infop write_info
= NULL
;
1255 status_t error
= UNKNOWN_ERROR
;
1257 if (bundle
->getVerbose()) {
1258 printf("Processing image to cache: %s => %s\n", source
.string(), dest
.string());
1261 // Get a file handler to read from
1262 fp
= fopen(source
.string(),"rb");
1264 fprintf(stderr
, "%s ERROR: Unable to open PNG file\n", source
.string());
1268 // Call libpng to get a struct to read image data into
1269 read_ptr
= png_create_read_struct(PNG_LIBPNG_VER_STRING
, NULL
, NULL
, NULL
);
1272 png_destroy_read_struct(&read_ptr
, &read_info
,NULL
);
1276 // Call libpng to get a struct to read image info into
1277 read_info
= png_create_info_struct(read_ptr
);
1280 png_destroy_read_struct(&read_ptr
, &read_info
,NULL
);
1284 // Set a jump point for libpng to long jump back to on error
1285 if (setjmp(png_jmpbuf(read_ptr
))) {
1287 png_destroy_read_struct(&read_ptr
, &read_info
,NULL
);
1291 // Set up libpng to read from our file.
1292 png_init_io(read_ptr
,fp
);
1294 // Actually read data from the file
1295 read_png(source
.string(), read_ptr
, read_info
, &imageInfo
);
1297 // We're done reading so we can clean up
1298 // Find old file size before releasing handle
1299 fseek(fp
, 0, SEEK_END
);
1300 size_t oldSize
= (size_t)ftell(fp
);
1302 png_destroy_read_struct(&read_ptr
, &read_info
,NULL
);
1304 // Check to see if we're dealing with a 9-patch
1305 // If we are, process appropriately
1306 if (source
.getBasePath().getPathExtension() == ".9") {
1307 if (do_9patch(source
.string(), &imageInfo
) != NO_ERROR
) {
1312 // Call libpng to create a structure to hold the processed image data
1313 // that can be written to disk
1314 write_ptr
= png_create_write_struct(PNG_LIBPNG_VER_STRING
, NULL
, NULL
, NULL
);
1316 png_destroy_write_struct(&write_ptr
, &write_info
);
1320 // Call libpng to create a structure to hold processed image info that can
1321 // be written to disk
1322 write_info
= png_create_info_struct(write_ptr
);
1324 png_destroy_write_struct(&write_ptr
, &write_info
);
1328 // Open up our destination file for writing
1329 fp
= fopen(dest
.string(), "wb");
1331 fprintf(stderr
, "%s ERROR: Unable to open PNG file\n", dest
.string());
1332 png_destroy_write_struct(&write_ptr
, &write_info
);
1336 // Set up libpng to write to our file
1337 png_init_io(write_ptr
, fp
);
1339 // Set up a jump for libpng to long jump back on on errors
1340 if (setjmp(png_jmpbuf(write_ptr
))) {
1342 png_destroy_write_struct(&write_ptr
, &write_info
);
1346 // Actually write out to the new png
1347 write_png(dest
.string(), write_ptr
, write_info
, imageInfo
,
1348 bundle
->getGrayscaleTolerance());
1350 if (bundle
->getVerbose()) {
1351 // Find the size of our new file
1352 FILE* reader
= fopen(dest
.string(), "rb");
1353 fseek(reader
, 0, SEEK_END
);
1354 size_t newSize
= (size_t)ftell(reader
);
1357 float factor
= ((float)newSize
)/oldSize
;
1358 int percent
= (int)(factor
*100);
1359 printf(" (processed image to cache entry %s: %d%% size of source)\n",
1360 dest
.string(), percent
);
1365 png_destroy_write_struct(&write_ptr
, &write_info
);
1370 status_t
postProcessImage(const sp
<AaptAssets
>& assets
,
1371 ResourceTable
* table
, const sp
<AaptFile
>& file
)
1373 String8
ext(file
->getPath().getPathExtension());
1375 // At this point, now that we have all the resource data, all we need to
1376 // do is compile XML files.
1377 if (strcmp(ext
.string(), ".xml") == 0) {
1378 return compileXmlFile(assets
, file
, table
);