diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/platform/image-decoders/png | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/platform/image-decoders/png')
-rw-r--r-- | Source/WebCore/platform/image-decoders/png/PNGImageDecoder.cpp | 727 | ||||
-rw-r--r-- | Source/WebCore/platform/image-decoders/png/PNGImageDecoder.h | 93 |
2 files changed, 626 insertions, 194 deletions
diff --git a/Source/WebCore/platform/image-decoders/png/PNGImageDecoder.cpp b/Source/WebCore/platform/image-decoders/png/PNGImageDecoder.cpp index 0c2b2d5d2..0abcaf2bd 100644 --- a/Source/WebCore/platform/image-decoders/png/PNGImageDecoder.cpp +++ b/Source/WebCore/platform/image-decoders/png/PNGImageDecoder.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006 Apple Computer, Inc. + * Copyright (C) 2006 Apple Inc. * Copyright (C) 2007-2009 Torch Mobile, Inc. * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. * @@ -7,6 +7,7 @@ * * Other contributors: * Stuart Parmenter <stuart@mozilla.com> + * Max Stepin <maxstepin@gmail.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -41,14 +42,9 @@ #include "PNGImageDecoder.h" #include "Color.h" -#include "png.h" -#include <wtf/PassOwnPtr.h> +#include <png.h> #include <wtf/StdLibExtras.h> -#if USE(QCMSLIB) -#include "qcms.h" -#endif - #if defined(PNG_LIBPNG_VER_MAJOR) && defined(PNG_LIBPNG_VER_MINOR) && (PNG_LIBPNG_VER_MAJOR > 1 || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 4)) #define JMPBUF(png_ptr) png_jmpbuf(png_ptr) #else @@ -101,6 +97,21 @@ static void PNGAPI pngComplete(png_structp png, png_infop) static_cast<PNGImageDecoder*>(png_get_progressive_ptr(png))->pngComplete(); } +#if ENABLE(APNG) +// Called when we have the frame header. +static void PNGAPI frameHeader(png_structp png, png_infop) +{ + static_cast<PNGImageDecoder*>(png_get_progressive_ptr(png))->frameHeader(); +} + +// Called when we found user chunks. +static int PNGAPI readChunks(png_structp png, png_unknown_chunkp chunk) +{ + static_cast<PNGImageDecoder*>(png_get_user_chunk_ptr(png))->readChunks(chunk); + return 1; +} +#endif + class PNGImageReader { WTF_MAKE_FAST_ALLOCATED; public: @@ -110,14 +121,16 @@ public: , m_decodingSizeOnly(false) , m_hasAlpha(false) , m_interlaceBuffer(0) -#if USE(QCMSLIB) - , m_transform(0) - , m_rowBuffer() -#endif { m_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, decodingFailed, decodingWarning); m_info = png_create_info_struct(m_png); png_set_progressive_read_fn(m_png, decoder, headerAvailable, rowAvailable, pngComplete); +#if ENABLE(APNG) + png_byte apngChunks[]= {"acTL\0fcTL\0fdAT\0"}; + png_set_keep_unknown_chunks(m_png, 1, apngChunks, 3); + png_set_read_user_chunk_fn(m_png, static_cast<png_voidp>(decoder), readChunks); + decoder->init(); +#endif } ~PNGImageReader() @@ -130,17 +143,12 @@ public: if (m_png && m_info) // This will zero the pointers. png_destroy_read_struct(&m_png, &m_info, 0); -#if USE(QCMSLIB) - if (m_transform) - qcms_transform_release(m_transform); - m_transform = 0; -#endif delete[] m_interlaceBuffer; m_interlaceBuffer = 0; m_readOffset = 0; } - bool decode(const SharedBuffer& data, bool sizeOnly) + bool decode(const SharedBuffer& data, bool sizeOnly, unsigned haltAtFrame) { m_decodingSizeOnly = sizeOnly; PNGImageDecoder* decoder = static_cast<PNGImageDecoder*>(png_get_progressive_ptr(m_png)); @@ -157,7 +165,7 @@ public: // We explicitly specify the superclass isSizeAvailable() because we // merely want to check if we've managed to set the size, not // (recursively) trigger additional decoding if we haven't. - if (sizeOnly ? decoder->ImageDecoder::isSizeAvailable() : decoder->isComplete()) + if (sizeOnly ? decoder->ImageDecoder::isSizeAvailable() : decoder->isCompleteAtIndex(haltAtFrame)) return true; } return false; @@ -174,33 +182,6 @@ public: png_bytep interlaceBuffer() const { return m_interlaceBuffer; } void createInterlaceBuffer(int size) { m_interlaceBuffer = new png_byte[size]; } -#if USE(QCMSLIB) - png_bytep rowBuffer() const { return m_rowBuffer.get(); } - void createRowBuffer(int size) { m_rowBuffer = std::make_unique<png_byte[]>(size); } - qcms_transform* colorTransform() const { return m_transform; } - - void createColorTransform(const ColorProfile& colorProfile, bool hasAlpha) - { - if (m_transform) - qcms_transform_release(m_transform); - m_transform = 0; - - if (colorProfile.isEmpty()) - return; - qcms_profile* deviceProfile = ImageDecoder::qcmsOutputDeviceProfile(); - if (!deviceProfile) - return; - qcms_profile* inputProfile = qcms_profile_from_memory(colorProfile.data(), colorProfile.size()); - if (!inputProfile) - return; - // We currently only support color profiles for RGB and RGBA images. - ASSERT(icSigRgbData == qcms_profile_get_color_space(inputProfile)); - qcms_data_type dataFormat = hasAlpha ? QCMS_DATA_RGBA_8 : QCMS_DATA_RGB_8; - // FIXME: Don't force perceptual intent if the image profile contains an intent. - m_transform = qcms_transform_create(inputProfile, dataFormat, deviceProfile, dataFormat, QCMS_INTENT_PERCEPTUAL); - qcms_profile_release(inputProfile); - } -#endif private: png_structp m_png; @@ -210,16 +191,35 @@ private: bool m_decodingSizeOnly; bool m_hasAlpha; png_bytep m_interlaceBuffer; -#if USE(QCMSLIB) - qcms_transform* m_transform; - std::unique_ptr<png_byte[]> m_rowBuffer; -#endif }; -PNGImageDecoder::PNGImageDecoder(ImageSource::AlphaOption alphaOption, - ImageSource::GammaAndColorProfileOption gammaAndColorProfileOption) +PNGImageDecoder::PNGImageDecoder(AlphaOption alphaOption, GammaAndColorProfileOption gammaAndColorProfileOption) : ImageDecoder(alphaOption, gammaAndColorProfileOption) , m_doNothingOnFailure(false) + , m_currentFrame(0) +#if ENABLE(APNG) + , m_png(nullptr) + , m_info(nullptr) + , m_isAnimated(false) + , m_frameInfo(false) + , m_frameIsHidden(false) + , m_hasInfo(false) + , m_gamma(45455) + , m_frameCount(1) + , m_playCount(0) + , m_totalFrames(0) + , m_sizePLTE(0) + , m_sizetRNS(0) + , m_sequenceNumber(0) + , m_width(0) + , m_height(0) + , m_xOffset(0) + , m_yOffset(0) + , m_delayNumerator(1) + , m_delayDenominator(1) + , m_dispose(0) + , m_blend(0) +#endif { } @@ -227,17 +227,29 @@ PNGImageDecoder::~PNGImageDecoder() { } +#if ENABLE(APNG) +RepetitionCount PNGImageDecoder::repetitionCount() const +{ + // APNG format uses 0 to indicate that an animation must play indefinitely. But + // the RepetitionCount enumeration uses RepetitionCountInfinite, so we need to adapt this. + if (!m_playCount) + return RepetitionCountInfinite; + + return m_playCount; +} +#endif + bool PNGImageDecoder::isSizeAvailable() { if (!ImageDecoder::isSizeAvailable()) - decode(true); + decode(true, 0); return ImageDecoder::isSizeAvailable(); } -bool PNGImageDecoder::setSize(unsigned width, unsigned height) +bool PNGImageDecoder::setSize(const IntSize& size) { - if (!ImageDecoder::setSize(width, height)) + if (!ImageDecoder::setSize(size)) return false; prepareScaleDataIfNecessary(); @@ -246,17 +258,23 @@ bool PNGImageDecoder::setSize(unsigned width, unsigned height) ImageFrame* PNGImageDecoder::frameBufferAtIndex(size_t index) { +#if ENABLE(APNG) + if (!isSizeAvailable()) + return nullptr; + + if (index >= frameCount()) + index = frameCount() - 1; +#else if (index) - return 0; + return nullptr; +#endif - if (m_frameBufferCache.isEmpty()) { + if (m_frameBufferCache.isEmpty()) m_frameBufferCache.resize(1); - m_frameBufferCache[0].setPremultiplyAlpha(m_premultiplyAlpha); - } - ImageFrame& frame = m_frameBufferCache[0]; - if (frame.status() != ImageFrame::FrameComplete) - decode(false); + ImageFrame& frame = m_frameBufferCache[index]; + if (!frame.isComplete()) + decode(false, index); return &frame; } @@ -264,41 +282,10 @@ bool PNGImageDecoder::setFailed() { if (m_doNothingOnFailure) return false; - m_reader.clear(); + m_reader = nullptr; return ImageDecoder::setFailed(); } -static void readColorProfile(png_structp png, png_infop info, ColorProfile& colorProfile) -{ - ASSERT(colorProfile.isEmpty()); - -#ifdef PNG_iCCP_SUPPORTED - char* profileName; - int compressionType; -#if (PNG_LIBPNG_VER < 10500) - png_charp profile; -#else - png_bytep profile; -#endif - png_uint_32 profileLength; - if (!png_get_iCCP(png, info, &profileName, &compressionType, &profile, &profileLength)) - return; - - // Only accept RGB color profiles from input class devices. - bool ignoreProfile = false; - char* profileData = reinterpret_cast<char*>(profile); - if (profileLength < ImageDecoder::iccColorProfileHeaderLength) - ignoreProfile = true; - else if (!ImageDecoder::rgbColorProfile(profileData, profileLength)) - ignoreProfile = true; - else if (!ImageDecoder::inputDeviceColorProfile(profileData, profileLength)) - ignoreProfile = true; - - if (!ignoreProfile) - colorProfile.append(profileData, profileLength); -#endif -} - void PNGImageDecoder::headerAvailable() { png_structp png = m_reader->pngPtr(); @@ -318,7 +305,7 @@ void PNGImageDecoder::headerAvailable() // will cease to exist. Note that we'll still properly set the failure flag // in this case as soon as we longjmp(). m_doNothingOnFailure = true; - bool result = setSize(width, height); + bool result = setSize(IntSize(width, height)); m_doNothingOnFailure = false; if (!result) { longjmp(JMPBUF(png), 1); @@ -330,14 +317,64 @@ void PNGImageDecoder::headerAvailable() // The options we set here match what Mozilla does. +#if ENABLE(APNG) + m_hasInfo = true; + if (m_isAnimated) { + png_save_uint_32(m_dataIHDR, 13); + memcpy(m_dataIHDR + 4, "IHDR", 4); + png_save_uint_32(m_dataIHDR + 8, width); + png_save_uint_32(m_dataIHDR + 12, height); + m_dataIHDR[16] = bitDepth; + m_dataIHDR[17] = colorType; + m_dataIHDR[18] = compressionType; + m_dataIHDR[19] = filterType; + m_dataIHDR[20] = interlaceType; + } +#endif + // Expand to ensure we use 24-bit for RGB and 32-bit for RGBA. - if (colorType == PNG_COLOR_TYPE_PALETTE || (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8)) + if (colorType == PNG_COLOR_TYPE_PALETTE) { +#if ENABLE(APNG) + if (m_isAnimated) { + png_colorp palette; + int paletteSize = 0; + png_get_PLTE(png, info, &palette, &paletteSize); + paletteSize *= 3; + png_save_uint_32(m_dataPLTE, paletteSize); + memcpy(m_dataPLTE + 4, "PLTE", 4); + memcpy(m_dataPLTE + 8, palette, paletteSize); + m_sizePLTE = paletteSize + 12; + } +#endif + png_set_expand(png); + } + + if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) png_set_expand(png); png_bytep trns = 0; int trnsCount = 0; + png_color_16p transValues; if (png_get_valid(png, info, PNG_INFO_tRNS)) { - png_get_tRNS(png, info, &trns, &trnsCount, 0); + png_get_tRNS(png, info, &trns, &trnsCount, &transValues); +#if ENABLE(APNG) + if (m_isAnimated) { + if (colorType == PNG_COLOR_TYPE_RGB) { + png_save_uint_16(m_datatRNS + 8, transValues->red); + png_save_uint_16(m_datatRNS + 10, transValues->green); + png_save_uint_16(m_datatRNS + 12, transValues->blue); + trnsCount = 6; + } else if (colorType == PNG_COLOR_TYPE_GRAY) { + png_save_uint_16(m_datatRNS + 8, transValues->gray); + trnsCount = 2; + } else if (colorType == PNG_COLOR_TYPE_PALETTE) + memcpy(m_datatRNS + 8, trns, trnsCount); + + png_save_uint_32(m_datatRNS, trnsCount); + memcpy(m_datatRNS + 4, "tRNS", 4); + m_sizetRNS = trnsCount + 12; + } +#endif png_set_expand(png); } @@ -347,21 +384,6 @@ void PNGImageDecoder::headerAvailable() if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png); - if ((colorType & PNG_COLOR_MASK_COLOR) && !m_ignoreGammaAndColorProfile) { - // We only support color profiles for color PALETTE and RGB[A] PNG. Supporting - // color profiles for gray-scale images is slightly tricky, at least using the - // CoreGraphics ICC library, because we expand gray-scale images to RGB but we - // do not similarly transform the color profile. We'd either need to transform - // the color profile or we'd need to decode into a gray-scale image buffer and - // hand that to CoreGraphics. - readColorProfile(png, info, m_colorProfile); -#if USE(QCMSLIB) - bool decodedImageHasAlpha = (colorType & PNG_COLOR_MASK_ALPHA) || trnsCount; - m_reader->createColorTransform(m_colorProfile, decodedImageHasAlpha); - m_colorProfile.clear(); -#endif - } - // Deal with gamma and keep it under our control. double gamma; if (!m_ignoreGammaAndColorProfile && png_get_gAMA(png, info, &gamma)) { @@ -370,6 +392,9 @@ void PNGImageDecoder::headerAvailable() png_set_gAMA(png, info, gamma); } png_set_gamma(png, cDefaultGamma, gamma); +#if ENABLE(APNG) + m_gamma = static_cast<int>(gamma * 100000); +#endif } else png_set_gamma(png, cDefaultGamma, cInverseGamma); @@ -396,67 +421,42 @@ void PNGImageDecoder::headerAvailable() } } -static inline void setPixelRGB(ImageFrame::PixelData* dest, png_bytep pixel) -{ - *dest = 0xFF000000U | pixel[0] << 16 | pixel[1] << 8 | pixel[2]; -} - -static inline void setPixelRGBA(ImageFrame::PixelData* dest, png_bytep pixel, unsigned char& nonTrivialAlphaMask) -{ - unsigned char a = pixel[3]; - *dest = a << 24 | pixel[0] << 16 | pixel[1] << 8 | pixel[2]; - nonTrivialAlphaMask |= (255 - a); -} - -static inline void setPixelPremultipliedRGBA(ImageFrame::PixelData* dest, png_bytep pixel, unsigned char& nonTrivialAlphaMask) -{ - unsigned char a = pixel[3]; - unsigned char r = fastDivideBy255(pixel[0] * a); - unsigned char g = fastDivideBy255(pixel[1] * a); - unsigned char b = fastDivideBy255(pixel[2] * a); - - *dest = a << 24 | r << 16 | g << 8 | b; - nonTrivialAlphaMask |= (255 - a); -} - void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, int) { if (m_frameBufferCache.isEmpty()) return; // Initialize the framebuffer if needed. - ImageFrame& buffer = m_frameBufferCache[0]; - if (buffer.status() == ImageFrame::FrameEmpty) { +#if ENABLE(APNG) + if (m_currentFrame >= frameCount()) + return; +#endif + ImageFrame& buffer = m_frameBufferCache[m_currentFrame]; + if (buffer.isEmpty()) { png_structp png = m_reader->pngPtr(); - if (!buffer.setSize(scaledSize().width(), scaledSize().height())) { + if (!buffer.initialize(scaledSize(), m_premultiplyAlpha)) { longjmp(JMPBUF(png), 1); return; } unsigned colorChannels = m_reader->hasAlpha() ? 4 : 3; - if (PNG_INTERLACE_ADAM7 == png_get_interlace_type(png, m_reader->infoPtr())) { - m_reader->createInterlaceBuffer(colorChannels * size().width() * size().height()); + if (PNG_INTERLACE_ADAM7 == png_get_interlace_type(png, m_reader->infoPtr()) + || m_currentFrame) { + if (!m_reader->interlaceBuffer()) + m_reader->createInterlaceBuffer(colorChannels * size().width() * size().height()); if (!m_reader->interlaceBuffer()) { longjmp(JMPBUF(png), 1); return; } } -#if USE(QCMSLIB) - if (m_reader->colorTransform()) { - m_reader->createRowBuffer(colorChannels * size().width()); - if (!m_reader->rowBuffer()) { - longjmp(JMPBUF(png), 1); - return; - } - } -#endif - buffer.setStatus(ImageFrame::FramePartial); + buffer.setDecoding(ImageFrame::Decoding::Partial); buffer.setHasAlpha(false); - buffer.setColorProfile(m_colorProfile); - // For PNGs, the frame always fills the entire image. - buffer.setOriginalFrameRect(IntRect(IntPoint(), size())); +#if ENABLE(APNG) + if (m_currentFrame) + initFrameBuffer(m_currentFrame); +#endif } /* libpng comments (here to explain what follows). @@ -505,27 +505,26 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, if (png_bytep interlaceBuffer = m_reader->interlaceBuffer()) { row = interlaceBuffer + (rowIndex * colorChannels * size().width()); +#if ENABLE(APNG) + if (m_currentFrame) { + png_progressive_combine_row(m_png, row, rowBuffer); + return; // Only do incremental image display for the first frame. + } +#endif png_progressive_combine_row(m_reader->pngPtr(), row, rowBuffer); } -#if USE(QCMSLIB) - if (qcms_transform* transform = m_reader->colorTransform()) { - qcms_transform_data(transform, row, m_reader->rowBuffer(), size().width()); - row = m_reader->rowBuffer(); - } -#endif - // Write the decoded row pixels to the frame buffer. - ImageFrame::PixelData* address = buffer.getAddr(0, y); + RGBA32* address = buffer.backingStore()->pixelAt(0, y); int width = scaledSize().width(); unsigned char nonTrivialAlphaMask = 0; #if ENABLE(IMAGE_DECODER_DOWN_SAMPLING) if (m_scaled) { - for (int x = 0; x < width; ++x) { + for (int x = 0; x < width; ++x, ++address) { png_bytep pixel = row + m_scaledColumns[x] * colorChannels; unsigned alpha = hasAlpha ? pixel[3] : 255; - buffer.setRGBA(address++, pixel[0], pixel[1], pixel[2], alpha); + buffer.backingStore()->setPixel(address, pixel[0], pixel[1], pixel[2], alpha); nonTrivialAlphaMask |= (255 - alpha); } } else @@ -533,46 +532,422 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, { png_bytep pixel = row; if (hasAlpha) { - if (buffer.premultiplyAlpha()) { - for (int x = 0; x < width; ++x, pixel += 4) - setPixelPremultipliedRGBA(address++, pixel, nonTrivialAlphaMask); - } else { - for (int x = 0; x < width; ++x, pixel += 4) - setPixelRGBA(address++, pixel, nonTrivialAlphaMask); + for (int x = 0; x < width; ++x, pixel += 4, ++address) { + unsigned alpha = pixel[3]; + buffer.backingStore()->setPixel(address, pixel[0], pixel[1], pixel[2], alpha); + nonTrivialAlphaMask |= (255 - alpha); } } else { - for (int x = 0; x < width; ++x, pixel += 3) - setPixelRGB(address++, pixel); + for (int x = 0; x < width; ++x, pixel += 3, ++address) + *address = makeRGB(pixel[0], pixel[1], pixel[2]); } } - if (nonTrivialAlphaMask && !buffer.hasAlpha()) buffer.setHasAlpha(true); } void PNGImageDecoder::pngComplete() { +#if ENABLE(APNG) + if (m_isAnimated) { + if (!processingFinish() && m_frameCount == m_currentFrame) + return; + + fallbackNotAnimated(); + } +#endif if (!m_frameBufferCache.isEmpty()) - m_frameBufferCache.first().setStatus(ImageFrame::FrameComplete); + m_frameBufferCache.first().setDecoding(ImageFrame::Decoding::Complete); } -void PNGImageDecoder::decode(bool onlySize) +void PNGImageDecoder::decode(bool onlySize, unsigned haltAtFrame) { if (failed()) return; if (!m_reader) - m_reader = adoptPtr(new PNGImageReader(this)); + m_reader = std::make_unique<PNGImageReader>(this); // If we couldn't decode the image but we've received all the data, decoding // has failed. - if (!m_reader->decode(*m_data, onlySize) && isAllDataReceived()) + if (!m_reader->decode(*m_data, onlySize, haltAtFrame) && isAllDataReceived()) setFailed(); // If we're done decoding the image, we don't need the PNGImageReader // anymore. (If we failed, |m_reader| has already been cleared.) else if (isComplete()) - m_reader.clear(); + m_reader = nullptr; } +#if ENABLE(APNG) +void PNGImageDecoder::readChunks(png_unknown_chunkp chunk) +{ + if (!memcmp(chunk->name, "acTL", 4) && chunk->size == 8) { + if (m_hasInfo || m_isAnimated) + return; + + m_frameCount = png_get_uint_32(chunk->data); + m_playCount = png_get_uint_32(chunk->data + 4); + + if (!m_frameCount || m_frameCount > PNG_UINT_31_MAX || m_playCount > PNG_UINT_31_MAX) { + fallbackNotAnimated(); + return; + } + + m_isAnimated = true; + if (!m_frameInfo) + m_frameIsHidden = true; + + if (m_frameBufferCache.size() == m_frameCount) + return; + + m_frameBufferCache.resize(m_frameCount); + } else if (!memcmp(chunk->name, "fcTL", 4) && chunk->size == 26) { + if (m_hasInfo && !m_isAnimated) + return; + + m_frameInfo = false; + + if (processingFinish()) { + fallbackNotAnimated(); + return; + } + + // At this point the old frame is done. Let's start a new one. + unsigned sequenceNumber = png_get_uint_32(chunk->data); + if (sequenceNumber != m_sequenceNumber++) { + fallbackNotAnimated(); + return; + } + + m_width = png_get_uint_32(chunk->data + 4); + m_height = png_get_uint_32(chunk->data + 8); + m_xOffset = png_get_uint_32(chunk->data + 12); + m_yOffset = png_get_uint_32(chunk->data + 16); + m_delayNumerator = png_get_uint_16(chunk->data + 20); + m_delayDenominator = png_get_uint_16(chunk->data + 22); + m_dispose = chunk->data[24]; + m_blend = chunk->data[25]; + + png_structp png = m_reader->pngPtr(); + png_infop info = m_reader->infoPtr(); + png_uint_32 width = png_get_image_width(png, info); + png_uint_32 height = png_get_image_height(png, info); + + if (m_width > cMaxPNGSize || m_height > cMaxPNGSize + || m_xOffset > cMaxPNGSize || m_yOffset > cMaxPNGSize + || m_xOffset + m_width > width + || m_yOffset + m_height > height + || m_dispose > 2 || m_blend > 1) { + fallbackNotAnimated(); + return; + } + + if (m_frameBufferCache.isEmpty()) + m_frameBufferCache.resize(1); + + if (m_currentFrame < m_frameBufferCache.size()) { + ImageFrame& buffer = m_frameBufferCache[m_currentFrame]; + + if (!m_delayDenominator) + buffer.setDuration(m_delayNumerator * 10); + else + buffer.setDuration(m_delayNumerator * 1000 / m_delayDenominator); + + if (m_dispose == 2) + buffer.setDisposalMethod(ImageFrame::DisposalMethod::RestoreToPrevious); + else if (m_dispose == 1) + buffer.setDisposalMethod(ImageFrame::DisposalMethod::RestoreToBackground); + else + buffer.setDisposalMethod(ImageFrame::DisposalMethod::DoNotDispose); + } + + m_frameInfo = true; + m_frameIsHidden = false; + + if (processingStart(chunk)) { + fallbackNotAnimated(); + return; + } + } else if (!memcmp(chunk->name, "fdAT", 4) && chunk->size >= 4) { + if (!m_frameInfo || !m_isAnimated) + return; + + unsigned sequenceNumber = png_get_uint_32(chunk->data); + if (sequenceNumber != m_sequenceNumber++) { + fallbackNotAnimated(); + return; + } + + if (setjmp(JMPBUF(m_png))) { + fallbackNotAnimated(); + return; + } + + png_save_uint_32(chunk->data, chunk->size - 4); + png_process_data(m_png, m_info, chunk->data, 4); + memcpy(chunk->data, "IDAT", 4); + png_process_data(m_png, m_info, chunk->data, chunk->size); + png_process_data(m_png, m_info, chunk->data, 4); + } +} + +void PNGImageDecoder::frameHeader() +{ + int colorType = png_get_color_type(m_png, m_info); + + if (colorType == PNG_COLOR_TYPE_PALETTE) + png_set_expand(m_png); + + int bitDepth = png_get_bit_depth(m_png, m_info); + if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) + png_set_expand(m_png); + + if (png_get_valid(m_png, m_info, PNG_INFO_tRNS)) + png_set_expand(m_png); + + if (bitDepth == 16) + png_set_strip_16(m_png); + + if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(m_png); + + double gamma; + if (png_get_gAMA(m_png, m_info, &gamma)) + png_set_gamma(m_png, cDefaultGamma, gamma); + + png_set_interlace_handling(m_png); + + png_read_update_info(m_png, m_info); +} + +void PNGImageDecoder::init() +{ + m_isAnimated = false; + m_frameInfo = false; + m_frameIsHidden = false; + m_hasInfo = false; + m_currentFrame = 0; + m_totalFrames = 0; + m_sequenceNumber = 0; +} + +void PNGImageDecoder::clearFrameBufferCache(size_t clearBeforeFrame) +{ + if (m_frameBufferCache.isEmpty()) + return; + + // See GIFImageDecoder for full explanation. + clearBeforeFrame = std::min(clearBeforeFrame, m_frameBufferCache.size() - 1); + const Vector<ImageFrame>::iterator end(m_frameBufferCache.begin() + clearBeforeFrame); + + Vector<ImageFrame>::iterator i(end); + for (; (i != m_frameBufferCache.begin()) && (i->isEmpty() || (i->disposalMethod() == ImageFrame::DisposalMethod::RestoreToPrevious)); --i) { + if (i->isComplete() && (i != end)) + i->clear(); + } + + // Now |i| holds the last frame we need to preserve; clear prior frames. + for (Vector<ImageFrame>::iterator j(m_frameBufferCache.begin()); j != i; ++j) { + ASSERT(!j->isPartial()); + if (j->isEmpty()) + j->clear(); + } +} + +void PNGImageDecoder::initFrameBuffer(size_t frameIndex) +{ + if (frameIndex >= frameCount()) + return; + + ImageFrame& buffer = m_frameBufferCache[frameIndex]; + + // The starting state for this frame depends on the previous frame's + // disposal method. + // + // Frames that use the DisposalMethod::RestoreToPrevious method are effectively + // no-ops in terms of changing the starting state of a frame compared to + // the starting state of the previous frame, so skip over them. (If the + // first frame specifies this method, it will get treated like + // DisposeOverwriteBgcolor below and reset to a completely empty image.) + const ImageFrame* prevBuffer = &m_frameBufferCache[--frameIndex]; + ImageFrame::DisposalMethod prevMethod = prevBuffer->disposalMethod(); + while (frameIndex && (prevMethod == ImageFrame::DisposalMethod::RestoreToPrevious)) { + prevBuffer = &m_frameBufferCache[--frameIndex]; + prevMethod = prevBuffer->disposalMethod(); + } + + png_structp png = m_reader->pngPtr(); + ASSERT(prevBuffer->isComplete()); + + if (prevMethod == ImageFrame::DisposalMethod::DoNotDispose) { + // Preserve the last frame as the starting state for this frame. + if (!prevBuffer->backingStore() || !buffer.initialize(*prevBuffer->backingStore())) + longjmp(JMPBUF(png), 1); + } else { + // We want to clear the previous frame to transparent, without + // affecting pixels in the image outside of the frame. + IntRect prevRect = prevBuffer->backingStore()->frameRect(); + if (!frameIndex || prevRect.contains(IntRect(IntPoint(), scaledSize()))) { + // Clearing the first frame, or a frame the size of the whole + // image, results in a completely empty image. + buffer.backingStore()->clear(); + buffer.setHasAlpha(true); + } else { + // Copy the whole previous buffer, then clear just its frame. + if (!prevBuffer->backingStore() || !buffer.initialize(*prevBuffer->backingStore())) { + longjmp(JMPBUF(png), 1); + return; + } + buffer.backingStore()->clearRect(prevRect); + buffer.setHasAlpha(true); + } + } + + IntRect frameRect(m_xOffset, m_yOffset, m_width, m_height); + + // Make sure the frameRect doesn't extend outside the buffer. + if (frameRect.maxX() > size().width()) + frameRect.setWidth(size().width() - m_xOffset); + if (frameRect.maxY() > size().height()) + frameRect.setHeight(size().height() - m_yOffset); + + int left = upperBoundScaledX(frameRect.x()); + int right = lowerBoundScaledX(frameRect.maxX(), left); + int top = upperBoundScaledY(frameRect.y()); + int bottom = lowerBoundScaledY(frameRect.maxY(), top); + buffer.backingStore()->setFrameRect(IntRect(left, top, right - left, bottom - top)); +} + +void PNGImageDecoder::frameComplete() +{ + if (m_frameIsHidden || m_currentFrame >= frameCount()) + return; + + ImageFrame& buffer = m_frameBufferCache[m_currentFrame]; + buffer.setDecoding(ImageFrame::Decoding::Complete); + + png_bytep interlaceBuffer = m_reader->interlaceBuffer(); + + if (m_currentFrame && interlaceBuffer) { + IntRect rect = buffer.backingStore()->frameRect(); + bool hasAlpha = m_reader->hasAlpha(); + unsigned colorChannels = hasAlpha ? 4 : 3; + bool nonTrivialAlpha = false; + if (m_blend && !hasAlpha) + m_blend = 0; + +#if ENABLE(IMAGE_DECODER_DOWN_SAMPLING) + for (int y = 0; y < rect.maxY() - rect.y(); ++y) { + png_bytep row = interlaceBuffer + (m_scaled ? m_scaledRows[y] : y) * colorChannels * size().width(); + RGBA32* address = buffer.backingStore()->pixelAt(rect.x(), y + rect.y()); + for (int x = 0; x < rect.maxX() - rect.x(); ++x) { + png_bytep pixel = row + (m_scaled ? m_scaledColumns[x] : x) * colorChannels; + unsigned alpha = hasAlpha ? pixel[3] : 255; + nonTrivialAlpha |= alpha < 255; + if (!m_blend) + buffer.backingStore()->setPixel(address++, pixel[0], pixel[1], pixel[2], alpha); + else + buffer.backingStore()->blendPixel(address++, pixel[0], pixel[1], pixel[2], alpha); + } + } +#else + ASSERT(!m_scaled); + png_bytep row = interlaceBuffer; + for (int y = rect.y(); y < rect.maxY(); ++y, row += colorChannels * size().width()) { + png_bytep pixel = row; + RGBA32* address = buffer.backingStore()->pixelAt(rect.x(), y); + for (int x = rect.x(); x < rect.maxX(); ++x, pixel += colorChannels) { + unsigned alpha = hasAlpha ? pixel[3] : 255; + nonTrivialAlpha |= alpha < 255; + if (!m_blend) + buffer.backingStore()->setPixel(address++, pixel[0], pixel[1], pixel[2], alpha); + else + buffer.backingStore()->blendPixel(address++, pixel[0], pixel[1], pixel[2], alpha); + } + } +#endif + + if (!nonTrivialAlpha) { + IntRect rect = buffer.backingStore()->frameRect(); + if (rect.contains(IntRect(IntPoint(), scaledSize()))) + buffer.setHasAlpha(false); + else { + size_t frameIndex = m_currentFrame; + const ImageFrame* prevBuffer = &m_frameBufferCache[--frameIndex]; + while (frameIndex && (prevBuffer->disposalMethod() == ImageFrame::DisposalMethod::RestoreToPrevious)) + prevBuffer = &m_frameBufferCache[--frameIndex]; + + IntRect prevRect = prevBuffer->backingStore()->frameRect(); + if ((prevBuffer->disposalMethod() == ImageFrame::DisposalMethod::RestoreToBackground) && !prevBuffer->hasAlpha() && rect.contains(prevRect)) + buffer.setHasAlpha(false); + } + } else if (!m_blend && !buffer.hasAlpha()) + buffer.setHasAlpha(nonTrivialAlpha); + } + m_currentFrame++; +} + +int PNGImageDecoder::processingStart(png_unknown_chunkp chunk) +{ + static png_byte dataPNG[8] = {137, 80, 78, 71, 13, 10, 26, 10}; + static png_byte datagAMA[16] = {0, 0, 0, 4, 103, 65, 77, 65}; + + if (!m_hasInfo) + return 0; + + m_totalFrames++; + + m_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, decodingFailed, 0); + m_info = png_create_info_struct(m_png); + if (setjmp(JMPBUF(m_png))) + return 1; + + png_set_crc_action(m_png, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); + png_set_progressive_read_fn(m_png, static_cast<png_voidp>(this), + WebCore::frameHeader, WebCore::rowAvailable, 0); + + memcpy(m_dataIHDR + 8, chunk->data + 4, 8); + png_save_uint_32(datagAMA + 8, m_gamma); + + png_process_data(m_png, m_info, dataPNG, 8); + png_process_data(m_png, m_info, m_dataIHDR, 25); + png_process_data(m_png, m_info, datagAMA, 16); + if (m_sizePLTE > 0) + png_process_data(m_png, m_info, m_dataPLTE, m_sizePLTE); + if (m_sizetRNS > 0) + png_process_data(m_png, m_info, m_datatRNS, m_sizetRNS); + + return 0; +} + +int PNGImageDecoder::processingFinish() +{ + static png_byte dataIEND[12] = {0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130}; + + if (!m_hasInfo) + return 0; + + if (m_totalFrames) { + if (setjmp(JMPBUF(m_png))) + return 1; + + png_process_data(m_png, m_info, dataIEND, 12); + png_destroy_read_struct(&m_png, &m_info, 0); + } + + frameComplete(); + return 0; +} + +void PNGImageDecoder::fallbackNotAnimated() +{ + m_isAnimated = false; + m_frameCount = 1; + m_playCount = 0; + m_currentFrame = 0; + m_frameBufferCache.resize(1); +} +#endif + } // namespace WebCore diff --git a/Source/WebCore/platform/image-decoders/png/PNGImageDecoder.h b/Source/WebCore/platform/image-decoders/png/PNGImageDecoder.h index ec2e85740..fee08f5b3 100644 --- a/Source/WebCore/platform/image-decoders/png/PNGImageDecoder.h +++ b/Source/WebCore/platform/image-decoders/png/PNGImageDecoder.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2006 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR @@ -23,52 +23,109 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef PNGImageDecoder_h -#define PNGImageDecoder_h +#pragma once #include "ImageDecoder.h" -#include <wtf/OwnPtr.h> +#if ENABLE(APNG) +#include <png.h> +#endif namespace WebCore { class PNGImageReader; // This class decodes the PNG image format. - class PNGImageDecoder : public ImageDecoder { + class PNGImageDecoder final : public ImageDecoder { public: - PNGImageDecoder(ImageSource::AlphaOption, ImageSource::GammaAndColorProfileOption); + PNGImageDecoder(AlphaOption, GammaAndColorProfileOption); virtual ~PNGImageDecoder(); // ImageDecoder - virtual String filenameExtension() const { return "png"; } - virtual bool isSizeAvailable(); - virtual bool setSize(unsigned width, unsigned height); - virtual ImageFrame* frameBufferAtIndex(size_t index); + String filenameExtension() const override { return "png"; } +#if ENABLE(APNG) + size_t frameCount() const override { return m_frameCount; } + RepetitionCount repetitionCount() const override; +#endif + bool isSizeAvailable() override; + bool setSize(const IntSize&) override; + ImageFrame* frameBufferAtIndex(size_t index) override; // CAUTION: setFailed() deletes |m_reader|. Be careful to avoid // accessing deleted memory, especially when calling this from inside // PNGImageReader! - virtual bool setFailed(); + bool setFailed() override; // Callbacks from libpng void headerAvailable(); void rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, int interlacePass); void pngComplete(); +#if ENABLE(APNG) + void readChunks(png_unknown_chunkp); + void frameHeader(); + + void init(); + void clearFrameBufferCache(size_t clearBeforeFrame) override; +#endif bool isComplete() const { - return !m_frameBufferCache.isEmpty() && (m_frameBufferCache.first().status() == ImageFrame::FrameComplete); + if (m_frameBufferCache.isEmpty()) + return false; + + for (auto& imageFrame : m_frameBufferCache) { + if (!imageFrame.isComplete()) + return false; + } + + return true; + } + + bool isCompleteAtIndex(size_t index) + { + return (index < m_frameBufferCache.size() && m_frameBufferCache[index].isComplete()); } private: // Decodes the image. If |onlySize| is true, stops decoding after // calculating the image size. If decoding fails but there is no more // data coming, sets the "decode failure" flag. - void decode(bool onlySize); + void decode(bool onlySize, unsigned haltAtFrame); +#if ENABLE(APNG) + void initFrameBuffer(size_t frameIndex); + void frameComplete(); + int processingStart(png_unknown_chunkp); + int processingFinish(); + void fallbackNotAnimated(); +#endif - OwnPtr<PNGImageReader> m_reader; + std::unique_ptr<PNGImageReader> m_reader; bool m_doNothingOnFailure; + unsigned m_currentFrame; +#if ENABLE(APNG) + png_structp m_png; + png_infop m_info; + bool m_isAnimated; + bool m_frameInfo; + bool m_frameIsHidden; + bool m_hasInfo; + int m_gamma; + size_t m_frameCount; + unsigned m_playCount; + unsigned m_totalFrames; + unsigned m_sizePLTE; + unsigned m_sizetRNS; + unsigned m_sequenceNumber; + unsigned m_width; + unsigned m_height; + unsigned m_xOffset; + unsigned m_yOffset; + unsigned m_delayNumerator; + unsigned m_delayDenominator; + unsigned m_dispose; + unsigned m_blend; + png_byte m_dataIHDR[12 + 13]; + png_byte m_dataPLTE[12 + 256 * 3]; + png_byte m_datatRNS[12 + 256]; +#endif }; } // namespace WebCore - -#endif |