2 // Copyright 2006 The Android Open Source Project
4 // Build resource files from raw assets.
11 #include <utils/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
), hasTransparency(true), is9Patch(false), allocRows(NULL
) { }
38 if (rows
&& rows
!= allocRows
) {
42 for (int i
=0; i
<(int)allocHeight
; i
++) {
57 Res_png_9patch info9Patch
;
59 png_uint_32 allocHeight
;
63 static void read_png(const char* imageName
,
64 png_structp read_ptr
, png_infop read_info
,
65 image_info
* outImageInfo
)
68 int bit_depth
, interlace_type
, compression_type
;
71 png_read_info(read_ptr
, read_info
);
73 png_get_IHDR(read_ptr
, read_info
, &outImageInfo
->width
,
74 &outImageInfo
->height
, &bit_depth
, &color_type
,
75 &interlace_type
, &compression_type
, NULL
);
77 //printf("Image %s:\n", imageName);
78 //printf("color_type=%d, bit_depth=%d, interlace_type=%d, compression_type=%d\n",
79 // color_type, bit_depth, interlace_type, compression_type);
81 if (color_type
== PNG_COLOR_TYPE_PALETTE
)
82 png_set_palette_to_rgb(read_ptr
);
84 if (color_type
== PNG_COLOR_TYPE_GRAY
&& bit_depth
< 8)
85 png_set_gray_1_2_4_to_8(read_ptr
);
87 if (png_get_valid(read_ptr
, read_info
, PNG_INFO_tRNS
)) {
88 //printf("Has PNG_INFO_tRNS!\n");
89 png_set_tRNS_to_alpha(read_ptr
);
93 png_set_strip_16(read_ptr
);
95 if ((color_type
&PNG_COLOR_MASK_ALPHA
) == 0)
96 png_set_add_alpha(read_ptr
, 0xFF, PNG_FILLER_AFTER
);
98 if (color_type
== PNG_COLOR_TYPE_GRAY
|| color_type
== PNG_COLOR_TYPE_GRAY_ALPHA
)
99 png_set_gray_to_rgb(read_ptr
);
101 png_read_update_info(read_ptr
, read_info
);
103 outImageInfo
->rows
= (png_bytepp
)malloc(
104 outImageInfo
->height
* png_sizeof(png_bytep
));
105 outImageInfo
->allocHeight
= outImageInfo
->height
;
106 outImageInfo
->allocRows
= outImageInfo
->rows
;
108 png_set_rows(read_ptr
, read_info
, outImageInfo
->rows
);
110 for (i
= 0; i
< (int)outImageInfo
->height
; i
++)
112 outImageInfo
->rows
[i
] = (png_bytep
)
113 malloc(png_get_rowbytes(read_ptr
, read_info
));
116 png_read_image(read_ptr
, outImageInfo
->rows
);
118 png_read_end(read_ptr
, read_info
);
120 NOISY(printf("Image %s: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
122 (int)outImageInfo
->width
, (int)outImageInfo
->height
,
123 bit_depth
, color_type
,
124 interlace_type
, compression_type
));
126 png_get_IHDR(read_ptr
, read_info
, &outImageInfo
->width
,
127 &outImageInfo
->height
, &bit_depth
, &color_type
,
128 &interlace_type
, &compression_type
, NULL
);
131 static bool is_tick(png_bytep p
, bool transparent
, const char** outError
)
138 *outError
= "Frame pixels must be either solid or transparent (not intermediate alphas)";
141 if (p
[0] != 0 || p
[1] != 0 || p
[2] != 0) {
142 *outError
= "Ticks in transparent frame must be black";
148 *outError
= "White frame must be a solid color (no alpha)";
150 if (p
[0] == 0xFF && p
[1] == 0xFF && p
[2] == 0xFF) {
153 if (p
[0] != 0 || p
[1] != 0 || p
[2] != 0) {
154 *outError
= "Ticks in white frame must be black";
166 static status_t
get_horizontal_ticks(
167 png_bytep row
, int width
, bool transparent
, bool required
,
168 int32_t* outLeft
, int32_t* outRight
, const char** outError
,
169 uint8_t* outDivs
, bool multipleAllowed
)
172 *outLeft
= *outRight
= -1;
173 int state
= TICK_START
;
176 for (i
=1; i
<width
-1; i
++) {
177 if (is_tick(row
+i
*4, transparent
, outError
)) {
178 if (state
== TICK_START
||
179 (state
== TICK_OUTSIDE_1
&& multipleAllowed
)) {
183 if (outDivs
!= NULL
) {
186 state
= TICK_INSIDE_1
;
187 } else if (state
== TICK_OUTSIDE_1
) {
188 *outError
= "Can't have more than one marked region along edge";
190 return UNKNOWN_ERROR
;
192 } else if (*outError
== NULL
) {
193 if (state
== TICK_INSIDE_1
) {
194 // We're done with this div. Move on to the next.
198 state
= TICK_OUTSIDE_1
;
202 return UNKNOWN_ERROR
;
206 if (required
&& !found
) {
207 *outError
= "No marked region found along edge";
209 return UNKNOWN_ERROR
;
215 static status_t
get_vertical_ticks(
216 png_bytepp rows
, int offset
, int height
, bool transparent
, bool required
,
217 int32_t* outTop
, int32_t* outBottom
, const char** outError
,
218 uint8_t* outDivs
, bool multipleAllowed
)
221 *outTop
= *outBottom
= -1;
222 int state
= TICK_START
;
225 for (i
=1; i
<height
-1; i
++) {
226 if (is_tick(rows
[i
]+offset
, transparent
, outError
)) {
227 if (state
== TICK_START
||
228 (state
== TICK_OUTSIDE_1
&& multipleAllowed
)) {
230 *outBottom
= height
-2;
232 if (outDivs
!= NULL
) {
235 state
= TICK_INSIDE_1
;
236 } else if (state
== TICK_OUTSIDE_1
) {
237 *outError
= "Can't have more than one marked region along edge";
239 return UNKNOWN_ERROR
;
241 } else if (*outError
== NULL
) {
242 if (state
== TICK_INSIDE_1
) {
243 // We're done with this div. Move on to the next.
247 state
= TICK_OUTSIDE_1
;
251 return UNKNOWN_ERROR
;
255 if (required
&& !found
) {
256 *outError
= "No marked region found along edge";
258 return UNKNOWN_ERROR
;
264 static uint32_t get_color(
265 png_bytepp rows
, int left
, int top
, int right
, int bottom
)
267 png_bytep color
= rows
[top
] + left
*4;
269 if (left
> right
|| top
> bottom
) {
270 return Res_png_9patch::TRANSPARENT_COLOR
;
273 while (top
<= bottom
) {
274 for (int i
= left
; i
<= right
; i
++) {
275 png_bytep p
= rows
[top
]+i
*4;
278 return Res_png_9patch::NO_COLOR
;
280 } else if (p
[0] != color
[0] || p
[1] != color
[1]
281 || p
[2] != color
[2] || p
[3] != color
[3]) {
282 return Res_png_9patch::NO_COLOR
;
289 return Res_png_9patch::TRANSPARENT_COLOR
;
291 return (color
[3]<<24) | (color
[0]<<16) | (color
[1]<<8) | color
[2];
294 static void select_patch(
295 int which
, int front
, int back
, int size
, int* start
, int* end
)
313 static uint32_t get_color(image_info
* image
, int hpatch
, int vpatch
)
315 int left
, right
, top
, bottom
;
317 hpatch
, image
->info9Patch
.xDivs
[0], image
->info9Patch
.xDivs
[1],
318 image
->width
, &left
, &right
);
320 vpatch
, image
->info9Patch
.yDivs
[0], image
->info9Patch
.yDivs
[1],
321 image
->height
, &top
, &bottom
);
322 //printf("Selecting h=%d v=%d: (%d,%d)-(%d,%d)\n",
323 // hpatch, vpatch, left, top, right, bottom);
324 const uint32_t c
= get_color(image
->rows
, left
, top
, right
, bottom
);
325 NOISY(printf("Color in (%d,%d)-(%d,%d): #%08x\n", left
, top
, right
, bottom
, c
));
329 static void examine_image(image_info
* image
)
331 bool hasTrans
= false;
332 for (int i
=0; i
<(int)image
->height
&& !hasTrans
; i
++) {
333 png_bytep p
= image
->rows
[i
];
334 for (int j
=0; j
<(int)image
->width
; j
++) {
335 if (p
[(j
*4)+3] != 0xFF) {
342 image
->hasTransparency
= hasTrans
;
345 static status_t
do_9patch(const char* imageName
, image_info
* image
)
347 image
->is9Patch
= true;
349 int W
= image
->width
;
350 int H
= image
->height
;
353 int maxSizeXDivs
= (W
/ 2 + 1) * sizeof(int32_t);
354 int maxSizeYDivs
= (H
/ 2 + 1) * sizeof(int32_t);
355 int32_t* xDivs
= (int32_t*) malloc(maxSizeXDivs
);
356 int32_t* yDivs
= (int32_t*) malloc(maxSizeYDivs
);
357 uint8_t numXDivs
= 0;
358 uint8_t numYDivs
= 0;
366 memset(xDivs
, -1, maxSizeXDivs
);
367 memset(yDivs
, -1, maxSizeYDivs
);
368 image
->info9Patch
.paddingLeft
= image
->info9Patch
.paddingRight
=
369 image
->info9Patch
.paddingTop
= image
->info9Patch
.paddingBottom
= -1;
371 png_bytep p
= image
->rows
[0];
372 bool transparent
= p
[3] == 0;
373 bool hasColor
= false;
375 const char* errorMsg
= NULL
;
377 const char* errorEdge
= "";
382 if (W
< 3 || H
< 3) {
383 errorMsg
= "Image must be at least 3x3 (1x1 without frame) pixels";
389 (p
[0] != 0xFF || p
[1] != 0xFF || p
[2] != 0xFF || p
[3] != 0xFF)) {
390 errorMsg
= "Must have one-pixel frame that is either transparent or white";
394 // Find left and right of sizing areas...
395 if (get_horizontal_ticks(p
, W
, transparent
, true, &xDivs
[0],
396 &xDivs
[1], &errorMsg
, &numXDivs
, true) != NO_ERROR
) {
397 errorPixel
= xDivs
[0];
402 // Find top and bottom of sizing areas...
403 if (get_vertical_ticks(image
->rows
, 0, H
, transparent
, true, &yDivs
[0],
404 &yDivs
[1], &errorMsg
, &numYDivs
, true) != NO_ERROR
) {
405 errorPixel
= yDivs
[0];
410 // Find left and right of padding area...
411 if (get_horizontal_ticks(image
->rows
[H
-1], W
, transparent
, false, &image
->info9Patch
.paddingLeft
,
412 &image
->info9Patch
.paddingRight
, &errorMsg
, NULL
, false) != NO_ERROR
) {
413 errorPixel
= image
->info9Patch
.paddingLeft
;
414 errorEdge
= "bottom";
418 // Find top and bottom of padding area...
419 if (get_vertical_ticks(image
->rows
, (W
-1)*4, H
, transparent
, false, &image
->info9Patch
.paddingTop
,
420 &image
->info9Patch
.paddingBottom
, &errorMsg
, NULL
, false) != NO_ERROR
) {
421 errorPixel
= image
->info9Patch
.paddingTop
;
426 // Copy patch data into image
427 image
->info9Patch
.numXDivs
= numXDivs
;
428 image
->info9Patch
.numYDivs
= numYDivs
;
429 image
->info9Patch
.xDivs
= xDivs
;
430 image
->info9Patch
.yDivs
= yDivs
;
432 // If padding is not yet specified, take values from size.
433 if (image
->info9Patch
.paddingLeft
< 0) {
434 image
->info9Patch
.paddingLeft
= xDivs
[0];
435 image
->info9Patch
.paddingRight
= W
- 2 - xDivs
[1];
437 // Adjust value to be correct!
438 image
->info9Patch
.paddingRight
= W
- 2 - image
->info9Patch
.paddingRight
;
440 if (image
->info9Patch
.paddingTop
< 0) {
441 image
->info9Patch
.paddingTop
= yDivs
[0];
442 image
->info9Patch
.paddingBottom
= H
- 2 - yDivs
[1];
444 // Adjust value to be correct!
445 image
->info9Patch
.paddingBottom
= H
- 2 - image
->info9Patch
.paddingBottom
;
448 NOISY(printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName
,
449 image
->info9Patch
.xDivs
[0], image
->info9Patch
.xDivs
[1],
450 image
->info9Patch
.yDivs
[0], image
->info9Patch
.yDivs
[1]));
451 NOISY(printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName
,
452 image
->info9Patch
.paddingLeft
, image
->info9Patch
.paddingRight
,
453 image
->info9Patch
.paddingTop
, image
->info9Patch
.paddingBottom
));
455 // Remove frame from image.
456 image
->rows
= (png_bytepp
)malloc((H
-2) * png_sizeof(png_bytep
));
457 for (i
=0; i
<(H
-2); i
++) {
458 image
->rows
[i
] = image
->allocRows
[i
+1];
459 memmove(image
->rows
[i
], image
->rows
[i
]+4, (W
-2)*4);
466 // Figure out the number of rows and columns in the N-patch
467 numCols
= numXDivs
+ 1;
468 if (xDivs
[0] == 0) { // Column 1 is strechable
471 if (xDivs
[numXDivs
- 1] == W
) {
474 numRows
= numYDivs
+ 1;
475 if (yDivs
[0] == 0) { // Row 1 is strechable
478 if (yDivs
[numYDivs
- 1] == H
) {
481 numColors
= numRows
* numCols
;
482 image
->info9Patch
.numColors
= numColors
;
483 image
->info9Patch
.colors
= (uint32_t*)malloc(numColors
* sizeof(uint32_t));
485 // Fill in color information for each patch.
490 // The first row always starts with the top being at y=0 and the bottom
491 // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
492 // the first row is stretchable along the Y axis, otherwise it is fixed.
493 // The last row always ends with the bottom being bitmap.height and the top
494 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
495 // yDivs[numYDivs-1]. In the former case the last row is stretchable along
496 // the Y axis, otherwise it is fixed.
498 // The first and last columns are similarly treated with respect to the X
501 // The above is to help explain some of the special casing that goes on the
504 // The initial yDiv and whether the first row is considered stretchable or
505 // not depends on whether yDiv[0] was zero or not.
506 for (j
= (yDivs
[0] == 0 ? 1 : 0);
507 j
<= numYDivs
&& top
< H
;
515 // The initial xDiv and whether the first column is considered
516 // stretchable or not depends on whether xDiv[0] was zero or not.
517 for (i
= xDivs
[0] == 0 ? 1 : 0;
518 i
<= numXDivs
&& left
< W
;
525 c
= get_color(image
->rows
, left
, top
, right
- 1, bottom
- 1);
526 image
->info9Patch
.colors
[colorIndex
++] = c
;
527 NOISY(if (c
!= Res_png_9patch::NO_COLOR
) hasColor
= true);
533 assert(colorIndex
== numColors
);
535 for (i
=0; i
<numColors
; i
++) {
537 if (i
== 0) printf("Colors in %s:\n ", imageName
);
538 printf(" #%08x", image
->info9Patch
.colors
[i
]);
539 if (i
== numColors
- 1) printf("\n");
543 image
->is9Patch
= true;
544 image
->info9Patch
.deviceToFile();
549 "ERROR: 9-patch image %s malformed.\n"
550 " %s.\n", imageName
, errorMsg
);
551 if (errorPixel
>= 0) {
553 " Found at pixel #%d along %s edge.\n", errorPixel
, errorEdge
);
556 " Found along %s edge.\n", errorEdge
);
558 return UNKNOWN_ERROR
;
563 static void checkNinePatchSerialization(Res_png_9patch
* inPatch
, void * data
)
565 size_t patchSize
= inPatch
->serializedSize();
566 void * newData
= malloc(patchSize
);
567 memcpy(newData
, data
, patchSize
);
568 Res_png_9patch
* outPatch
= inPatch
->deserialize(newData
);
569 assert(outPatch
->numXDivs
== inPatch
->numXDivs
);
570 assert(outPatch
->numYDivs
== inPatch
->numYDivs
);
571 assert(outPatch
->paddingLeft
== inPatch
->paddingLeft
);
572 assert(outPatch
->paddingRight
== inPatch
->paddingRight
);
573 assert(outPatch
->paddingTop
== inPatch
->paddingTop
);
574 assert(outPatch
->paddingBottom
== inPatch
->paddingBottom
);
575 for (int i
= 0; i
< outPatch
->numXDivs
; i
++) {
576 assert(outPatch
->xDivs
[i
] == inPatch
->xDivs
[i
]);
578 for (int i
= 0; i
< outPatch
->numYDivs
; i
++) {
579 assert(outPatch
->yDivs
[i
] == inPatch
->yDivs
[i
]);
581 for (int i
= 0; i
< outPatch
->numColors
; i
++) {
582 assert(outPatch
->colors
[i
] == inPatch
->colors
[i
]);
586 static bool patch_equals(Res_png_9patch
& patch1
, Res_png_9patch
& patch2
) {
587 if (!(patch1
.numXDivs
== patch2
.numXDivs
&&
588 patch1
.numYDivs
== patch2
.numYDivs
&&
589 patch1
.numColors
== patch2
.numColors
&&
590 patch1
.paddingLeft
== patch2
.paddingLeft
&&
591 patch1
.paddingRight
== patch2
.paddingRight
&&
592 patch1
.paddingTop
== patch2
.paddingTop
&&
593 patch1
.paddingBottom
== patch2
.paddingBottom
)) {
596 for (int i
= 0; i
< patch1
.numColors
; i
++) {
597 if (patch1
.colors
[i
] != patch2
.colors
[i
]) {
601 for (int i
= 0; i
< patch1
.numXDivs
; i
++) {
602 if (patch1
.xDivs
[i
] != patch2
.xDivs
[i
]) {
606 for (int i
= 0; i
< patch1
.numYDivs
; i
++) {
607 if (patch1
.yDivs
[i
] != patch2
.yDivs
[i
]) {
614 static void write_png(const char* imageName
,
615 png_structp write_ptr
, png_infop write_info
,
616 image_info
& imageInfo
)
618 png_uint_32 width
, height
;
620 int bit_depth
, interlace_type
, compression_type
;
623 png_unknown_chunk unknowns
[1];
625 png_set_compression_level(write_ptr
, Z_BEST_COMPRESSION
);
627 color_type
= PNG_COLOR_MASK_COLOR
;
628 if (imageInfo
.hasTransparency
) {
629 color_type
|= PNG_COLOR_MASK_ALPHA
;
632 png_set_IHDR(write_ptr
, write_info
, imageInfo
.width
, imageInfo
.height
,
633 8, color_type
, PNG_INTERLACE_NONE
,
634 PNG_COMPRESSION_TYPE_DEFAULT
, PNG_FILTER_TYPE_DEFAULT
);
636 if (imageInfo
.is9Patch
) {
637 NOISY(printf("Adding 9-patch info...\n"));
638 strcpy((char*)unknowns
[0].name
, "npTc");
639 unknowns
[0].data
= (png_byte
*)imageInfo
.info9Patch
.serialize();
640 unknowns
[0].size
= imageInfo
.info9Patch
.serializedSize();
641 // TODO: remove the check below when everything works
642 checkNinePatchSerialization(&imageInfo
.info9Patch
, unknowns
[0].data
);
643 png_set_keep_unknown_chunks(write_ptr
, PNG_HANDLE_CHUNK_ALWAYS
,
644 (png_byte
*)"npTc", 1);
645 png_set_unknown_chunks(write_ptr
, write_info
, unknowns
, 1);
646 // XXX I can't get this to work without forcibly changing
647 // the location to what I want... which apparently is supposed
648 // to be a private API, but everything else I have tried results
649 // in the location being set to what I -last- wrote so I never
651 png_set_unknown_chunk_location(write_ptr
, write_info
, 0, PNG_HAVE_PLTE
);
654 png_write_info(write_ptr
, write_info
);
656 if (!imageInfo
.hasTransparency
) {
657 png_set_filler(write_ptr
, 0, PNG_FILLER_AFTER
);
660 png_write_image(write_ptr
, imageInfo
.rows
);
662 png_write_end(write_ptr
, write_info
);
664 png_get_IHDR(write_ptr
, write_info
, &width
, &height
,
665 &bit_depth
, &color_type
, &interlace_type
,
666 &compression_type
, NULL
);
668 NOISY(printf("Image written: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
669 (int)width
, (int)height
, bit_depth
, color_type
, interlace_type
,
673 status_t
preProcessImage(Bundle
* bundle
, const sp
<AaptAssets
>& assets
,
674 const sp
<AaptFile
>& file
, String8
* outNewLeafName
)
676 String8
ext(file
->getPath().getPathExtension());
678 // We currently only process PNG images.
679 if (strcmp(ext
.string(), ".png") != 0) {
683 // Example of renaming a file:
684 //*outNewLeafName = file->getPath().getBasePath().getFileName();
685 //outNewLeafName->append(".nupng");
687 String8
printableName(file
->getPrintableSource());
689 png_structp read_ptr
= NULL
;
690 png_infop read_info
= NULL
;
693 image_info imageInfo
;
695 png_structp write_ptr
= NULL
;
696 png_infop write_info
= NULL
;
698 status_t error
= UNKNOWN_ERROR
;
700 const size_t nameLen
= file
->getPath().length();
702 fp
= fopen(file
->getSourceFile().string(), "rb");
704 fprintf(stderr
, "%s: ERROR: Unable to open PNG file\n", printableName
.string());
708 read_ptr
= png_create_read_struct(PNG_LIBPNG_VER_STRING
, 0, (png_error_ptr
)NULL
,
709 (png_error_ptr
)NULL
);
714 read_info
= png_create_info_struct(read_ptr
);
719 if (setjmp(png_jmpbuf(read_ptr
))) {
723 png_init_io(read_ptr
, fp
);
725 read_png(printableName
.string(), read_ptr
, read_info
, &imageInfo
);
727 examine_image(&imageInfo
);
730 const char* name
= file
->getPath().string();
731 if (name
[nameLen
-5] == '9' && name
[nameLen
-6] == '.') {
732 if (do_9patch(printableName
.string(), &imageInfo
) != NO_ERROR
) {
738 write_ptr
= png_create_write_struct(PNG_LIBPNG_VER_STRING
, 0, (png_error_ptr
)NULL
,
739 (png_error_ptr
)NULL
);
745 write_info
= png_create_info_struct(write_ptr
);
751 png_set_write_fn(write_ptr
, (void*)file
.get(),
752 png_write_aapt_file
, png_flush_aapt_file
);
754 if (setjmp(png_jmpbuf(write_ptr
)))
759 write_png(printableName
.string(), write_ptr
, write_info
, imageInfo
);
763 if (bundle
->getVerbose()) {
764 fseek(fp
, 0, SEEK_END
);
765 size_t oldSize
= (size_t)ftell(fp
);
766 size_t newSize
= file
->getSize();
767 float factor
= ((float)newSize
)/oldSize
;
768 int percent
= (int)(factor
*100);
769 printf(" (processed image %s: %d%% size of source)\n", printableName
.string(), percent
);
774 png_destroy_read_struct(&read_ptr
, &read_info
, (png_infopp
)NULL
);
780 png_destroy_write_struct(&write_ptr
, &write_info
);
783 if (error
!= NO_ERROR
) {
784 fprintf(stderr
, "ERROR: Failure processing PNG image %s\n",
785 file
->getPrintableSource().string());
792 status_t
postProcessImage(const sp
<AaptAssets
>& assets
,
793 ResourceTable
* table
, const sp
<AaptFile
>& file
)
795 String8
ext(file
->getPath().getPathExtension());
797 // At this point, now that we have all the resource data, all we need to
798 // do is compile XML files.
799 if (strcmp(ext
.string(), ".xml") == 0) {
800 return compileXmlFile(assets
, file
, table
);