// This warning triggers false positives way too often in here. #ifdefined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic ignored "-Wclobbered" #endif
// FIXME (scroggo): We can use png_jumpbuf directly once Google3 is on 1.6 #define PNG_JMPBUF(x) png_jmpbuf((png_structp) x)
// When setjmp is first called, it returns 0, meaning longjmp was not called.
constexpr int kSetJmpOkay = 0; // An error internal to libpng.
constexpr int kPngError = 1; // Passed to longjmp when we have decoded as many lines as we need.
constexpr int kStopDecoding = 2;
class AutoCleanPng : public SkNoncopyable { public: /* * This class does not take ownership of stream or reader, but if codecPtr * is non-NULL, and decodeBounds succeeds, it will have created a new * SkCodec (pointed to by *codecPtr) which will own/ref them, as well as * the png_ptr and info_ptr.
*/
AutoCleanPng(png_structp png_ptr, SkStream* stream, SkPngChunkReader* reader,
SkCodec** codecPtr)
: fPng_ptr(png_ptr)
, fInfo_ptr(nullptr)
, fStream(stream)
, fChunkReader(reader)
, fOutCodec(codecPtr)
{}
~AutoCleanPng() { // fInfo_ptr will never be non-nullptr unless fPng_ptr is. if (fPng_ptr) {
png_infopp info_pp = fInfo_ptr ? &fInfo_ptr : nullptr;
png_destroy_read_struct(&fPng_ptr, info_pp, nullptr);
}
}
/** * Reads enough of the input stream to decode the bounds. * @return false if the stream is not a valid PNG (or too short). * true if it read enough of the stream to determine the bounds. * In the latter case, the stream may have been read beyond the * point to determine the bounds, and the png_ptr will have saved * any extra data. Further, if the codecPtr supplied to the * constructor was not NULL, it will now point to a new SkCodec, * which owns (or refs, in the case of the SkPngChunkReader) the * inputs. If codecPtr was NULL, the png_ptr and info_ptr are * unowned, and it is up to the caller to destroy them.
*/ bool decodeBounds();
// Arbitrary buffer size, though note that it matches (below) // SkPngCodec::processData(). FIXME: Can we better suit this to the size of // the PNG header?
constexpr size_t kBufferSize = 4096; char buffer[kBufferSize];
{ // Parse the signature. if (fStream->read(buffer, 8) < 8) { returnfalse;
}
while (true) { // Parse chunk length and type. if (fStream->read(buffer, 8) < 8) { // We have read to the end of the input without decoding bounds. break;
}
if (is_chunk(chunk, "IDAT")) {
this->infoCallback(length); returntrue;
}
png_process_data(fPng_ptr, fInfo_ptr, chunk, 8); // Process the full chunk + CRC. if (!process_data(fPng_ptr, fInfo_ptr, fStream, buffer, kBufferSize, length + 4)) { returnfalse;
}
}
returnfalse;
}
bool SkPngCodec::processData() { switch (setjmp(PNG_JMPBUF(fPng_ptr))) { case kPngError: // There was an error. Stop processing data. // FIXME: Do we need to discard png_ptr? returnfalse; case kStopDecoding: // We decoded all the lines we want. returntrue; case kSetJmpOkay: // Everything is okay. break; default: // No other values should be passed to longjmp.
SkASSERT(false);
}
staticfloat png_fixed_point_to_float(png_fixed_point x) { // We multiply by the same factor that libpng used to convert // fixed point -> double. Since we want floats, we choose to // do the conversion ourselves rather than convert // fixed point -> double -> float. return ((float) x) * 0.00001f;
}
staticfloat png_inverted_fixed_point_to_float(png_fixed_point x) { // This is necessary because the gAMA chunk actually stores 1/gamma. return 1.0f / png_fixed_point_to_float(x);
}
#endif// LIBPNG >= 1.6
// If there is no color profile information, it will use sRGB.
std::unique_ptr<SkEncodedInfo::ICCProfile> read_color_profile(png_structp png_ptr,
png_infop info_ptr) {
#if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6) // First check for an ICC profile
png_bytep profile;
png_uint_32 length; // The below variables are unused, however, we need to pass them in anyway or // png_get_iCCP() will return nothing. // Could knowing the |name| of the profile ever be interesting? Maybe for debugging?
png_charp name; // The |compression| is uninteresting since: // (1) libpng has already decompressed the profile for us. // (2) "deflate" is the only mode of decompression that libpng supports. int compression; if (PNG_INFO_iCCP == png_get_iCCP(png_ptr, info_ptr, &name, &compression, &profile,
&length)) { auto data = SkData::MakeWithCopy(profile, length); return SkEncodedInfo::ICCProfile::Make(std::move(data));
}
// Second, check for sRGB. // Note that Blink does this first. This code checks ICC first, with the thinking that // an image has both truly wants the potentially more specific ICC chunk, with sRGB as a // backup in case the decoder does not support full color management. if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { // TODO(https://crbug.com/362304558): Consider the intent field from the // `sRGB` chunk. return nullptr;
}
skcms_Matrix3x3 tmp; if (skcms_PrimariesToXYZD50(rx, ry, gx, gy, bx, by, wx, wy, &tmp)) {
toXYZD50 = tmp;
} else { // Note that Blink simply returns nullptr in this case. We'll fall // back to srgb.
}
}
skcms_TransferFunction fn; if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &gamma)) {
fn.a = 1.0f;
fn.b = fn.c = fn.d = fn.e = fn.f = 0.0f;
fn.g = png_inverted_fixed_point_to_float(gamma);
} else { // Default to sRGB gamma if the image has color space information, // but does not specify gamma. // Note that Blink would again return nullptr in this case.
fn = *skcms_sRGB_TransferFunction();
}
// If there is no swizzler, all rows are needed. if (!this->swizzler() || this->swizzler()->rowNeeded(rowNum - fFirstRow)) {
this->applyXformRow(fDst, row);
fDst = SkTAddOffset<void>(fDst, fRowBytes);
fRowsWrittenToOutput++;
}
if (fRowsWrittenToOutput == fRowsNeeded) { // Fake error to stop decoding scanlines.
longjmp(PNG_JMPBUF(this->png_ptr()), kStopDecoding);
}
}
};
staticvoid InterlacedRowCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int pass) { auto decoder = static_cast<SkPngInterlacedDecoder*>(png_get_progressive_ptr(png_ptr));
decoder->interlacedRowCallback(row, rowNum, pass);
}
private: constint fNumberPasses; int fFirstRow; int fLastRow; void* fDst;
size_t fRowBytes; int fLinesDecoded; bool fInterlacedComplete;
size_t fPng_rowbytes;
AutoTMalloc<png_byte> fInterlaceBuffer;
// FIXME: Currently sharing interlaced callback for all rows and subset. It's not // as expensive as the subset version of non-interlaced, but it still does extra // work. void interlacedRowCallback(png_bytep row, int rowNum, int pass) { if (rowNum < fFirstRow || rowNum > fLastRow || fInterlacedComplete) { // Ignore this row return;
}
if (0 == pass) { // The first pass initializes all rows.
SkASSERT(row);
SkASSERT(fLinesDecoded == rowNum - fFirstRow);
fLinesDecoded++;
} else {
SkASSERT(fLinesDecoded == fLastRow - fFirstRow + 1); if (fNumberPasses - 1 == pass && rowNum == fLastRow) { // Last pass, and we have read all of the rows we care about.
fInterlacedComplete = true; if (fLastRow != this->dimensions().height() - 1 ||
(this->swizzler() && this->swizzler()->sampleY() != 1)) { // Fake error to stop decoding scanlines. Only stop if we're not decoding the // whole image, in which case processing the rest of the image might be // expensive. When decoding the whole image, read through the IEND chunk to // preserve Android behavior of leaving the input stream in the right place.
longjmp(PNG_JMPBUF(this->png_ptr()), kStopDecoding);
}
}
}
}
constbool success = this->processData();
png_bytep srcRow = fInterlaceBuffer.get(); // FIXME: When resuming, this may rewrite rows that did not change. for (int rowNum = 0; rowNum < fLinesDecoded; rowNum++) {
this->applyXformRow(dst, srcRow);
dst = SkTAddOffset<void>(dst, rowBytes);
srcRow = SkTAddOffset<png_byte>(srcRow, fPng_rowbytes);
} if (success && fInterlacedComplete) { return kSuccess;
}
if (rowsDecoded) {
*rowsDecoded = fLinesDecoded;
}
return log_and_return_error(success);
}
void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override { // FIXME: We could skip rows in the interlace buffer that we won't put in the output.
this->setUpInterlaceBuffer(lastRow - firstRow + 1);
png_set_progressive_read_fn(this->png_ptr(), this, nullptr, InterlacedRowCallback, nullptr);
fFirstRow = firstRow;
fLastRow = lastRow;
fDst = dst;
fRowBytes = rowBytes;
fLinesDecoded = 0;
}
Result decode(int* rowsDecoded) override { constbool success = this->processData();
// Now apply Xforms on all the rows that were decoded. if (!fLinesDecoded) { if (rowsDecoded) {
*rowsDecoded = 0;
} return log_and_return_error(success);
}
// FIXME: For resuming interlace, we may swizzle a row that hasn't changed. But it // may be too tricky/expensive to handle that correctly.
// Offset srcRow by get_start_coord rows. We do not need to account for fFirstRow, // since the first row in fInterlaceBuffer corresponds to fFirstRow. int srcRow = get_start_coord(sampleY); void* dst = fDst; int rowsWrittenToOutput = 0; while (rowsWrittenToOutput < rowsNeeded && srcRow < fLinesDecoded) {
png_bytep src = SkTAddOffset<png_byte>(fInterlaceBuffer.get(), fPng_rowbytes * srcRow);
this->applyXformRow(dst, src);
dst = SkTAddOffset<void>(dst, fRowBytes);
rowsWrittenToOutput++;
srcRow += sampleY;
}
if (success && fInterlacedComplete) { return kSuccess;
}
if (rowsDecoded) {
*rowsDecoded = rowsWrittenToOutput;
} return log_and_return_error(success);
}
// Reads the header and initializes the output fields, if not NULL. // // @param stream Input data. Will be read to get enough information to properly // setup the codec. // @param chunkReader SkPngChunkReader, for reading unknown chunks. May be NULL. // If not NULL, png_ptr will hold an *unowned* pointer to it. The caller is // expected to continue to own it for the lifetime of the png_ptr. // @param outCodec Optional output variable. If non-NULL, will be set to a new // SkPngCodec on success. // @param png_ptrp Optional output variable. If non-NULL, will be set to a new // png_structp on success. // @param info_ptrp Optional output variable. If non-NULL, will be set to a new // png_infop on success; // @return if kSuccess, the caller is responsible for calling // png_destroy_read_struct(png_ptrp, info_ptrp). // Otherwise, the passed in fields (except stream) are unchanged. static SkCodec::Result read_header(SkStream* stream, SkPngChunkReader* chunkReader,
SkCodec** outCodec,
png_structp* png_ptrp, png_infop* info_ptrp) { // The image is known to be a PNG. Decode enough to know the SkImageInfo.
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr,
sk_error_fn, sk_warning_fn); if (!png_ptr) { return SkCodec::kInternalError;
}
#ifdef PNG_SET_OPTION_SUPPORTED // This setting ensures that we display images with incorrect CMF bytes. // See crbug.com/807324.
png_set_option(png_ptr, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON); #endif
if (setjmp(PNG_JMPBUF(png_ptr))) { return SkCodec::kInvalidInput;
}
#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED // Hookup our chunkReader so we can see any user-chunks the caller may be interested in. // This needs to be installed before we read the png header. Android may store ninepatch // chunks in the header. if (chunkReader) {
png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_const_bytep)"", 0);
png_set_read_user_chunk_fn(png_ptr, (png_voidp) chunkReader, sk_read_user_chunk);
} #endif
if (!decodedBounds) { return SkCodec::kIncompleteInput;
}
// On success, decodeBounds releases ownership of png_ptr and info_ptr. if (png_ptrp) {
*png_ptrp = png_ptr;
} if (info_ptrp) {
*info_ptrp = info_ptr;
}
// decodeBounds takes care of setting outCodec if (outCodec) {
SkASSERT(*outCodec);
} return SkCodec::kSuccess;
}
// TODO(https://crbug.com/359245096): Should we support 16-bits of precision // for gray images? if (bitDepth == 16 && (PNG_COLOR_TYPE_GRAY == encodedColorType ||
PNG_COLOR_TYPE_GRAY_ALPHA == encodedColorType)) {
bitDepth = 8;
png_set_strip_16(fPng_ptr);
}
// Now determine the default colorType and alphaType and set the required transforms. // Often, we depend on SkSwizzler to perform any transforms that we need. However, we // still depend on libpng for many of the rare and PNG-specific cases.
SkEncodedInfo::Color color;
SkEncodedInfo::Alpha alpha; switch (encodedColorType) { case PNG_COLOR_TYPE_PALETTE: // Extract multiple pixels with bit depths of 1, 2, and 4 from a single // byte into separate bytes (useful for paletted and grayscale images). if (bitDepth < 8) { // TODO: Should we use SkSwizzler here?
bitDepth = 8;
png_set_packing(fPng_ptr);
}
color = SkEncodedInfo::kPalette_Color; // Set the alpha depending on if a transparency chunk exists.
alpha = png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS) ?
SkEncodedInfo::kUnpremul_Alpha : SkEncodedInfo::kOpaque_Alpha; break; case PNG_COLOR_TYPE_RGB: if (png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS)) { // Convert to RGBA if transparency chunk exists.
png_set_tRNS_to_alpha(fPng_ptr);
color = SkEncodedInfo::kRGBA_Color;
alpha = SkEncodedInfo::kBinary_Alpha;
} else {
color = SkEncodedInfo::kRGB_Color;
alpha = SkEncodedInfo::kOpaque_Alpha;
} break; case PNG_COLOR_TYPE_GRAY: // Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel. if (bitDepth < 8) { // TODO: Should we use SkSwizzler here?
bitDepth = 8;
png_set_expand_gray_1_2_4_to_8(fPng_ptr);
}
if (png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS)) {
png_set_tRNS_to_alpha(fPng_ptr);
color = SkEncodedInfo::kGrayAlpha_Color;
alpha = SkEncodedInfo::kBinary_Alpha;
} else {
color = SkEncodedInfo::kGray_Color;
alpha = SkEncodedInfo::kOpaque_Alpha;
} break; case PNG_COLOR_TYPE_GRAY_ALPHA:
color = SkEncodedInfo::kGrayAlpha_Color;
alpha = SkEncodedInfo::kUnpremul_Alpha; break; case PNG_COLOR_TYPE_RGBA:
color = SkEncodedInfo::kRGBA_Color;
alpha = SkEncodedInfo::kUnpremul_Alpha; break; default: // All the color types have been covered above.
SkASSERT(false);
color = SkEncodedInfo::kRGBA_Color;
alpha = SkEncodedInfo::kUnpremul_Alpha;
}
void SkPngCodec::destroyReadStruct() { if (fPng_ptr) { // We will never have a nullptr fInfo_ptr with a non-nullptr fPng_ptr
SkASSERT(fInfo_ptr);
png_destroy_read_struct((png_struct**)&fPng_ptr, (png_info**)&fInfo_ptr, nullptr);
fPng_ptr = nullptr;
fInfo_ptr = nullptr;
}
}
/////////////////////////////////////////////////////////////////////////////// // Getting the pixels ///////////////////////////////////////////////////////////////////////////////
SkCodec::Result SkPngCodec::initializeXforms(const SkImageInfo& dstInfo, const Options& options) { if (setjmp(PNG_JMPBUF((png_struct*)fPng_ptr))) {
SkCodecPrintf("Failed on png_read_update_info.\n"); return kInvalidInput;
}
png_read_update_info(fPng_ptr, fInfo_ptr);
// `SkPngCodec` doesn't support APNG - the `frameWidth` is always the same // as the full image width. int frameWidth = dstInfo.width();
bool SkPngCodec::onRewind() { // This sets fPng_ptr and fInfo_ptr to nullptr. If read_header // succeeds, they will be repopulated, and if it fails, they will // remain nullptr. Any future accesses to fPng_ptr and fInfo_ptr will // come through this function which will rewind and again attempt // to reinitialize them.
this->destroyReadStruct();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.