Images.cpp revision b798689749c64baba81f02e10cf2157c747d6b46
1//
2// Copyright 2006 The Android Open Source Project
3//
4// Build resource files from raw assets.
5//
6
7#define PNG_INTERNAL
8
9#include "Images.h"
10
11#include <utils/ResourceTypes.h>
12#include <utils/ByteOrder.h>
13
14#include <png.h>
15
16#define NOISY(x) //x
17
18static void
19png_write_aapt_file(png_structp png_ptr, png_bytep data, png_size_t length)
20{
21    status_t err = ((AaptFile*)png_ptr->io_ptr)->writeData(data, length);
22    if (err != NO_ERROR) {
23        png_error(png_ptr, "Write Error");
24    }
25}
26
27
28static void
29png_flush_aapt_file(png_structp png_ptr)
30{
31}
32
33// This holds an image as 8bpp RGBA.
34struct image_info
35{
36    image_info() : rows(NULL), hasTransparency(true), is9Patch(false), allocRows(NULL) { }
37    ~image_info() {
38        if (rows && rows != allocRows) {
39            free(rows);
40        }
41        if (allocRows) {
42            for (int i=0; i<(int)allocHeight; i++) {
43                free(allocRows[i]);
44            }
45            free(allocRows);
46        }
47    }
48
49    png_uint_32 width;
50    png_uint_32 height;
51    png_bytepp rows;
52
53    bool hasTransparency;
54
55    // 9-patch info.
56    bool is9Patch;
57    Res_png_9patch info9Patch;
58
59    png_uint_32 allocHeight;
60    png_bytepp allocRows;
61};
62
63static void read_png(const char* imageName,
64                     png_structp read_ptr, png_infop read_info,
65                     image_info* outImageInfo)
66{
67    int color_type;
68    int bit_depth, interlace_type, compression_type;
69    int i;
70
71    png_read_info(read_ptr, read_info);
72
73    png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
74       &outImageInfo->height, &bit_depth, &color_type,
75       &interlace_type, &compression_type, NULL);
76
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);
80
81    if (color_type == PNG_COLOR_TYPE_PALETTE)
82        png_set_palette_to_rgb(read_ptr);
83
84    if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
85        png_set_gray_1_2_4_to_8(read_ptr);
86
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);
90    }
91
92    if (bit_depth == 16)
93        png_set_strip_16(read_ptr);
94
95    if ((color_type&PNG_COLOR_MASK_ALPHA) == 0)
96        png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
97
98    if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
99        png_set_gray_to_rgb(read_ptr);
100
101    png_read_update_info(read_ptr, read_info);
102
103    outImageInfo->rows = (png_bytepp)malloc(
104        outImageInfo->height * png_sizeof(png_bytep));
105    outImageInfo->allocHeight = outImageInfo->height;
106    outImageInfo->allocRows = outImageInfo->rows;
107
108    png_set_rows(read_ptr, read_info, outImageInfo->rows);
109
110    for (i = 0; i < (int)outImageInfo->height; i++)
111    {
112        outImageInfo->rows[i] = (png_bytep)
113            malloc(png_get_rowbytes(read_ptr, read_info));
114    }
115
116    png_read_image(read_ptr, outImageInfo->rows);
117
118    png_read_end(read_ptr, read_info);
119
120    NOISY(printf("Image %s: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
121                 imageName,
122                 (int)outImageInfo->width, (int)outImageInfo->height,
123                 bit_depth, color_type,
124                 interlace_type, compression_type));
125
126    png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
127       &outImageInfo->height, &bit_depth, &color_type,
128       &interlace_type, &compression_type, NULL);
129}
130
131static bool is_tick(png_bytep p, bool transparent, const char** outError)
132{
133    if (transparent) {
134        if (p[3] == 0) {
135            return false;
136        }
137        if (p[3] != 0xff) {
138            *outError = "Frame pixels must be either solid or transparent (not intermediate alphas)";
139            return false;
140        }
141        if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
142            *outError = "Ticks in transparent frame must be black";
143        }
144        return true;
145    }
146
147    if (p[3] != 0xFF) {
148        *outError = "White frame must be a solid color (no alpha)";
149    }
150    if (p[0] == 0xFF && p[1] == 0xFF && p[2] == 0xFF) {
151        return false;
152    }
153    if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
154        *outError = "Ticks in white frame must be black";
155        return false;
156    }
157    return true;
158}
159
160enum {
161    TICK_START,
162    TICK_INSIDE_1,
163    TICK_OUTSIDE_1
164};
165
166static 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)
170{
171    int i;
172    *outLeft = *outRight = -1;
173    int state = TICK_START;
174    bool found = false;
175
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)) {
180                *outLeft = i-1;
181                *outRight = width-2;
182                found = true;
183                if (outDivs != NULL) {
184                    *outDivs += 2;
185                }
186                state = TICK_INSIDE_1;
187            } else if (state == TICK_OUTSIDE_1) {
188                *outError = "Can't have more than one marked region along edge";
189                *outLeft = i;
190                return UNKNOWN_ERROR;
191            }
192        } else if (*outError == NULL) {
193            if (state == TICK_INSIDE_1) {
194                // We're done with this div.  Move on to the next.
195                *outRight = i-1;
196                outRight += 2;
197                outLeft += 2;
198                state = TICK_OUTSIDE_1;
199            }
200        } else {
201            *outLeft = i;
202            return UNKNOWN_ERROR;
203        }
204    }
205
206    if (required && !found) {
207        *outError = "No marked region found along edge";
208        *outLeft = -1;
209        return UNKNOWN_ERROR;
210    }
211
212    return NO_ERROR;
213}
214
215static 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)
219{
220    int i;
221    *outTop = *outBottom = -1;
222    int state = TICK_START;
223    bool found = false;
224
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)) {
229                *outTop = i-1;
230                *outBottom = height-2;
231                found = true;
232                if (outDivs != NULL) {
233                    *outDivs += 2;
234                }
235                state = TICK_INSIDE_1;
236            } else if (state == TICK_OUTSIDE_1) {
237                *outError = "Can't have more than one marked region along edge";
238                *outTop = i;
239                return UNKNOWN_ERROR;
240            }
241        } else if (*outError == NULL) {
242            if (state == TICK_INSIDE_1) {
243                // We're done with this div.  Move on to the next.
244                *outBottom = i-1;
245                outTop += 2;
246                outBottom += 2;
247                state = TICK_OUTSIDE_1;
248            }
249        } else {
250            *outTop = i;
251            return UNKNOWN_ERROR;
252        }
253    }
254
255    if (required && !found) {
256        *outError = "No marked region found along edge";
257        *outTop = -1;
258        return UNKNOWN_ERROR;
259    }
260
261    return NO_ERROR;
262}
263
264static uint32_t get_color(
265    png_bytepp rows, int left, int top, int right, int bottom)
266{
267    png_bytep color = rows[top] + left*4;
268
269    if (left > right || top > bottom) {
270        return Res_png_9patch::TRANSPARENT_COLOR;
271    }
272
273    while (top <= bottom) {
274        for (int i = left; i <= right; i++) {
275            png_bytep p = rows[top]+i*4;
276            if (color[3] == 0) {
277                if (p[3] != 0) {
278                    return Res_png_9patch::NO_COLOR;
279                }
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;
283            }
284        }
285        top++;
286    }
287
288    if (color[3] == 0) {
289        return Res_png_9patch::TRANSPARENT_COLOR;
290    }
291    return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
292}
293
294static void select_patch(
295    int which, int front, int back, int size, int* start, int* end)
296{
297    switch (which) {
298    case 0:
299        *start = 0;
300        *end = front-1;
301        break;
302    case 1:
303        *start = front;
304        *end = back-1;
305        break;
306    case 2:
307        *start = back;
308        *end = size-1;
309        break;
310    }
311}
312
313static uint32_t get_color(image_info* image, int hpatch, int vpatch)
314{
315    int left, right, top, bottom;
316    select_patch(
317        hpatch, image->info9Patch.xDivs[0], image->info9Patch.xDivs[1],
318        image->width, &left, &right);
319    select_patch(
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));
326    return c;
327}
328
329static void examine_image(image_info* image)
330{
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) {
336                hasTrans = true;
337                break;
338            }
339        }
340    }
341
342    image->hasTransparency = hasTrans;
343}
344
345static status_t do_9patch(const char* imageName, image_info* image)
346{
347    image->is9Patch = true;
348
349    int W = image->width;
350    int H = image->height;
351    int i, j;
352
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;
359    int8_t numColors;
360    int numRows;
361    int numCols;
362    int top;
363    int left;
364    int right;
365    int bottom;
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;
370
371    png_bytep p = image->rows[0];
372    bool transparent = p[3] == 0;
373    bool hasColor = false;
374
375    const char* errorMsg = NULL;
376    int errorPixel = -1;
377    const char* errorEdge = "";
378
379    int colorIndex = 0;
380
381    // Validate size...
382    if (W < 3 || H < 3) {
383        errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
384        goto getout;
385    }
386
387    // Validate frame...
388    if (!transparent &&
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";
391        goto getout;
392    }
393
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];
398        errorEdge = "top";
399        goto getout;
400    }
401
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];
406        errorEdge = "left";
407        goto getout;
408    }
409
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";
415        goto getout;
416    }
417
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;
422        errorEdge = "right";
423        goto getout;
424    }
425
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;
431
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];
436    } else {
437        // Adjust value to be correct!
438        image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
439    }
440    if (image->info9Patch.paddingTop < 0) {
441        image->info9Patch.paddingTop = yDivs[0];
442        image->info9Patch.paddingBottom = H - 2 - yDivs[1];
443    } else {
444        // Adjust value to be correct!
445        image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
446    }
447
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));
454
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);
460    }
461    image->width -= 2;
462    W = image->width;
463    image->height -= 2;
464    H = image->height;
465
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
469        numCols--;
470    }
471    if (xDivs[numXDivs - 1] == W) {
472        numCols--;
473    }
474    numRows = numYDivs + 1;
475    if (yDivs[0] == 0) {  // Row 1 is strechable
476        numRows--;
477    }
478    if (yDivs[numYDivs - 1] == H) {
479        numRows--;
480    }
481    numColors = numRows * numCols;
482    image->info9Patch.numColors = numColors;
483    image->info9Patch.colors = (uint32_t*)malloc(numColors * sizeof(uint32_t));
484
485    // Fill in color information for each patch.
486
487    uint32_t c;
488    top = 0;
489
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.
497    //
498    // The first and last columns are similarly treated with respect to the X
499    // axis.
500    //
501    // The above is to help explain some of the special casing that goes on the
502    // code below.
503
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;
508          j++) {
509        if (j == numYDivs) {
510            bottom = H;
511        } else {
512            bottom = yDivs[j];
513        }
514        left = 0;
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;
519              i++) {
520            if (i == numXDivs) {
521                right = W;
522            } else {
523                right = xDivs[i];
524            }
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);
528            left = right;
529        }
530        top = bottom;
531    }
532
533    assert(colorIndex == numColors);
534
535    for (i=0; i<numColors; i++) {
536        if (hasColor) {
537            if (i == 0) printf("Colors in %s:\n ", imageName);
538            printf(" #%08x", image->info9Patch.colors[i]);
539            if (i == numColors - 1) printf("\n");
540        }
541    }
542
543    image->is9Patch = true;
544    image->info9Patch.deviceToFile();
545
546getout:
547    if (errorMsg) {
548        fprintf(stderr,
549            "ERROR: 9-patch image %s malformed.\n"
550            "       %s.\n", imageName, errorMsg);
551        if (errorPixel >= 0) {
552            fprintf(stderr,
553            "       Found at pixel #%d along %s edge.\n", errorPixel, errorEdge);
554        } else {
555            fprintf(stderr,
556            "       Found along %s edge.\n", errorEdge);
557        }
558        return UNKNOWN_ERROR;
559    }
560    return NO_ERROR;
561}
562
563static void checkNinePatchSerialization(Res_png_9patch* inPatch,  void * data)
564{
565    if (sizeof(void*) != sizeof(int32_t)) {
566        // can't deserialize on a non-32 bit system
567        return;
568    }
569    size_t patchSize = inPatch->serializedSize();
570    void * newData = malloc(patchSize);
571    memcpy(newData, data, patchSize);
572    Res_png_9patch* outPatch = inPatch->deserialize(newData);
573    // deserialization is done in place, so outPatch == newData
574    assert(outPatch == newData);
575    assert(outPatch->numXDivs == inPatch->numXDivs);
576    assert(outPatch->numYDivs == inPatch->numYDivs);
577    assert(outPatch->paddingLeft == inPatch->paddingLeft);
578    assert(outPatch->paddingRight == inPatch->paddingRight);
579    assert(outPatch->paddingTop == inPatch->paddingTop);
580    assert(outPatch->paddingBottom == inPatch->paddingBottom);
581    for (int i = 0; i < outPatch->numXDivs; i++) {
582        assert(outPatch->xDivs[i] == inPatch->xDivs[i]);
583    }
584    for (int i = 0; i < outPatch->numYDivs; i++) {
585        assert(outPatch->yDivs[i] == inPatch->yDivs[i]);
586    }
587    for (int i = 0; i < outPatch->numColors; i++) {
588        assert(outPatch->colors[i] == inPatch->colors[i]);
589    }
590    free(newData);
591}
592
593static bool patch_equals(Res_png_9patch& patch1, Res_png_9patch& patch2) {
594    if (!(patch1.numXDivs == patch2.numXDivs &&
595          patch1.numYDivs == patch2.numYDivs &&
596          patch1.numColors == patch2.numColors &&
597          patch1.paddingLeft == patch2.paddingLeft &&
598          patch1.paddingRight == patch2.paddingRight &&
599          patch1.paddingTop == patch2.paddingTop &&
600          patch1.paddingBottom == patch2.paddingBottom)) {
601            return false;
602    }
603    for (int i = 0; i < patch1.numColors; i++) {
604        if (patch1.colors[i] != patch2.colors[i]) {
605            return false;
606        }
607    }
608    for (int i = 0; i < patch1.numXDivs; i++) {
609        if (patch1.xDivs[i] != patch2.xDivs[i]) {
610            return false;
611        }
612    }
613    for (int i = 0; i < patch1.numYDivs; i++) {
614        if (patch1.yDivs[i] != patch2.yDivs[i]) {
615            return false;
616        }
617    }
618    return true;
619}
620
621
622static void analyze_image(image_info &imageInfo, png_colorp rgbPalette, png_bytep alphaPalette,
623                          int *paletteEntries, bool *hasTransparency, int *colorType,
624                          png_bytepp outRows)
625{
626    int w = imageInfo.width;
627    int h = imageInfo.height;
628    bool trans = imageInfo.hasTransparency;
629    int i, j, rr, gg, bb, aa, idx;
630    uint32_t colors[256], col;
631    int num_colors = 0;
632
633    bool isOpaque = true;
634    bool isPalette = true;
635    bool isGrayscale = true;
636
637    // Scan the entire image and determine if:
638    // 1. Every pixel has R == G == B (grayscale)
639    // 2. Every pixel has A == 255 (opaque)
640    // 3. There are no more than 256 distinct RGBA colors
641    for (j = 0; j < h; j++) {
642        png_bytep row = imageInfo.rows[j];
643        png_bytep out = outRows[j];
644        for (i = 0; i < w; i++) {
645            rr = *row++;
646            gg = *row++;
647            bb = *row++;
648            aa = *row++;
649            if (!trans) {
650                // Ignore the actually byte and assume alpha == 255
651                aa = 0xff;
652            }
653
654            // Check if image is really grayscale
655            if (isGrayscale) {
656              if (rr != gg || rr != bb) {
657                isGrayscale = false;
658              }
659            }
660
661            // Check if image is really opaque
662            if (isOpaque) {
663              if (aa != 0xff) {
664                isOpaque = false;
665              }
666            }
667
668            // Check if image is really <= 256 colors
669            if (isPalette) {
670                col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
671                bool match = false;
672                for (idx = 0; idx < num_colors; idx++) {
673                    if (colors[idx] == col) {
674                        match = true;
675                        break;
676                    }
677                }
678
679                // Write the palette index for the pixel to outRows optimistically
680                // We might overwrite it later if we decide to encode as gray or
681                // gray + alpha
682                *out++ = idx;
683                if (!match) {
684                    if (num_colors == 256) {
685                        isPalette = false;
686                    } else {
687                        colors[num_colors++] = col;
688                    }
689                }
690            }
691        }
692    }
693
694    *paletteEntries = 0;
695    *hasTransparency = !isOpaque;
696    int bpp = isOpaque ? 3 : 4;
697    int paletteSize = w * h + bpp * num_colors;
698
699    // Choose the best color type for the image.
700    // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
701    // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
702    //     is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
703    // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
704    //     small, otherwise use COLOR_TYPE_RGB{_ALPHA}
705    if (isGrayscale) {
706        if (isOpaque) {
707            *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
708        } else {
709            // Use a simple heuristic to determine whether using a palette will
710            // save space versus using gray + alpha for each pixel.
711            // This doesn't take into account chunk overhead, filtering, LZ
712            // compression, etc.
713            if (isPalette && (paletteSize < 2 * w * h)) {
714                *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
715            } else {
716                *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
717            }
718        }
719    } else if (isPalette && (paletteSize < bpp * w * h)) {
720        *colorType = PNG_COLOR_TYPE_PALETTE;
721    } else {
722        *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
723    }
724
725    // Perform postprocessing of the image or palette data based on the final
726    // color type chosen
727
728    if (*colorType == PNG_COLOR_TYPE_PALETTE) {
729        // Create separate RGB and Alpha palettes and set the number of colors
730        *paletteEntries = num_colors;
731
732        // Create the RGB and alpha palettes
733        for (int idx = 0; idx < num_colors; idx++) {
734            col = colors[idx];
735            rgbPalette[idx].red   = (png_byte) ((col >> 24) & 0xff);
736            rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
737            rgbPalette[idx].blue  = (png_byte) ((col >>  8) & 0xff);
738            alphaPalette[idx]     = (png_byte)  (col        & 0xff);
739        }
740    } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
741        // If the image is gray or gray + alpha, compact the pixels into outRows
742        for (j = 0; j < h; j++) {
743            png_bytep row = imageInfo.rows[j];
744            png_bytep out = outRows[j];
745            for (i = 0; i < w; i++) {
746                rr = *row++;
747                gg = *row++;
748                bb = *row++;
749                aa = *row++;
750
751                *out++ = rr;
752                if (!isOpaque) {
753                    *out++ = aa;
754                }
755            }
756        }
757    }
758}
759
760
761static void write_png(const char* imageName,
762                      png_structp write_ptr, png_infop write_info,
763                      image_info& imageInfo)
764{
765    bool optimize = true;
766    png_uint_32 width, height;
767    int color_type;
768    int bit_depth, interlace_type, compression_type;
769    int i;
770
771    png_unknown_chunk unknowns[1];
772
773    png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * png_sizeof(png_bytep));
774    if (outRows == (png_bytepp) 0) {
775        printf("Can't allocate output buffer!\n");
776        exit(1);
777    }
778    for (i = 0; i < (int) imageInfo.height; i++) {
779        outRows[i] = (png_bytep) malloc(2 * (int) imageInfo.width);
780        if (outRows[i] == (png_bytep) 0) {
781            printf("Can't allocate output buffer!\n");
782            exit(1);
783        }
784    }
785
786    png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
787
788    NOISY(printf("Writing image %s: w = %d, h = %d, trans = %s\n", imageName,
789          (int) imageInfo.width, (int) imageInfo.height,
790          imageInfo.hasTransparency ? "true" : "false"));
791
792    png_color rgbPalette[256];
793    png_byte alphaPalette[256];
794    bool hasTransparency;
795    int paletteEntries;
796
797    if (optimize) {
798        analyze_image(imageInfo, rgbPalette, alphaPalette, &paletteEntries, &hasTransparency,
799                      &color_type, outRows);
800        switch (color_type) {
801        case PNG_COLOR_TYPE_PALETTE:
802            NOISY(printf("Image %s has %d colors%s, using PNG_COLOR_TYPE_PALETTE\n",
803                         imageName, paletteEntries,
804                         hasTransparency ? " (with alpha)" : ""));
805            break;
806        case PNG_COLOR_TYPE_GRAY:
807            NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName));
808            break;
809        case PNG_COLOR_TYPE_GRAY_ALPHA:
810            NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName));
811            break;
812        case PNG_COLOR_TYPE_RGB:
813            NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName));
814            break;
815        case PNG_COLOR_TYPE_RGB_ALPHA:
816            NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName));
817            break;
818        }
819    } else {
820        // Force RGB or RGB_ALPHA color type, copy transparency from input
821        paletteEntries = 0;
822        hasTransparency = imageInfo.hasTransparency;
823        color_type = hasTransparency ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB;
824    }
825
826    png_set_IHDR(write_ptr, write_info, imageInfo.width, imageInfo.height,
827                 8, color_type, PNG_INTERLACE_NONE,
828                 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
829
830    if (color_type == PNG_COLOR_TYPE_PALETTE) {
831        png_set_PLTE(write_ptr, write_info, rgbPalette, paletteEntries);
832        if (hasTransparency) {
833            png_set_tRNS(write_ptr, write_info, alphaPalette, paletteEntries, (png_color_16p) 0);
834        }
835       png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
836    } else {
837       png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
838    }
839
840    if (imageInfo.is9Patch) {
841        NOISY(printf("Adding 9-patch info...\n"));
842        strcpy((char*)unknowns[0].name, "npTc");
843        unknowns[0].data = (png_byte*)imageInfo.info9Patch.serialize();
844        unknowns[0].size = imageInfo.info9Patch.serializedSize();
845        // TODO: remove the check below when everything works
846        checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[0].data);
847        png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS,
848                                    (png_byte*)"npTc", 1);
849        png_set_unknown_chunks(write_ptr, write_info, unknowns, 1);
850        // XXX I can't get this to work without forcibly changing
851        // the location to what I want...  which apparently is supposed
852        // to be a private API, but everything else I have tried results
853        // in the location being set to what I -last- wrote so I never
854        // get written. :p
855        png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE);
856    }
857
858    png_write_info(write_ptr, write_info);
859
860    if (!imageInfo.hasTransparency) {
861        png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
862    }
863
864    if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
865        png_write_image(write_ptr, imageInfo.rows);
866    } else {
867        png_write_image(write_ptr, outRows);
868    }
869
870    png_write_end(write_ptr, write_info);
871
872    for (i = 0; i < (int) imageInfo.height; i++) {
873        free(outRows[i]);
874    }
875    free(outRows);
876
877    png_get_IHDR(write_ptr, write_info, &width, &height,
878       &bit_depth, &color_type, &interlace_type,
879       &compression_type, NULL);
880
881    NOISY(printf("Image written: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
882                 (int)width, (int)height, bit_depth, color_type, interlace_type,
883                 compression_type));
884}
885
886status_t preProcessImage(Bundle* bundle, const sp<AaptAssets>& assets,
887                         const sp<AaptFile>& file, String8* outNewLeafName)
888{
889    String8 ext(file->getPath().getPathExtension());
890
891    // We currently only process PNG images.
892    if (strcmp(ext.string(), ".png") != 0) {
893        return NO_ERROR;
894    }
895
896    // Example of renaming a file:
897    //*outNewLeafName = file->getPath().getBasePath().getFileName();
898    //outNewLeafName->append(".nupng");
899
900    String8 printableName(file->getPrintableSource());
901
902    png_structp read_ptr = NULL;
903    png_infop read_info = NULL;
904    FILE* fp;
905
906    image_info imageInfo;
907
908    png_structp write_ptr = NULL;
909    png_infop write_info = NULL;
910
911    status_t error = UNKNOWN_ERROR;
912
913    const size_t nameLen = file->getPath().length();
914
915    fp = fopen(file->getSourceFile().string(), "rb");
916    if (fp == NULL) {
917        fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string());
918        goto bail;
919    }
920
921    read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
922                                        (png_error_ptr)NULL);
923    if (!read_ptr) {
924        goto bail;
925    }
926
927    read_info = png_create_info_struct(read_ptr);
928    if (!read_info) {
929        goto bail;
930    }
931
932    if (setjmp(png_jmpbuf(read_ptr))) {
933        goto bail;
934    }
935
936    png_init_io(read_ptr, fp);
937
938    read_png(printableName.string(), read_ptr, read_info, &imageInfo);
939
940    examine_image(&imageInfo);
941
942    if (nameLen > 6) {
943        const char* name = file->getPath().string();
944        if (name[nameLen-5] == '9' && name[nameLen-6] == '.') {
945            if (do_9patch(printableName.string(), &imageInfo) != NO_ERROR) {
946                goto bail;
947            }
948        }
949    }
950
951    write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
952                                        (png_error_ptr)NULL);
953    if (!write_ptr)
954    {
955        goto bail;
956    }
957
958    write_info = png_create_info_struct(write_ptr);
959    if (!write_info)
960    {
961        goto bail;
962    }
963
964    png_set_write_fn(write_ptr, (void*)file.get(),
965                     png_write_aapt_file, png_flush_aapt_file);
966
967    if (setjmp(png_jmpbuf(write_ptr)))
968    {
969        goto bail;
970    }
971
972    write_png(printableName.string(), write_ptr, write_info, imageInfo);
973
974    error = NO_ERROR;
975
976    if (bundle->getVerbose()) {
977        fseek(fp, 0, SEEK_END);
978        size_t oldSize = (size_t)ftell(fp);
979        size_t newSize = file->getSize();
980        float factor = ((float)newSize)/oldSize;
981        int percent = (int)(factor*100);
982        printf("    (processed image %s: %d%% size of source)\n", printableName.string(), percent);
983    }
984
985bail:
986    if (read_ptr) {
987        png_destroy_read_struct(&read_ptr, &read_info, (png_infopp)NULL);
988    }
989    if (fp) {
990        fclose(fp);
991    }
992    if (write_ptr) {
993        png_destroy_write_struct(&write_ptr, &write_info);
994    }
995
996    if (error != NO_ERROR) {
997        fprintf(stderr, "ERROR: Failure processing PNG image %s\n",
998                file->getPrintableSource().string());
999    }
1000    return error;
1001}
1002
1003
1004
1005status_t postProcessImage(const sp<AaptAssets>& assets,
1006                          ResourceTable* table, const sp<AaptFile>& file)
1007{
1008    String8 ext(file->getPath().getPathExtension());
1009
1010    // At this point, now that we have all the resource data, all we need to
1011    // do is compile XML files.
1012    if (strcmp(ext.string(), ".xml") == 0) {
1013        return compileXmlFile(assets, file, table);
1014    }
1015
1016    return NO_ERROR;
1017}
1018