#include "Images.h"
-#include <utils/ResourceTypes.h>
+#include <androidfw/ResourceTypes.h>
#include <utils/ByteOrder.h>
#include <png.h>
// This holds an image as 8bpp RGBA.
struct image_info
{
- image_info() : rows(NULL), hasTransparency(true), is9Patch(false), allocRows(NULL) { }
+ image_info() : rows(NULL), is9Patch(false), allocRows(NULL) { }
~image_info() {
if (rows && rows != allocRows) {
free(rows);
}
free(allocRows);
}
+ free(info9Patch.xDivs);
+ free(info9Patch.yDivs);
+ free(info9Patch.colors);
}
png_uint_32 width;
png_uint_32 height;
png_bytepp rows;
- bool hasTransparency;
-
// 9-patch info.
bool is9Patch;
Res_png_9patch info9Patch;
return c;
}
-static void examine_image(image_info* image)
-{
- bool hasTrans = false;
- for (int i=0; i<(int)image->height && !hasTrans; i++) {
- png_bytep p = image->rows[i];
- for (int j=0; j<(int)image->width; j++) {
- if (p[(j*4)+3] != 0xFF) {
- hasTrans = true;
- break;
- }
- }
- }
-
- image->hasTransparency = hasTrans;
-}
-
static status_t do_9patch(const char* imageName, image_info* image)
{
image->is9Patch = true;
int H = image->height;
int i, j;
- int maxSizeXDivs = (W / 2 + 1) * sizeof(int32_t);
- int maxSizeYDivs = (H / 2 + 1) * sizeof(int32_t);
+ int maxSizeXDivs = W * sizeof(int32_t);
+ int maxSizeYDivs = H * sizeof(int32_t);
int32_t* xDivs = (int32_t*) malloc(maxSizeXDivs);
int32_t* yDivs = (int32_t*) malloc(maxSizeYDivs);
uint8_t numXDivs = 0;
const char* errorMsg = NULL;
int errorPixel = -1;
- const char* errorEdge = "";
+ const char* errorEdge = NULL;
int colorIndex = 0;
if (yDivs[numYDivs - 1] == H) {
numRows--;
}
+
+ // Make sure the amount of rows and columns will fit in the number of
+ // colors we can use in the 9-patch format.
+ if (numRows * numCols > 0x7F) {
+ errorMsg = "Too many rows and columns in 9-patch perimeter";
+ goto getout;
+ }
+
numColors = numRows * numCols;
image->info9Patch.numColors = numColors;
image->info9Patch.colors = (uint32_t*)malloc(numColors * sizeof(uint32_t));
fprintf(stderr,
"ERROR: 9-patch image %s malformed.\n"
" %s.\n", imageName, errorMsg);
- if (errorPixel >= 0) {
- fprintf(stderr,
- " Found at pixel #%d along %s edge.\n", errorPixel, errorEdge);
- } else {
- fprintf(stderr,
- " Found along %s edge.\n", errorEdge);
+ if (errorEdge != NULL) {
+ if (errorPixel >= 0) {
+ fprintf(stderr,
+ " Found at pixel #%d along %s edge.\n", errorPixel, errorEdge);
+ } else {
+ fprintf(stderr,
+ " Found along %s edge.\n", errorEdge);
+ }
}
return UNKNOWN_ERROR;
}
return true;
}
+static void dump_image(int w, int h, png_bytepp rows, int color_type)
+{
+ int i, j, rr, gg, bb, aa;
+
+ int bpp;
+ if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
+ bpp = 1;
+ } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ bpp = 2;
+ } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
+ // We use a padding byte even when there is no alpha
+ bpp = 4;
+ } else {
+ printf("Unknown color type %d.\n", color_type);
+ }
-static void analyze_image(image_info &imageInfo, png_colorp rgbPalette, png_bytep alphaPalette,
+ for (j = 0; j < h; j++) {
+ png_bytep row = rows[j];
+ for (i = 0; i < w; i++) {
+ rr = row[0];
+ gg = row[1];
+ bb = row[2];
+ aa = row[3];
+ row += bpp;
+
+ if (i == 0) {
+ printf("Row %d:", j);
+ }
+ switch (bpp) {
+ case 1:
+ printf(" (%d)", rr);
+ break;
+ case 2:
+ printf(" (%d %d", rr, gg);
+ break;
+ case 3:
+ printf(" (%d %d %d)", rr, gg, bb);
+ break;
+ case 4:
+ printf(" (%d %d %d %d)", rr, gg, bb, aa);
+ break;
+ }
+ if (i == (w - 1)) {
+ NOISY(printf("\n"));
+ }
+ }
+ }
+}
+
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#define ABS(a) ((a)<0?-(a):(a))
+
+static void analyze_image(const char *imageName, image_info &imageInfo, int grayscaleTolerance,
+ png_colorp rgbPalette, png_bytep alphaPalette,
int *paletteEntries, bool *hasTransparency, int *colorType,
png_bytepp outRows)
{
int w = imageInfo.width;
int h = imageInfo.height;
- bool trans = imageInfo.hasTransparency;
int i, j, rr, gg, bb, aa, idx;
uint32_t colors[256], col;
int num_colors = 0;
+ int maxGrayDeviation = 0;
bool isOpaque = true;
bool isPalette = true;
// 1. Every pixel has R == G == B (grayscale)
// 2. Every pixel has A == 255 (opaque)
// 3. There are no more than 256 distinct RGBA colors
+
+ // NOISY(printf("Initial image data:\n"));
+ // dump_image(w, h, imageInfo.rows, PNG_COLOR_TYPE_RGB_ALPHA);
+
for (j = 0; j < h; j++) {
png_bytep row = imageInfo.rows[j];
png_bytep out = outRows[j];
gg = *row++;
bb = *row++;
aa = *row++;
- if (!trans) {
- // Ignore the actually byte and assume alpha == 255
- aa = 0xff;
+
+ int odev = maxGrayDeviation;
+ maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
+ if (maxGrayDeviation > odev) {
+ NOISY(printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
+ maxGrayDeviation, i, j, rr, gg, bb, aa));
}
// Check if image is really grayscale
if (isGrayscale) {
- if (rr != gg || rr != bb) {
- isGrayscale = false;
- }
+ if (rr != gg || rr != bb) {
+ NOISY(printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
+ i, j, rr, gg, bb, aa));
+ isGrayscale = false;
+ }
}
// Check if image is really opaque
if (isOpaque) {
- if (aa != 0xff) {
- isOpaque = false;
- }
+ if (aa != 0xff) {
+ NOISY(printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
+ i, j, rr, gg, bb, aa));
+ isOpaque = false;
+ }
}
// Check if image is really <= 256 colors
*out++ = idx;
if (!match) {
if (num_colors == 256) {
+ NOISY(printf("Found 257th color at %d, %d\n", i, j));
isPalette = false;
} else {
colors[num_colors++] = col;
int bpp = isOpaque ? 3 : 4;
int paletteSize = w * h + bpp * num_colors;
+ NOISY(printf("isGrayscale = %s\n", isGrayscale ? "true" : "false"));
+ NOISY(printf("isOpaque = %s\n", isOpaque ? "true" : "false"));
+ NOISY(printf("isPalette = %s\n", isPalette ? "true" : "false"));
+ NOISY(printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
+ paletteSize, 2 * w * h, bpp * w * h));
+ NOISY(printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance));
+
// Choose the best color type for the image.
// 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
// 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
} else if (isPalette && (paletteSize < bpp * w * h)) {
*colorType = PNG_COLOR_TYPE_PALETTE;
} else {
- *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+ if (maxGrayDeviation <= grayscaleTolerance) {
+ printf("%s: forcing image to gray (max deviation = %d)\n", imageName, maxGrayDeviation);
+ *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
+ } else {
+ *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+ }
}
// Perform postprocessing of the image or palette data based on the final
bb = *row++;
aa = *row++;
- *out++ = rr;
+ if (isGrayscale) {
+ *out++ = rr;
+ } else {
+ *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+ }
if (!isOpaque) {
*out++ = aa;
}
- }
+ }
}
}
}
static void write_png(const char* imageName,
png_structp write_ptr, png_infop write_info,
- image_info& imageInfo)
+ image_info& imageInfo, int grayscaleTolerance)
{
bool optimize = true;
png_uint_32 width, height;
int i;
png_unknown_chunk unknowns[1];
+ unknowns[0].data = NULL;
png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * png_sizeof(png_bytep));
if (outRows == (png_bytepp) 0) {
png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
- NOISY(printf("Writing image %s: w = %d, h = %d, trans = %s\n", imageName,
- (int) imageInfo.width, (int) imageInfo.height,
- imageInfo.hasTransparency ? "true" : "false"));
+ NOISY(printf("Writing image %s: w = %d, h = %d\n", imageName,
+ (int) imageInfo.width, (int) imageInfo.height));
png_color rgbPalette[256];
png_byte alphaPalette[256];
bool hasTransparency;
int paletteEntries;
- if (optimize) {
- analyze_image(imageInfo, rgbPalette, alphaPalette, &paletteEntries, &hasTransparency,
- &color_type, outRows);
- switch (color_type) {
- case PNG_COLOR_TYPE_PALETTE:
- NOISY(printf("Image %s has %d colors%s, using PNG_COLOR_TYPE_PALETTE\n",
- imageName, paletteEntries,
- hasTransparency ? " (with alpha)" : ""));
- break;
- case PNG_COLOR_TYPE_GRAY:
- NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName));
- break;
- case PNG_COLOR_TYPE_GRAY_ALPHA:
- NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName));
- break;
- case PNG_COLOR_TYPE_RGB:
- NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName));
- break;
- case PNG_COLOR_TYPE_RGB_ALPHA:
- NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName));
- break;
- }
- } else {
- // Force RGB or RGB_ALPHA color type, copy transparency from input
- paletteEntries = 0;
- hasTransparency = imageInfo.hasTransparency;
- color_type = hasTransparency ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB;
+ analyze_image(imageName, imageInfo, grayscaleTolerance, rgbPalette, alphaPalette,
+ &paletteEntries, &hasTransparency, &color_type, outRows);
+
+ // If the image is a 9-patch, we need to preserve it as a ARGB file to make
+ // sure the pixels will not be pre-dithered/clamped until we decide they are
+ if (imageInfo.is9Patch && (color_type == PNG_COLOR_TYPE_RGB ||
+ color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)) {
+ color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+
+ switch (color_type) {
+ case PNG_COLOR_TYPE_PALETTE:
+ NOISY(printf("Image %s has %d colors%s, using PNG_COLOR_TYPE_PALETTE\n",
+ imageName, paletteEntries,
+ hasTransparency ? " (with alpha)" : ""));
+ break;
+ case PNG_COLOR_TYPE_GRAY:
+ NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName));
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName));
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName));
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName));
+ break;
}
png_set_IHDR(write_ptr, write_info, imageInfo.width, imageInfo.height,
}
png_write_info(write_ptr, write_info);
-
- if (!imageInfo.hasTransparency) {
- png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
- }
+ png_bytepp rows;
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
- png_write_image(write_ptr, imageInfo.rows);
+ png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
+ rows = imageInfo.rows;
} else {
- png_write_image(write_ptr, outRows);
+ rows = outRows;
}
+ png_write_image(write_ptr, rows);
+
+// NOISY(printf("Final image data:\n"));
+// dump_image(imageInfo.width, imageInfo.height, rows, color_type);
png_write_end(write_ptr, write_info);
free(outRows[i]);
}
free(outRows);
+ free(unknowns[0].data);
png_get_IHDR(write_ptr, write_info, &width, &height,
&bit_depth, &color_type, &interlace_type,
compression_type));
}
-status_t preProcessImage(Bundle* bundle, const sp<AaptAssets>& assets,
+status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
const sp<AaptFile>& file, String8* outNewLeafName)
{
String8 ext(file->getPath().getPathExtension());
String8 printableName(file->getPrintableSource());
+ if (bundle->getVerbose()) {
+ printf("Processing image: %s\n", printableName.string());
+ }
+
png_structp read_ptr = NULL;
png_infop read_info = NULL;
FILE* fp;
read_png(printableName.string(), read_ptr, read_info, &imageInfo);
- examine_image(&imageInfo);
-
if (nameLen > 6) {
const char* name = file->getPath().string();
if (name[nameLen-5] == '9' && name[nameLen-6] == '.') {
goto bail;
}
- write_png(printableName.string(), write_ptr, write_info, imageInfo);
+ write_png(printableName.string(), write_ptr, write_info, imageInfo,
+ bundle->getGrayscaleTolerance());
error = NO_ERROR;
return error;
}
+status_t preProcessImageToCache(const Bundle* bundle, const String8& source, const String8& dest)
+{
+ png_structp read_ptr = NULL;
+ png_infop read_info = NULL;
+
+ FILE* fp;
+
+ image_info imageInfo;
+
+ png_structp write_ptr = NULL;
+ png_infop write_info = NULL;
+
+ status_t error = UNKNOWN_ERROR;
+
+ if (bundle->getVerbose()) {
+ printf("Processing image to cache: %s => %s\n", source.string(), dest.string());
+ }
+
+ // Get a file handler to read from
+ fp = fopen(source.string(),"rb");
+ if (fp == NULL) {
+ fprintf(stderr, "%s ERROR: Unable to open PNG file\n", source.string());
+ return error;
+ }
+
+ // Call libpng to get a struct to read image data into
+ read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!read_ptr) {
+ fclose(fp);
+ png_destroy_read_struct(&read_ptr, &read_info,NULL);
+ return error;
+ }
+
+ // Call libpng to get a struct to read image info into
+ read_info = png_create_info_struct(read_ptr);
+ if (!read_info) {
+ fclose(fp);
+ png_destroy_read_struct(&read_ptr, &read_info,NULL);
+ return error;
+ }
+
+ // Set a jump point for libpng to long jump back to on error
+ if (setjmp(png_jmpbuf(read_ptr))) {
+ fclose(fp);
+ png_destroy_read_struct(&read_ptr, &read_info,NULL);
+ return error;
+ }
+ // Set up libpng to read from our file.
+ png_init_io(read_ptr,fp);
+
+ // Actually read data from the file
+ read_png(source.string(), read_ptr, read_info, &imageInfo);
+
+ // We're done reading so we can clean up
+ // Find old file size before releasing handle
+ fseek(fp, 0, SEEK_END);
+ size_t oldSize = (size_t)ftell(fp);
+ fclose(fp);
+ png_destroy_read_struct(&read_ptr, &read_info,NULL);
+
+ // Check to see if we're dealing with a 9-patch
+ // If we are, process appropriately
+ if (source.getBasePath().getPathExtension() == ".9") {
+ if (do_9patch(source.string(), &imageInfo) != NO_ERROR) {
+ return error;
+ }
+ }
+
+ // Call libpng to create a structure to hold the processed image data
+ // that can be written to disk
+ write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!write_ptr) {
+ png_destroy_write_struct(&write_ptr, &write_info);
+ return error;
+ }
+
+ // Call libpng to create a structure to hold processed image info that can
+ // be written to disk
+ write_info = png_create_info_struct(write_ptr);
+ if (!write_info) {
+ png_destroy_write_struct(&write_ptr, &write_info);
+ return error;
+ }
+
+ // Open up our destination file for writing
+ fp = fopen(dest.string(), "wb");
+ if (!fp) {
+ fprintf(stderr, "%s ERROR: Unable to open PNG file\n", dest.string());
+ png_destroy_write_struct(&write_ptr, &write_info);
+ return error;
+ }
+
+ // Set up libpng to write to our file
+ png_init_io(write_ptr, fp);
+
+ // Set up a jump for libpng to long jump back on on errors
+ if (setjmp(png_jmpbuf(write_ptr))) {
+ fclose(fp);
+ png_destroy_write_struct(&write_ptr, &write_info);
+ return error;
+ }
+
+ // Actually write out to the new png
+ write_png(dest.string(), write_ptr, write_info, imageInfo,
+ bundle->getGrayscaleTolerance());
+
+ if (bundle->getVerbose()) {
+ // Find the size of our new file
+ FILE* reader = fopen(dest.string(), "rb");
+ fseek(reader, 0, SEEK_END);
+ size_t newSize = (size_t)ftell(reader);
+ fclose(reader);
+
+ float factor = ((float)newSize)/oldSize;
+ int percent = (int)(factor*100);
+ printf(" (processed image to cache entry %s: %d%% size of source)\n",
+ dest.string(), percent);
+ }
+
+ //Clean up
+ fclose(fp);
+ png_destroy_write_struct(&write_ptr, &write_info);
+
+ return NO_ERROR;
+}
status_t postProcessImage(const sp<AaptAssets>& assets,
ResourceTable* table, const sp<AaptFile>& file)