2016-09-14 17:35:43 -07:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2016 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 "compile/Png.h"
|
|
|
|
|
|
|
|
#include <android-base/errors.h>
|
|
|
|
#include <android-base/macros.h>
|
|
|
|
#include <png.h>
|
2016-10-19 12:18:14 -07:00
|
|
|
#include <zlib.h>
|
|
|
|
#include <algorithm>
|
2016-09-14 17:35:43 -07:00
|
|
|
#include <unordered_map>
|
|
|
|
#include <unordered_set>
|
|
|
|
|
|
|
|
namespace aapt {
|
|
|
|
|
|
|
|
// Size in bytes of the PNG signature.
|
|
|
|
constexpr size_t kPngSignatureSize = 8u;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Custom deleter that destroys libpng read and info structs.
|
|
|
|
*/
|
|
|
|
class PngReadStructDeleter {
|
2016-10-19 12:18:14 -07:00
|
|
|
public:
|
|
|
|
explicit PngReadStructDeleter(png_structp readPtr, png_infop infoPtr)
|
|
|
|
: mReadPtr(readPtr), mInfoPtr(infoPtr) {}
|
2016-09-14 17:35:43 -07:00
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
~PngReadStructDeleter() {
|
|
|
|
png_destroy_read_struct(&mReadPtr, &mInfoPtr, nullptr);
|
|
|
|
}
|
2016-09-14 17:35:43 -07:00
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
private:
|
|
|
|
png_structp mReadPtr;
|
|
|
|
png_infop mInfoPtr;
|
2016-09-14 17:35:43 -07:00
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
|
2016-09-14 17:35:43 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Custom deleter that destroys libpng write and info structs.
|
|
|
|
*/
|
|
|
|
class PngWriteStructDeleter {
|
2016-10-19 12:18:14 -07:00
|
|
|
public:
|
|
|
|
explicit PngWriteStructDeleter(png_structp writePtr, png_infop infoPtr)
|
|
|
|
: mWritePtr(writePtr), mInfoPtr(infoPtr) {}
|
2016-09-14 17:35:43 -07:00
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
~PngWriteStructDeleter() { png_destroy_write_struct(&mWritePtr, &mInfoPtr); }
|
2016-09-14 17:35:43 -07:00
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
private:
|
|
|
|
png_structp mWritePtr;
|
|
|
|
png_infop mInfoPtr;
|
2016-09-14 17:35:43 -07:00
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter);
|
2016-09-14 17:35:43 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
// Custom warning logging method that uses IDiagnostics.
|
|
|
|
static void logWarning(png_structp pngPtr, png_const_charp warningMsg) {
|
2016-10-19 12:18:14 -07:00
|
|
|
IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(pngPtr);
|
|
|
|
diag->warn(DiagMessage() << warningMsg);
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Custom error logging method that uses IDiagnostics.
|
|
|
|
static void logError(png_structp pngPtr, png_const_charp errorMsg) {
|
2016-10-19 12:18:14 -07:00
|
|
|
IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(pngPtr);
|
|
|
|
diag->error(DiagMessage() << errorMsg);
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
static void readDataFromStream(png_structp pngPtr, png_bytep buffer,
|
|
|
|
png_size_t len) {
|
|
|
|
io::InputStream* in = (io::InputStream*)png_get_io_ptr(pngPtr);
|
|
|
|
|
|
|
|
const void* inBuffer;
|
|
|
|
int inLen;
|
|
|
|
if (!in->Next(&inBuffer, &inLen)) {
|
|
|
|
if (in->HadError()) {
|
|
|
|
std::string err = in->GetError();
|
|
|
|
png_error(pngPtr, err.c_str());
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const size_t bytesRead = std::min(static_cast<size_t>(inLen), len);
|
|
|
|
memcpy(buffer, inBuffer, bytesRead);
|
|
|
|
if (bytesRead != static_cast<size_t>(inLen)) {
|
|
|
|
in->BackUp(inLen - static_cast<int>(bytesRead));
|
|
|
|
}
|
|
|
|
}
|
2016-09-14 17:35:43 -07:00
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
static void writeDataToStream(png_structp pngPtr, png_bytep buffer,
|
|
|
|
png_size_t len) {
|
|
|
|
io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(pngPtr);
|
2016-09-14 17:35:43 -07:00
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
void* outBuffer;
|
|
|
|
int outLen;
|
|
|
|
while (len > 0) {
|
|
|
|
if (!out->Next(&outBuffer, &outLen)) {
|
|
|
|
if (out->HadError()) {
|
|
|
|
std::string err = out->GetError();
|
|
|
|
png_error(pngPtr, err.c_str());
|
|
|
|
}
|
|
|
|
return;
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
const size_t bytesWritten = std::min(static_cast<size_t>(outLen), len);
|
|
|
|
memcpy(outBuffer, buffer, bytesWritten);
|
2016-09-14 17:35:43 -07:00
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
// Advance the input buffer.
|
|
|
|
buffer += bytesWritten;
|
|
|
|
len -= bytesWritten;
|
2016-09-14 17:35:43 -07:00
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
// Advance the output buffer.
|
|
|
|
outLen -= static_cast<int>(bytesWritten);
|
|
|
|
}
|
2016-09-14 17:35:43 -07:00
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
// If the entire output buffer wasn't used, backup.
|
|
|
|
if (outLen > 0) {
|
|
|
|
out->BackUp(outLen);
|
|
|
|
}
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<Image> readPng(IAaptContext* context, io::InputStream* in) {
|
2016-10-19 12:18:14 -07:00
|
|
|
// Read the first 8 bytes of the file looking for the PNG signature.
|
|
|
|
// Bail early if it does not match.
|
|
|
|
const png_byte* signature;
|
|
|
|
int bufferSize;
|
|
|
|
if (!in->Next((const void**)&signature, &bufferSize)) {
|
|
|
|
context->getDiagnostics()->error(
|
|
|
|
DiagMessage() << android::base::SystemErrorCodeToString(errno));
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (static_cast<size_t>(bufferSize) < kPngSignatureSize ||
|
|
|
|
png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
|
|
|
|
context->getDiagnostics()->error(
|
|
|
|
DiagMessage() << "file signature does not match PNG signature");
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start at the beginning of the first chunk.
|
|
|
|
in->BackUp(bufferSize - static_cast<int>(kPngSignatureSize));
|
|
|
|
|
|
|
|
// Create and initialize the png_struct with the default error and warning
|
|
|
|
// handlers.
|
|
|
|
// The header version is also passed in to ensure that this was built against
|
|
|
|
// the same
|
|
|
|
// version of libpng.
|
|
|
|
png_structp readPtr =
|
|
|
|
png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
|
|
|
if (readPtr == nullptr) {
|
|
|
|
context->getDiagnostics()->error(
|
|
|
|
DiagMessage() << "failed to create libpng read png_struct");
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create and initialize the memory for image header and data.
|
|
|
|
png_infop infoPtr = png_create_info_struct(readPtr);
|
|
|
|
if (infoPtr == nullptr) {
|
|
|
|
context->getDiagnostics()->error(
|
|
|
|
DiagMessage() << "failed to create libpng read png_info");
|
|
|
|
png_destroy_read_struct(&readPtr, nullptr, nullptr);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Automatically release PNG resources at end of scope.
|
|
|
|
PngReadStructDeleter pngReadDeleter(readPtr, infoPtr);
|
|
|
|
|
|
|
|
// libpng uses longjmp to jump to an error handling routine.
|
|
|
|
// setjmp will only return true if it was jumped to, aka there was
|
|
|
|
// an error.
|
|
|
|
if (setjmp(png_jmpbuf(readPtr))) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle warnings ourselves via IDiagnostics.
|
|
|
|
png_set_error_fn(readPtr, (png_voidp)context->getDiagnostics(), logError,
|
|
|
|
logWarning);
|
|
|
|
|
|
|
|
// Set up the read functions which read from our custom data sources.
|
|
|
|
png_set_read_fn(readPtr, (png_voidp)in, readDataFromStream);
|
|
|
|
|
|
|
|
// Skip the signature that we already read.
|
|
|
|
png_set_sig_bytes(readPtr, kPngSignatureSize);
|
|
|
|
|
|
|
|
// Read the chunk headers.
|
|
|
|
png_read_info(readPtr, infoPtr);
|
|
|
|
|
|
|
|
// Extract image meta-data from the various chunk headers.
|
|
|
|
uint32_t width, height;
|
|
|
|
int bitDepth, colorType, interlaceMethod, compressionMethod, filterMethod;
|
|
|
|
png_get_IHDR(readPtr, infoPtr, &width, &height, &bitDepth, &colorType,
|
|
|
|
&interlaceMethod, &compressionMethod, &filterMethod);
|
|
|
|
|
|
|
|
// When the image is read, expand it so that it is in RGBA 8888 format
|
|
|
|
// so that image handling is uniform.
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (interlaceMethod != PNG_INTERLACE_NONE) {
|
|
|
|
png_set_interlace_handling(readPtr);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Once all the options for reading have been set, we need to flush
|
|
|
|
// them to libpng.
|
|
|
|
png_read_update_info(readPtr, infoPtr);
|
|
|
|
|
|
|
|
// 9-patch uses int32_t to index images, so we cap the image dimensions to
|
|
|
|
// something
|
|
|
|
// that can always be represented by 9-patch.
|
|
|
|
if (width > std::numeric_limits<int32_t>::max() ||
|
|
|
|
height > std::numeric_limits<int32_t>::max()) {
|
|
|
|
context->getDiagnostics()->error(DiagMessage()
|
|
|
|
<< "PNG image dimensions are too large: "
|
|
|
|
<< width << "x" << height);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<Image> outputImage = util::make_unique<Image>();
|
|
|
|
outputImage->width = static_cast<int32_t>(width);
|
|
|
|
outputImage->height = static_cast<int32_t>(height);
|
|
|
|
|
|
|
|
const size_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
|
|
|
|
assert(rowBytes == 4 * width); // RGBA
|
|
|
|
|
|
|
|
// Allocate one large block to hold the image.
|
|
|
|
outputImage->data =
|
|
|
|
std::unique_ptr<uint8_t[]>(new uint8_t[height * rowBytes]);
|
|
|
|
|
|
|
|
// Create an array of rows that index into the data block.
|
|
|
|
outputImage->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]);
|
|
|
|
for (uint32_t h = 0; h < height; h++) {
|
|
|
|
outputImage->rows[h] = outputImage->data.get() + (h * rowBytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Actually read the image pixels.
|
|
|
|
png_read_image(readPtr, outputImage->rows.get());
|
|
|
|
|
|
|
|
// Finish reading. This will read any other chunks after the image data.
|
|
|
|
png_read_end(readPtr, infoPtr);
|
|
|
|
|
|
|
|
return outputImage;
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-10-19 12:18:14 -07:00
|
|
|
* Experimentally chosen constant to be added to the overhead of using color
|
|
|
|
* type
|
|
|
|
* PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette
|
|
|
|
* chunk.
|
|
|
|
* Without this, many small PNGs encoded with palettes are larger after
|
|
|
|
* compression than
|
2016-09-14 17:35:43 -07:00
|
|
|
* the same PNGs encoded as RGBA.
|
|
|
|
*/
|
|
|
|
constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u;
|
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
// Pick a color type by which to encode the image, based on which color type
|
|
|
|
// will take
|
2016-09-14 17:35:43 -07:00
|
|
|
// the least amount of disk space.
|
|
|
|
//
|
|
|
|
// 9-patch images traditionally have not been encoded with palettes.
|
|
|
|
// The original rationale was to avoid dithering until after scaling,
|
|
|
|
// but I don't think this would be an issue with palettes. Either way,
|
|
|
|
// our naive size estimation tends to be wrong for small images like 9-patches
|
|
|
|
// and using palettes balloons the size of the resulting 9-patch.
|
|
|
|
// In order to not regress in size, restrict 9-patch to not use palettes.
|
|
|
|
|
|
|
|
// The options are:
|
|
|
|
//
|
|
|
|
// - RGB
|
|
|
|
// - RGBA
|
|
|
|
// - RGB + cheap alpha
|
|
|
|
// - Color palette
|
|
|
|
// - Color palette + cheap alpha
|
|
|
|
// - Color palette + alpha palette
|
|
|
|
// - Grayscale
|
|
|
|
// - Grayscale + cheap alpha
|
|
|
|
// - Grayscale + alpha
|
|
|
|
//
|
2016-10-19 12:18:14 -07:00
|
|
|
static int pickColorType(int32_t width, int32_t height, bool grayScale,
|
|
|
|
bool convertibleToGrayScale, bool hasNinePatch,
|
2016-09-14 17:35:43 -07:00
|
|
|
size_t colorPaletteSize, size_t alphaPaletteSize) {
|
2016-10-19 12:18:14 -07:00
|
|
|
const size_t paletteChunkSize = 16 + colorPaletteSize * 3;
|
|
|
|
const size_t alphaChunkSize = 16 + alphaPaletteSize;
|
|
|
|
const size_t colorAlphaDataChunkSize = 16 + 4 * width * height;
|
|
|
|
const size_t colorDataChunkSize = 16 + 3 * width * height;
|
|
|
|
const size_t grayScaleAlphaDataChunkSize = 16 + 2 * width * height;
|
|
|
|
const size_t paletteDataChunkSize = 16 + width * height;
|
|
|
|
|
|
|
|
if (grayScale) {
|
|
|
|
if (alphaPaletteSize == 0) {
|
|
|
|
// This is the smallest the data can be.
|
|
|
|
return PNG_COLOR_TYPE_GRAY;
|
|
|
|
} else if (colorPaletteSize <= 256 && !hasNinePatch) {
|
|
|
|
// This grayscale has alpha and can fit within a palette.
|
|
|
|
// See if it is worth fitting into a palette.
|
|
|
|
const size_t paletteThreshold = paletteChunkSize + alphaChunkSize +
|
|
|
|
paletteDataChunkSize +
|
|
|
|
kPaletteOverheadConstant;
|
|
|
|
if (grayScaleAlphaDataChunkSize > paletteThreshold) {
|
|
|
|
return PNG_COLOR_TYPE_PALETTE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return PNG_COLOR_TYPE_GRAY_ALPHA;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (colorPaletteSize <= 256 && !hasNinePatch) {
|
|
|
|
// This image can fit inside a palette. Let's see if it is worth it.
|
|
|
|
size_t totalSizeWithPalette = paletteDataChunkSize + paletteChunkSize;
|
|
|
|
size_t totalSizeWithoutPalette = colorDataChunkSize;
|
|
|
|
if (alphaPaletteSize > 0) {
|
|
|
|
totalSizeWithPalette += alphaPaletteSize;
|
|
|
|
totalSizeWithoutPalette = colorAlphaDataChunkSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (totalSizeWithoutPalette >
|
|
|
|
totalSizeWithPalette + kPaletteOverheadConstant) {
|
|
|
|
return PNG_COLOR_TYPE_PALETTE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (convertibleToGrayScale) {
|
2016-09-14 17:35:43 -07:00
|
|
|
if (alphaPaletteSize == 0) {
|
2016-10-19 12:18:14 -07:00
|
|
|
return PNG_COLOR_TYPE_GRAY;
|
|
|
|
} else {
|
|
|
|
return PNG_COLOR_TYPE_GRAY_ALPHA;
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
2016-10-19 12:18:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (alphaPaletteSize == 0) {
|
|
|
|
return PNG_COLOR_TYPE_RGB;
|
|
|
|
}
|
|
|
|
return PNG_COLOR_TYPE_RGBA;
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
// Assigns indices to the color and alpha palettes, encodes them, and then
|
|
|
|
// invokes
|
2016-09-14 17:35:43 -07:00
|
|
|
// png_set_PLTE/png_set_tRNS.
|
|
|
|
// This must be done before writing image data.
|
2016-10-19 12:18:14 -07:00
|
|
|
// Image data must be transformed to use the indices assigned within the
|
|
|
|
// palette.
|
2016-09-14 17:35:43 -07:00
|
|
|
static void writePalette(png_structp writePtr, png_infop writeInfoPtr,
|
2016-10-19 12:18:14 -07:00
|
|
|
std::unordered_map<uint32_t, int>* colorPalette,
|
|
|
|
std::unordered_set<uint32_t>* alphaPalette) {
|
|
|
|
assert(colorPalette->size() <= 256);
|
|
|
|
assert(alphaPalette->size() <= 256);
|
|
|
|
|
|
|
|
// Populate the PNG palette struct and assign indices to the color
|
|
|
|
// palette.
|
|
|
|
|
|
|
|
// Colors in the alpha palette should have smaller indices.
|
|
|
|
// This will ensure that we can truncate the alpha palette if it is
|
|
|
|
// smaller than the color palette.
|
|
|
|
int index = 0;
|
|
|
|
for (uint32_t color : *alphaPalette) {
|
|
|
|
(*colorPalette)[color] = index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assign the rest of the entries.
|
|
|
|
for (auto& entry : *colorPalette) {
|
|
|
|
if (entry.second == -1) {
|
|
|
|
entry.second = index++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the PNG color palette struct.
|
|
|
|
auto colorPaletteBytes =
|
|
|
|
std::unique_ptr<png_color[]>(new png_color[colorPalette->size()]);
|
|
|
|
|
|
|
|
std::unique_ptr<png_byte[]> alphaPaletteBytes;
|
|
|
|
if (!alphaPalette->empty()) {
|
|
|
|
alphaPaletteBytes =
|
|
|
|
std::unique_ptr<png_byte[]>(new png_byte[alphaPalette->size()]);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto& entry : *colorPalette) {
|
|
|
|
const uint32_t color = entry.first;
|
|
|
|
const int index = entry.second;
|
|
|
|
assert(index >= 0);
|
|
|
|
assert(static_cast<size_t>(index) < colorPalette->size());
|
|
|
|
|
|
|
|
png_colorp slot = colorPaletteBytes.get() + index;
|
|
|
|
slot->red = color >> 24;
|
|
|
|
slot->green = color >> 16;
|
|
|
|
slot->blue = color >> 8;
|
|
|
|
|
|
|
|
const png_byte alpha = color & 0x000000ff;
|
|
|
|
if (alpha != 0xff && alphaPaletteBytes) {
|
|
|
|
assert(static_cast<size_t>(index) < alphaPalette->size());
|
|
|
|
alphaPaletteBytes[index] = alpha;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The bytes get copied here, so it is safe to release colorPaletteBytes at
|
|
|
|
// the end of function
|
|
|
|
// scope.
|
|
|
|
png_set_PLTE(writePtr, writeInfoPtr, colorPaletteBytes.get(),
|
|
|
|
colorPalette->size());
|
|
|
|
|
|
|
|
if (alphaPaletteBytes) {
|
|
|
|
png_set_tRNS(writePtr, writeInfoPtr, alphaPaletteBytes.get(),
|
|
|
|
alphaPalette->size(), nullptr);
|
|
|
|
}
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Write the 9-patch custom PNG chunks to writeInfoPtr. This must be done before
|
|
|
|
// writing image data.
|
|
|
|
static void writeNinePatch(png_structp writePtr, png_infop writeInfoPtr,
|
|
|
|
const NinePatch* ninePatch) {
|
2016-10-19 12:18:14 -07:00
|
|
|
// The order of the chunks is important.
|
|
|
|
// 9-patch code in older platforms expects the 9-patch chunk to
|
|
|
|
// be last.
|
|
|
|
|
|
|
|
png_unknown_chunk unknownChunks[3];
|
|
|
|
memset(unknownChunks, 0, sizeof(unknownChunks));
|
|
|
|
|
|
|
|
size_t index = 0;
|
|
|
|
size_t chunkLen = 0;
|
|
|
|
|
|
|
|
std::unique_ptr<uint8_t[]> serializedOutline =
|
|
|
|
ninePatch->serializeRoundedRectOutline(&chunkLen);
|
|
|
|
strcpy((char*)unknownChunks[index].name, "npOl");
|
|
|
|
unknownChunks[index].size = chunkLen;
|
|
|
|
unknownChunks[index].data = (png_bytep)serializedOutline.get();
|
|
|
|
unknownChunks[index].location = PNG_HAVE_PLTE;
|
|
|
|
index++;
|
|
|
|
|
|
|
|
std::unique_ptr<uint8_t[]> serializedLayoutBounds;
|
|
|
|
if (ninePatch->layoutBounds.nonZero()) {
|
|
|
|
serializedLayoutBounds = ninePatch->serializeLayoutBounds(&chunkLen);
|
|
|
|
strcpy((char*)unknownChunks[index].name, "npLb");
|
2016-09-14 17:35:43 -07:00
|
|
|
unknownChunks[index].size = chunkLen;
|
2016-10-19 12:18:14 -07:00
|
|
|
unknownChunks[index].data = (png_bytep)serializedLayoutBounds.get();
|
2016-09-14 17:35:43 -07:00
|
|
|
unknownChunks[index].location = PNG_HAVE_PLTE;
|
|
|
|
index++;
|
2016-10-19 12:18:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<uint8_t[]> serializedNinePatch =
|
|
|
|
ninePatch->serializeBase(&chunkLen);
|
|
|
|
strcpy((char*)unknownChunks[index].name, "npTc");
|
|
|
|
unknownChunks[index].size = chunkLen;
|
|
|
|
unknownChunks[index].data = (png_bytep)serializedNinePatch.get();
|
|
|
|
unknownChunks[index].location = PNG_HAVE_PLTE;
|
|
|
|
index++;
|
|
|
|
|
|
|
|
// Handle all unknown chunks. We are manually setting the chunks here,
|
|
|
|
// so we will only ever handle our custom chunks.
|
|
|
|
png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0);
|
|
|
|
|
|
|
|
// Set the actual chunks here. The data gets copied, so our buffers can
|
|
|
|
// safely go out of scope.
|
|
|
|
png_set_unknown_chunks(writePtr, writeInfoPtr, unknownChunks, index);
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
bool writePng(IAaptContext* context, const Image* image,
|
|
|
|
const NinePatch* ninePatch, io::OutputStream* out,
|
|
|
|
const PngOptions& options) {
|
|
|
|
// Create and initialize the write png_struct with the default error and
|
|
|
|
// warning handlers.
|
|
|
|
// The header version is also passed in to ensure that this was built against
|
|
|
|
// the same
|
|
|
|
// version of libpng.
|
|
|
|
png_structp writePtr =
|
|
|
|
png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
|
|
|
if (writePtr == nullptr) {
|
|
|
|
context->getDiagnostics()->error(
|
|
|
|
DiagMessage() << "failed to create libpng write png_struct");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allocate memory to store image header data.
|
|
|
|
png_infop writeInfoPtr = png_create_info_struct(writePtr);
|
|
|
|
if (writeInfoPtr == nullptr) {
|
|
|
|
context->getDiagnostics()->error(
|
|
|
|
DiagMessage() << "failed to create libpng write png_info");
|
|
|
|
png_destroy_write_struct(&writePtr, nullptr);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Automatically release PNG resources at end of scope.
|
|
|
|
PngWriteStructDeleter pngWriteDeleter(writePtr, writeInfoPtr);
|
|
|
|
|
|
|
|
// libpng uses longjmp to jump to error handling routines.
|
|
|
|
// setjmp will return true only if it was jumped to, aka, there was an error.
|
|
|
|
if (setjmp(png_jmpbuf(writePtr))) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle warnings with our IDiagnostics.
|
|
|
|
png_set_error_fn(writePtr, (png_voidp)context->getDiagnostics(), logError,
|
|
|
|
logWarning);
|
|
|
|
|
|
|
|
// Set up the write functions which write to our custom data sources.
|
|
|
|
png_set_write_fn(writePtr, (png_voidp)out, writeDataToStream, nullptr);
|
|
|
|
|
|
|
|
// We want small files and can take the performance hit to achieve this goal.
|
|
|
|
png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
|
|
|
|
|
|
|
|
// Begin analysis of the image data.
|
|
|
|
// 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 (palette).
|
|
|
|
std::unordered_map<uint32_t, int> colorPalette;
|
|
|
|
std::unordered_set<uint32_t> alphaPalette;
|
|
|
|
bool needsToZeroRGBChannelsOfTransparentPixels = false;
|
|
|
|
bool grayScale = true;
|
|
|
|
int maxGrayDeviation = 0;
|
|
|
|
|
|
|
|
for (int32_t y = 0; y < image->height; y++) {
|
|
|
|
const uint8_t* row = image->rows[y];
|
|
|
|
for (int32_t x = 0; x < image->width; x++) {
|
|
|
|
int red = *row++;
|
|
|
|
int green = *row++;
|
|
|
|
int blue = *row++;
|
|
|
|
int alpha = *row++;
|
|
|
|
|
|
|
|
if (alpha == 0) {
|
|
|
|
// The color is completely transparent.
|
|
|
|
// For purposes of palettes and grayscale optimization,
|
|
|
|
// treat all channels as 0x00.
|
|
|
|
needsToZeroRGBChannelsOfTransparentPixels =
|
|
|
|
needsToZeroRGBChannelsOfTransparentPixels ||
|
|
|
|
(red != 0 || green != 0 || blue != 0);
|
|
|
|
red = green = blue = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert the color into the color palette.
|
|
|
|
const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha;
|
|
|
|
colorPalette[color] = -1;
|
|
|
|
|
|
|
|
// If the pixel has non-opaque alpha, insert it into the
|
|
|
|
// alpha palette.
|
|
|
|
if (alpha != 0xff) {
|
|
|
|
alphaPalette.insert(color);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the image is indeed grayscale.
|
|
|
|
if (grayScale) {
|
|
|
|
if (red != green || red != blue) {
|
|
|
|
grayScale = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate the gray scale deviation so that it can be compared
|
|
|
|
// with the threshold.
|
|
|
|
maxGrayDeviation = std::max(std::abs(red - green), maxGrayDeviation);
|
|
|
|
maxGrayDeviation = std::max(std::abs(green - blue), maxGrayDeviation);
|
|
|
|
maxGrayDeviation = std::max(std::abs(blue - red), maxGrayDeviation);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (context->verbose()) {
|
|
|
|
DiagMessage msg;
|
|
|
|
msg << " paletteSize=" << colorPalette.size()
|
|
|
|
<< " alphaPaletteSize=" << alphaPalette.size()
|
|
|
|
<< " maxGrayDeviation=" << maxGrayDeviation
|
|
|
|
<< " grayScale=" << (grayScale ? "true" : "false");
|
|
|
|
context->getDiagnostics()->note(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
const bool convertibleToGrayScale =
|
|
|
|
maxGrayDeviation <= options.grayScaleTolerance;
|
|
|
|
|
|
|
|
const int newColorType = pickColorType(
|
|
|
|
image->width, image->height, grayScale, convertibleToGrayScale,
|
|
|
|
ninePatch != nullptr, colorPalette.size(), alphaPalette.size());
|
|
|
|
|
|
|
|
if (context->verbose()) {
|
|
|
|
DiagMessage msg;
|
|
|
|
msg << "encoding PNG ";
|
|
|
|
if (ninePatch) {
|
|
|
|
msg << "(with 9-patch) as ";
|
|
|
|
}
|
|
|
|
switch (newColorType) {
|
|
|
|
case PNG_COLOR_TYPE_GRAY:
|
|
|
|
msg << "GRAY";
|
|
|
|
break;
|
|
|
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
|
|
|
msg << "GRAY + ALPHA";
|
|
|
|
break;
|
|
|
|
case PNG_COLOR_TYPE_RGB:
|
|
|
|
msg << "RGB";
|
|
|
|
break;
|
|
|
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
|
|
|
msg << "RGBA";
|
|
|
|
break;
|
|
|
|
case PNG_COLOR_TYPE_PALETTE:
|
|
|
|
msg << "PALETTE";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
msg << "unknown type " << newColorType;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
context->getDiagnostics()->note(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
png_set_IHDR(writePtr, writeInfoPtr, image->width, image->height, 8,
|
|
|
|
newColorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
|
|
|
PNG_FILTER_TYPE_DEFAULT);
|
|
|
|
|
|
|
|
if (newColorType & PNG_COLOR_MASK_PALETTE) {
|
|
|
|
// Assigns indices to the palette, and writes the encoded palette to the
|
|
|
|
// libpng writePtr.
|
|
|
|
writePalette(writePtr, writeInfoPtr, &colorPalette, &alphaPalette);
|
|
|
|
png_set_filter(writePtr, 0, PNG_NO_FILTERS);
|
|
|
|
} else {
|
|
|
|
png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ninePatch) {
|
|
|
|
writeNinePatch(writePtr, writeInfoPtr, ninePatch);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Flush our updates to the header.
|
|
|
|
png_write_info(writePtr, writeInfoPtr);
|
|
|
|
|
|
|
|
// Write out each row of image data according to its encoding.
|
|
|
|
if (newColorType == PNG_COLOR_TYPE_PALETTE) {
|
|
|
|
// 1 byte/pixel.
|
|
|
|
auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width]);
|
2016-09-14 17:35:43 -07:00
|
|
|
|
|
|
|
for (int32_t y = 0; y < image->height; y++) {
|
2016-10-19 12:18:14 -07:00
|
|
|
png_const_bytep inRow = image->rows[y];
|
|
|
|
for (int32_t x = 0; x < image->width; x++) {
|
|
|
|
int rr = *inRow++;
|
|
|
|
int gg = *inRow++;
|
|
|
|
int bb = *inRow++;
|
|
|
|
int aa = *inRow++;
|
|
|
|
if (aa == 0) {
|
|
|
|
// Zero out color channels when transparent.
|
|
|
|
rr = gg = bb = 0;
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa;
|
|
|
|
const int idx = colorPalette[color];
|
|
|
|
assert(idx != -1);
|
|
|
|
outRow[x] = static_cast<png_byte>(idx);
|
|
|
|
}
|
|
|
|
png_write_row(writePtr, outRow.get());
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
2016-10-19 12:18:14 -07:00
|
|
|
} else if (newColorType == PNG_COLOR_TYPE_GRAY ||
|
|
|
|
newColorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
|
|
|
|
const size_t bpp = newColorType == PNG_COLOR_TYPE_GRAY ? 1 : 2;
|
|
|
|
auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
|
2016-09-14 17:35:43 -07:00
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
for (int32_t y = 0; y < image->height; y++) {
|
|
|
|
png_const_bytep inRow = image->rows[y];
|
|
|
|
for (int32_t x = 0; x < image->width; x++) {
|
|
|
|
int rr = inRow[x * 4];
|
|
|
|
int gg = inRow[x * 4 + 1];
|
|
|
|
int bb = inRow[x * 4 + 2];
|
|
|
|
int aa = inRow[x * 4 + 3];
|
|
|
|
if (aa == 0) {
|
|
|
|
// Zero out the gray channel when transparent.
|
|
|
|
rr = gg = bb = 0;
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
if (grayScale) {
|
|
|
|
// The image was already grayscale, red == green == blue.
|
|
|
|
outRow[x * bpp] = inRow[x * 4];
|
|
|
|
} else {
|
|
|
|
// The image is convertible to grayscale, use linear-luminance of
|
|
|
|
// sRGB colorspace:
|
|
|
|
// https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
|
|
|
|
outRow[x * bpp] =
|
|
|
|
(png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
2016-10-19 12:18:14 -07:00
|
|
|
|
|
|
|
if (bpp == 2) {
|
|
|
|
// Write out alpha if we have it.
|
|
|
|
outRow[x * bpp + 1] = aa;
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
2016-10-19 12:18:14 -07:00
|
|
|
}
|
|
|
|
png_write_row(writePtr, outRow.get());
|
|
|
|
}
|
|
|
|
} else if (newColorType == PNG_COLOR_TYPE_RGB ||
|
|
|
|
newColorType == PNG_COLOR_TYPE_RGBA) {
|
|
|
|
const size_t bpp = newColorType == PNG_COLOR_TYPE_RGB ? 3 : 4;
|
|
|
|
if (needsToZeroRGBChannelsOfTransparentPixels) {
|
|
|
|
// The source RGBA data can't be used as-is, because we need to zero out
|
|
|
|
// the RGB
|
|
|
|
// values of transparent pixels.
|
|
|
|
auto outRow =
|
|
|
|
std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
|
|
|
|
|
|
|
|
for (int32_t y = 0; y < image->height; y++) {
|
|
|
|
png_const_bytep inRow = image->rows[y];
|
|
|
|
for (int32_t x = 0; x < image->width; x++) {
|
|
|
|
int rr = *inRow++;
|
|
|
|
int gg = *inRow++;
|
|
|
|
int bb = *inRow++;
|
|
|
|
int aa = *inRow++;
|
|
|
|
if (aa == 0) {
|
|
|
|
// Zero out the RGB channels when transparent.
|
|
|
|
rr = gg = bb = 0;
|
|
|
|
}
|
|
|
|
outRow[x * bpp] = rr;
|
|
|
|
outRow[x * bpp + 1] = gg;
|
|
|
|
outRow[x * bpp + 2] = bb;
|
|
|
|
if (bpp == 4) {
|
|
|
|
outRow[x * bpp + 3] = aa;
|
|
|
|
}
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
2016-10-19 12:18:14 -07:00
|
|
|
png_write_row(writePtr, outRow.get());
|
|
|
|
}
|
2016-09-14 17:35:43 -07:00
|
|
|
} else {
|
2016-10-19 12:18:14 -07:00
|
|
|
// The source image can be used as-is, just tell libpng whether or not to
|
|
|
|
// ignore
|
|
|
|
// the alpha channel.
|
|
|
|
if (newColorType == PNG_COLOR_TYPE_RGB) {
|
|
|
|
// Delete the extraneous alpha values that we appended to our buffer
|
|
|
|
// when reading the original values.
|
|
|
|
png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
|
|
|
|
}
|
|
|
|
png_write_image(writePtr, image->rows.get());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
assert(false && "unreachable");
|
|
|
|
}
|
|
|
|
|
|
|
|
png_write_end(writePtr, writeInfoPtr);
|
|
|
|
return true;
|
2016-09-14 17:35:43 -07:00
|
|
|
}
|
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
} // namespace aapt
|