This also enables an AAPT behavior that CTS tests have come to depend on. Small files that compress negatively (get larger) are stored uncompressed. Some CTS tests assume this and try to open these files by mmapping them, which is only possible if they are uncompressed. Bug: 35461578 Test: make aapt2_tests Change-Id: Id622a6150fe72477ad65d67d1bad897a8ee2ffb9
1324 lines
40 KiB
C++
1324 lines
40 KiB
C++
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "Png.h"
|
|
|
|
#include <png.h>
|
|
#include <zlib.h>
|
|
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "androidfw/ResourceTypes.h"
|
|
|
|
#include "Source.h"
|
|
#include "util/BigBuffer.h"
|
|
#include "util/Util.h"
|
|
|
|
namespace aapt {
|
|
|
|
constexpr bool kDebug = false;
|
|
|
|
struct PngInfo {
|
|
~PngInfo() {
|
|
for (png_bytep row : rows) {
|
|
if (row != nullptr) {
|
|
delete[] row;
|
|
}
|
|
}
|
|
|
|
delete[] xDivs;
|
|
delete[] yDivs;
|
|
}
|
|
|
|
void* serialize9Patch() {
|
|
void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs,
|
|
yDivs, colors.data());
|
|
reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile();
|
|
return serialized;
|
|
}
|
|
|
|
uint32_t width = 0;
|
|
uint32_t height = 0;
|
|
std::vector<png_bytep> rows;
|
|
|
|
bool is9Patch = false;
|
|
android::Res_png_9patch info9Patch;
|
|
int32_t* xDivs = nullptr;
|
|
int32_t* yDivs = nullptr;
|
|
std::vector<uint32_t> colors;
|
|
|
|
// Layout padding.
|
|
bool haveLayoutBounds = false;
|
|
int32_t layoutBoundsLeft;
|
|
int32_t layoutBoundsTop;
|
|
int32_t layoutBoundsRight;
|
|
int32_t layoutBoundsBottom;
|
|
|
|
// Round rect outline description.
|
|
int32_t outlineInsetsLeft;
|
|
int32_t outlineInsetsTop;
|
|
int32_t outlineInsetsRight;
|
|
int32_t outlineInsetsBottom;
|
|
float outlineRadius;
|
|
uint8_t outlineAlpha;
|
|
};
|
|
|
|
static void readDataFromStream(png_structp readPtr, png_bytep data,
|
|
png_size_t length) {
|
|
std::istream* input =
|
|
reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
|
|
if (!input->read(reinterpret_cast<char*>(data), length)) {
|
|
png_error(readPtr, strerror(errno));
|
|
}
|
|
}
|
|
|
|
static void writeDataToStream(png_structp writePtr, png_bytep data,
|
|
png_size_t length) {
|
|
BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
|
|
png_bytep buf = outBuffer->NextBlock<png_byte>(length);
|
|
memcpy(buf, data, length);
|
|
}
|
|
|
|
static void flushDataToStream(png_structp /*writePtr*/) {}
|
|
|
|
static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
|
|
IDiagnostics* diag =
|
|
reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
|
|
diag->Warn(DiagMessage() << warningMessage);
|
|
}
|
|
|
|
static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr,
|
|
PngInfo* outInfo) {
|
|
if (setjmp(png_jmpbuf(readPtr))) {
|
|
diag->Error(DiagMessage() << "failed reading png");
|
|
return false;
|
|
}
|
|
|
|
png_set_sig_bytes(readPtr, kPngSignatureSize);
|
|
png_read_info(readPtr, infoPtr);
|
|
|
|
int colorType, bitDepth, interlaceType, compressionType;
|
|
png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth,
|
|
&colorType, &interlaceType, &compressionType, nullptr);
|
|
|
|
if (colorType == PNG_COLOR_TYPE_PALETTE) {
|
|
png_set_palette_to_rgb(readPtr);
|
|
}
|
|
|
|
if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
|
|
png_set_expand_gray_1_2_4_to_8(readPtr);
|
|
}
|
|
|
|
if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
|
|
png_set_tRNS_to_alpha(readPtr);
|
|
}
|
|
|
|
if (bitDepth == 16) {
|
|
png_set_strip_16(readPtr);
|
|
}
|
|
|
|
if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
|
|
png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
|
|
}
|
|
|
|
if (colorType == PNG_COLOR_TYPE_GRAY ||
|
|
colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
|
|
png_set_gray_to_rgb(readPtr);
|
|
}
|
|
|
|
png_set_interlace_handling(readPtr);
|
|
png_read_update_info(readPtr, infoPtr);
|
|
|
|
const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
|
|
outInfo->rows.resize(outInfo->height);
|
|
for (size_t i = 0; i < outInfo->height; i++) {
|
|
outInfo->rows[i] = new png_byte[rowBytes];
|
|
}
|
|
|
|
png_read_image(readPtr, outInfo->rows.data());
|
|
png_read_end(readPtr, infoPtr);
|
|
return true;
|
|
}
|
|
|
|
static void checkNinePatchSerialization(android::Res_png_9patch* inPatch,
|
|
void* data) {
|
|
size_t patchSize = inPatch->serializedSize();
|
|
void* newData = malloc(patchSize);
|
|
memcpy(newData, data, patchSize);
|
|
android::Res_png_9patch* outPatch = inPatch->deserialize(newData);
|
|
outPatch->fileToDevice();
|
|
// deserialization is done in place, so outPatch == newData
|
|
assert(outPatch == newData);
|
|
assert(outPatch->numXDivs == inPatch->numXDivs);
|
|
assert(outPatch->numYDivs == inPatch->numYDivs);
|
|
assert(outPatch->paddingLeft == inPatch->paddingLeft);
|
|
assert(outPatch->paddingRight == inPatch->paddingRight);
|
|
assert(outPatch->paddingTop == inPatch->paddingTop);
|
|
assert(outPatch->paddingBottom == inPatch->paddingBottom);
|
|
/* for (int i = 0; i < outPatch->numXDivs; i++) {
|
|
assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]);
|
|
}
|
|
for (int i = 0; i < outPatch->numYDivs; i++) {
|
|
assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]);
|
|
}
|
|
for (int i = 0; i < outPatch->numColors; i++) {
|
|
assert(outPatch->getColors()[i] == inPatch->getColors()[i]);
|
|
}*/
|
|
free(newData);
|
|
}
|
|
|
|
/*static void dump_image(int w, int h, const png_byte* const* 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);
|
|
}
|
|
|
|
for (j = 0; j < h; j++) {
|
|
const png_byte* 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)) {
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
|
|
#ifdef MAX
|
|
#undef MAX
|
|
#endif
|
|
#ifdef ABS
|
|
#undef ABS
|
|
#endif
|
|
|
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
|
#define ABS(a) ((a) < 0 ? -(a) : (a))
|
|
|
|
static void analyze_image(IDiagnostics* diag, const PngInfo& 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;
|
|
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;
|
|
bool isGrayscale = true;
|
|
|
|
// Scan the entire image and determine if:
|
|
// 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
|
|
|
|
if (kDebug) {
|
|
printf("Initial image data:\n");
|
|
// dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA);
|
|
}
|
|
|
|
for (j = 0; j < h; j++) {
|
|
const png_byte* row = imageInfo.rows[j];
|
|
png_bytep out = outRows[j];
|
|
for (i = 0; i < w; i++) {
|
|
rr = *row++;
|
|
gg = *row++;
|
|
bb = *row++;
|
|
aa = *row++;
|
|
|
|
int odev = maxGrayDeviation;
|
|
maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
|
|
maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
|
|
maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
|
|
if (maxGrayDeviation > odev) {
|
|
if (kDebug) {
|
|
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) {
|
|
if (kDebug) {
|
|
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) {
|
|
if (kDebug) {
|
|
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
|
|
if (isPalette) {
|
|
col = (uint32_t)((rr << 24) | (gg << 16) | (bb << 8) | aa);
|
|
bool match = false;
|
|
for (idx = 0; idx < num_colors; idx++) {
|
|
if (colors[idx] == col) {
|
|
match = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Write the palette index for the pixel to outRows optimistically
|
|
// We might overwrite it later if we decide to encode as gray or
|
|
// gray + alpha
|
|
*out++ = idx;
|
|
if (!match) {
|
|
if (num_colors == 256) {
|
|
if (kDebug) {
|
|
printf("Found 257th color at %d, %d\n", i, j);
|
|
}
|
|
isPalette = false;
|
|
} else {
|
|
colors[num_colors++] = col;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
*paletteEntries = 0;
|
|
*hasTransparency = !isOpaque;
|
|
int bpp = isOpaque ? 3 : 4;
|
|
int paletteSize = w * h + bpp * num_colors;
|
|
|
|
if (kDebug) {
|
|
printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
|
|
printf("isOpaque = %s\n", isOpaque ? "true" : "false");
|
|
printf("isPalette = %s\n", isPalette ? "true" : "false");
|
|
printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", paletteSize,
|
|
2 * w * h, bpp * w * h);
|
|
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
|
|
// is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
|
|
// 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is
|
|
// sufficiently
|
|
// small, otherwise use COLOR_TYPE_RGB{_ALPHA}
|
|
if (isGrayscale) {
|
|
if (isOpaque) {
|
|
*colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
|
|
} else {
|
|
// Use a simple heuristic to determine whether using a palette will
|
|
// save space versus using gray + alpha for each pixel.
|
|
// This doesn't take into account chunk overhead, filtering, LZ
|
|
// compression, etc.
|
|
if (isPalette && (paletteSize < 2 * w * h)) {
|
|
*colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
|
|
} else {
|
|
*colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
|
|
}
|
|
}
|
|
} else if (isPalette && (paletteSize < bpp * w * h)) {
|
|
*colorType = PNG_COLOR_TYPE_PALETTE;
|
|
} else {
|
|
if (maxGrayDeviation <= grayscaleTolerance) {
|
|
diag->Note(DiagMessage() << "forcing image to gray (max deviation = "
|
|
<< 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
|
|
// color type chosen
|
|
|
|
if (*colorType == PNG_COLOR_TYPE_PALETTE) {
|
|
// Create separate RGB and Alpha palettes and set the number of colors
|
|
*paletteEntries = num_colors;
|
|
|
|
// Create the RGB and alpha palettes
|
|
for (int idx = 0; idx < num_colors; idx++) {
|
|
col = colors[idx];
|
|
rgbPalette[idx].red = (png_byte)((col >> 24) & 0xff);
|
|
rgbPalette[idx].green = (png_byte)((col >> 16) & 0xff);
|
|
rgbPalette[idx].blue = (png_byte)((col >> 8) & 0xff);
|
|
alphaPalette[idx] = (png_byte)(col & 0xff);
|
|
}
|
|
} else if (*colorType == PNG_COLOR_TYPE_GRAY ||
|
|
*colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
|
|
// If the image is gray or gray + alpha, compact the pixels into outRows
|
|
for (j = 0; j < h; j++) {
|
|
const png_byte* row = imageInfo.rows[j];
|
|
png_bytep out = outRows[j];
|
|
for (i = 0; i < w; i++) {
|
|
rr = *row++;
|
|
gg = *row++;
|
|
bb = *row++;
|
|
aa = *row++;
|
|
|
|
if (isGrayscale) {
|
|
*out++ = rr;
|
|
} else {
|
|
*out++ = (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
|
|
}
|
|
if (!isOpaque) {
|
|
*out++ = aa;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool writePng(IDiagnostics* diag, png_structp writePtr,
|
|
png_infop infoPtr, PngInfo* info, int grayScaleTolerance) {
|
|
if (setjmp(png_jmpbuf(writePtr))) {
|
|
diag->Error(DiagMessage() << "failed to write png");
|
|
return false;
|
|
}
|
|
|
|
uint32_t width, height;
|
|
int colorType, bitDepth, interlaceType, compressionType;
|
|
|
|
png_unknown_chunk unknowns[3];
|
|
unknowns[0].data = nullptr;
|
|
unknowns[1].data = nullptr;
|
|
unknowns[2].data = nullptr;
|
|
|
|
png_bytepp outRows =
|
|
(png_bytepp)malloc((int)info->height * sizeof(png_bytep));
|
|
if (outRows == (png_bytepp)0) {
|
|
printf("Can't allocate output buffer!\n");
|
|
exit(1);
|
|
}
|
|
for (uint32_t i = 0; i < info->height; i++) {
|
|
outRows[i] = (png_bytep)malloc(2 * (int)info->width);
|
|
if (outRows[i] == (png_bytep)0) {
|
|
printf("Can't allocate output buffer!\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
|
|
|
|
if (kDebug) {
|
|
diag->Note(DiagMessage() << "writing image: w = " << info->width
|
|
<< ", h = " << info->height);
|
|
}
|
|
|
|
png_color rgbPalette[256];
|
|
png_byte alphaPalette[256];
|
|
bool hasTransparency;
|
|
int paletteEntries;
|
|
|
|
analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette,
|
|
&paletteEntries, &hasTransparency, &colorType, 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 (info->is9Patch &&
|
|
(colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY ||
|
|
colorType == PNG_COLOR_TYPE_PALETTE)) {
|
|
colorType = PNG_COLOR_TYPE_RGB_ALPHA;
|
|
}
|
|
|
|
if (kDebug) {
|
|
switch (colorType) {
|
|
case PNG_COLOR_TYPE_PALETTE:
|
|
diag->Note(DiagMessage() << "has " << paletteEntries << " colors"
|
|
<< (hasTransparency ? " (with alpha)" : "")
|
|
<< ", using PNG_COLOR_TYPE_PALLETTE");
|
|
break;
|
|
case PNG_COLOR_TYPE_GRAY:
|
|
diag->Note(DiagMessage()
|
|
<< "is opaque gray, using PNG_COLOR_TYPE_GRAY");
|
|
break;
|
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
|
diag->Note(DiagMessage()
|
|
<< "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
|
|
break;
|
|
case PNG_COLOR_TYPE_RGB:
|
|
diag->Note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
|
|
break;
|
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
|
diag->Note(DiagMessage()
|
|
<< "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
|
|
break;
|
|
}
|
|
}
|
|
|
|
png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType,
|
|
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
|
PNG_FILTER_TYPE_DEFAULT);
|
|
|
|
if (colorType == PNG_COLOR_TYPE_PALETTE) {
|
|
png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
|
|
if (hasTransparency) {
|
|
png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries,
|
|
(png_color_16p)0);
|
|
}
|
|
png_set_filter(writePtr, 0, PNG_NO_FILTERS);
|
|
} else {
|
|
png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
|
|
}
|
|
|
|
if (info->is9Patch) {
|
|
int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0);
|
|
int pIndex = info->haveLayoutBounds ? 2 : 1;
|
|
int bIndex = 1;
|
|
int oIndex = 0;
|
|
|
|
// Chunks ordered thusly because older platforms depend on the base 9 patch
|
|
// data being last
|
|
png_bytep chunkNames = info->haveLayoutBounds
|
|
? (png_bytep) "npOl\0npLb\0npTc\0"
|
|
: (png_bytep) "npOl\0npTc";
|
|
|
|
// base 9 patch data
|
|
if (kDebug) {
|
|
diag->Note(DiagMessage() << "adding 9-patch info..");
|
|
}
|
|
strcpy((char*)unknowns[pIndex].name, "npTc");
|
|
unknowns[pIndex].data = (png_byte*)info->serialize9Patch();
|
|
unknowns[pIndex].size = info->info9Patch.serializedSize();
|
|
// TODO: remove the check below when everything works
|
|
checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data);
|
|
|
|
// automatically generated 9 patch outline data
|
|
int chunkSize = sizeof(png_uint_32) * 6;
|
|
strcpy((char*)unknowns[oIndex].name, "npOl");
|
|
unknowns[oIndex].data = (png_byte*)calloc(chunkSize, 1);
|
|
png_byte outputData[chunkSize];
|
|
memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
|
|
((float*)outputData)[4] = info->outlineRadius;
|
|
((png_uint_32*)outputData)[5] = info->outlineAlpha;
|
|
memcpy(unknowns[oIndex].data, &outputData, chunkSize);
|
|
unknowns[oIndex].size = chunkSize;
|
|
|
|
// optional optical inset / layout bounds data
|
|
if (info->haveLayoutBounds) {
|
|
int chunkSize = sizeof(png_uint_32) * 4;
|
|
strcpy((char*)unknowns[bIndex].name, "npLb");
|
|
unknowns[bIndex].data = (png_byte*)calloc(chunkSize, 1);
|
|
memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
|
|
unknowns[bIndex].size = chunkSize;
|
|
}
|
|
|
|
for (int i = 0; i < chunkCount; i++) {
|
|
unknowns[i].location = PNG_HAVE_PLTE;
|
|
}
|
|
png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, chunkNames,
|
|
chunkCount);
|
|
png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
|
|
|
|
#if PNG_LIBPNG_VER < 10600
|
|
// Deal with unknown chunk location bug in 1.5.x and earlier.
|
|
png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE);
|
|
if (info->haveLayoutBounds) {
|
|
png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
png_write_info(writePtr, infoPtr);
|
|
|
|
png_bytepp rows;
|
|
if (colorType == PNG_COLOR_TYPE_RGB ||
|
|
colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
|
|
if (colorType == PNG_COLOR_TYPE_RGB) {
|
|
png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
|
|
}
|
|
rows = info->rows.data();
|
|
} else {
|
|
rows = outRows;
|
|
}
|
|
png_write_image(writePtr, rows);
|
|
|
|
if (kDebug) {
|
|
printf("Final image data:\n");
|
|
// dump_image(info->width, info->height, rows, colorType);
|
|
}
|
|
|
|
png_write_end(writePtr, infoPtr);
|
|
|
|
for (uint32_t i = 0; i < info->height; i++) {
|
|
free(outRows[i]);
|
|
}
|
|
free(outRows);
|
|
free(unknowns[0].data);
|
|
free(unknowns[1].data);
|
|
free(unknowns[2].data);
|
|
|
|
png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType,
|
|
&interlaceType, &compressionType, nullptr);
|
|
|
|
if (kDebug) {
|
|
diag->Note(DiagMessage() << "image written: w = " << width
|
|
<< ", h = " << height << ", d = " << bitDepth
|
|
<< ", colors = " << colorType
|
|
<< ", inter = " << interlaceType
|
|
<< ", comp = " << compressionType);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
constexpr uint32_t kColorWhite = 0xffffffffu;
|
|
constexpr uint32_t kColorTick = 0xff000000u;
|
|
constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu;
|
|
|
|
enum class TickType { kNone, kTick, kLayoutBounds, kBoth };
|
|
|
|
static TickType tickType(png_bytep p, bool transparent, const char** outError) {
|
|
png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
|
|
|
|
if (transparent) {
|
|
if (p[3] == 0) {
|
|
return TickType::kNone;
|
|
}
|
|
if (color == kColorLayoutBoundsTick) {
|
|
return TickType::kLayoutBounds;
|
|
}
|
|
if (color == kColorTick) {
|
|
return TickType::kTick;
|
|
}
|
|
|
|
// Error cases
|
|
if (p[3] != 0xff) {
|
|
*outError =
|
|
"Frame pixels must be either solid or transparent "
|
|
"(not intermediate alphas)";
|
|
return TickType::kNone;
|
|
}
|
|
|
|
if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
|
|
*outError = "Ticks in transparent frame must be black or red";
|
|
}
|
|
return TickType::kTick;
|
|
}
|
|
|
|
if (p[3] != 0xFF) {
|
|
*outError = "White frame must be a solid color (no alpha)";
|
|
}
|
|
if (color == kColorWhite) {
|
|
return TickType::kNone;
|
|
}
|
|
if (color == kColorTick) {
|
|
return TickType::kTick;
|
|
}
|
|
if (color == kColorLayoutBoundsTick) {
|
|
return TickType::kLayoutBounds;
|
|
}
|
|
|
|
if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
|
|
*outError = "Ticks in white frame must be black or red";
|
|
return TickType::kNone;
|
|
}
|
|
return TickType::kTick;
|
|
}
|
|
|
|
enum class TickState { kStart, kInside1, kOutside1 };
|
|
|
|
static bool getHorizontalTicks(png_bytep row, int width, bool transparent,
|
|
bool required, int32_t* outLeft,
|
|
int32_t* outRight, const char** outError,
|
|
uint8_t* outDivs, bool multipleAllowed) {
|
|
*outLeft = *outRight = -1;
|
|
TickState state = TickState::kStart;
|
|
bool found = false;
|
|
|
|
for (int i = 1; i < width - 1; i++) {
|
|
if (tickType(row + i * 4, transparent, outError) == TickType::kTick) {
|
|
if (state == TickState::kStart ||
|
|
(state == TickState::kOutside1 && multipleAllowed)) {
|
|
*outLeft = i - 1;
|
|
*outRight = width - 2;
|
|
found = true;
|
|
if (outDivs != NULL) {
|
|
*outDivs += 2;
|
|
}
|
|
state = TickState::kInside1;
|
|
} else if (state == TickState::kOutside1) {
|
|
*outError = "Can't have more than one marked region along edge";
|
|
*outLeft = i;
|
|
return false;
|
|
}
|
|
} else if (!*outError) {
|
|
if (state == TickState::kInside1) {
|
|
// We're done with this div. Move on to the next.
|
|
*outRight = i - 1;
|
|
outRight += 2;
|
|
outLeft += 2;
|
|
state = TickState::kOutside1;
|
|
}
|
|
} else {
|
|
*outLeft = i;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (required && !found) {
|
|
*outError = "No marked region found along edge";
|
|
*outLeft = -1;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool getVerticalTicks(png_bytepp rows, int offset, int height,
|
|
bool transparent, bool required, int32_t* outTop,
|
|
int32_t* outBottom, const char** outError,
|
|
uint8_t* outDivs, bool multipleAllowed) {
|
|
*outTop = *outBottom = -1;
|
|
TickState state = TickState::kStart;
|
|
bool found = false;
|
|
|
|
for (int i = 1; i < height - 1; i++) {
|
|
if (tickType(rows[i] + offset, transparent, outError) == TickType::kTick) {
|
|
if (state == TickState::kStart ||
|
|
(state == TickState::kOutside1 && multipleAllowed)) {
|
|
*outTop = i - 1;
|
|
*outBottom = height - 2;
|
|
found = true;
|
|
if (outDivs != NULL) {
|
|
*outDivs += 2;
|
|
}
|
|
state = TickState::kInside1;
|
|
} else if (state == TickState::kOutside1) {
|
|
*outError = "Can't have more than one marked region along edge";
|
|
*outTop = i;
|
|
return false;
|
|
}
|
|
} else if (!*outError) {
|
|
if (state == TickState::kInside1) {
|
|
// We're done with this div. Move on to the next.
|
|
*outBottom = i - 1;
|
|
outTop += 2;
|
|
outBottom += 2;
|
|
state = TickState::kOutside1;
|
|
}
|
|
} else {
|
|
*outTop = i;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (required && !found) {
|
|
*outError = "No marked region found along edge";
|
|
*outTop = -1;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width,
|
|
bool transparent,
|
|
bool /* required */,
|
|
int32_t* outLeft, int32_t* outRight,
|
|
const char** outError) {
|
|
*outLeft = *outRight = 0;
|
|
|
|
// Look for left tick
|
|
if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) {
|
|
// Starting with a layout padding tick
|
|
int i = 1;
|
|
while (i < width - 1) {
|
|
(*outLeft)++;
|
|
i++;
|
|
if (tickType(row + i * 4, transparent, outError) !=
|
|
TickType::kLayoutBounds) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Look for right tick
|
|
if (tickType(row + (width - 2) * 4, transparent, outError) ==
|
|
TickType::kLayoutBounds) {
|
|
// Ending with a layout padding tick
|
|
int i = width - 2;
|
|
while (i > 1) {
|
|
(*outRight)++;
|
|
i--;
|
|
if (tickType(row + i * 4, transparent, outError) !=
|
|
TickType::kLayoutBounds) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset,
|
|
int height, bool transparent,
|
|
bool /* required */, int32_t* outTop,
|
|
int32_t* outBottom,
|
|
const char** outError) {
|
|
*outTop = *outBottom = 0;
|
|
|
|
// Look for top tick
|
|
if (tickType(rows[1] + offset, transparent, outError) ==
|
|
TickType::kLayoutBounds) {
|
|
// Starting with a layout padding tick
|
|
int i = 1;
|
|
while (i < height - 1) {
|
|
(*outTop)++;
|
|
i++;
|
|
if (tickType(rows[i] + offset, transparent, outError) !=
|
|
TickType::kLayoutBounds) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Look for bottom tick
|
|
if (tickType(rows[height - 2] + offset, transparent, outError) ==
|
|
TickType::kLayoutBounds) {
|
|
// Ending with a layout padding tick
|
|
int i = height - 2;
|
|
while (i > 1) {
|
|
(*outBottom)++;
|
|
i--;
|
|
if (tickType(rows[i] + offset, transparent, outError) !=
|
|
TickType::kLayoutBounds) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX,
|
|
int endY, int dX, int dY, int* outInset) {
|
|
uint8_t maxOpacity = 0;
|
|
int inset = 0;
|
|
*outInset = 0;
|
|
for (int x = startX, y = startY; x != endX && y != endY;
|
|
x += dX, y += dY, inset++) {
|
|
png_byte* color = rows[y] + x * 4;
|
|
uint8_t opacity = color[3];
|
|
if (opacity > maxOpacity) {
|
|
maxOpacity = opacity;
|
|
*outInset = inset;
|
|
}
|
|
if (opacity == 0xff) return;
|
|
}
|
|
}
|
|
|
|
static uint8_t maxAlphaOverRow(png_bytep row, int startX, int endX) {
|
|
uint8_t maxAlpha = 0;
|
|
for (int x = startX; x < endX; x++) {
|
|
uint8_t alpha = (row + x * 4)[3];
|
|
if (alpha > maxAlpha) maxAlpha = alpha;
|
|
}
|
|
return maxAlpha;
|
|
}
|
|
|
|
static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY,
|
|
int endY) {
|
|
uint8_t maxAlpha = 0;
|
|
for (int y = startY; y < endY; y++) {
|
|
uint8_t alpha = (rows[y] + offsetX * 4)[3];
|
|
if (alpha > maxAlpha) maxAlpha = alpha;
|
|
}
|
|
return maxAlpha;
|
|
}
|
|
|
|
static void getOutline(PngInfo* image) {
|
|
int midX = image->width / 2;
|
|
int midY = image->height / 2;
|
|
int endX = image->width - 2;
|
|
int endY = image->height - 2;
|
|
|
|
// find left and right extent of nine patch content on center row
|
|
if (image->width > 4) {
|
|
findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0,
|
|
&image->outlineInsetsLeft);
|
|
findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0,
|
|
&image->outlineInsetsRight);
|
|
} else {
|
|
image->outlineInsetsLeft = 0;
|
|
image->outlineInsetsRight = 0;
|
|
}
|
|
|
|
// find top and bottom extent of nine patch content on center column
|
|
if (image->height > 4) {
|
|
findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1,
|
|
&image->outlineInsetsTop);
|
|
findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1,
|
|
&image->outlineInsetsBottom);
|
|
} else {
|
|
image->outlineInsetsTop = 0;
|
|
image->outlineInsetsBottom = 0;
|
|
}
|
|
|
|
int innerStartX = 1 + image->outlineInsetsLeft;
|
|
int innerStartY = 1 + image->outlineInsetsTop;
|
|
int innerEndX = endX - image->outlineInsetsRight;
|
|
int innerEndY = endY - image->outlineInsetsBottom;
|
|
int innerMidX = (innerEndX + innerStartX) / 2;
|
|
int innerMidY = (innerEndY + innerStartY) / 2;
|
|
|
|
// assuming the image is a round rect, compute the radius by marching
|
|
// diagonally from the top left corner towards the center
|
|
image->outlineAlpha = std::max(
|
|
maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
|
|
maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
|
|
|
|
int diagonalInset = 0;
|
|
findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX,
|
|
innerMidY, 1, 1, &diagonalInset);
|
|
|
|
/* Determine source radius based upon inset:
|
|
* sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
|
|
* sqrt(2) * r = sqrt(2) * i + r
|
|
* (sqrt(2) - 1) * r = sqrt(2) * i
|
|
* r = sqrt(2) / (sqrt(2) - 1) * i
|
|
*/
|
|
image->outlineRadius = 3.4142f * diagonalInset;
|
|
|
|
if (kDebug) {
|
|
printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
|
|
image->outlineInsetsLeft, image->outlineInsetsTop,
|
|
image->outlineInsetsRight, image->outlineInsetsBottom,
|
|
image->outlineRadius, image->outlineAlpha);
|
|
}
|
|
}
|
|
|
|
static uint32_t getColor(png_bytepp rows, int left, int top, int right,
|
|
int bottom) {
|
|
png_bytep color = rows[top] + left * 4;
|
|
|
|
if (left > right || top > bottom) {
|
|
return android::Res_png_9patch::TRANSPARENT_COLOR;
|
|
}
|
|
|
|
while (top <= bottom) {
|
|
for (int i = left; i <= right; i++) {
|
|
png_bytep p = rows[top] + i * 4;
|
|
if (color[3] == 0) {
|
|
if (p[3] != 0) {
|
|
return android::Res_png_9patch::NO_COLOR;
|
|
}
|
|
} else if (p[0] != color[0] || p[1] != color[1] || p[2] != color[2] ||
|
|
p[3] != color[3]) {
|
|
return android::Res_png_9patch::NO_COLOR;
|
|
}
|
|
}
|
|
top++;
|
|
}
|
|
|
|
if (color[3] == 0) {
|
|
return android::Res_png_9patch::TRANSPARENT_COLOR;
|
|
}
|
|
return (color[3] << 24) | (color[0] << 16) | (color[1] << 8) | color[2];
|
|
}
|
|
|
|
static bool do9Patch(PngInfo* image, std::string* outError) {
|
|
image->is9Patch = true;
|
|
|
|
int W = image->width;
|
|
int H = image->height;
|
|
int i, j;
|
|
|
|
const int maxSizeXDivs = W * sizeof(int32_t);
|
|
const int maxSizeYDivs = H * sizeof(int32_t);
|
|
int32_t* xDivs = image->xDivs = new int32_t[W];
|
|
int32_t* yDivs = image->yDivs = new int32_t[H];
|
|
uint8_t numXDivs = 0;
|
|
uint8_t numYDivs = 0;
|
|
|
|
int8_t numColors;
|
|
int numRows;
|
|
int numCols;
|
|
int top;
|
|
int left;
|
|
int right;
|
|
int bottom;
|
|
memset(xDivs, -1, maxSizeXDivs);
|
|
memset(yDivs, -1, maxSizeYDivs);
|
|
image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1;
|
|
image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
|
|
image->layoutBoundsLeft = image->layoutBoundsRight = 0;
|
|
image->layoutBoundsTop = image->layoutBoundsBottom = 0;
|
|
|
|
png_bytep p = image->rows[0];
|
|
bool transparent = p[3] == 0;
|
|
bool hasColor = false;
|
|
|
|
const char* errorMsg = nullptr;
|
|
int errorPixel = -1;
|
|
const char* errorEdge = nullptr;
|
|
|
|
int colorIndex = 0;
|
|
std::vector<png_bytep> newRows;
|
|
|
|
// Validate size...
|
|
if (W < 3 || H < 3) {
|
|
errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
|
|
goto getout;
|
|
}
|
|
|
|
// Validate frame...
|
|
if (!transparent &&
|
|
(p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
|
|
errorMsg = "Must have one-pixel frame that is either transparent or white";
|
|
goto getout;
|
|
}
|
|
|
|
// Find left and right of sizing areas...
|
|
if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1],
|
|
&errorMsg, &numXDivs, true)) {
|
|
errorPixel = xDivs[0];
|
|
errorEdge = "top";
|
|
goto getout;
|
|
}
|
|
|
|
// Find top and bottom of sizing areas...
|
|
if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0],
|
|
&yDivs[1], &errorMsg, &numYDivs, true)) {
|
|
errorPixel = yDivs[0];
|
|
errorEdge = "left";
|
|
goto getout;
|
|
}
|
|
|
|
// Copy patch size data into image...
|
|
image->info9Patch.numXDivs = numXDivs;
|
|
image->info9Patch.numYDivs = numYDivs;
|
|
|
|
// Find left and right of padding area...
|
|
if (!getHorizontalTicks(image->rows[H - 1], W, transparent, false,
|
|
&image->info9Patch.paddingLeft,
|
|
&image->info9Patch.paddingRight, &errorMsg, nullptr,
|
|
false)) {
|
|
errorPixel = image->info9Patch.paddingLeft;
|
|
errorEdge = "bottom";
|
|
goto getout;
|
|
}
|
|
|
|
// Find top and bottom of padding area...
|
|
if (!getVerticalTicks(image->rows.data(), (W - 1) * 4, H, transparent, false,
|
|
&image->info9Patch.paddingTop,
|
|
&image->info9Patch.paddingBottom, &errorMsg, nullptr,
|
|
false)) {
|
|
errorPixel = image->info9Patch.paddingTop;
|
|
errorEdge = "right";
|
|
goto getout;
|
|
}
|
|
|
|
// Find left and right of layout padding...
|
|
getHorizontalLayoutBoundsTicks(image->rows[H - 1], W, transparent, false,
|
|
&image->layoutBoundsLeft,
|
|
&image->layoutBoundsRight, &errorMsg);
|
|
|
|
getVerticalLayoutBoundsTicks(image->rows.data(), (W - 1) * 4, H, transparent,
|
|
false, &image->layoutBoundsTop,
|
|
&image->layoutBoundsBottom, &errorMsg);
|
|
|
|
image->haveLayoutBounds =
|
|
image->layoutBoundsLeft != 0 || image->layoutBoundsRight != 0 ||
|
|
image->layoutBoundsTop != 0 || image->layoutBoundsBottom != 0;
|
|
|
|
if (image->haveLayoutBounds) {
|
|
if (kDebug) {
|
|
printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft,
|
|
image->layoutBoundsTop, image->layoutBoundsRight,
|
|
image->layoutBoundsBottom);
|
|
}
|
|
}
|
|
|
|
// use opacity of pixels to estimate the round rect outline
|
|
getOutline(image);
|
|
|
|
// If padding is not yet specified, take values from size.
|
|
if (image->info9Patch.paddingLeft < 0) {
|
|
image->info9Patch.paddingLeft = xDivs[0];
|
|
image->info9Patch.paddingRight = W - 2 - xDivs[1];
|
|
} else {
|
|
// Adjust value to be correct!
|
|
image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
|
|
}
|
|
if (image->info9Patch.paddingTop < 0) {
|
|
image->info9Patch.paddingTop = yDivs[0];
|
|
image->info9Patch.paddingBottom = H - 2 - yDivs[1];
|
|
} else {
|
|
// Adjust value to be correct!
|
|
image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
|
|
}
|
|
|
|
/* if (kDebug) {
|
|
printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
|
|
xDivs[0], xDivs[1],
|
|
yDivs[0], yDivs[1]);
|
|
printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
|
|
image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
|
|
image->info9Patch.paddingTop,
|
|
image->info9Patch.paddingBottom);
|
|
}*/
|
|
|
|
// Remove frame from image.
|
|
newRows.resize(H - 2);
|
|
for (i = 0; i < H - 2; i++) {
|
|
newRows[i] = image->rows[i + 1];
|
|
memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
|
|
}
|
|
image->rows.swap(newRows);
|
|
|
|
image->width -= 2;
|
|
W = image->width;
|
|
image->height -= 2;
|
|
H = image->height;
|
|
|
|
// Figure out the number of rows and columns in the N-patch
|
|
numCols = numXDivs + 1;
|
|
if (xDivs[0] == 0) { // Column 1 is strechable
|
|
numCols--;
|
|
}
|
|
if (xDivs[numXDivs - 1] == W) {
|
|
numCols--;
|
|
}
|
|
numRows = numYDivs + 1;
|
|
if (yDivs[0] == 0) { // Row 1 is strechable
|
|
numRows--;
|
|
}
|
|
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->colors.resize(numColors);
|
|
|
|
// Fill in color information for each patch.
|
|
|
|
uint32_t c;
|
|
top = 0;
|
|
|
|
// The first row always starts with the top being at y=0 and the bottom
|
|
// being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
|
|
// the first row is stretchable along the Y axis, otherwise it is fixed.
|
|
// The last row always ends with the bottom being bitmap.height and the top
|
|
// being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
|
|
// yDivs[numYDivs-1]. In the former case the last row is stretchable along
|
|
// the Y axis, otherwise it is fixed.
|
|
//
|
|
// The first and last columns are similarly treated with respect to the X
|
|
// axis.
|
|
//
|
|
// The above is to help explain some of the special casing that goes on the
|
|
// code below.
|
|
|
|
// The initial yDiv and whether the first row is considered stretchable or
|
|
// not depends on whether yDiv[0] was zero or not.
|
|
for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) {
|
|
if (j == numYDivs) {
|
|
bottom = H;
|
|
} else {
|
|
bottom = yDivs[j];
|
|
}
|
|
left = 0;
|
|
// The initial xDiv and whether the first column is considered
|
|
// stretchable or not depends on whether xDiv[0] was zero or not.
|
|
for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) {
|
|
if (i == numXDivs) {
|
|
right = W;
|
|
} else {
|
|
right = xDivs[i];
|
|
}
|
|
c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
|
|
image->colors[colorIndex++] = c;
|
|
if (kDebug) {
|
|
if (c != android::Res_png_9patch::NO_COLOR) {
|
|
hasColor = true;
|
|
}
|
|
}
|
|
left = right;
|
|
}
|
|
top = bottom;
|
|
}
|
|
|
|
assert(colorIndex == numColors);
|
|
|
|
if (kDebug && hasColor) {
|
|
for (i = 0; i < numColors; i++) {
|
|
if (i == 0) printf("Colors:\n");
|
|
printf(" #%08x", image->colors[i]);
|
|
if (i == numColors - 1) printf("\n");
|
|
}
|
|
}
|
|
getout:
|
|
if (errorMsg) {
|
|
std::stringstream err;
|
|
err << "9-patch malformed: " << errorMsg;
|
|
if (errorEdge) {
|
|
err << "." << std::endl;
|
|
if (errorPixel >= 0) {
|
|
err << "Found at pixel #" << errorPixel << " along " << errorEdge
|
|
<< " edge";
|
|
} else {
|
|
err << "Found along " << errorEdge << " edge";
|
|
}
|
|
}
|
|
*outError = err.str();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Png::process(const Source& source, std::istream* input,
|
|
BigBuffer* outBuffer, const PngOptions& options) {
|
|
png_byte signature[kPngSignatureSize];
|
|
|
|
// Read the PNG signature first.
|
|
if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
|
|
mDiag->Error(DiagMessage() << strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
// If the PNG signature doesn't match, bail early.
|
|
if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
|
|
mDiag->Error(DiagMessage() << "not a valid png file");
|
|
return false;
|
|
}
|
|
|
|
bool result = false;
|
|
png_structp readPtr = nullptr;
|
|
png_infop infoPtr = nullptr;
|
|
png_structp writePtr = nullptr;
|
|
png_infop writeInfoPtr = nullptr;
|
|
PngInfo pngInfo = {};
|
|
|
|
readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
|
|
if (!readPtr) {
|
|
mDiag->Error(DiagMessage() << "failed to allocate read ptr");
|
|
goto bail;
|
|
}
|
|
|
|
infoPtr = png_create_info_struct(readPtr);
|
|
if (!infoPtr) {
|
|
mDiag->Error(DiagMessage() << "failed to allocate info ptr");
|
|
goto bail;
|
|
}
|
|
|
|
png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr,
|
|
logWarning);
|
|
|
|
// Set the read function to read from std::istream.
|
|
png_set_read_fn(readPtr, (png_voidp)input, readDataFromStream);
|
|
|
|
if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) {
|
|
goto bail;
|
|
}
|
|
|
|
if (util::EndsWith(source.path, ".9.png")) {
|
|
std::string errorMsg;
|
|
if (!do9Patch(&pngInfo, &errorMsg)) {
|
|
mDiag->Error(DiagMessage() << errorMsg);
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
writePtr =
|
|
png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
|
|
if (!writePtr) {
|
|
mDiag->Error(DiagMessage() << "failed to allocate write ptr");
|
|
goto bail;
|
|
}
|
|
|
|
writeInfoPtr = png_create_info_struct(writePtr);
|
|
if (!writeInfoPtr) {
|
|
mDiag->Error(DiagMessage() << "failed to allocate write info ptr");
|
|
goto bail;
|
|
}
|
|
|
|
png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
|
|
|
|
// Set the write function to write to std::ostream.
|
|
png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream,
|
|
flushDataToStream);
|
|
|
|
if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo,
|
|
options.grayscale_tolerance)) {
|
|
goto bail;
|
|
}
|
|
|
|
result = true;
|
|
bail:
|
|
if (readPtr) {
|
|
png_destroy_read_struct(&readPtr, &infoPtr, nullptr);
|
|
}
|
|
|
|
if (writePtr) {
|
|
png_destroy_write_struct(&writePtr, &writeInfoPtr);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
} // namespace aapt
|