Chromium Code Reviews| Index: third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp |
| diff --git a/third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp b/third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp |
| index 177b58af86dbbebe6f631714da4501214ac11b31..825f0c356d6abd30c2b0ecd6d2e3bc2c8ded2e01 100644 |
| --- a/third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp |
| +++ b/third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp |
| @@ -38,9 +38,11 @@ |
| #include "platform/image-decoders/png/PNGImageReader.h" |
| +#include "platform/image-decoders/FastSharedBufferReader.h" |
| #include "platform/image-decoders/SegmentReader.h" |
| -#include "png.h" |
| +#include "platform/image-decoders/png/PNGImageDecoder.h" |
| #include "wtf/PtrUtil.h" |
| +#include "zlib.h" |
| #include <memory> |
| namespace { |
|
Noel Gordon
2017/02/21 13:53:56
I did a quick pass over this file only for now.
scroggo_chromium
2017/02/23 22:09:54
Acknowledged.
|
| @@ -72,45 +74,625 @@ void PNGAPI pngFailed(png_structp png, png_const_charp) { |
| namespace blink { |
| -PNGImageReader::PNGImageReader(PNGImageDecoder* decoder, size_t readOffset) |
| - : m_decoder(decoder), |
| - m_readOffset(readOffset), |
| - m_currentBufferSize(0), |
| - m_decodingSizeOnly(false), |
| - m_hasAlpha(false) { |
| +PNGImageReader::PNGImageReader(PNGImageDecoder* decoder, size_t initialOffset) |
| + : m_width(0), |
| + m_height(0), |
| + m_decoder(decoder), |
| + m_initialOffset(initialOffset), |
| + m_readOffset(initialOffset), |
| + m_progressiveDecodeOffset(0), |
| + m_idatOffset(0), |
| + m_idatIsPartOfAnimation(false), |
| + m_expectIdats(true), |
| + m_isAnimated(false), |
| + m_parsedSignature(false), |
| + m_parsedIHDR(false), |
| + m_parseCompleted(false), |
| + m_reportedFrameCount(0), |
| + m_nextSequenceNumber(0), |
| + m_fctlNeedsDatChunk(false), |
| + m_ignoreAnimation(false) { |
| m_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, pngFailed, 0); |
| m_info = png_create_info_struct(m_png); |
| - png_set_progressive_read_fn(m_png, m_decoder, pngHeaderAvailable, |
| - pngRowAvailable, pngComplete); |
| + png_set_progressive_read_fn(m_png, m_decoder, nullptr, pngRowAvailable, |
| + pngComplete); |
|
Noel Gordon
2017/02/21 13:53:57
pngFrameComplete, here and anywhere else it's used
scroggo_chromium
2017/02/23 22:09:54
Done.
|
| } |
| PNGImageReader::~PNGImageReader() { |
| png_destroy_read_struct(m_png ? &m_png : 0, m_info ? &m_info : 0, 0); |
| DCHECK(!m_png && !m_info); |
| +} |
| - m_readOffset = 0; |
| +// This method reads from the FastSharedBufferReader, starting at offset, |
| +// and returns |length| bytes in the form of a pointer to a const png_byte*. |
| +// This function is used to make it easy to access data from the reader in a |
| +// png friendly way, and pass it to libpng for decoding. |
| +// |
| +// Pre-conditions before using this: |
| +// - |reader|.size() >= |readOffset| + |length| |
| +// - |buffer|.size() >= |length| |
| +// - |length| <= |kBufferSize| |
| +// |
| +// The reason for the last two precondition is that currently the png signature |
| +// plus IHDR chunk (8B + 25B = 33B) is the largest chunk that is read using this |
| +// method. If the data is not consecutive, it is stored in |buffer|, which must |
| +// have the size of (at least) |length|, but there's no need for it to be larger |
| +// than |kBufferSize|. |
| +static constexpr size_t kBufferSize = 33; |
| +const png_byte* readAsConstPngBytep(const FastSharedBufferReader& reader, |
| + size_t readOffset, |
| + size_t length, |
| + char* buffer) { |
| + DCHECK(length <= kBufferSize); |
| + return reinterpret_cast<const png_byte*>( |
| + reader.getConsecutiveData(readOffset, length, buffer)); |
| } |
| -bool PNGImageReader::decode(const SegmentReader& data, bool sizeOnly) { |
| - m_decodingSizeOnly = sizeOnly; |
| +// This is used as a value for the byteLength of a frameInfo struct to |
| +// indicate that it is the first frame and its byteLength is not yet known. 1 |
| +// is a safe value since the byteLength field of a frame is at least 12, in |
| +// the case of an empty fdAT or IDAT chunk. |
| +static constexpr size_t kFirstFrameIndicator = 1; |
| + |
| +// Return false on a fatal error |
| +bool PNGImageReader::decode(SegmentReader& data, size_t index) { |
| + if (index >= m_frameInfo.size()) |
| + return true; |
| + |
| + const FastSharedBufferReader reader(&data); |
| + |
| + if (!m_isAnimated) { |
| + if (setjmp(JMPBUF(m_png))) { |
|
Noel Gordon
2017/02/21 13:53:56
if (setjmp(JMPBUF(m_png)))
return false;
scroggo_chromium
2017/02/23 22:09:55
Done.
|
| + return false; |
| + } |
| + DCHECK_EQ(0u, index); |
| + m_progressiveDecodeOffset += processData( |
| + reader, m_frameInfo[0].startOffset + m_progressiveDecodeOffset, 0); |
| + return true; |
| + } |
| + |
| + if (index > 0 && m_progressiveDecodeOffset > 0) { |
|
Noel Gordon
2017/02/21 13:53:56
DCHECK(m_isAnimated);
if (index > 0 && m_progress
scroggo_chromium
2017/02/23 22:09:54
Done.
|
| + clearDecodeState(0); |
| + } |
| + |
| + const bool decodeAsNewPNG = !m_png; |
| + if (decodeAsNewPNG) { |
| + DCHECK(!m_info); |
| + m_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, pngFailed, 0); |
| + m_info = png_create_info_struct(m_png); |
| + png_set_progressive_read_fn(m_png, m_decoder, pngHeaderAvailable, |
| + pngRowAvailable, pngComplete); |
| + } |
| - // We need to do the setjmp here. Otherwise bad things will happen. |
| if (setjmp(JMPBUF(m_png))) |
| - return m_decoder->setFailed(); |
| + return false; |
| + |
| + if (decodeAsNewPNG) |
| + startFrameDecoding(reader, index); |
| + |
| + if (index == 0 && |
|
Noel Gordon
2017/02/21 13:53:56
comparisons to 0; use !index && ...
scroggo_chromium
2017/02/23 22:09:54
Done.
|
| + (!firstFrameFullyReceived() || m_progressiveDecodeOffset > 0)) { |
| + const bool decodedFrameCompletely = progressivelyDecodeFirstFrame(reader); |
| + if (!decodedFrameCompletely) |
| + return true; |
| + |
| + m_progressiveDecodeOffset = 0; |
| + } else { |
| + decodeFrame(reader, index); |
| + } |
| + |
| + png_byte IEND[12] = {0, 0, 0, 0, 'I', 'E', 'N', 'D', 174, 66, 96, 130}; |
|
Noel Gordon
2017/02/21 13:53:56
Could this be
static png_byte IEND[12] ?
scroggo_chromium
2017/02/23 22:09:54
Done.
|
| + png_process_data(m_png, m_info, IEND, 12); |
| + png_destroy_read_struct(&m_png, &m_info, 0); |
| + DCHECK(!m_png && !m_info); |
| + |
| + return true; |
| +} |
| + |
| +void PNGImageReader::startFrameDecoding(const FastSharedBufferReader& reader, |
| + size_t index) { |
| + // If the frame is the size of the whole image, just re-process all header |
| + // data up to the first frame. |
| + const IntRect& frameRect = m_frameInfo[index].frameRect; |
| + if (frameRect.location() == IntPoint() && |
| + frameRect.size() == m_decoder->size()) { |
| + processData(reader, m_initialOffset, m_idatOffset); |
| + return; |
| + } |
| + |
| + // Process the IHDR chunk, but change the width and height so it reflects |
| + // the frame's width and height. ImageDecoder will apply the x,y offset. |
| + char readBuffer[kBufferSize]; |
| + |
| + // |headerSize| is equal to |kBufferSize|, but adds more semantic insight. |
| + constexpr size_t headerSize = 33; |
| + png_byte header[headerSize]; |
| + const png_byte* chunk = |
| + readAsConstPngBytep(reader, m_initialOffset, headerSize, readBuffer); |
| + memcpy(header, chunk, headerSize); |
| + |
| + png_save_uint_32(header + 16, frameRect.width()); |
| + png_save_uint_32(header + 20, frameRect.height()); |
| + // IHDR has been modified, so tell libpng to ignore CRC errors. |
| + png_set_crc_action(m_png, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); |
| + png_process_data(m_png, m_info, header, headerSize); |
| + |
| + // Process the rest of the header chunks. |
| + processData(reader, m_initialOffset + headerSize, m_idatOffset - headerSize); |
| +} |
| + |
| +// Determine if the bytes 4 to 7 of |chunk| indicate that it is a |tag| chunk. |
| +// - The length of |chunk| must be >= 8 |
| +// - The length of |tag| must be = 4 |
| +static inline bool isChunk(const png_byte* chunk, const char* tag) { |
| + return memcmp(chunk + 4, tag, 4) == 0; |
| +} |
| + |
| +bool PNGImageReader::progressivelyDecodeFirstFrame( |
| + const FastSharedBufferReader& reader) { |
| + char readBuffer[8]; // large enough to identify a chunk. |
| + size_t offset = m_frameInfo[0].startOffset; |
| + |
| + // Loop while there is enough data to do progressive decoding. |
| + while (reader.size() >= offset + 8) { |
| + // At the beginning of each loop, the offset is at the start of a chunk. |
| + const png_byte* chunk = readAsConstPngBytep(reader, offset, 8, readBuffer); |
| + const png_uint_32 length = png_get_uint_32(chunk); |
| + DCHECK(length <= PNG_UINT_31_MAX); |
| + |
| + // When an fcTL or IEND chunk is encountered, the frame data has ended. |
| + // Return true, since all frame data is decoded. |
| + if (isChunk(chunk, "fcTL") || isChunk(chunk, "IEND")) |
| + return true; |
| + |
| + // If this chunk was already decoded, move on to the next. |
| + if (m_progressiveDecodeOffset >= offset + length + 12) { |
| + offset += length + 12; |
| + continue; |
| + } |
| + |
| + // At this point, three scenarios are possible: |
| + // 1) Some bytes of this chunk were already decoded in a previous call. |
| + // Continue from there. |
| + // 2) This is an fdAT chunk. Convert it to an IDAT chunk to decode. |
| + // 3) This is any other chunk, most likely an IDAT chunk. |
| + size_t endOffsetChunk = offset + length + 12; |
| + |
| + if (m_progressiveDecodeOffset >= offset + 8) { |
| + offset = m_progressiveDecodeOffset; |
| + } else if (isChunk(chunk, "fdAT")) { |
| + processFdatChunkAsIdat(length); |
| + // Skip the sequence number |
|
Noel Gordon
2017/02/21 13:53:57
Sentences end with a period.
scroggo_chromium
2017/02/23 22:09:54
Done.
|
| + offset += 12; |
| + } else { |
| + png_process_data(m_png, m_info, const_cast<png_byte*>(chunk), 8); |
| + offset += 8; |
| + } |
| + |
| + size_t bytesLeftInChunk = endOffsetChunk - offset; |
| + size_t bytesDecoded = processData(reader, offset, bytesLeftInChunk); |
| + m_progressiveDecodeOffset = offset + bytesDecoded; |
| + if (bytesDecoded < bytesLeftInChunk) |
| + return false; |
| + offset += bytesDecoded; |
| + } |
| + |
| + return false; |
| +} |
| + |
| +void PNGImageReader::processFdatChunkAsIdat(png_uint_32 fdatLength) { |
| + // An fdAT chunk is build up as follows: |
| + // - |length| (4B) |
| + // - fdAT tag (4B) |
| + // - sequence number (4B) |
| + // - frame data (|length| - 4B) |
| + // - CRC (4B) |
| + // Thus, to reformat this into an IDAT chunk, do the following: |
| + // - write |length| - 4 as the new length, since the sequence number |
| + // must be removed. |
| + // - change the tag to IDAT. |
| + // - omit the sequence number from the data part of the chunk. |
| + png_byte chunkIDAT[] = {0, 0, 0, 0, 'I', 'D', 'A', 'T'}; |
|
Noel Gordon
2017/02/21 13:53:57
static png_byte chunkIDAT[] ?
scroggo_chromium
2017/02/23 22:09:54
We could, but then we'd need to copy it, since the
|
| + png_save_uint_32(chunkIDAT, fdatLength - 4); |
| + // The CRC is incorrect when applied to the modified fdAT. |
| + png_set_crc_action(m_png, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); |
| + png_process_data(m_png, m_info, chunkIDAT, 8); |
| +} |
| + |
| +void PNGImageReader::decodeFrame(const FastSharedBufferReader& reader, |
| + size_t index) { |
| + size_t offset = m_frameInfo[index].startOffset; |
| + size_t endOffset = offset + m_frameInfo[index].byteLength; |
| + char readBuffer[8]; |
| + |
| + while (offset < endOffset) { |
| + const png_byte* chunk = readAsConstPngBytep(reader, offset, 8, readBuffer); |
| + const png_uint_32 length = png_get_uint_32(chunk); |
| + DCHECK(length <= PNG_UINT_31_MAX); |
| + |
| + if (isChunk(chunk, "fdAT")) { |
| + processFdatChunkAsIdat(length); |
| + // The frame data and the CRC span |length| bytes, so skip the |
| + // sequence number and process |length| bytes to decode the frame. |
| + processData(reader, offset + 12, length); |
| + } else { |
| + png_process_data(m_png, m_info, const_cast<png_byte*>(chunk), 8); |
| + processData(reader, offset + 8, length + 4); |
| + } |
| + offset += 12 + length; |
| + } |
| +} |
| + |
| +// Compute the CRC and compare to the stored value |
|
Noel Gordon
2017/02/21 13:53:56
Sentences.
scroggo_chromium
2017/02/23 22:09:54
Done.
|
| +static bool checkCrc(const FastSharedBufferReader& reader, |
| + size_t chunkStart, |
| + size_t chunkLength) { |
| + const size_t kSizeNeededForfcTL = 26 + 4; |
| + char readBuffer[kSizeNeededForfcTL]; |
| + DCHECK(chunkLength + 4 <= kSizeNeededForfcTL); |
| + const png_byte* chunk = |
| + readAsConstPngBytep(reader, chunkStart + 4, chunkLength + 4, readBuffer); |
| + |
| + char crcBuffer[4]; |
| + const png_byte* crcLoc = |
| + readAsConstPngBytep(reader, chunkStart + 8 + chunkLength, 4, crcBuffer); |
| + const png_uint_32 crc = png_get_uint_32(crcLoc); |
| + const png_uint_32 actual = crc32(crc32(0, Z_NULL, 0), chunk, chunkLength + 4); |
| + return actual == crc; |
| +} |
| + |
| +bool PNGImageReader::checkSequenceNumber(const png_byte* position) { |
| + png_uint_32 sequenceNumber = png_get_uint_32(position); |
| + if (sequenceNumber != m_nextSequenceNumber || |
| + sequenceNumber > PNG_UINT_31_MAX) |
| + return false; |
| + |
| + m_nextSequenceNumber++; |
|
Noel Gordon
2017/02/21 13:53:56
++m_nextSequenceNumber;
scroggo_chromium
2017/02/23 22:09:54
Done.
|
| + return true; |
| +} |
| + |
| +// Return false if there was a fatal error; true otherwise. |
| +bool PNGImageReader::parse(SegmentReader& data, PNGParseQuery query) { |
|
Noel Gordon
2017/02/21 13:53:57
bool PNGImageReader::parse(SegmentReader& data, Pa
Noel Gordon
2017/02/21 13:53:57
bool PNGImageReader::parse(SegmentReader& data, Pa
scroggo_chromium
2017/02/23 22:09:55
Done.
|
| + if (m_parseCompleted) |
| + return true; |
| + |
| + const FastSharedBufferReader reader(&data); |
| + |
| + if (!parseSize(reader)) |
| + return false; |
| + |
| + if (query == PNGParseQuery::PNGSizeQuery || |
|
Noel Gordon
2017/02/21 13:53:57
if (query == ParseQuery::Size ...
scroggo_chromium
2017/02/23 22:09:55
Done.
|
| + !m_decoder->isDecodedSizeAvailable()) |
| + return true; |
| + // For non animated images (identified by no acTL chunk before the IDAT), |
| + // there is no need to continue parsing. |
| + if (!m_isAnimated) { |
| + DCHECK(m_frameInfo.isEmpty()); |
| + FrameInfo frame; |
| + frame.startOffset = m_readOffset; |
| + frame.duration = 0; |
| + frame.frameRect = IntRect(IntPoint(), {m_width, m_height}); |
| + frame.disposalMethod = ImageFrame::DisposalMethod::DisposeKeep; |
| + frame.alphaBlend = ImageFrame::AlphaBlendSource::BlendAtopBgcolor; |
| + m_frameInfo.append(frame); |
|
Noel Gordon
2017/02/21 13:53:56
append -> push_back
scroggo_chromium
2017/02/23 22:09:55
Done.
|
| + m_parseCompleted = true; |
| + return true; |
| + } |
| + |
| + char readBuffer[kBufferSize]; |
|
Noel Gordon
2017/02/21 13:53:56
DCHECK_EQ(query, ParseQuery::MetaData);
DCHECK(m_i
scroggo_chromium
2017/02/23 22:09:54
Done.
|
| + |
| + // At this point, the query is FrameMetaDataQuery and the image is animated. |
|
Noel Gordon
2017/02/21 13:53:56
// Loop over the data and manually register all fr
scroggo_chromium
2017/02/23 22:09:54
Done.
|
| + // Loop over the data and manually register all frames. Nothing is passed to |
| + // libpng for processing. A frame is registered on the next fcTL chunk or |
| + // when the IEND chunk is found. This ensures that only complete frames are |
| + // reported, unless there is an error in the stream. |
| + while (reader.size() >= m_readOffset + 8) { |
| + const png_byte* chunk = |
| + readAsConstPngBytep(reader, m_readOffset, 8, readBuffer); |
| + const size_t length = png_get_uint_32(chunk); |
| + if (length > PNG_UINT_31_MAX) |
| + return false; |
| + |
| + const bool IDAT = isChunk(chunk, "IDAT"); |
| + if (IDAT && !m_expectIdats) { |
|
Noel Gordon
2017/02/21 13:53:56
{ } not needed.
scroggo_chromium
2017/02/23 22:09:55
Done.
|
| + return false; |
| + } |
| + |
| + const bool fdAT = isChunk(chunk, "fdAT"); |
| + if (fdAT && m_expectIdats) { |
|
Noel Gordon
2017/02/21 13:53:56
{ } not needed.
scroggo_chromium
2017/02/23 22:09:55
Done.
|
| + return false; |
| + } |
| + |
| + if (fdAT || (IDAT && m_idatIsPartOfAnimation)) { |
| + m_fctlNeedsDatChunk = false; |
| + if (m_newFrame.startOffset == 0) { |
| + // Beginning of a new frame's data. |
| + m_newFrame.startOffset = m_readOffset; |
| + |
| + if (m_frameInfo.isEmpty()) { |
| + // This is the first frame. Report it immediately so it can be |
| + // decoded progressively. |
| + m_newFrame.byteLength = kFirstFrameIndicator; |
| + m_frameInfo.append(m_newFrame); |
|
Noel Gordon
2017/02/21 13:53:56
append -> push_back.
scroggo_chromium
2017/02/23 22:09:55
Done.
|
| + } |
| + } |
| + |
| + if (fdAT) { |
| + if (reader.size() < m_readOffset + 8 + 4) { |
|
Noel Gordon
2017/02/21 13:53:56
{ } not needed.
if (reader.size() < m_readOffset
scroggo_chromium
2017/02/23 22:09:55
Done.
|
| + return true; |
| + } |
| + const png_byte* sequenceNumPosition = |
| + readAsConstPngBytep(reader, m_readOffset + 8, 4, readBuffer); |
| + if (!checkSequenceNumber(sequenceNumPosition)) |
| + return false; |
| + } |
| + |
| + } else if (isChunk(chunk, "fcTL") || isChunk(chunk, "IEND")) { |
| + // This marks the end of the previous frame. |
| + if (m_newFrame.startOffset != 0) { |
| + m_newFrame.byteLength = m_readOffset - m_newFrame.startOffset; |
| + if (m_frameInfo[0].byteLength == kFirstFrameIndicator) { |
| + m_frameInfo[0].byteLength = m_newFrame.byteLength; |
| + } else { |
| + m_frameInfo.append(m_newFrame); |
|
Noel Gordon
2017/02/21 13:53:56
append -> push_back
scroggo_chromium
2017/02/23 22:09:54
Done.
|
| + if (isChunk(chunk, "fcTL")) { |
| + if (m_frameInfo.size() >= m_reportedFrameCount) |
| + return false; |
| + } else { |
| + // IEND |
| + if (m_frameInfo.size() != m_reportedFrameCount) |
| + return false; |
| + } |
| + } |
| + |
| + m_newFrame.startOffset = 0; |
| + } |
| + |
| + if (reader.size() < m_readOffset + 12 + length) |
| + return true; |
| + |
| + if (isChunk(chunk, "IEND")) { |
| + m_parseCompleted = true; |
| + return true; |
| + } |
| + |
| + if (length != 26 || !checkCrc(reader, m_readOffset, length)) |
| + return false; |
| + |
| + chunk = readAsConstPngBytep(reader, m_readOffset + 8, length, readBuffer); |
| + if (!parseFrameInfo(chunk)) |
| + return false; |
| + |
| + m_expectIdats = false; |
| + } else if (isChunk(chunk, "acTL")) { |
| + // There should only be one acTL chunk, and it should be before the |
| + // IDAT chunk. |
| + return false; |
| + } |
| + m_readOffset += 12 + length; |
| + } |
| + return true; |
| +} |
| + |
| +// If |length| == 0, read until the stream ends. |
| +// @return: number of bytes processed. |
|
Noel Gordon
2017/02/21 13:53:57
@?
scroggo_chromium
2017/02/23 22:09:55
Done.
|
| +size_t PNGImageReader::processData(const FastSharedBufferReader& reader, |
| + size_t offset, |
| + size_t length) { |
| const char* segment; |
| - while (size_t segmentLength = data.getSomeData(segment, m_readOffset)) { |
| - m_readOffset += segmentLength; |
| - m_currentBufferSize = m_readOffset; |
| + size_t totalProcessedBytes = 0; |
| + while (reader.size() > offset) { |
| + size_t segmentLength = reader.getSomeData(segment, offset); |
| + if (length > 0 && segmentLength + totalProcessedBytes > length) |
| + segmentLength = length - totalProcessedBytes; |
| + |
| png_process_data(m_png, m_info, |
| - reinterpret_cast<png_bytep>(const_cast<char*>(segment)), |
| + reinterpret_cast<png_byte*>(const_cast<char*>(segment)), |
| segmentLength); |
| - if (sizeOnly ? m_decoder->isDecodedSizeAvailable() |
| - : m_decoder->frameIsCompleteAtIndex(0)) |
| + offset += segmentLength; |
| + totalProcessedBytes += segmentLength; |
| + if (totalProcessedBytes == length) |
| + return length; |
| + } |
| + return totalProcessedBytes; |
| +} |
| + |
| +// Process up to the start of the IDAT with libpng. |
| +// Return false for a fatal error. True otherwise. |
| +bool PNGImageReader::parseSize(const FastSharedBufferReader& reader) { |
| + if (m_decoder->isDecodedSizeAvailable()) |
| + return true; |
| + |
| + char readBuffer[kBufferSize]; |
| + |
| + if (setjmp(JMPBUF(m_png))) { |
|
Noel Gordon
2017/02/21 13:53:56
if (setjmp(JMPBUF(m_png)))
return false;
scroggo_chromium
2017/02/23 22:09:55
Done.
|
| + return false; |
| + } |
| + |
| + if (!m_parsedSignature) { |
| + if (reader.size() < m_readOffset + 8) |
| return true; |
| + |
| + const png_byte* chunk = |
| + readAsConstPngBytep(reader, m_readOffset, 8, readBuffer); |
| + png_process_data(m_png, m_info, const_cast<png_byte*>(chunk), 8); |
| + m_readOffset += 8; |
| + m_parsedSignature = true; |
| + // Initialize the newFrame by setting the readOffset to 0. |
| + m_newFrame.startOffset = 0; |
| } |
| - return false; |
| + // This loop peeks at the chunk tag until the IDAT chunk is found. APNG |
| + // chunks are handled manually. Other tags are passed on to libpng for general |
| + // parsing. Peek at chunks by looking at the first 8 bytes, which contain the |
| + // length and the chunk tag. |
| + for (png_uint_32 length = 0; reader.size() >= m_readOffset + 8; |
| + m_readOffset += length + 12) { |
| + const png_byte* chunk = |
| + readAsConstPngBytep(reader, m_readOffset, 8, readBuffer); |
| + length = png_get_uint_32(chunk); |
| + |
| + // If we encounter the IDAT chunk, we're done with the png header |
| + if (isChunk(chunk, "IDAT")) { |
| + // Done with header chunks. |
| + m_idatOffset = m_readOffset; |
| + m_fctlNeedsDatChunk = false; |
| + if (m_ignoreAnimation) |
| + m_isAnimated = false; |
| + if (!m_isAnimated || 1 == m_reportedFrameCount) |
| + m_decoder->setRepetitionCount(cAnimationNone); |
| + // Call headerAvailable manually. IDAT will be supplied (possibly |
| + // modified) when decoding the frame. |
| + m_decoder->headerAvailable(); |
| + return true; |
| + } |
| + |
| + // 12 is the length, tag and crc part of the chunk, which are all 4B. |
| + if (reader.size() < m_readOffset + length + 12) |
| + break; |
| + |
| + if (isChunk(chunk, "acTL")) { |
| + if (m_ignoreAnimation) |
| + continue; |
| + if (m_isAnimated || length != 8 || !m_parsedIHDR || |
| + !checkCrc(reader, m_readOffset, 8)) { |
| + m_ignoreAnimation = true; |
| + continue; |
| + } |
| + chunk = readAsConstPngBytep(reader, m_readOffset + 8, length, readBuffer); |
| + m_reportedFrameCount = png_get_uint_32(chunk); |
| + if (!m_reportedFrameCount || m_reportedFrameCount > PNG_UINT_31_MAX) { |
| + m_ignoreAnimation = true; |
| + continue; |
| + } |
| + png_uint_32 repetitionCount = png_get_uint_32(chunk + 4); |
| + if (repetitionCount > PNG_UINT_31_MAX) { |
| + m_ignoreAnimation = true; |
| + continue; |
| + } |
| + m_isAnimated = true; |
| + m_decoder->setRepetitionCount((int)repetitionCount - 1); |
| + } else if (isChunk(chunk, "fcTL")) { |
| + if (m_ignoreAnimation) |
| + continue; |
| + if (length != 26 || !m_parsedIHDR || |
| + !checkCrc(reader, m_readOffset, 26)) { |
| + m_ignoreAnimation = true; |
| + continue; |
| + } |
| + chunk = readAsConstPngBytep(reader, m_readOffset + 8, length, readBuffer); |
| + if (!parseFrameInfo(chunk) || |
| + m_newFrame.frameRect != IntRect(0, 0, m_width, m_height)) { |
| + m_ignoreAnimation = true; |
| + continue; |
| + } |
| + m_idatIsPartOfAnimation = true; |
| + } else if (isChunk(chunk, "fdAT")) { |
| + m_ignoreAnimation = true; |
| + } else { |
| + png_process_data(m_png, m_info, const_cast<png_byte*>(chunk), 8); |
| + processData(reader, m_readOffset + 8, length + 4); |
| + if (isChunk(chunk, "IHDR")) { |
| + m_parsedIHDR = true; |
| + m_width = png_get_image_width(m_png, m_info); |
| + m_height = png_get_image_height(m_png, m_info); |
| + } |
| + } |
| + } |
| + |
| + // Not enough data to call headerAvailable. |
| + return true; |
| +} |
| + |
| +bool PNGImageReader::firstFrameFullyReceived() const { |
| + DCHECK_GT(m_frameInfo.size(), 0u); |
| + return m_frameInfo[0].byteLength != kFirstFrameIndicator; |
| +} |
| + |
| +void PNGImageReader::clearDecodeState(size_t frameIndex) { |
| + if (frameIndex == 0) { |
| + png_destroy_read_struct(m_png ? &m_png : nullptr, |
| + m_info ? &m_info : nullptr, 0); |
| + DCHECK(!m_png && !m_info); |
| + m_progressiveDecodeOffset = 0; |
| + } |
| +} |
| + |
| +size_t PNGImageReader::frameCount() const { |
| + return m_frameInfo.size(); |
| +} |
| + |
| +const PNGImageReader::FrameInfo& PNGImageReader::frameInfo(size_t index) const { |
| + DCHECK(index < m_frameInfo.size()); |
| + return m_frameInfo[index]; |
| +} |
| + |
| +// These constants map to the frame control parameters defined in fcTL chunks, |
| +// as specified at https://wiki.mozilla.org/APNG_Specification. |
| +#define kAPNG_DISPOSE_OP_NONE 0 |
| +#define kAPNG_DISPOSE_OP_BACKGROUND 1 |
| +#define kAPNG_DISPOSE_OP_PREVIOUS 2 |
| + |
| +#define kAPNG_BLEND_OP_SOURCE 0 |
| +#define kAPNG_BLEND_OP_OVER 1 |
| + |
| +// Extract the frame control info and store it in m_newFrame. The length check |
| +// on the data chunk has been done by the calling code. |
| +bool PNGImageReader::parseFrameInfo(const png_byte* data) { |
| + if (m_fctlNeedsDatChunk) |
| + return false; |
| + |
| + if (!checkSequenceNumber(data)) |
| + return false; |
| + |
| + png_uint_32 frameWidth = png_get_uint_32(data + 4); |
| + png_uint_32 frameHeight = png_get_uint_32(data + 8); |
| + png_uint_32 xOffset = png_get_uint_32(data + 12); |
| + png_uint_32 yOffset = png_get_uint_32(data + 16); |
| + |
| + if (!frameWidth || !frameHeight || xOffset + frameWidth > m_width || |
| + yOffset + frameHeight > m_height) |
| + return false; |
| + |
| + png_uint_16 delayNumerator = png_get_uint_16(data + 20); |
| + png_uint_16 delayDenominator = png_get_uint_16(data + 22); |
| + |
| + m_newFrame.duration = (delayDenominator == 0) |
| + ? delayNumerator * 10 |
| + : delayNumerator * 1000 / delayDenominator; |
| + m_newFrame.frameRect = IntRect(xOffset, yOffset, frameWidth, frameHeight); |
| + const uint8_t disposalMethod = data[24]; |
| + switch (disposalMethod) { |
| + case kAPNG_DISPOSE_OP_NONE: |
| + m_newFrame.disposalMethod = ImageFrame::DisposalMethod::DisposeKeep; |
| + break; |
| + case kAPNG_DISPOSE_OP_BACKGROUND: |
| + m_newFrame.disposalMethod = |
| + ImageFrame::DisposalMethod::DisposeOverwriteBgcolor; |
| + break; |
| + case kAPNG_DISPOSE_OP_PREVIOUS: |
| + m_newFrame.disposalMethod = |
| + ImageFrame::DisposalMethod::DisposeOverwritePrevious; |
| + break; |
| + default: |
| + return false; |
| + } |
| + |
| + const uint8_t alphaBlend = data[25]; |
| + switch (alphaBlend) { |
| + case kAPNG_BLEND_OP_SOURCE: |
| + m_newFrame.alphaBlend = ImageFrame::AlphaBlendSource::BlendAtopBgcolor; |
| + break; |
| + case kAPNG_BLEND_OP_OVER: |
| + m_newFrame.alphaBlend = |
| + ImageFrame::AlphaBlendSource::BlendAtopPreviousFrame; |
| + break; |
| + default: |
| + return false; |
| + } |
| + m_fctlNeedsDatChunk = true; |
| + return true; |
| } |
| -} // namespace blink |
| +}; // namespace blink |
|
Noel Gordon
2017/02/21 13:53:56
; not needed.
scroggo_chromium
2017/02/23 22:09:55
Done.
|