|
|
Created:
4 years, 2 months ago by joostouwerling Modified:
3 years, 8 months ago CC:
chromium-reviews, shans, rjwright, blink-reviews-animation_chromium.org, darktears, blink-reviews, Eric Willigers Target Ref:
refs/pending/heads/master Project:
chromium Visibility:
Public. |
DescriptionImplement Animated PNG.
Implement an internal query system in PNGImageDecoder to parse the size and
frame data, by passing a PNGImageReader::PNGParseQuery to
PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query.
For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT
frame chunk. At this point, the size should have been decoded from the IHDR
chunk.
With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This
will parse the complete data stream it has so far and reports frames when
they're fully seen, so PNGImageDecoder won't report information on partial
frames. This prevents decoding half of the frame on top of the other half of
the previous frame, in case of blending and/or disposal. The expection for
this is the first frame, which is reported as soon as the starting point of
the frame is received, so it can be decoded progressively.
When the reader does not encounter an acTL chunk before the first IDAT chunk, the
image is considered to be non-animated, and frame count parsing is stopped, even if
frames may appear after the IDAT chunk(s). The reason for this is a) that it is
required by the specification, so most APNG files should have it, and b) that
it does not slow down decoding for non-animated PNG's, which is the something
like 99.9% of the use cases for now.
The following behavioral decisions are implemented:
- An fcTL with the wrong chunk length results in a failed decoder, since the
decoder can't make any sensible predicitions about missing data.
- An acTL with the wrong chunk length is ignored, so the resulting image will
be non animated.
- The sequence numbers in fcTL and fdAT chunks are ignored.
- The frame count in the acTL chunk is ignored. Instead, the actual number of
complete frames that are in the stream is reported.
- A frame with a frame rect that falls outside of the original image's rect is
clipped, so it fits within the image. The frame data outside of the clipped
rectangle is ignored.
Reimplement frame decoding for non-animated PNGs. When the decoder reads a
non-animated PNG, it will reuse the existing libpng pointers and continue
processing the data stream from the first IDAT chunk, where it ended after a
PNGFrameCountQuery. This is similar to the old approach and is still used to
retain performance. Progressive decoding is supported.
Frame decoding for animated images is implemented. Single frames are decoded
by mocking them as complete PNG images. This is necessary since libpng does
not support animated PNG. This works by recreating libpng pointers, and reprocess
the PNG header data, but with the frame's width and height. Then, the frame data
is processed, by converting fdAT chunks to IDAT chunks. With this approach, the
existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does
not need many changes.
Progressive decoding is supported for non-animated PNGs and for the first frame
of animated PNGs.
Failures during decoding or parsing are handled differently, based on the
image and state of the decoder:
1) When a non-animated PNG, or the first frame of an animated PNG, can't be
decoded or parsed, set the decoder to the failed state, because there are no
frames we can show to the client. Also set the state to failed if a parse error
occurs before any frames were received.
2) When a decoding failure occurs for non-first frames, we still want to show
earlier frames. This means, if frame n has a failure during decoding,
the frame count is adjusted to n - 1.
3) When a parsing failure occurs, the frame count is adjusted to the number of
successfully parsed frames, since we can still show those.
BUG=1171
BUG=437662
R=scroggo@google.com,cblume@google.com
Patch Set 1 #
Total comments: 31
Patch Set 2 : Tests for size and repetition count. Decoder stores repetition count for APNG #
Total comments: 3
Patch Set 3 : Implement frame meta data decoding, include tests #
Total comments: 69
Patch Set 4 : Fixes in accordance with feedback on patch set 3 #Patch Set 5 : Extra tests for invalid images; new image for animation tests #
Total comments: 7
Patch Set 6 : Basic frame decoding with tests, no alpha blending and disposal yet #
Total comments: 75
Patch Set 7 : Processed feedback patch 6 #
Total comments: 9
Patch Set 8 : Progressive decoding for animated images #
Total comments: 64
Patch Set 9 : Fixed feedback on patch 8 #
Total comments: 6
Patch Set 10 : Resynced with master & to 2 space tabs #Patch Set 11 : Implemented feedback on patch set 9 #
Total comments: 2
Patch Set 12 : Implement disposal of frames #
Total comments: 26
Patch Set 13 : Process feedback on patch 12 #
Total comments: 12
Patch Set 14 : Decode later frames row by row #
Total comments: 22
Patch Set 15 : Implement alpha blending. #Patch Set 16 : Fix feedback on patch set 14 #
Total comments: 8
Patch Set 17 : Revisit ImageFrame alpha setting. #
Total comments: 9
Patch Set 18 : Change behavior on failure during decoding or parsing. #
Total comments: 22
Patch Set 19 : Verify the current behavior for in- and overcomplete frames is OK with tests. #Patch Set 20 : Add test to verify IEND before IDAT invalidates decoder. #Patch Set 21 : Add test to verify frameIsComplete behavior. #
Total comments: 6
Patch Set 22 : Add test to verify erroneous frame parameters default correctly. #Patch Set 23 : Apply WebKit formatting. #Patch Set 24 : Fix feedback on patch 16, 17, 18 and 21. #Patch Set 25 : Rebase master on top of CL #
Total comments: 26
Patch Set 26 : Fix feedback on patch set 25 #Patch Set 27 : Create m_reader on construction and remove |m_offset| #
Total comments: 12
Patch Set 28 : Fix feedback patch 27 #Patch Set 29 : Move PNGParseQuery to PNGImageReader. Make PNGImageReader a class member instead of pointer #Patch Set 30 : Merge master and apply WebKit formatting #Patch Set 31 : Make sure first frame is initialized correctly when setMemoryAllocator is called #Patch Set 32 : Fix wrong alpha blend and disposal for static PNG #
Total comments: 2
Patch Set 33 : Use constants for mapping fcTL blend and disposal parameters. #
Total comments: 1
Patch Set 34 : Disable LayoutTest canvas/tests/2d.drawImage.animated.apng #
Total comments: 4
Patch Set 35 : Change test to prevent access to possible invalid memory. #Patch Set 36 : Fix invalid frame pointer and not destroying FastSharedBuffer on PNG error. #Patch Set 37 : Another try to disable 2d.drawImage.animated.poster #Patch Set 38 : Fix memory leaks by using FastSharedReadBuffer and defining it smartly #
Total comments: 6
Patch Set 39 : Fix feedback on previous patches #Messages
Total messages: 151 (80 generated)
cblume@chromium.org changed reviewers: + cblume@chromium.org
https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:80: // Similar reasoning to GIFImageDecodeer. If decoding fails, we don't Probably don't need to mention the GIF decoder. The description itself was clear. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:88: return decoder->frameIsCompleteAtIndex(0); I think this shouldn't be index 0. Maybe? The old decode() had a comment explaining that once the decoding is done or we have failed, we no longer need the PNGImageReader. decode() became parse() and the comment was removed. But it seems to be doing the same thing. I think we only really want to return true from isComplete once the final frame has been decoded, right? As a side note, how does that work when the frames are purged? https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:230: // dealing with a non animated PNG. When a GIF says it has 3 frames but it actually has 5, I think we go with the 5 frames. I could be wrong about that. But that said, we may want to have a same behavior, which ever it is. There may not be a chunk telling us how many frames there are. Despite that, if we find multiple frames, maybe we want to use them.
https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:80: // Similar reasoning to GIFImageDecodeer. If decoding fails, we don't On 2016/10/01 12:01:14, cblume wrote: > Probably don't need to mention the GIF decoder. > The description itself was clear. Acknowledged. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:88: return decoder->frameIsCompleteAtIndex(0); On 2016/10/01 12:01:14, cblume wrote: > I think this shouldn't be index 0. Maybe? You're correct. This slipped through since it was not relevant for decoding the frame count in the simple approach of this CL. I'll correct this in a later CL. > The old decode() had a comment explaining that once the decoding is done or we > have failed, we no longer need the PNGImageReader. > > decode() became parse() and the comment was removed. But it seems to be doing > the same thing. > > I think we only really want to return true from isComplete once the final frame > has been decoded, right? Hmm... would it be considered complete if the client purges all frames expect the final frame? We arguably do not need the reader if we have all frames cached, so in that sense, isComplete should return if all frames are complete. > As a side note, how does that work when the frames are purged? One way could be, and I think this is how it works for GIF, is to go over the data with a FastSharedBufferReader when collecting metadata and keep track of where frame chunks start and end. Especially if we will collect all metadata upfront (see the comment below) this seems the most logical approach to me. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:230: // dealing with a non animated PNG. On 2016/10/01 12:01:14, cblume wrote: > When a GIF says it has 3 frames but it actually has 5, I think we go with the 5 > frames. I could be wrong about that. > > But that said, we may want to have a same behavior, which ever it is. There may > not be a chunk telling us how many frames there are. Despite that, if we find > multiple frames, maybe we want to use them. This is similar to the discussion in issue 2045293002. The consensus seems that fetching all the meta data, as far as we can, is the approach we want to take. In that case, it makes sense to take the same approach as the GIF image decoder, indeed. For APNG, this means we need to decide how to handle some forms of wrongly formatted files. Most importantly, each frame starts with one frame control chunk and is followed by one or more frame data chunks. All these chunks have a sequence number, which starts at 0 for the first frame control chunk, and must increment by 1 for each next chunk. See https://wiki.mozilla.org/APNG_Specification#Chunk_Sequence_Numbers for more details. How do we deal with a) chunk sequence numbers that are out of order, and b) gaps in the sequence numbers? The spec page linked to above suggests treating these cases as erroneous images, but we can try to repair them by, for a), rearranging the chunks and for b), try to ignore the gaps and see if the chunks still makes sense, or maybe ignoring frames with gaps and only take well sequenced frames into account? That being said, afaik there is no data available on how many images out there on the web are wrongly formatted. If, say, 99.9% is well formatted, it may not be worth the extra effort to fetch all metadata upfront. But that's another discussion.
https://codereview.chromium.org/2386453003/diff/20001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/20001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:65: EXPECT_EQ(false, data.get()->isEmpty()); Line 65 is a little weird, changed it to data()->isEmpty() and moved it above line 64. Will upload in next patch set
https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:88: return decoder->frameIsCompleteAtIndex(0); On 2016/10/01 17:51:38, joostouwerling wrote: > Hmm... would it be considered complete if the client purges all frames expect > the final frame? We arguably do not need the reader if we have all frames > cached, so in that sense, isComplete should return if all frames are complete. I took a quick look at the GIF decoder and it doesn't release the reader. I think the reasoning is: - If we have a still image, we can decode it and destroy the reader. If that image itself is ever purged, a new ImageDecoder will be created, which creates a new reader. - If we have an animated image, the animation frames are purged constantly and we need to reread them. So we never get to destroy the reader. If my understanding is correct, we should change the parse() function below to not m_reader.reset(). In addition to that, the GIF decoder doesn't seem to have an isComplete(). It has one for frame index. Maybe there is no real concept of being complete since frames are purged. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:230: // dealing with a non animated PNG. On 2016/10/01 17:51:39, joostouwerling wrote: > How do we deal with a) chunk sequence numbers that are out of order, and b) gaps > in the sequence numbers? The spec page linked to above suggests treating these > cases as erroneous images, but we can try to repair them by, for a), rearranging > the chunks and for b), try to ignore the gaps and see if the chunks still makes > sense, or maybe ignoring frames with gaps and only take well sequenced frames > into account? I'm not very familiar with how we handle errors in files like gif / webp. I think we ignore the error and assume it was a mistake. So for example, if the indices are wrong we might just ignore the index and don't rearrange or anything. Just assume the next frame is indeed the next frame. I'm not too worried about this, however. Like you said, there might not be too many apngs out there with errors in them. So long as there isn't already a history for bad files, I'm okay simply not displaying them the way an author may have intended. It should be their responsibility to format the image correctly. So whatever you think is best is fine by me.
https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:88: return decoder->frameIsCompleteAtIndex(0); On 2016/10/02 19:55:43, cblume wrote: > On 2016/10/01 17:51:38, joostouwerling wrote: > > Hmm... would it be considered complete if the client purges all frames expect > > the final frame? We arguably do not need the reader if we have all frames > > cached, so in that sense, isComplete should return if all frames are complete. > > I took a quick look at the GIF decoder and it doesn't release the reader. > > I think the reasoning is: > - If we have a still image, we can decode it and destroy the reader. If that > image itself is ever purged, a new ImageDecoder will be created, which creates a > new reader. > - If we have an animated image, the animation frames are purged constantly and > we need to reread them. So we never get to destroy the reader. > > If my understanding is correct, we should change the parse() function below to > not m_reader.reset(). Ack. > > In addition to that, the GIF decoder doesn't seem to have an isComplete(). It > has one for frame index. Maybe there is no real concept of being complete since > frames are purged. Note that isComplete is just an inline function (not a method of PNGImageDecoder) that is *probably* only used for naming clarity reasons in this context. With your reasoning above it makes sense that GIFImageDecoder does not have a similar function. Though, for static PNG there was a reason to delete the reader, apparently. My guess would be to free up memory. If we are dealing with static PNG's, we can still release the reader when the frame is loaded.
scroggo@chromium.org changed reviewers: + scroggo@chromium.org
> Initial start with APNGs. Includes tests for frame count, decodeFrameCount > implementation and a query system between PNGImageReader and PNGImageDecoder. nit: First line should be under 50 characters, and give a high level overview. https://www.git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#... http://chris.beams.io/posts/git-commit/ These also recommend using the imperative tense: e.g. "Implement APNG... Include tests ..." https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:88: return decoder->frameIsCompleteAtIndex(0); On 2016/10/03 14:39:10, joostouwerling wrote: > On 2016/10/02 19:55:43, cblume wrote: > > On 2016/10/01 17:51:38, joostouwerling wrote: > > > Hmm... would it be considered complete if the client purges all frames > expect > > > the final frame? We arguably do not need the reader if we have all frames > > > cached, so in that sense, isComplete should return if all frames are > complete. > > > > I took a quick look at the GIF decoder and it doesn't release the reader. > > > > I think the reasoning is: > > - If we have a still image, we can decode it and destroy the reader. If that > > image itself is ever purged, a new ImageDecoder will be created, which creates > a > > new reader. > > - If we have an animated image, the animation frames are purged constantly and > > we need to reread them. So we never get to destroy the reader. > > > > If my understanding is correct, we should change the parse() function below to > > not m_reader.reset(). > > Ack. > > > > > In addition to that, the GIF decoder doesn't seem to have an isComplete(). It > > has one for frame index. Maybe there is no real concept of being complete > since > > frames are purged. > > Note that isComplete is just an inline function (not a method of > PNGImageDecoder) that is *probably* only used for naming clarity reasons in this > context. With your reasoning above it makes sense that GIFImageDecoder does not > have a similar function. > > Though, for static PNG there was a reason to delete the reader, apparently. My > guess would be to free up memory. If we are dealing with static PNG's, we can > still release the reader when the frame is loaded. I would remove the method isComplete entirely. I don't think it provides any clarity. BTW, frameIsCompleteAtIndex is a very confusing method. It was introduced for animated images, but it was really intended to mean that we've received (and parsed - only way for us to know it was received) all the data for an image frame. But for single frame images we use to mean FrameStatus::FrameComplete (FWIW, in that case we don't know whether we've received "enough" data for fully frame until we decode). There was an effort to make this more clear (see crrev.com/1962563002), but the person working on it has dropped it. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:229: // If we see a frame, but the frame count has not yet been decoded, we are > the frame count has not yet been decoded Nit: I would lean towards using the word "parsed" here. I don't think "decoded" is wrong, per se, but elsewhere (e.g. GIFImageDecoder) we use "parse" to mean reading through the stream and storing the metadata, and "decode" to mean decoding the pixels. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:230: // dealing with a non animated PNG. On 2016/10/02 19:55:43, cblume wrote: > On 2016/10/01 17:51:39, joostouwerling wrote: > > How do we deal with a) chunk sequence numbers that are out of order, and b) > gaps > > in the sequence numbers? The spec page linked to above suggests treating these > > cases as erroneous images, but we can try to repair them by, for a), > rearranging > > the chunks and for b), try to ignore the gaps and see if the chunks still > makes > > sense, or maybe ignoring frames with gaps and only take well sequenced frames > > into account? > > I'm not very familiar with how we handle errors in files like gif / webp. I > think we ignore the error and assume it was a mistake. GIF does not have any notion of number of frames (you just keep decoding until there are no more frames) or sequence numbers. There are some errors that we fix (e.g. first frame is outside the overall bounds [1]) and others we ignore (e.g. second frame is outside the overall bounds - we just clip). And yet other errors we call setFailed(), which deletes the reader [2]. I think really we just want to stop trying to parse after that, but it also has the effect that we cannot redecode old frames. Currently, if an image has an error i frames in, we stop looping, even if we're supposed to loop [3]. I think we have some inconsistent code around failed() - in some cases we try to report valid values even though we've already failed (e.g. in GIFImageDecoder::decodeFrameCount [4], which you've mimicked here), even though we've lost some functionality (e.g. decoding old frames). [1] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... [2] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... [3] crbug.com/267883 [4] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... > > So for example, if the indices are wrong we might just ignore the index and > don't rearrange or anything. Just assume the next frame is indeed the next > frame. That sounds like the right approach to me. If we get some reports/complaints that we did the wrong thing, we can revisit. > > > I'm not too worried about this, however. Like you said, there might not be too > many apngs out there with errors in them. So long as there isn't already a > history for bad files, I'm okay simply not displaying them the way an author may > have intended. It should be their responsibility to format the image correctly. > So whatever you think is best is fine by me. Back to the specific (potential) error here - we must be reading an IDAT chunk without having seen either an acTL or an fcTL. The missing acTL seems less important to me - it tells how many frames there will be (which we may not respect) and how many times to loop (we can come up with a sensible default). But the fcTL chunk tells us - how long to display this frame - the rectangle of this frame - how to dispose of this frame - how to blend this frame Without all that information, I don't know how we can sensibly guess what the author intended and try to animate (assuming there's a frame that follows this one). So I think it's correct to interpret this as meaning that it is a single frame image. All that said, perhaps we can know this before we get here? The way I imagine parse() working (haven't gotten there yet), it will try to parse the whole stream and then we can come back and decode individual frames. As soon as parse sees the IDAT chunk, we know there's only one frame, so by the time we get to this method we do not need to check. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h (right): https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:47: void parse(PNGParseQuery); I think this can be private? (It also does not fit into the pattern of "overrides from ImageDecoder".) https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:56: bool isDecodedFrameCountAvailable() const; I think these can be private as well. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:2: * Copyright (C) 2016 Google Inc. All rights reserved. I think you want a simpler copyright header: // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. (From https://www.chromium.org/blink/coding-style#TOC-License) https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:37: #include "public/platform/WebSize.h" I don't think you need this (or WebData.h). Make sure you need the rest. I think you can drop a few more, too. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:128: m_readOffset = 0; No reason to set an integer variable in the destructor. No one will read it after this. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:148: if (PNGImageDecoder::PNGParseQuery::PNGSizeQuery == query This sure looks like it should be a switch statement to me. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:157: && m_decoder->frameIsCompleteAtIndex(0)) I don't understand PNGFrameDataQuery. What is it asking for? And why do we want to know whether the first frame is complete? https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:179: numFrames = png_get_uint_32(data); Nit: I might call these frameCount and repetitionCount, to be consistent with variable names elsewhere. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h (right): https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:77: inline bool readAnimationControl(const png_byte* data); I think the compiler is going to ignore this "inline" since the method definition is not here. https://codereview.chromium.org/2386453003/diff/20001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/20001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:122: return failed() ? cAnimationLoopOnce : m_repetitionCount; This should perhaps be a FIXME regarding crbug.com/267883
https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:230: // dealing with a non animated PNG. On 2016/10/04 14:50:16, scroggo_chromium wrote: > Back to the specific (potential) error here - we must be reading an IDAT chunk > without having seen either an acTL or an fcTL. The missing acTL seems less > important to me - it tells how many frames there will be (which we may not > respect) and how many times to loop (we can come up with a sensible default). > But the fcTL chunk tells us > > - how long to display this frame > - the rectangle of this frame > - how to dispose of this frame > - how to blend this frame > > Without all that information, I don't know how we can sensibly guess what the > author intended and try to animate (assuming there's a frame that follows this > one). So I think it's correct to interpret this as meaning that it is a single > frame image. > > All that said, perhaps we can know this before we get here? The way I imagine > parse() working (haven't gotten there yet), it will try to parse the whole > stream and then we can come back and decode individual frames. As soon as parse > sees the IDAT chunk, we know there's only one frame, so by the time we get to > this method we do not need to check. If there is no fcTL before the IDAT chunk, it only serves as a "default" image, and is not part of the animation. So in that case, we only need to look at the subsequent fcTL and fdAT chunks. But there may be some UX improvements possible, like showing the default image until we've decoded enough to have a somewhat smooth animation. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:157: && m_decoder->frameIsCompleteAtIndex(0)) On 2016/10/04 14:50:16, scroggo_chromium wrote: > I don't understand PNGFrameDataQuery. What is it asking for? And why do we want > to know whether the first frame is complete? This is the query I want to use for decoding an actual frame. It needs to be annotated (somehow) with the frame id. My guess is that I will extract decoding a single frame into it's own method, in a later design. The is-first-frame-complete check was merely to not break the static PNG decoding at this point.
Description was changed from ========== Initial start with APNGs. Includes tests for frame count, decodeFrameCount implementation and a query system between PNGImageReader and PNGImageDecoder. My initial start with the implementation of animated PNG files. I started with the two tests found in PNGImageDecoderTest, which test three images for their frame count. Copyright for these images can be found at the end of this description. I extracted PNGImageReader into it's own file (for convenience, and it got some more body as well). The old decode method is changed to a parse method, which takes a data stream and a PNGImageDecoder::PNGParseQuery. This query can either be PNGFrameCountQuery, PNGSizeQuery or PNGFrameDataQuery. This method is called from PNGImageDecoder's parse method. The frame count is considered to be decoded if: - an acTL chunk is found, which will give the number of frames - actual frame data is found before any acTL chunk. This basically sets the image to not-animated. For now, it does not correct if the actual number of frames in the stream is different (todo?) The animation chunks are extracted using libpng's png_set_keep_unknown_chunks, parsed in PNGImageReader::readChunk. Currently only the acTL chunk is considered and this feeds PNGImageDecoder the number of frames via PNGImageReader::readAnimationControl. PNGImageDecoder got some extra methods and members to determine whether or not the frame count has been decoded, namely - PNGImageDecoder::isDecodedFrameCountAvailable() - PNGImageDecoder::animationControlAvailable(numFrames, numReps) - parse(PNGParseQuery) - decodeFrameCount() - m_frameCount - m_frameCountDecoded Furthermore, decode() and decodeSize() now use the parse method. decode(bool) has been replaced by parse(PNGParseQuery). Image copyright: - png-animated-idat-not-part-of-animation.png: created by myself - png-animated-idat-part-of-animation.png: found at Wikipedia, free to use, see https://commons.wikimedia.org/wiki/File:Animated_PNG_example_bouncing_beach_b... - png-simple.png: Google's logo from the homepage BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Initial start with APNGs. Includes tests for frame count, decodeFrameCount implementation and a query system between PNGImageReader and PNGImageDecoder. My initial start with the implementation of animated PNG files. I started with the two tests found in PNGImageDecoderTest, which test three images for their frame count. Copyright for these images can be found at the end of this description. I extracted PNGImageReader into it's own file (for convenience, and it got some more body as well). The old decode method is changed to a parse method, which takes a data stream and a PNGImageDecoder::PNGParseQuery. This query can either be PNGFrameCountQuery, PNGSizeQuery or PNGFrameDataQuery. This method is called from PNGImageDecoder's parse method. The frame count is considered to be decoded if: - an acTL chunk is found, which will give the number of frames - actual frame data is found before any acTL chunk. This basically sets the image to not-animated. For now, it does not correct if the actual number of frames in the stream is different (todo?) The animation chunks are extracted using libpng's png_set_keep_unknown_chunks, parsed in PNGImageReader::readChunk. Currently only the acTL chunk is considered and this feeds PNGImageDecoder the number of frames via PNGImageReader::readAnimationControl. PNGImageDecoder got some extra methods and members to determine whether or not the frame count has been decoded, namely - PNGImageDecoder::isDecodedFrameCountAvailable() - PNGImageDecoder::animationControlAvailable(numFrames, numReps) - parse(PNGParseQuery) - decodeFrameCount() - m_frameCount - m_frameCountDecoded Furthermore, decode() and decodeSize() now use the parse method. decode(bool) has been replaced by parse(PNGParseQuery). Image copyright: - png-animated-idat-not-part-of-animation.png: created by myself - png-animated-idat-part-of-animation.png: found at Wikipedia, free to use, see https://commons.wikimedia.org/wiki/File:Animated_PNG_example_bouncing_beach_b... - png-simple.png: Google's logo from the homepage BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
This is an implementation of parsing the frame count. Some remarks: A frame is recognized when the fcTL chunk is seen, all fdAT / IDAT chunks, and the first 8 bytes of the next fcTL or IEND chunk. This ensures that all data is available, and even more frankly, the reader knows how much bytes are in this frame. The last part (i.e. the 8 bytes) is necessary do determine the chunk id, to know if we've seen all frame data. The animation control chunk provides the frame count and the repetition count, but only the latter is used. The number of frames that is provided to the decoder is the actual number of frames that has been seen in the data stream. This is a simple approach to avoid the decision on what to do when the indicated frame count is different from the actual frame count, and it also "ensures" that the client won't request the frame data for frame 15 when the animation control said there were 20 frames, but we've only parsed 10 so far. An open question is what we want to do with the IDAT when it is *not* part of the animation. Do we want to ignore it and just start decoding the first frame, or could it be used for enhancing the user experience by e.g. showing it while the other frames are loading? https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h (right): https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:47: void parse(PNGParseQuery); On 2016/10/04 14:50:16, scroggo_chromium wrote: > I think this can be private? (It also does not fit into the pattern of > "overrides from ImageDecoder".) Acknowledged. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:56: bool isDecodedFrameCountAvailable() const; On 2016/10/04 14:50:16, scroggo_chromium wrote: > I think these can be private as well. Acknowledged. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:2: * Copyright (C) 2016 Google Inc. All rights reserved. On 2016/10/04 14:50:16, scroggo_chromium wrote: > I think you want a simpler copyright header: > > // Copyright 2016 The Chromium Authors. All rights reserved. > // Use of this source code is governed by a BSD-style license that can be > // found in the LICENSE file. > > (From https://www.chromium.org/blink/coding-style#TOC-License) Acknowledged. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:37: #include "public/platform/WebSize.h" On 2016/10/04 14:50:16, scroggo_chromium wrote: > I don't think you need this (or WebData.h). Make sure you need the rest. I think > you can drop a few more, too. Acknowledged. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:128: m_readOffset = 0; On 2016/10/04 14:50:17, scroggo_chromium wrote: > No reason to set an integer variable in the destructor. No one will read it > after this. Acknowledged. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:148: if (PNGImageDecoder::PNGParseQuery::PNGSizeQuery == query On 2016/10/04 14:50:16, scroggo_chromium wrote: > This sure looks like it should be a switch statement to me. Acknowledged. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:179: numFrames = png_get_uint_32(data); On 2016/10/04 14:50:17, scroggo_chromium wrote: > Nit: I might call these frameCount and repetitionCount, to be consistent with > variable names elsewhere. Acknowledged. https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h (right): https://codereview.chromium.org/2386453003/diff/1/third_party/WebKit/Source/p... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:77: inline bool readAnimationControl(const png_byte* data); On 2016/10/04 14:50:17, scroggo_chromium wrote: > I think the compiler is going to ignore this "inline" since the method > definition is not here. Acknowledged. https://codereview.chromium.org/2386453003/diff/20001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/20001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:122: return failed() ? cAnimationLoopOnce : m_repetitionCount; On 2016/10/04 14:50:17, scroggo_chromium wrote: > This should perhaps be a FIXME regarding crbug.com/267883 Acknowledged.
> An open question is what we want to do with the IDAT when it is *not* part of > the animation. Do we want to ignore it and just start decoding the first frame, > or could it be used for enhancing the user experience by e.g. showing it while > the other frames are loading? What do other browsers do? https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:78: return m_frameCount; If we failed, does this get set to m_frame_bufferCache.size()? If not, I think this is incorrect. But if so, you can simplify the later return statement to just say "return m_frameCount". In fact, this could perhaps be if (!m_metaDataDecoded) { parse(...); } return m_frameCount; You can override setFailed() to update m_frameCount, and that might be the easiest way to make sure this happens. (It would be good to add a test that verifies this is correct.) https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:89: m_reader->decode(*m_data, index); Are we guaranteed to have an m_reader here? https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:101: { Curly braces go on their own line for methods, but I think for if statements they go on the same line, e.g. if (condition) { (That said, once you remove the print statement, there should be no braces, according to the style guide.) https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:102: SkDebugf("Setting failed in PNGImageDecoder::parse"); You'll need to remove this before submitting. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:116: // FIXME(joostouwerling): crbug.com/267883 It is good to have the bug as a reference, but it would also be helpful to say a little something here like This matches the existing behavior to loop once if decoding fails, but this should be changed to stick with m_repititionCount to match other browsers. See crbug.com/267883 https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:124: const auto& frameInfo = m_reader->frameInfo(index); I'm not convinced using "auto" here helps readability (see https://google.github.io/styleguide/cppguide.html#auto) https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:125: auto buffer = &m_frameBufferCache[index]; If we use "auto" here, I think it should be "auto*", but I think it would clearer to specify the type. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:360: void PNGImageDecoder::complete() This method does nothing now. Does it still get called? https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h (right): https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:58: // while parsing PNGMetaDataQuery. Ifm_metaDataDecoded is true, the decoder nit: Missing a space between "If" and "m_metaDataDecoded" https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:65: // @TODO(joostouwerling) this needs some more work. What more work is needed? https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:31: EXPECT_EQ(false, data->isEmpty()); This can be EXPECT_FALSE(data->isEmpty()) Or maybe it should be ASSERT_FALSE. EXPECT is an error when running the test, but ASSERT is a fatal error, so the test will stop. I'm not sure what happens when called from a helper function that is expected to return a value, though. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:53: * Test whether the querying for the size of the image works if we present the nit: "Test whether querying..." ("the" is not needed here). https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:56: void testSizeByteByByte(const char *pngFile, size_t frameOffset, IntSize expectedSize) Why is this variable named "frameOffset"? I think this is actually the minimum bytes needed to decode the size? https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:60: EXPECT_EQ(false, data->isEmpty()); This should probably be ASSERT_FALSE https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:79: struct FrameInfo { I find this confusing. I guess this is just the publicly interesting members of PNGImageReader::FrameInfo? https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:114: void compareFrameWithExpectation(FrameInfo& expected, ImageFrame* frame) I think both of these parameters can be marked const? https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:117: EXPECT_EQ(expected.offset, frame->originalFrameRect().location()); If you combine offset and size into an IntRect, I think you can combine this line and the next one into: EXPECT_EQ(expected.rect, frame->originalFrameRect()); https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:147: EXPECT_EQ(expectedFrameCount, decoder->frameCount()); This should probably be ASSERT_EQ. If frameCount() returns less, frameBufferAtIndex will start returning nullptr, resulting in some bad dereferences. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:185: EXPECT_EQ(false, data->isEmpty()); ASSERT_FALSE https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:189: RefPtr<SharedBuffer> tempData = SharedBuffer::create(data->data(), length); We have some infrastructure to detect if your test takes longer than 50ms. If so, it will file a bug and assign it to you. But you should go ahead and time this (gtest will print out the time for each test). If it's too long, crrev.com/1405053005/ shows how we shortened similar tests in the past. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:196: EXPECT_EQ(framesParsed + 1, decoder->frameCount()); Why don't they match exactly? https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:223: EXPECT_EQ(expectedFrameCount, decoder->frameCount()); nit: I would prefer to hardcode these numbers e.g. EXPECT_EQ(1, decoder->frameCount()); I don't feel terribly strongly, though, and perhaps you have an argument for doing it this way. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:89: SkDebugf("In pngFailed with err %s.", err); You'll need to remove this before submitting. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:97: /* I think most of the comments in ImageDecoder use //. According to https://google.github.io/styleguide/cppguide.html#Comments, we should be consistent (and // is more common). https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:136: * Decode the frame at `index` Lots of places in the code would refer to this as |index|, although I have not found that in the style guide, and I think someone has recommended against it in a code review... https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:150: * - reader.size() >= offset + length nit: readOffset https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:151: * - |m_readBuffer| = 26 >= length Does m_readBuffer need to be a member variable? I think this would be easier to follow if you just put a char buffer on the stack in the method that calls this, and pass it as a parameter. You can also set the size to a constant and compare: constexpr size_t kBufferSize = 26; static png_byte* readAsPngBytep(..., length, buffer) { ASSERT(length <= kBufferSize) return ... } (It doesn't guarantee someone won't call your method with a smaller buffer, but it should be more obvious not to do that.) Then at the call site: { ... char buffer[kBufferSize]; ... var = readAsPngBytep(..., length, buffer); ... } Now that we don't use m_readBuffer, this can be a static function in this file instead of a member function. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:157: png_byte* PNGImageReader::readAsPngBytep(const FastSharedBufferReader &reader, nit: & should go next to the type: FastSharedBufferReader& reader https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:160: return const_cast<png_byte*>(reinterpret_cast<const png_byte*>( Why the const_cast? I suppose the methods you'll call do not specify const? I hope nothing is modifying the returned bytes! https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:175: if (!parseSize(data)) nit: This could be one if statement: if (!m_decoder->isDecodedSizeAvailable() && !parseSize(data)) https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:185: * At this point, the query is FrameMetaDataQuery. Let's go ahead and loop nit: "Let's go ahead and" is informal, and probably not necessary. How about: Loop over the data ... https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:188: * that all frame data is available, and that the frame data chunks have the This is true, but I think it could be more explicit. The interesting point is that we try not to report information about partial frames. How about saying something like: This ensures that only complete frames are reported (unless there is an error in the stream). https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:193: size_t length = png_get_uint_32(chunk); nit: I think this can be const. Same for the bools down below. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:224: return false; If you return false here, and then receive more data, does this work the way you expect it to? It looks to me like the next time you try to parse you will add an extra frameInfo to m_frameInfo. First time in the loop: - m_newFrame.readOffset == 0 && isFrameData => m_newFrame.readOffset is set to m_readOffset - isFCTL or isIEND && m_newFrame.readOffset != 0 (based on the step above) => append(m_newFrame) => m_newFrame.readOffset is set to 0 Next time in the loop looks to be the same (we haven't changed m_readOffset, so we're reading the same data, and m_newFrame.readOffset is back to zero), so we add another frame to m_frameInfo. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:245: size_t decodedLength = 0; nit: I think if you put the word "total" in this name it will make it clearer what it's for. totalDecodedLength? totalDecoded? totalProcessed? https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:246: while (size_t segmentLength = data.getSomeData(segment, offset)) { I think you need to add decodedLength to offset, or else you'll just be reading the same bit of information forever. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:249: png_process_data(m_png, m_info, Do you need to call setjmp in this method in case there is an error? Or is the intent that a longjmp will unwind back to the caller? https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:261: * It returns true when it succeeds. If there is not enough data or when the Again, I think some formatting can make this clearer. The following sentence is just a fragment: "If there is not enough data or when the decoding by libpng fails." I think you mean to say that it returns false. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:279: if (reader.size() < m_readOffset + 8) I'm assuming m_readOffset will be zero at this point? I think other ImageDecoders use this as a signal that m_parsedSignature is false, so you could drop the extra boolean. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:300: chunk = readAsPngBytep(reader, m_readOffset, 8); I think you should separately declare png_byte* chunk each place it's used. You don't actually take advantage of the fact that you're using the same variable (and you don't want to). https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:301: png_uint_32 length = png_get_uint_32(chunk); nit: I think this can be const. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:305: * Indicate this to libpng by sending the beginning of the IDAT chunk We're telling libpng that we're done with the header? What does libpng do with that information? https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:312: }; No need for a ";" here. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:363: void PNGImageReader::parseAnimationControl(const png_byte* data) nit: It seems like you could put this method's contents directly in the one place it's used, since it's so small. It would also make me feel better about you reading a seemingly arbitrary amount of data from the pointer. (The call-site checks to make sure those numbers are safe.) https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h (right): https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:52: IntPoint offset; Why not combine offset and size into an IntRect? https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:60: * the point where the PNGParseQuery can be answered. It returns true when I think some better formatting will make this easier to follow: @return true when the query is fulfilled. false when: A) not enough data has been provided B) when the data processing by libpng fails. In this case, it also calls setFailed() on m_decoder https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:63: * 2), it also calls setFailed() at the m_decoder. nit: "on m_decoder" https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:103: bool m_idatPartOfAnimation; nit: I think this is more clear if you insert the word "is" "m_idatIsPartOfAnimation" It might be helpful to have an explanation of what that means, too.
All comments that I did not address in this response can be considered silently acknowledged. > > An open question is what we want to do with the IDAT when it is *not* part of > > the animation. Do we want to ignore it and just start decoding the first frame, > > or could it be used for enhancing the user experience by e.g. showing it while > > the other frames are loading? > What do other browsers do? Firefox ignores it. The other browser are a little hard to analyse since I can't see their code. Since Firefox is the initiator of the APNG spec, I'd say it is wise to follow their example. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:78: return m_frameCount; On 2016/10/11 20:13:09, scroggo_chromium wrote: > If we failed, does this get set to m_frame_bufferCache.size()? If not, I think > this is incorrect. But if so, you can simplify the later return statement to > just say "return m_frameCount". > > In fact, this could perhaps be > > if (!m_metaDataDecoded) { > parse(...); > } > > return m_frameCount; > > You can override setFailed() to update m_frameCount, and that might be the > easiest way to make sure this happens. > > (It would be good to add a test that verifies this is correct.) I'm not sure this answers completely what you're getting at, but: if m_metaDataDecoded is set to true, it means m_reader was able to parse the frame count up to the IEND chunk and has returned true. Since it has returned true, the parse() method of the decoder will update m_frameCount to the number of frames the reader has found. But your proposal looks cleaner. I'll see if I can nicely implement it. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:89: m_reader->decode(*m_data, index); On 2016/10/11 20:13:09, scroggo_chromium wrote: > Are we guaranteed to have an m_reader here? m_reader is not deleted by the decoder anymore. The prerequisite for this method is that decodeFrameCount() is called, since that method will create m_reader and populates the frame info. I assume that all clients use the ImageDecoders in this way. I can add a check if the requested index is within the number of frames we found, but the result won't change so much with it. Note that most work on this method is done in the next iteration, where frames are decoded. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:101: { On 2016/10/11 20:13:09, scroggo_chromium wrote: > Curly braces go on their own line for methods, but I think for if statements > they go on the same line, e.g. > > if (condition) { > > (That said, once you remove the print statement, there should be no braces, > according to the style guide.) Ack, that was due to debugging. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h (right): https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:65: // @TODO(joostouwerling) this needs some more work. On 2016/10/11 20:13:09, scroggo_chromium wrote: > What more work is needed? Reminder for me to implement decode(size_t). Will omit it from patches in the future. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:56: void testSizeByteByByte(const char *pngFile, size_t frameOffset, IntSize expectedSize) On 2016/10/11 20:13:09, scroggo_chromium wrote: > Why is this variable named "frameOffset"? I think this is actually the minimum > bytes needed to decode the size? I understand the confusion and will change the name of the variable, but for some background information: the size only becomes available when the first frame (i.e. IDAT) chunk is parsed by libpng, because at that point it will call the headerAvailable callback. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:79: struct FrameInfo { On 2016/10/11 20:13:10, scroggo_chromium wrote: > I find this confusing. I guess this is just the publicly interesting members of > PNGImageReader::FrameInfo? Yes. I can rephrase it so that becomes clearer. I prefer this instead of using the FrameInfo struct of PNGImageReader, since, like you said, I don't need to add the extra overhead here for the non-interesting bits. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:189: RefPtr<SharedBuffer> tempData = SharedBuffer::create(data->data(), length); On 2016/10/11 20:13:10, scroggo_chromium wrote: > We have some infrastructure to detect if your test takes longer than 50ms. If > so, it will file a bug and assign it to you. But you should go ahead and time > this (gtest will print out the time for each test). If it's too long, > crrev.com/1405053005/ shows how we shortened similar tests in the past. Yep, my test runs in 800ms. I'll first work on a smaller image with fewer frames, and see if that solves it for now. As far as I can see there is no difference with testing a 3px by 3px image with 3 frames. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:196: EXPECT_EQ(framesParsed + 1, decoder->frameCount()); On 2016/10/11 20:13:10, scroggo_chromium wrote: > Why don't they match exactly? When the code is at an offset where a new frame should appear, framesParsed has not been incremented yet. That happens two lines later. This makes it easy to access the frame info since the index is framecount minus 1. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:223: EXPECT_EQ(expectedFrameCount, decoder->frameCount()); On 2016/10/11 20:13:10, scroggo_chromium wrote: > nit: I would prefer to hardcode these numbers e.g. > > EXPECT_EQ(1, decoder->frameCount()); > > I don't feel terribly strongly, though, and perhaps you have an argument for > doing it this way. I prefer, but also not very strongly, this output: Value of: decoder->frameCount() Actual: 1 Expected: expectedFrameCount Which is: 10 to Value of: decoder->frameCount() Actual: 1 Expected: 10u Which is: 10 https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:136: * Decode the frame at `index` On 2016/10/11 20:13:11, scroggo_chromium wrote: > Lots of places in the code would refer to this as |index|, although I have not > found that in the style guide, and I think someone has recommended against it in > a code review... The |var| notation confuses me because my mind interprets it as the length of something. But I'll modify it for consistency. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:151: * - |m_readBuffer| = 26 >= length On 2016/10/11 20:13:10, scroggo_chromium wrote: > Does m_readBuffer need to be a member variable? > > I think this would be easier to follow if you just put a char buffer on the > stack in the method that calls this, and pass it as a parameter. You can also > set the size to a constant and compare: > > constexpr size_t kBufferSize = 26; > > static png_byte* readAsPngBytep(..., length, buffer) > { > ASSERT(length <= kBufferSize) > return ... > } > > (It doesn't guarantee someone won't call your method with a smaller buffer, but > it should be more obvious not to do that.) > > Then at the call site: > > { > ... > char buffer[kBufferSize]; > ... > var = readAsPngBytep(..., length, buffer); > ... > } > > Now that we don't use m_readBuffer, this can be a static function in this file > instead of a member function. The reason I created this function is so I don't need to do two casts every time I want to get data processed by libpng, since libpng requires non-const png_byte* as the data stream. Now that I'm thinking about this, I would say that it is better to wait with the const_cast until the png_process_data call. With that in mind, it may not be beneficial to have a separate function for this at all. The advantage of my approach is that you need less code to read the data, and can make a very natural call to readAsPngBytep, by providing just the reader, offset and length. But I agree that it is more prone to misuse and pollutes the class a little. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:160: return const_cast<png_byte*>(reinterpret_cast<const png_byte*>( On 2016/10/11 20:13:11, scroggo_chromium wrote: > Why the const_cast? I suppose the methods you'll call do not specify const? I > hope nothing is modifying the returned bytes! See comments above. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:224: return false; On 2016/10/11 20:13:11, scroggo_chromium wrote: > If you return false here, and then receive more data, does this work the way you > expect it to? It looks to me like the next time you try to parse you will add an > extra frameInfo to m_frameInfo. > > First time in the loop: > > - m_newFrame.readOffset == 0 && isFrameData > => m_newFrame.readOffset is set to m_readOffset > - isFCTL or isIEND > && m_newFrame.readOffset != 0 (based on the step above) > => append(m_newFrame) > => m_newFrame.readOffset is set to 0 > > Next time in the loop looks to be the same (we haven't changed m_readOffset, so > we're reading the same data, and m_newFrame.readOffset is back to zero), so we > add another frame to m_frameInfo. Afaik it should be fine since isFrameData and (isFCTL || isIEND) are mutually exclusive, so both will never be called in the same loop. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:249: png_process_data(m_png, m_info, On 2016/10/11 20:13:10, scroggo_chromium wrote: > Do you need to call setjmp in this method in case there is an error? Or is the > intent that a longjmp will unwind back to the caller? Unwinding to the caller is sufficient, for now. I'll revisit this in further iterations. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:279: if (reader.size() < m_readOffset + 8) On 2016/10/11 20:13:10, scroggo_chromium wrote: > I'm assuming m_readOffset will be zero at this point? I think other > ImageDecoders use this as a signal that m_parsedSignature is false, so you could > drop the extra boolean. I (wrongly?) assumed that PNGImageReader was constructed with a readOffset value so it could pass in byte streams where the PNG image was not starting at byte 0 (for whatever reason). If that's not the case, then yes, checking if the m_readOffset is zero would suffice.
On 2016/10/12 20:49:47, joostouwerling wrote: > > > An open question is what we want to do with the IDAT when it is *not* part > of > > > the animation. Do we want to ignore it and just start decoding the first > frame, > > > or could it be used for enhancing the user experience by e.g. showing it > while > > > the other frames are loading? > > > What do other browsers do? > > Firefox ignores it. The other browser are a little hard to analyse since I can't > see their code. Since Firefox is the initiator of the APNG spec, I'd say it is > wise to follow their example. Agreed. Also, now that I think about it some more, I think if the IDAT is not part of the animation, the intent is to show something different when APNG is not supported. So if we showed it while the rest of the image was loading, that would be confusing. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:89: m_reader->decode(*m_data, index); On 2016/10/12 20:49:46, joostouwerling wrote: > On 2016/10/11 20:13:09, scroggo_chromium wrote: > > Are we guaranteed to have an m_reader here? > > m_reader is not deleted by the decoder anymore. > > The prerequisite for this method is that decodeFrameCount() is called, since > that method will create m_reader and populates the frame info. I assume that all > clients use the ImageDecoders in this way. I believe that is correct, but it is fragile. Note that the GIF override of this method [1] calls parse, in case the client did not call decodeFrameCount. parse should be fairly cheap if we have already parsed. [1] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... > I can add a check if the requested > index is within the number of frames we found, but the result won't change so > much with it. That doesn't seem necessary. I presume m_reader->decode() will do that check. > > Note that most work on this method is done in the next iteration, where frames > are decoded. https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:223: EXPECT_EQ(expectedFrameCount, decoder->frameCount()); On 2016/10/12 20:49:46, joostouwerling wrote: > On 2016/10/11 20:13:10, scroggo_chromium wrote: > > nit: I would prefer to hardcode these numbers e.g. > > > > EXPECT_EQ(1, decoder->frameCount()); > > > > I don't feel terribly strongly, though, and perhaps you have an argument for > > doing it this way. > > I prefer, but also not very strongly, this output: > > Value of: decoder->frameCount() > Actual: 1 > Expected: expectedFrameCount > Which is: 10 > > to > > Value of: decoder->frameCount() > Actual: 1 > Expected: 10u > Which is: 10 Wow, haha, I have not paid that much attention to the output. I agree that the former is much more interesting! https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:136: * Decode the frame at `index` On 2016/10/12 20:49:47, joostouwerling wrote: > On 2016/10/11 20:13:11, scroggo_chromium wrote: > > Lots of places in the code would refer to this as |index|, although I have not > > found that in the style guide, and I think someone has recommended against it > in > > a code review... > > The |var| notation confuses me because my mind interprets it as the length of > something. But I'll modify it for consistency. Yay, math! https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:224: return false; On 2016/10/12 20:49:47, joostouwerling wrote: > On 2016/10/11 20:13:11, scroggo_chromium wrote: > > If you return false here, and then receive more data, does this work the way > you > > expect it to? It looks to me like the next time you try to parse you will add > an > > extra frameInfo to m_frameInfo. > > > > First time in the loop: > > > > - m_newFrame.readOffset == 0 && isFrameData > > => m_newFrame.readOffset is set to m_readOffset > > - isFCTL or isIEND > > && m_newFrame.readOffset != 0 (based on the step above) > > => append(m_newFrame) > > => m_newFrame.readOffset is set to 0 > > > > Next time in the loop looks to be the same (we haven't changed m_readOffset, > so > > we're reading the same data, and m_newFrame.readOffset is back to zero), so we > > add another frame to m_frameInfo. > > Afaik it should be fine since isFrameData and (isFCTL || isIEND) are mutually > exclusive, so both will never be called in the same loop. Ah yes, those are mutually exclusive, since |chunk| cannot say both e.g. "IDAT" and "IEND". So we don't actually need to check for fcTL or IEND if isFrameData was true. What do you think about rearranging the code to something like: if (isFrameData) { .... } else { bool isFCTL = ... .... } https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:279: if (reader.size() < m_readOffset + 8) On 2016/10/12 20:49:46, joostouwerling wrote: > On 2016/10/11 20:13:10, scroggo_chromium wrote: > > I'm assuming m_readOffset will be zero at this point? I think other > > ImageDecoders use this as a signal that m_parsedSignature is false, so you > could > > drop the extra boolean. > > I (wrongly?) assumed that PNGImageReader was constructed with a readOffset value > so it could pass in byte streams where the PNG image was not starting at byte 0 > (for whatever reason). If that's not the case, then yes, checking if the > m_readOffset is zero would suffice. Oops - no, you are correct. In particular, I think the offset is used when the PNG is inside an ICO.
https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:78: return m_frameCount; On 2016/10/12 20:49:46, joostouwerling wrote: > On 2016/10/11 20:13:09, scroggo_chromium wrote: > > If we failed, does this get set to m_frame_bufferCache.size()? If not, I think > > this is incorrect. But if so, you can simplify the later return statement to > > just say "return m_frameCount". > > > > In fact, this could perhaps be > > > > if (!m_metaDataDecoded) { > > parse(...); > > } > > > > return m_frameCount; > > > > You can override setFailed() to update m_frameCount, and that might be the > > easiest way to make sure this happens. > > > > (It would be good to add a test that verifies this is correct.) > > I'm not sure this answers completely what you're getting at, but: if > m_metaDataDecoded is set to true, it means m_reader was able to parse the frame > count up to the IEND chunk and has returned true. Since it has returned true, > the parse() method of the decoder will update m_frameCount to the number of > frames the reader has found. > > But your proposal looks cleaner. I'll see if I can nicely implement it. Now that I've been thinking about his, I don't think there is a need for changing |m_frameCount| when decoding fails, at all. When the first query for the frame count succeeds (up to a certain point in the stream), it will set |m_frameCount| to the number of frames it has seen so far, at the end of parse(PNGParseQuery). This propagates back to ImageDecoder::frameCount() where it will resize |m_frameBufferCache|. Now, if we change the following: -------------- if (query == PNGParseQuery::PNGMetaDataQuery) m_frameCount = m_reader->frameCount(); ===> if (!failed() && query == PNGParseQuery::PNGMetaDataQuery) m_frameCount = m_reader->frameCount(); -------------- When the decoding fails on the next decodeFrameCount call, or through any other call for that matter, |m_frameCount| won't be changed and will still be equal to |m_frameBufferCache|.size(), which is the desired behavior, if I'm correct. That is, what is the desired behavior when, on the first decodeFrameCount call, we've parsed the metadata of the first three frames, but it fails on the fourth frame. Do we want to report a complete failure or do we want to report the image having three frames?
On 2016/10/13 18:50:22, joostouwerling wrote: > https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... > File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp > (right): > > https://codereview.chromium.org/2386453003/diff/40001/third_party/WebKit/Sour... > third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:78: > return m_frameCount; > On 2016/10/12 20:49:46, joostouwerling wrote: > > On 2016/10/11 20:13:09, scroggo_chromium wrote: > > > If we failed, does this get set to m_frame_bufferCache.size()? If not, I > think > > > this is incorrect. But if so, you can simplify the later return statement to > > > just say "return m_frameCount". > > > > > > In fact, this could perhaps be > > > > > > if (!m_metaDataDecoded) { > > > parse(...); > > > } > > > > > > return m_frameCount; > > > > > > You can override setFailed() to update m_frameCount, and that might be the > > > easiest way to make sure this happens. > > > > > > (It would be good to add a test that verifies this is correct.) > > > > I'm not sure this answers completely what you're getting at, but: if > > m_metaDataDecoded is set to true, it means m_reader was able to parse the > frame > > count up to the IEND chunk and has returned true. Since it has returned true, > > the parse() method of the decoder will update m_frameCount to the number of > > frames the reader has found. > > > > But your proposal looks cleaner. I'll see if I can nicely implement it. > > Now that I've been thinking about his, I don't think there is a need for > changing |m_frameCount| when decoding fails, at all. When the first query for > the frame count succeeds (up to a certain point in the stream), it will set > |m_frameCount| to the number of frames it has seen so far, at the end of > parse(PNGParseQuery). This propagates back to ImageDecoder::frameCount() where > it will resize |m_frameBufferCache|. > > Now, if we change the following: > > -------------- > if (query == PNGParseQuery::PNGMetaDataQuery) > m_frameCount = m_reader->frameCount(); > > ===> > > if (!failed() && query == PNGParseQuery::PNGMetaDataQuery) > m_frameCount = m_reader->frameCount(); > -------------- > > When the decoding fails on the next decodeFrameCount call, or through any other > call for that matter, |m_frameCount| won't be changed and will still be equal to > |m_frameBufferCache|.size(), which is the desired behavior, if I'm correct. > > That is, what is the desired behavior when, on the first decodeFrameCount call, > we've parsed the metadata of the first three frames, but it fails on the fourth > frame. Do we want to report a complete failure or do we want to report the image > having three frames? I'd say we should report having three frames. (FWIW, we currently turn certain types of failures into complete failure in GIF, but we shouldn't.)
Patchset #4 (id:60001) has been deleted
Description was changed from ========== Initial start with APNGs. Includes tests for frame count, decodeFrameCount implementation and a query system between PNGImageReader and PNGImageDecoder. My initial start with the implementation of animated PNG files. I started with the two tests found in PNGImageDecoderTest, which test three images for their frame count. Copyright for these images can be found at the end of this description. I extracted PNGImageReader into it's own file (for convenience, and it got some more body as well). The old decode method is changed to a parse method, which takes a data stream and a PNGImageDecoder::PNGParseQuery. This query can either be PNGFrameCountQuery, PNGSizeQuery or PNGFrameDataQuery. This method is called from PNGImageDecoder's parse method. The frame count is considered to be decoded if: - an acTL chunk is found, which will give the number of frames - actual frame data is found before any acTL chunk. This basically sets the image to not-animated. For now, it does not correct if the actual number of frames in the stream is different (todo?) The animation chunks are extracted using libpng's png_set_keep_unknown_chunks, parsed in PNGImageReader::readChunk. Currently only the acTL chunk is considered and this feeds PNGImageDecoder the number of frames via PNGImageReader::readAnimationControl. PNGImageDecoder got some extra methods and members to determine whether or not the frame count has been decoded, namely - PNGImageDecoder::isDecodedFrameCountAvailable() - PNGImageDecoder::animationControlAvailable(numFrames, numReps) - parse(PNGParseQuery) - decodeFrameCount() - m_frameCount - m_frameCountDecoded Furthermore, decode() and decodeSize() now use the parse method. decode(bool) has been replaced by parse(PNGParseQuery). Image copyright: - png-animated-idat-not-part-of-animation.png: created by myself - png-animated-idat-part-of-animation.png: found at Wikipedia, free to use, see https://commons.wikimedia.org/wiki/File:Animated_PNG_example_bouncing_beach_b... - png-simple.png: Google's logo from the homepage BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Support size and frame meta data decoding. Include tests for meta data decoding. Extract PNGImageReader into it's own file, for convenience, and it got some more body as well. Change the old decode method to a parse method, which takes a data stream and a PNGImageDecoder::PNGParseQuery. This query can either be PNGSizeQuery or PNGFrameMetaDataQuery. This method is called from PNGImageDecoder's parse method. Extract the animation chunks using libpng's png_set_keep_unknown_chunks, and parse them in PNGImageReader::readChunk. The acTL chunk is only considered for the repetition count. For the number of frames, the actual number of frames in the stream is used. For each fcTL chunk, the frame information is extracted and stored in an array. A frame is registered when the next frame start. In that way, we know for sure that only complete frames are reported. TODO: frame decoding BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
Description was changed from ========== Implement Animated PNG. Support size and frame meta data decoding. Include tests for meta data decoding. Extract PNGImageReader into it's own file, for convenience, and it got some more body as well. Change the old decode method to a parse method, which takes a data stream and a PNGImageDecoder::PNGParseQuery. This query can either be PNGSizeQuery or PNGFrameMetaDataQuery. This method is called from PNGImageDecoder's parse method. Extract the animation chunks using libpng's png_set_keep_unknown_chunks, and parse them in PNGImageReader::readChunk. The acTL chunk is only considered for the repetition count. For the number of frames, the actual number of frames in the stream is used. For each fcTL chunk, the frame information is extracted and stored in an array. A frame is registered when the next frame start. In that way, we know for sure that only complete frames are reported. TODO: frame decoding BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Support size and frame meta data decoding. Include tests for meta data decoding. Extract PNGImageReader into it's own file, for convenience, and it got some more body as well. Change the old decode method to a parse method, which takes a data stream and a PNGImageDecoder::PNGParseQuery. This query can either be PNGSizeQuery or PNGFrameMetaDataQuery. This method is called from PNGImageDecoder's parse method. Extract the animation chunks using libpng's png_set_keep_unknown_chunks, and parse them in PNGImageReader::readChunk. The acTL chunk is only considered for the repetition count. For the number of frames, the actual number of frames in the stream is used. For each fcTL chunk, the frame information is extracted and stored in an array. A frame is registered when the next frame start. In that way, we know for sure that only complete frames are reported. TODO: frame decoding BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
PTAL I've processed @scroggo's feedback, and improved the test suite by: a) adding two tests for erroneous and incomplete streams b) using a smaller image for animation tests, so ByteByByteMetaData runs in ~3ms instead of ~840 c) using an imager for animation tests with a wider variety of frame properties.
https://codereview.chromium.org/2386453003/diff/100001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/100001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:45: nit: extra line? https://codereview.chromium.org/2386453003/diff/100001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:110: m_repetitionCount = (repetitionCount == 0) ? cAnimationLoopInfinite Is this part of the spec, 0 means repeat forever instead of once? If so, is this the correct place to have format-specific behavior? Should that be in PNGReader? https://codereview.chromium.org/2386453003/diff/100001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h (right): https://codereview.chromium.org/2386453003/diff/100001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:58: void setMetaDataDecoded() { m_metaDataDecoded = true; }; These two methods are public because they get called by PNGImageReader I am guessing? I assume the client is not calling these. If that is the case, maybe we can make them private and make PNGImageReader a friend?
https://codereview.chromium.org/2386453003/diff/100001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h (right): https://codereview.chromium.org/2386453003/diff/100001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:58: void setMetaDataDecoded() { m_metaDataDecoded = true; }; On 2016/10/15 00:30:50, cblume wrote: > These two methods are public because they get called by PNGImageReader I am > guessing? > > I assume the client is not calling these. > > If that is the case, maybe we can make them private and make PNGImageReader a > friend? A similar suggestion came up crrev.com/2045293002 (specifically here [1]). My preference would be to leave these as public, for the following reasons: 1. Most real clients of this class don't know they have a PNGImageDecoder - they just know it's an ImageDecoder. (I suppose they could call the public constructor, or cast an ImageDecoder to this class, but I think that already goes against the spirit of ImageDecoder. An exception is ICOImageDecoder, which creates a PNGImageDecoder directly.) 2. Making the PNGImageReader a friend allows it to access *all* the internals of PNGImageDecoder. Weighed against 1, this seems more dangerous to me. FWIW, Google's C++ style guide [2] suggests that "friend" classes should be in the same file. Otherwise, I don't think it pushes us strongly in one direction or another. [1] https://codereview.chromium.org/2045293002/diff/260001/src/codec/SkGifCodec.h... [2] https://engdoc.corp.google.com/eng/doc/devguide/cpp/styleguide.shtml?cl=head#...
https://codereview.chromium.org/2386453003/diff/100001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/100001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:194: // discovered. The fourth frame should *not* be registered since the reader This is the natural extension of what we discussed earlier - we want to make sure a frame is complete (i.e. the next frame/chunk is present) before we report it, so the client doesn't try to draw an incomplete frame. But maybe there should be an exception for the case where all data was received, and we should allow the client to try to decode this frame and decide for themselves whether to display it if it's partial? Not sure what the right decision is here, but it is good that this is documented here.
https://codereview.chromium.org/2386453003/diff/100001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h (right): https://codereview.chromium.org/2386453003/diff/100001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:58: void setMetaDataDecoded() { m_metaDataDecoded = true; }; On 2016/10/17 13:24:00, scroggo_chromium wrote: > On 2016/10/15 00:30:50, cblume wrote: > > These two methods are public because they get called by PNGImageReader I am > > guessing? > > > > I assume the client is not calling these. > > > > If that is the case, maybe we can make them private and make PNGImageReader a > > friend? > > A similar suggestion came up crrev.com/2045293002 (specifically here [1]). My > preference would be to leave these as public, for the following reasons: > > 1. Most real clients of this class don't know they have a PNGImageDecoder - they > just know it's an ImageDecoder. (I suppose they could call the public > constructor, or cast an ImageDecoder to this class, but I think that already > goes against the spirit of ImageDecoder. An exception is ICOImageDecoder, which > creates a PNGImageDecoder directly.) > > 2. Making the PNGImageReader a friend allows it to access *all* the internals of > PNGImageDecoder. Weighed against 1, this seems more dangerous to me. > > FWIW, Google's C++ style guide [2] suggests that "friend" classes should be in > the same file. Otherwise, I don't think it pushes us strongly in one direction > or another. > > [1] > https://codereview.chromium.org/2045293002/diff/260001/src/codec/SkGifCodec.h... > [2] > https://engdoc.corp.google.com/eng/doc/devguide/cpp/styleguide.shtml?cl=head#... Okay, I agree. I definitely agree that I dislike granting access to *all* private things. And I like what you are saying about it not being part of the ImageDecoder interface. Works for me. :)
Patchset #6 (id:120001) has been deleted
Description was changed from ========== Implement Animated PNG. Support size and frame meta data decoding. Include tests for meta data decoding. Extract PNGImageReader into it's own file, for convenience, and it got some more body as well. Change the old decode method to a parse method, which takes a data stream and a PNGImageDecoder::PNGParseQuery. This query can either be PNGSizeQuery or PNGFrameMetaDataQuery. This method is called from PNGImageDecoder's parse method. Extract the animation chunks using libpng's png_set_keep_unknown_chunks, and parse them in PNGImageReader::readChunk. The acTL chunk is only considered for the repetition count. For the number of frames, the actual number of frames in the stream is used. For each fcTL chunk, the frame information is extracted and stored in an array. A frame is registered when the next frame start. In that way, we know for sure that only complete frames are reported. TODO: frame decoding BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. TODO: - progressive frame decoding for animated images - alpha blending - disposal operations BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
Description was changed from ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. TODO: - progressive frame decoding for animated images - alpha blending - disposal operations BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. TODO: - progressive frame decoding for animated images - alpha blending - disposal operations BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
Description was changed from ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. TODO: - progressive frame decoding for animated images - alpha blending - disposal operations BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. TODO: - progressive frame decoding for animated images - alpha blending - disposal operations BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
Description was changed from ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. TODO: - progressive frame decoding for animated images - alpha blending - disposal operations BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. TODO: - progressive frame decoding for animated images - alpha blending - disposal operations BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
Description was changed from ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. TODO: - progressive frame decoding for animated images - alpha blending - disposal operations BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. TODO: - progressive frame decoding for animated images - alpha blending - disposal operations BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
PTAL. This patch implements frame decoding and tests. Does not yet support alpha blending and disposal operations, but when you build Chromium with this patch it will show animation :) https://codereview.chromium.org/2386453003/diff/100001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/100001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:110: m_repetitionCount = (repetitionCount == 0) ? cAnimationLoopInfinite On 2016/10/15 00:30:50, cblume wrote: > Is this part of the spec, 0 means repeat forever instead of once? > > If so, is this the correct place to have format-specific behavior? Should that > be in PNGReader? Yes, that is part of the spec. I've reasoned here that the decoder should interpret the "raw" data the reader providers. One could say that mapping the AlpaBlend byte to an ImageFrame::AlphaBlendSource value is similar, and the same for DisposalOp, so I moved them to the decoder as well. I don't have a strong preference for doing this in the decoder or in the reader, though.
https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:68: , m_decodedFrameCount(false) This variable is never read. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:117: // should be changed to stick with m_repititionCount to match other browsers. nit: m_repetitionCount https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:128: inline ImageFrame::DisposalMethod getDisposalMethod(uint8_t disposalMethod) nit: These inline methods should also be static (or in a namespace). https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:131: case 0: I *think* this should line up with switch - pretty sure I've seen that elsewhere in Blink, but I didn't see anything in the style guide. I'd say look for switch statements in the surrounding code and match that. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:136: return ImageFrame::DisposalMethod::DisposeOverwritePrevious; There should be a default case here. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:138: return ImageFrame::DisposalMethod::DisposeNotSpecified; Personally, I find it odd that we distinguish between DisposeNotSpecified and DisposeKeep, since we appear to treat them the same. But this is how we do it in GIF as well. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:302: // For PNGs, the frame always fills the entire image. I assume this statement is no longer true? https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:303: buffer.zeroFillPixelData(); I assume we used to do this elsewhere? Can we remove a different call? https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:305: const IntRect& rect = buffer.originalFrameRect(); I suggest calling this frameRect. That makes a little clearer, several lines below, when you refer to it again. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:324: int y = rowIndex + rect.y(); Has the frameRect already been intersected with the image size? i.e. can frameRect.y() be negative? https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:365: ImageFrame::PixelData* address = buffer.getAddr(rect.x(), y); Again, I could imagine that the frameRect has not been intersected with the size, so x() could be negative. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:371: for (int x = rect.x(); x < rect.maxX(); ++x, pixel += 4) { Similarly, we need to make sure we do not go beyond the width of the image. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:395: if (failed()) Maybe add a TODO here, regarding being able to show complete frames even if a later frame fails. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:400: return ImageDecoder::frameIsCompleteAtIndex(index); This is probably the right thing to do if it's non-animated, but for an animated image, I think you want to know whether we've received all the data for this frame - not whether we've fully decoded it. (I'm on the record for disliking this method - it means different things depending on whether it is an animated image versus a non-animated image. Sort of ... GIF treats it the same for animated versus non-animated, but WEBP treats them differently. ICO also uses what I think of as the animated version (is all data received)). https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:118: // It assumes the missing bytes should return in a failed decoding. I think you mean "result" instead of "return"? (Also, I think "a failed decode" sounds better than "decoding" here.) https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:139: void testInvalidFctlSize(const char *pngFile, size_t offsetFctl, * should go next to the type (although I think Matt said the auto-formatter corrects it to be next to the name :( https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:157: invalidData->append(const_cast<const char*>( I don't think you should need a const cast to cast from non-const to const. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:161: invalidData->append(data->data() + offsetFctl + 4, 34u); Where did 34u come from? Is fcTL 38 bytes? https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:185: diffActlData->append(data->data() + offsetActl + 12, I take it the acTL chunk is 16 bytes? It would be good to add comments explaining these. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:189: EXPECT_EQ(expectedFrameCount, decoder->frameCount()); Maybe assert that expectedFrameCount != injectedFrameCount? If they are the same, this test is not very informative. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:200: std::unique_ptr<ImageDecoder> decoder; nit: extra space https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:248: match = false; Alternatively, could this just be something like for (...) ASSERT_EQ(truncatedHashes[i], progressiveHashes[i]); https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:411: // ... and then decode frames from |reallocatedData|. What is interesting about this test? (Will PNGImageDecoder even do anything here? Or will it still have the frames cached?) https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:145: // should have the size of (at least) kBufferSize. should -> must https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:146: constexpr size_t kBufferSize = 33; nit: I think this should be static. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:188: const png_byte* chunk = readAsConstPngBytep(reader, offset, 8, readBuffer); Doesn't your comments up above tell us that readBuffer's size must equal kBufferSize? https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:192: png_byte chunkIDAT[] = {0, 0, 0, 0, 'I', 'D', 'A', 'T'}; Can these various chunks be constexpr? https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:193: png_save_uint_32(chunkIDAT, length - 4); Why do we need to subtract 4 here? https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:197: // include the CRC at the end of the chunk as well. Why? Sorry, I do not follow. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:216: // create new ones. CRC errors are ignored, so fdAT chunks can be processed Is there a risk to ignoring CRC errors? https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:217: // as IDAT's without recalculating the CRC value. nit: No need for the ' here. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:218: png_destroy_read_struct(m_png ? &m_png : 0, m_info ? &m_info : 0, 0); A couple of comments: - Will we ever have m_info but no m_png? I think we should be able to say something like: if (m_png) png_destroy_read_struct(&m_png, m_info ? &m_info : nullptr); - I assume we do *not* want to do this if we've already started decoding frame |index| and are now continuing it. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:233: IntRect& frameRect = m_frameInfo[index].frameRect; I think this can be const https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:242: // This step may be omitted if width and height are equal to the image size. Instead of "may be", I'd say that it is omitted - perhaps with a reference to the block above. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:247: constexpr size_t headerSize = 33; nit: kHeaderSize https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:251: memcpy(header, chunk, headerSize); Alternatively, you could always use readBuffer: if (reinterpret_cast<char*>(chunk) != readBuffer) memcpy(readBuffer, chunk, kBufferSize); We should probably go with whatever is clearer https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:252: png_save_uint_32(header + 16, frameRect.width()); Here I'm guessing you do *not* want the clipped width and height. We should have tests for these cases. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:264: png_process_data(m_png, m_info, IEND, 12); We need to be careful about our setjmps. This is called by decode, which calls setjmp, but in between, decode calls startFrameDecoding, which also calls setjmp. So if this longjmps, I think it will jump back to a function which has already exited, which would be bad. Is it possible this call will longjmp? https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:350: longjmp(JMPBUF(m_png), 1); Why did you longjmp instead of returning setFailed()? https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:482: longjmp(JMPBUF(m_png), 1); Again, why did you longjmp instead of calling setFailed()? https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h (right): https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:97: size_t m_initialOffset; I'm assuming this will be unchanged. Can you mark it const? https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:99: size_t m_bytesInfo; Can you add a comment explaining what this means? https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:102: // IDAT chunk(s). In that case, the IDAT data is the first frame. Otherwise, the IDAT is ignored. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:115: void endFrameDecoding(); nit: I'd put this after startFrameDecoding, since it logically happens afterwards.
I processed @scroggo's feedback in a new patch. Some comments are still open for debate. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:131: case 0: On 2016/10/25 14:59:06, scroggo_chromium wrote: > I *think* this should line up with switch - pretty sure I've seen that elsewhere > in Blink, but I didn't see anything in the style guide. I'd say look for switch > statements in the surrounding code and match that. ImageDecoder.cpp and GIFImageDecoder.cpp both indent case statements. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:303: buffer.zeroFillPixelData(); On 2016/10/25 14:59:06, scroggo_chromium wrote: > I assume we used to do this elsewhere? Can we remove a different call? This call will be superfluous when blending and disposing is applied. For now, I used it as an insurance that I was working with a fully transparent black frame. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:324: int y = rowIndex + rect.y(); On 2016/10/25 14:59:06, scroggo_chromium wrote: > Has the frameRect already been intersected with the image size? i.e. can > frameRect.y() be negative? Yes, intersection happens in initializeNewFrame(), which is called when the frame count changes. This also answers the comments below. In this case, the row index that is provided to the PNG callback in PNGImageReader is an png_uint_32, so |y| can never be negative. However, the int-type is the best option here since the comparison to size().height(), which returns an int, does not compile with a size_t. The same holds for the |x|-values in the loops below. Another option would be to cast the size().height() to a size_t but I don't think that's better. The check for |y| < 0 could be to detect overflow but I'm not sure if that is a realistic scenario. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:400: return ImageDecoder::frameIsCompleteAtIndex(index); On 2016/10/25 14:59:05, scroggo_chromium wrote: > This is probably the right thing to do if it's non-animated, but for an animated > image, I think you want to know whether we've received all the data for this > frame - not whether we've fully decoded it. > > (I'm on the record for disliking this method - it means different things > depending on whether it is an animated image versus a non-animated image. Sort > of ... GIF treats it the same for animated versus non-animated, but WEBP treats > them differently. ICO also uses what I think of as the animated version (is all > data received)). If GIF is able to get away (that is, since a decoded frame is a step further down the road than having received all data) with returning true when it has just received all data, for non-animated GIF's, does that not imply that is all the client needs to know? As far as I can see in the call hierarchy of frameIsCompleteAtIndex() it never depends on actually having decoded the frame data. But I may be wrong in this since the call hierarchy is pretty deep and it is used in several local variables, so please correct me if I'm wrong. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:161: invalidData->append(data->data() + offsetFctl + 4, 34u); On 2016/10/25 14:59:06, scroggo_chromium wrote: > Where did 34u come from? Is fcTL 38 bytes? Yes, 26 bytes of data and 12 for length, tag and crc. I'll make the comment more explicit on this, as well as for the other numbers in the tests. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:411: // ... and then decode frames from |reallocatedData|. On 2016/10/25 14:59:06, scroggo_chromium wrote: > What is interesting about this test? > > (Will PNGImageDecoder even do anything here? Or will it still have the frames > cached?) It should have the frame meta data cached, not the actual frames. It tests whether decoding still works when supplying data from a different location in memory, since it does not explicitly parses the stream again. This is a test that I found in the WebP test suite and I *think* it emulates what's happening with the DeferredImageDecoder's. My reasoning was here, that if this is a reasonable scenario for webp, it would also be realistic for apng. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:188: const png_byte* chunk = readAsConstPngBytep(reader, offset, 8, readBuffer); On 2016/10/25 14:59:07, scroggo_chromium wrote: > Doesn't your comments up above tell us that readBuffer's size must equal > kBufferSize? I modified the comment to make this more clear. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:192: png_byte chunkIDAT[] = {0, 0, 0, 0, 'I', 'D', 'A', 'T'}; On 2016/10/25 14:59:07, scroggo_chromium wrote: > Can these various chunks be constexpr? This one should semantically not be constexpr, since the next line writes to it. For the IEND chunk later on, it would mean that it needs to be cast to a non-const array on the next line, since libpng does not accept const png bytes, which seems like a waste of cycles to me. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:197: // include the CRC at the end of the chunk as well. On 2016/10/25 14:59:07, scroggo_chromium wrote: > Why? Sorry, I do not follow. An fdAT chunk is build up like: |length| (4B) -> tag (4B) -> sequence number (4B) -> frame data (|length| - 4B) -> CRC (4B) So the actual frame data plus the CRC data is |length| bytes long. This is also the reason |length| - 4 is written as the new length to the IDAT chunk byte array. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:216: // create new ones. CRC errors are ignored, so fdAT chunks can be processed On 2016/10/25 14:59:07, scroggo_chromium wrote: > Is there a risk to ignoring CRC errors? They are meant as a checksum to see if the data received corresponds to what was intended to be sent. At this point, it does not make sense to check for CRC errors, since they would have been calculated on data we already received over the network, since we just feed reformatted fdAT data at this point. That being said, we can check if the fdAT data we received is correct, either during parsing or during decoding. It will add some processing time, since the code now jumps |length| bytes ahead in the stream to the next frame, but it makes sure that not data is missed or corrupted by the network. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:218: png_destroy_read_struct(m_png ? &m_png : 0, m_info ? &m_info : 0, 0); On 2016/10/25 14:59:07, scroggo_chromium wrote: > A couple of comments: > - Will we ever have m_info but no m_png? I think we should be able to say > something like: > > if (m_png) > png_destroy_read_struct(&m_png, m_info ? &m_info > : nullptr); > - I assume we do *not* want to do this if we've already started decoding frame > |index| and are now continuing it. Does that happen for frames with |index| > 0, since we only report them when all data is available? For |index| = 0 for animated images, I want to take a similar approach as for non animated pngs. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:251: memcpy(header, chunk, headerSize); On 2016/10/25 14:59:07, scroggo_chromium wrote: > Alternatively, you could always use readBuffer: > > if (reinterpret_cast<char*>(chunk) != readBuffer) > memcpy(readBuffer, chunk, kBufferSize); > > We should probably go with whatever is clearer Your option saves 33B of memory but I think it's more verbose, especially since it also needs another const_cast. I would probably go for the memory savings but I'm not sure what the general rule of wisdom is for Chromium. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:252: png_save_uint_32(header + 16, frameRect.width()); On 2016/10/25 14:59:07, scroggo_chromium wrote: > Here I'm guessing you do *not* want the clipped width and height. We should have > tests for these cases. The width and height that are written here are non-clipped. The clipping happens in PNGImageDecoder::initializeNewFrame() and is taken into account in PNGImageDecoder::rowAvailable(). I have tests that check whether an overflowing frame is clipped correctly: AnimatedPNGTests.frameRectIsClipped. Is that what you mean, or do I not understand what you're at? https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:264: png_process_data(m_png, m_info, IEND, 12); On 2016/10/25 14:59:07, scroggo_chromium wrote: > We need to be careful about our setjmps. This is called by decode, which calls > setjmp, but in between, decode calls startFrameDecoding, which also calls > setjmp. So if this longjmps, I think it will jump back to a function which has > already exited, which would be bad. Is it possible this call will longjmp? I improved this by setting the setjmp only in decode, and for both animated and non animated images. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:482: longjmp(JMPBUF(m_png), 1); On 2016/10/25 14:59:06, scroggo_chromium wrote: > Again, why did you longjmp instead of calling setFailed()? The previous comment can be handled with setFailed(), but not this one, since it is called via libpng. It needs to get back to parse() to return false to the decoder, and at the same time prevent unnecessary further parsing.
https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:324: int y = rowIndex + rect.y(); On 2016/10/26 15:44:16, joostouwerling wrote: > On 2016/10/25 14:59:06, scroggo_chromium wrote: > > Has the frameRect already been intersected with the image size? i.e. can > > frameRect.y() be negative? > > Yes, intersection happens in initializeNewFrame(), which is called when the > frame count changes. This also answers the comments below. In this case, the row > index that is provided to the PNG callback in PNGImageReader is an png_uint_32, > so |y| can never be negative. However, the int-type is the best option here > since the comparison to size().height(), which returns an int, does not compile > with a size_t. The same holds for the |x|-values in the loops below. Another > option would be to cast the size().height() to a size_t but I don't think that's > better. > > The check for |y| < 0 could be to detect overflow but I'm not sure if that is a > realistic scenario. Ok good. No need to check for y < 0. My concern was about rect.y() being negative. If it's not, this will never be negative. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:411: // ... and then decode frames from |reallocatedData|. On 2016/10/26 15:44:17, joostouwerling wrote: > On 2016/10/25 14:59:06, scroggo_chromium wrote: > > What is interesting about this test? > > > > (Will PNGImageDecoder even do anything here? Or will it still have the frames > > cached?) > > It should have the frame meta data cached, not the actual frames. It tests > whether decoding still works when supplying data from a different location in > memory, since it does not explicitly parses the stream again. This is a test > that I found in the WebP test suite and I *think* it emulates what's happening > with the DeferredImageDecoder's. My reasoning was here, that if this is a > reasonable scenario for webp, it would also be realistic for apng. Ok, that makes sense. You asked me a question in person about comments I made on code that was copied from somewhere else. (i.e. should we make changes in response to my comments in both places?) I think the real answer is we should consolidate the code. ImageDecoderTestHelpers has a bunch of methods that are called in multiple tests. If this is the same as in a WEBP test, we should put a method in that file to share. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:216: // create new ones. CRC errors are ignored, so fdAT chunks can be processed On 2016/10/26 15:44:17, joostouwerling wrote: > On 2016/10/25 14:59:07, scroggo_chromium wrote: > > Is there a risk to ignoring CRC errors? > > They are meant as a checksum to see if the data received corresponds to what was > intended to be sent. At this point, it does not make sense to check for CRC > errors, since they would have been calculated on data we already received over > the network, since we just feed reformatted fdAT data at this point. > > That being said, we can check if the fdAT data we received is correct, either > during parsing or during decoding. It will add some processing time, since the > code now jumps |length| bytes ahead in the stream to the next frame, but it > makes sure that not data is missed or corrupted by the network. The main concern I would have is that libpng would have thrown a fatal error due to a CRC mismatch prior to some other worse failure (i.e. libpng does not check for some kind of problem later because such a problem would have resulted in the fatal CRC error). https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:218: png_destroy_read_struct(m_png ? &m_png : 0, m_info ? &m_info : 0, 0); On 2016/10/26 15:44:17, joostouwerling wrote: > On 2016/10/25 14:59:07, scroggo_chromium wrote: > > A couple of comments: > > - Will we ever have m_info but no m_png? I think we should be able to say > > something like: > > > > if (m_png) > > png_destroy_read_struct(&m_png, m_info ? &m_info > > : nullptr); > > - I assume we do *not* want to do this if we've already started decoding frame > > |index| and are now continuing it. > > Does that happen for frames with |index| > 0, since we only report them when all > data is available? Good question. Just because all the data is available does not mean that the data represents a complete frame. So it's possible that we would call this a second time for an incomplete frame, even though there's nothing more to decode. > For |index| = 0 for animated images, I want to take a similar > approach as for non animated pngs. Which is to say that it could be incomplete the first time around, and we'll need to continue decoding the second time? sgtm https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:251: memcpy(header, chunk, headerSize); On 2016/10/26 15:44:17, joostouwerling wrote: > On 2016/10/25 14:59:07, scroggo_chromium wrote: > > Alternatively, you could always use readBuffer: > > > > if (reinterpret_cast<char*>(chunk) != readBuffer) > > memcpy(readBuffer, chunk, kBufferSize); > > > > We should probably go with whatever is clearer > > Your option saves 33B of memory but I think it's more verbose, especially since > it also needs another const_cast. I would probably go for the memory savings but > I'm not sure what the general rule of wisdom is for Chromium. I don't think (a constant, stack-allocated, briefly living) 33 bytes of memory should determine our decision. I was confused by the fact that we had two buffers, and I thought my way might be more clear, but I do not have a strong preference. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:252: png_save_uint_32(header + 16, frameRect.width()); On 2016/10/26 15:44:17, joostouwerling wrote: > On 2016/10/25 14:59:07, scroggo_chromium wrote: > > Here I'm guessing you do *not* want the clipped width and height. We should > have > > tests for these cases. > > The width and height that are written here are non-clipped. The clipping happens > in PNGImageDecoder::initializeNewFrame() and is taken into account in > PNGImageDecoder::rowAvailable(). Okay, I see that now. Maybe add a comment where you declare frameRect? > > I have tests that check whether an overflowing frame is clipped correctly: > AnimatedPNGTests.frameRectIsClipped. Is that what you mean, or do I not > understand what you're at? I just looked over that test. I don't think it's a realistic test, and I've commented on it there. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:264: png_process_data(m_png, m_info, IEND, 12); On 2016/10/26 15:44:17, joostouwerling wrote: > On 2016/10/25 14:59:07, scroggo_chromium wrote: > > We need to be careful about our setjmps. This is called by decode, which calls > > setjmp, but in between, decode calls startFrameDecoding, which also calls > > setjmp. So if this longjmps, I think it will jump back to a function which has > > already exited, which would be bad. Is it possible this call will longjmp? > > I improved this by setting the setjmp only in decode, and for both animated and > non animated images. sgtm https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:482: longjmp(JMPBUF(m_png), 1); On 2016/10/26 15:44:17, joostouwerling wrote: > On 2016/10/25 14:59:06, scroggo_chromium wrote: > > Again, why did you longjmp instead of calling setFailed()? > > The previous comment can be handled with setFailed(), but not this one, since it > is called via libpng. It needs to get back to parse() to return false to the > decoder, and at the same time prevent unnecessary further parsing. Ah yes, of course. https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:174: ASSERT(injectedFrameCount != expectedFrameCount); Sorry, I meant ASSERT_NE(injectedFrameCount, expectedFrameCount) ASSERT will crash (arguably that should be caught before checking it in) but ASSERT_NE is a part of gtest, which will report an error and exit the test early. https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:498: // Change the width and height of the frame so it falls outside the image. I don't think this is a realistic test. Although you've changed the encoded width and height, presumably the data still represents the original frame, which is a subset of the image dimensions. A more interesting test would be a PNG with a frame that is truly bigger than the image getting clipped to the image dimensions. https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h (right): https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:101: size_t m_bytesInfo; How about m_idatOffset? m_bytesInfo doesn't mean anything to me, even after reading the description. (Perhaps m_infoBytes is more clear, but I m_idatOffset fits in with the other variables named *Offset.
Patchset #8 (id:180001) has been deleted
Implemented progressive decoding for the first frame of animated images, and included tests. Solved more feedback points of scroggo@ and added two comments. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:216: // create new ones. CRC errors are ignored, so fdAT chunks can be processed On 2016/10/26 18:25:58, scroggo_chromium wrote: > On 2016/10/26 15:44:17, joostouwerling wrote: > > On 2016/10/25 14:59:07, scroggo_chromium wrote: > > > Is there a risk to ignoring CRC errors? > > > > They are meant as a checksum to see if the data received corresponds to what > was > > intended to be sent. At this point, it does not make sense to check for CRC > > errors, since they would have been calculated on data we already received over > > the network, since we just feed reformatted fdAT data at this point. > > > > That being said, we can check if the fdAT data we received is correct, either > > during parsing or during decoding. It will add some processing time, since the > > code now jumps |length| bytes ahead in the stream to the next frame, but it > > makes sure that not data is missed or corrupted by the network. > > The main concern I would have is that libpng would have thrown a fatal error due > to a CRC mismatch prior to some other worse failure (i.e. libpng does not check > for some kind of problem later because such a problem would have resulted in the > fatal CRC error). This is a good question and I don't know exactly what the dangers are. As far as my understanding goes, libpng gradually calculates the CRC while processing the data it is fed. At the end it checks if this matches to what the CRC value in the stream says. It would throw an error if they differ. I also think it is mainly used to determine if the network stream lost bytes - if someone has malicious intents they can also modify the CRC chunk. Missing bytes would result in a png process error, which only fails the decode. This code only ignores CRC errors for frame data though - all data before the IDAT chunk is verified. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:218: png_destroy_read_struct(m_png ? &m_png : 0, m_info ? &m_info : 0, 0); On 2016/10/26 18:25:58, scroggo_chromium wrote: > On 2016/10/26 15:44:17, joostouwerling wrote: > > On 2016/10/25 14:59:07, scroggo_chromium wrote: > > > A couple of comments: > > > - Will we ever have m_info but no m_png? I think we should be able to say > > > something like: > > > > > > if (m_png) > > > png_destroy_read_struct(&m_png, m_info ? &m_info > > > : nullptr); > > > - I assume we do *not* want to do this if we've already started decoding > frame > > > |index| and are now continuing it. > > > > Does that happen for frames with |index| > 0, since we only report them when > all > > data is available? > > Good question. Just because all the data is available does not mean that the > data represents a complete frame. So it's possible that we would call this a > second time for an incomplete frame, even though there's nothing more to decode. I don't think that this is the right place to take that into account. The reader only reports frames with |index| > 0 if it has seen all it's frame data. Thus, if the decoder asks the reader to decode a frame with |index| > 0, the reader will always signal the IEND chunk. If the decoder receives this and is unhappy with the data it got at that point, it also knows that requesting a decode again won't give different data.
Description was changed from ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. TODO: - progressive frame decoding for animated images - alpha blending - disposal operations BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. TODO: - alpha blending - disposal operations BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:218: png_destroy_read_struct(m_png ? &m_png : 0, m_info ? &m_info : 0, 0); On 2016/10/27 20:29:55, joostouwerling wrote: > On 2016/10/26 18:25:58, scroggo_chromium wrote: > > On 2016/10/26 15:44:17, joostouwerling wrote: > > > On 2016/10/25 14:59:07, scroggo_chromium wrote: > > > > A couple of comments: > > > > - Will we ever have m_info but no m_png? I think we should be able to say > > > > something like: > > > > > > > > if (m_png) > > > > png_destroy_read_struct(&m_png, m_info ? &m_info > > > > : nullptr); > > > > - I assume we do *not* want to do this if we've already started decoding > > frame > > > > |index| and are now continuing it. > > > > > > Does that happen for frames with |index| > 0, since we only report them when > > all > > > data is available? > > > > Good question. Just because all the data is available does not mean that the > > data represents a complete frame. So it's possible that we would call this a > > second time for an incomplete frame, even though there's nothing more to > decode. > > I don't think that this is the right place to take that into account. The reader > only reports frames with |index| > 0 if it has seen all it's frame data. Thus, > if the decoder asks the reader to decode a frame with |index| > 0, the reader > will always signal the IEND chunk. If the decoder receives this and is unhappy > with the data it got at that point, it also knows that requesting a decode again > won't give different data. Just to make sure I understand: because you've provided an IEND chunk, libpng's callback for end of image will be called, and our callback[1] sets the ImageFrame to FrameComplete[2], so decode will not be called again[3]. Is that right? [1] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... [2] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... [3] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:174: ASSERT(injectedFrameCount != expectedFrameCount); On 2016/10/26 18:25:58, scroggo_chromium wrote: > Sorry, I meant > > ASSERT_NE(injectedFrameCount, expectedFrameCount) > > ASSERT will crash (arguably that should be caught before checking it in) but > ASSERT_NE is a part of gtest, which will report an error and exit the test > early. (It is helpful if you respond to comments that recommend changes, even if it's just "Done" or "Ack". Otherwise I might think you missed something, especially if the fix is not right on that line.) https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:498: // Change the width and height of the frame so it falls outside the image. On 2016/10/26 18:25:58, scroggo_chromium wrote: > I don't think this is a realistic test. Although you've changed the encoded > width and height, presumably the data still represents the original frame, which > is a subset of the image dimensions. > > A more interesting test would be a PNG with a frame that is truly bigger than > the image getting clipped to the image dimensions. Maybe it's silly of me to say "this is what a broken image will look like". That's what we've seen in GIF, and we try to correct for it. But there could certainly be other errors that we don't correct for. I guess I just find this one unlikely - the frame reports dimensions that don't match its actual data, but the actual data matches a correctly clipped frame. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:324: if (y >= size().height()) Since you removed the check for y < 0, please add an ASSERT. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:252: // This test checks whether providing the full data, after the first frame has I find this sentence confusing. Instead of saying "whether", why not say something like "check that <something that should happen> happens" e.g. Check that providing the full data after the first frame was partially decoded properly continues where it left off. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:281: // This tests if the frame buffer contents change when progressively decoding This test verifies that the frame buffer contents change ... https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:284: // uses the full data size of the image for it's value. its* https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:588: // For explanation of this test, see the definition of I think this comment is unnecessary. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:593: "//LayoutTests/fast/images/resources/png-animated-idat-part-of-animation.png", 249u); I just noticed this - these new tests start with "//" when "/" would be sufficient (and other instances just use "/"). https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:596: // For explanation of this test, see the definition of Again, this comment is unnecessary. If I'm curious about what this test does, I'll look up the one method it calls, even without this comment. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:178: // For non animated PNG's, we don't want to waste CPU time with recreating More importantly, if we are resuming the first frame, recreating the struct would mean we start the decode over again. Unless we start processing from the beginning of the IDAT (which would be wasteful), we'd either make the decode fail (i.e. report failure) or decode the wrong thing. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:181: m_decodeOffset += processData( Why not continue to use m_frameInfo[0].readOffset? That's more clear to me. I find it confusing that m_decodeOffset only applies in some cases. (But maybe there's a good reason to separate them, which I still don't understand?) https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:186: // Progressive decoding is only done if: I find the term "progressive decoding" a little bit confusing. We're still using libpng's progressive read function. The difference is we'll never have to resume in the case of later frames. It occurs to me that there's a potential downside to only decoding later frames when they're complete (i.e. have all the data they report to have). Let's say we supported progressively decoding later frames. The client could begin decoding frame i (where i > 0) in a background thread or if it has spare cycles as soon as some data becomes available, even though they won't use that frame yet (our client already handles this, for the case of an animated gif). Since we don't allow this, when the frame is actually complete, the client will have to wait longer for the decode when they need it. That doesn't necessarily mean it's the wrong decision, but we should consider that. (As I stated, the GIF decoder made a different decision, but I think WEBP made the same decision as we have for APNG.) cblume@, any thoughts? https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:187: // - It is the first frame, thus |index| == 0 I think you're saying that both of these statements have to be true? Maybe end this line with AND (or "both" in the line above)? I initially read this as an OR. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:189: // but we're only halfway in a progressive decode, started earlier. I don't think you mean precisely halfway. partway? Another way of saying this (that I think is more clear) is that we skip progressive decoding (is there a word for non-progressive decoding?) if we have all the data for the first frame and have not already started decoding it. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:190: bool firstFrameIncomplete = m_frameInfo[0].byteLength == kFirstFrameIndicator; I find the word "Incomplete" here confusing, since we have FrameStatus::FrameComplete. I started to suggest something like bool firstFrameNotFullyReceived (kind of long and hard to read...) or bool firstFrameFullyReceived = byteLength != kFirstFrameInd that's a little easier to read, but non-intuitive since you'll later say !firstFrameFullyReceived. Even worse, though, I think that's not even true. It doesn't tell us whether we have all the data for the first frame. It just tells whether we *know* that we have all the data for the first frame. (Right?) https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:197: if (!progressiveDecode || m_decodeOffset == 0) I think this can be if (!progressiveDecode || !progressiveDecodingAlreadyStarted) https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:198: startFrameDecoding(data, index); If the first frame fills the image size, can we skip recreating the png struct? https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:200: // By default, a frame will be considered to be decoded completely, unless Try to keep comments focused on why, rather than what. I don't think this comment really explains anything, but it tells us what the next few lines say. How about the following: bool decodedFrameCompletely; if (progressiveDecode) decodedFrameCompletely = progressivelyDecodeFirstFrame(data) else { decodeFrame(data, index); // For a non-progressive decode, we already have all the data we are going to get, // so consider the frame complete. decodedFrameCompletely = true; } Bigger picture issue, though - if frame 2 is incomplete, in the sense that it only has half the data that it should, should we indicate that in some way to the client? Maybe they don't want to bother with decoding frame 3 in that case? I think this will result in marking frame 2 as FrameComplete, so they'll assume that frame 3 will composite correctly with it. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:209: // Finish decoding by sending the IEND chunk, but only if the frame was nit: I find this a little wordy. How about: // Send the IEND chunk if the frame is completely decoded. (Maybe this comment is unnecessary, since it doesn't explain why. The interesting info in the comment is that endFrameDecoding sends the IEND chunk. But maybe that's not interesting at this level.) https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:213: nit: No need for blank line https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:281: m_decodeOffset = 0; Maybe I don't understand this variable. We're done progressively decoding the first frame, so why do we need to reset it? Is this in case the first frame gets cleared and then we need to redecode it later? (In that case, we won't need the progressive case, since we know we already have all the data, right?) https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:291: // At this point, three scenario's are possible: nit: scenarios* https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:292: // 1) Some bytes of this chunk are already decoded in a previous call, nit: are -> were https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:307: // Scenario 1: |m_decodeOffset| is ahead of the chunk tag. Why does this happen? I guess we didn't update readOffset to account for having read further? Why not? https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:458: if (m_frameInfo[0].byteLength == kFirstFrameIndicator) What would happen if we had a broken APNG with the IEND or fcTL chunk before any fdAT or IDAT chunks? I'm guessing that m_frameInfo would be empty, so this call would crash. (We might be safe in the case of IEND before IDAT, because I think our attempt to parseSize would have failed before we reach here. And maybe the readOffset will be zero in the other cases, so we won't reach here. But it's not obvious without making sure I really understand the code, so I think this is a good area to have tests.)
https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:186: // Progressive decoding is only done if: On 2016/10/28 14:20:33, scroggo_chromium wrote: > I find the term "progressive decoding" a little bit confusing. We're still using > libpng's progressive read function. The difference is we'll never have to resume > in the case of later frames. > > It occurs to me that there's a potential downside to only decoding later frames > when they're complete (i.e. have all the data they report to have). Let's say we > supported progressively decoding later frames. The client could begin decoding > frame i (where i > 0) in a background thread or if it has spare cycles as soon > as some data becomes available, even though they won't use that frame yet (our > client already handles this, for the case of an animated gif). Since we don't > allow this, when the frame is actually complete, the client will have to wait > longer for the decode when they need it. > > That doesn't necessarily mean it's the wrong decision, but we should consider > that. (As I stated, the GIF decoder made a different decision, but I think WEBP > made the same decision as we have for APNG.) cblume@, any thoughts? If it isn't too much trouble, I would love the ability to decode in advance. Right now Chrome does lazy decoding when it urgently needs a frame. But with things like big.LITTLE I would have preferred to have been decoding in advance on a core that takes less energy overall. It would take us a while to implement this in Chrome. But when I step back, that is definitely something I want. Because we won't be able to use it for a while, and because it may make landing this CL harder, I'm fine with putting it off until later.
https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:218: png_destroy_read_struct(m_png ? &m_png : 0, m_info ? &m_info : 0, 0); On 2016/10/28 14:20:32, scroggo_chromium wrote: > On 2016/10/27 20:29:55, joostouwerling wrote: > > On 2016/10/26 18:25:58, scroggo_chromium wrote: > > > On 2016/10/26 15:44:17, joostouwerling wrote: > > > > On 2016/10/25 14:59:07, scroggo_chromium wrote: > > > > > A couple of comments: > > > > > - Will we ever have m_info but no m_png? I think we should be able to > say > > > > > something like: > > > > > > > > > > if (m_png) > > > > > png_destroy_read_struct(&m_png, m_info ? &m_info > > > > > : nullptr); > > > > > - I assume we do *not* want to do this if we've already started decoding > > > frame > > > > > |index| and are now continuing it. > > > > > > > > Does that happen for frames with |index| > 0, since we only report them > when > > > all > > > > data is available? > > > > > > Good question. Just because all the data is available does not mean that the > > > data represents a complete frame. So it's possible that we would call this a > > > second time for an incomplete frame, even though there's nothing more to > > decode. > > > > I don't think that this is the right place to take that into account. The > reader > > only reports frames with |index| > 0 if it has seen all it's frame data. Thus, > > if the decoder asks the reader to decode a frame with |index| > 0, the reader > > will always signal the IEND chunk. If the decoder receives this and is unhappy > > with the data it got at that point, it also knows that requesting a decode > again > > won't give different data. > > Just to make sure I understand: because you've provided an IEND chunk, libpng's > callback for end of image will be called, and our callback[1] sets the > ImageFrame to FrameComplete[2], so decode will not be called again[3]. Is that > right? > > [1] > https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... > [2] > https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... > [3] > https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... Right. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:251: memcpy(header, chunk, headerSize); On 2016/10/26 18:25:58, scroggo_chromium wrote: > On 2016/10/26 15:44:17, joostouwerling wrote: > > On 2016/10/25 14:59:07, scroggo_chromium wrote: > > > Alternatively, you could always use readBuffer: > > > > > > if (reinterpret_cast<char*>(chunk) != readBuffer) > > > memcpy(readBuffer, chunk, kBufferSize); > > > > > > We should probably go with whatever is clearer > > > > Your option saves 33B of memory but I think it's more verbose, especially > since > > it also needs another const_cast. I would probably go for the memory savings > but > > I'm not sure what the general rule of wisdom is for Chromium. > > I don't think (a constant, stack-allocated, briefly living) 33 bytes of memory > should determine our decision. I was confused by the fact that we had two > buffers, and I thought my way might be more clear, but I do not have a strong > preference. Acknowledged. https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:252: png_save_uint_32(header + 16, frameRect.width()); On 2016/10/26 18:25:58, scroggo_chromium wrote: > On 2016/10/26 15:44:17, joostouwerling wrote: > > On 2016/10/25 14:59:07, scroggo_chromium wrote: > > > Here I'm guessing you do *not* want the clipped width and height. We should > > have > > > tests for these cases. > > > > The width and height that are written here are non-clipped. The clipping > happens > > in PNGImageDecoder::initializeNewFrame() and is taken into account in > > PNGImageDecoder::rowAvailable(). > > Okay, I see that now. Maybe add a comment where you declare frameRect? Done > > > > I have tests that check whether an overflowing frame is clipped correctly: > > AnimatedPNGTests.frameRectIsClipped. Is that what you mean, or do I not > > understand what you're at? > > I just looked over that test. I don't think it's a realistic test, and I've > commented on it there. https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:174: ASSERT(injectedFrameCount != expectedFrameCount); On 2016/10/28 14:20:32, scroggo_chromium wrote: > On 2016/10/26 18:25:58, scroggo_chromium wrote: > > Sorry, I meant > > > > ASSERT_NE(injectedFrameCount, expectedFrameCount) > > > > ASSERT will crash (arguably that should be caught before checking it in) but > > ASSERT_NE is a part of gtest, which will report an error and exit the test > > early. > > (It is helpful if you respond to comments that recommend changes, even if it's > just "Done" or "Ack". Otherwise I might think you missed something, especially > if the fix is not right on that line.) Acknowledged. https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:498: // Change the width and height of the frame so it falls outside the image. On 2016/10/28 14:20:32, scroggo_chromium wrote: > On 2016/10/26 18:25:58, scroggo_chromium wrote: > > I don't think this is a realistic test. Although you've changed the encoded > > width and height, presumably the data still represents the original frame, > which > > is a subset of the image dimensions. > > > > A more interesting test would be a PNG with a frame that is truly bigger than > > the image getting clipped to the image dimensions. > > Maybe it's silly of me to say "this is what a broken image will look like". > That's what we've seen in GIF, and we try to correct for it. But there could > certainly be other errors that we don't correct for. I guess I just find this > one unlikely - the frame reports dimensions that don't match its actual data, > but the actual data matches a correctly clipped frame. I don't completely understand what you mean with "this is what a broken image will look like", but I agree with your general point and added this improvement to my todo list. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:324: if (y >= size().height()) On 2016/10/28 14:20:32, scroggo_chromium wrote: > Since you removed the check for y < 0, please add an ASSERT. Done. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:252: // This test checks whether providing the full data, after the first frame has On 2016/10/28 14:20:33, scroggo_chromium wrote: > I find this sentence confusing. Instead of saying "whether", why not say > something like "check that <something that should happen> happens" > > e.g. > > Check that providing the full data after the first frame was partially decoded > properly continues where it left off. Done. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:281: // This tests if the frame buffer contents change when progressively decoding On 2016/10/28 14:20:32, scroggo_chromium wrote: > This test verifies that the frame buffer contents change ... Done. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:284: // uses the full data size of the image for it's value. On 2016/10/28 14:20:32, scroggo_chromium wrote: > its* Done. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:588: // For explanation of this test, see the definition of On 2016/10/28 14:20:33, scroggo_chromium wrote: > I think this comment is unnecessary. Done. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:593: "//LayoutTests/fast/images/resources/png-animated-idat-part-of-animation.png", 249u); On 2016/10/28 14:20:33, scroggo_chromium wrote: > I just noticed this - these new tests start with "//" when "/" would be > sufficient (and other instances just use "/"). Done. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:596: // For explanation of this test, see the definition of On 2016/10/28 14:20:32, scroggo_chromium wrote: > Again, this comment is unnecessary. If I'm curious about what this test does, > I'll look up the one method it calls, even without this comment. Done. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:178: // For non animated PNG's, we don't want to waste CPU time with recreating On 2016/10/28 14:20:33, scroggo_chromium wrote: > More importantly, if we are resuming the first frame, recreating the struct > would mean we start the decode over again. Unless we start processing from the > beginning of the IDAT (which would be wasteful), we'd either make the decode > fail (i.e. report failure) or decode the wrong thing. Done. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:181: m_decodeOffset += processData( On 2016/10/28 14:20:33, scroggo_chromium wrote: > Why not continue to use m_frameInfo[0].readOffset? > > That's more clear to me. I find it confusing that m_decodeOffset only applies in > some cases. (But maybe there's a good reason to separate them, which I still > don't understand?) I use m_decodeOffset to keep track how far the first frame is decoded progressively. I did not create the variable before since I can reuse the frame offset for non animated images, but since it was necessary to have it for animated images, I decided to use it here for consistency as well. I will rename it to m_progressiveDecodeOffset to make its usage more clear. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:186: // Progressive decoding is only done if: On 2016/10/28 14:20:33, scroggo_chromium wrote: > I find the term "progressive decoding" a little bit confusing. We're still using > libpng's progressive read function. The difference is we'll never have to resume > in the case of later frames. Right. But I think we still need to use the progressive read functions to work with the APNG chunks, or at least, if we don't want to copy the image data, since we need to modify small parts the data stream before it is fed to libpng (i.e the fdAT chunks). > It occurs to me that there's a potential downside to only decoding later frames > when they're complete (i.e. have all the data they report to have). Let's say we > supported progressively decoding later frames. The client could begin decoding > frame i (where i > 0) in a background thread or if it has spare cycles as soon > as some data becomes available, even though they won't use that frame yet (our > client already handles this, for the case of an animated gif). Since we don't > allow this, when the frame is actually complete, the client will have to wait > longer for the decode when they need it. > > That doesn't necessarily mean it's the wrong decision, but we should consider > that. (As I stated, the GIF decoder made a different decision, but I think WEBP > made the same decision as we have for APNG.) cblume@, any thoughts? https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:186: // Progressive decoding is only done if: On 2016/10/28 17:29:17, cblume wrote: > On 2016/10/28 14:20:33, scroggo_chromium wrote: > > I find the term "progressive decoding" a little bit confusing. We're still > using > > libpng's progressive read function. The difference is we'll never have to > resume > > in the case of later frames. > > > > It occurs to me that there's a potential downside to only decoding later > frames > > when they're complete (i.e. have all the data they report to have). Let's say > we > > supported progressively decoding later frames. The client could begin decoding > > frame i (where i > 0) in a background thread or if it has spare cycles as soon > > as some data becomes available, even though they won't use that frame yet (our > > client already handles this, for the case of an animated gif). Since we don't > > allow this, when the frame is actually complete, the client will have to wait > > longer for the decode when they need it. > > > > That doesn't necessarily mean it's the wrong decision, but we should consider > > that. (As I stated, the GIF decoder made a different decision, but I think > WEBP > > made the same decision as we have for APNG.) cblume@, any thoughts? > > If it isn't too much trouble, I would love the ability to decode in advance. > > Right now Chrome does lazy decoding when it urgently needs a frame. But with > things like big.LITTLE I would have preferred to have been decoding in advance > on a core that takes less energy overall. > > It would take us a while to implement this in Chrome. But when I step back, that > is definitely something I want. > > Because we won't be able to use it for a while, and because it may make landing > this CL harder, I'm fine with putting it off until later. It would not be too difficult to implement progressive decoding for each frame, I guess. We can report a frame when the frame control is seen, instead of all data, and we need to keep track of which frame we were decoding before and how far in the stream we've came. But the infrastructure for the actual decoding is not different than for frame 0, as far as I can see it now. But I am fine with postponing it to make the landing process smoother. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:187: // - It is the first frame, thus |index| == 0 On 2016/10/28 14:20:33, scroggo_chromium wrote: > I think you're saying that both of these statements have to be true? Maybe end > this line with AND (or "both" in the line above)? I initially read this as an > OR. Done. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:189: // but we're only halfway in a progressive decode, started earlier. On 2016/10/28 14:20:33, scroggo_chromium wrote: > I don't think you mean precisely halfway. partway? > > Another way of saying this (that I think is more clear) is that we skip > progressive decoding (is there a word for non-progressive decoding?) if we have > all the data for the first frame and have not already started decoding it. Ack. Good question about the word. The antonyms don't really work on this one. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:190: bool firstFrameIncomplete = m_frameInfo[0].byteLength == kFirstFrameIndicator; On 2016/10/28 14:20:33, scroggo_chromium wrote: > I find the word "Incomplete" here confusing, since we have > FrameStatus::FrameComplete. > > I started to suggest something like > > bool firstFrameNotFullyReceived > > (kind of long and hard to read...) > > or > > bool firstFrameFullyReceived = byteLength != kFirstFrameInd > > that's a little easier to read, but non-intuitive since you'll later say > !firstFrameFullyReceived. Even worse, though, I think that's not even true. It > doesn't tell us whether we have all the data for the first frame. It just tells > whether we *know* that we have all the data for the first frame. (Right?) Right, if the client would have supplied more data without having called parse(PNGMetaDataQuery) we would have all the data but don't know about it. I choose firstFrameLengthKnown since it implies what we know at this point. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:197: if (!progressiveDecode || m_decodeOffset == 0) On 2016/10/28 14:20:33, scroggo_chromium wrote: > I think this can be > > if (!progressiveDecode || !progressiveDecodingAlreadyStarted) Done. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:198: startFrameDecoding(data, index); On 2016/10/28 14:20:33, scroggo_chromium wrote: > If the first frame fills the image size, can we skip recreating the png struct? We could, but there needs to be a check that this is the original png struct, and not one used for a different frame. That could happen if this frame was decoded a second time. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:200: // By default, a frame will be considered to be decoded completely, unless On 2016/10/28 14:20:33, scroggo_chromium wrote: > Try to keep comments focused on why, rather than what. I don't think this > comment really explains anything, but it tells us what the next few lines say. > How about the following: > > bool decodedFrameCompletely; > if (progressiveDecode) > decodedFrameCompletely = progressivelyDecodeFirstFrame(data) > else { > decodeFrame(data, index); > // For a non-progressive decode, we already have all the data we are going to > get, > // so consider the frame complete. > decodedFrameCompletely = true; > } Acknowledged. > Bigger picture issue, though - if frame 2 is incomplete, in the sense that it > only has half the data that it should, should we indicate that in some way to > the client? Maybe they don't want to bother with decoding frame 3 in that case? > I think this will result in marking frame 2 as FrameComplete, so they'll assume > that frame 3 will composite correctly with it. You mean a frame that says it has |n| rows of |m| columns, but provides less data than that? I commented elsewhere on this as well, but my take is that the decoder can take this into account if it receives the complete() call before it has received all the data it anticipated. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:209: // Finish decoding by sending the IEND chunk, but only if the frame was On 2016/10/28 14:20:33, scroggo_chromium wrote: > nit: I find this a little wordy. How about: > > // Send the IEND chunk if the frame is completely decoded. > > (Maybe this comment is unnecessary, since it doesn't explain why. The > interesting info in the comment is that endFrameDecoding sends the IEND chunk. > But maybe that's not interesting at this level.) I'd say it is interesting to know that this will call the complete() method in |m_decoder|. Updated it. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:213: On 2016/10/28 14:20:33, scroggo_chromium wrote: > nit: No need for blank line Done. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:281: m_decodeOffset = 0; On 2016/10/28 14:20:34, scroggo_chromium wrote: > Maybe I don't understand this variable. We're done progressively decoding the > first frame, so why do we need to reset it? Is this in case the first frame gets > cleared and then we need to redecode it later? (In that case, we won't need the > progressive case, since we know we already have all the data, right?) I apparently removed a comment here by accident, which explained this. In the case the frame is cleared, and is decoded for a second time, |m_decodeOffset| > 0 will set |progressiveDecodingAlreadyStarted| to true. This is undesired since it will try to continue progressive decoding from the end of the frame data, instead of treating it as any other fully seen frame. Now that I think about this, I'd say a better place to do this is in decode(), if this method returns true, so it is closer to the code where it makes a difference. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:291: // At this point, three scenario's are possible: On 2016/10/28 14:20:34, scroggo_chromium wrote: > nit: scenarios* Done. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:292: // 1) Some bytes of this chunk are already decoded in a previous call, On 2016/10/28 14:20:34, scroggo_chromium wrote: > nit: are -> were Done. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:307: // Scenario 1: |m_decodeOffset| is ahead of the chunk tag. On 2016/10/28 14:20:34, scroggo_chromium wrote: > Why does this happen? I guess we didn't update readOffset to account for having > read further? Why not? This can happen if a chunk is partially decoded. We can't set |offset| to where we stopped in the previous call since we won't know how many bytes are left in this chunk. That is necessary to know, to make sure we (possibly) convert the next fdAT chunk to an IDAT chunk. A downside of this method is that we need to skip over the chunks that are already decoded before we get to the required offset. The advantage is that we don't need to store something like |m_bytesLeftInChunkForProgressiveDecode| and can omit more complex logic in this method. The nice thing about this structure is that we're always at the beginning of a chunk in each loop. For APNG we can't use m_frameInfo[0].readOffset to keep track of how far we've decoded the frame, since it may be necessary to re-decode the frame when it has been cleared. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:458: if (m_frameInfo[0].byteLength == kFirstFrameIndicator) On 2016/10/28 14:20:34, scroggo_chromium wrote: > What would happen if we had a broken APNG with the IEND or fcTL chunk before any > fdAT or IDAT chunks? I'm guessing that m_frameInfo would be empty, so this call > would crash. (We might be safe in the case of IEND before IDAT, because I think > our attempt to parseSize would have failed before we reach here. And maybe the > readOffset will be zero in the other cases, so we won't reach here. But it's not > obvious without making sure I really understand the code, so I think this is a > good area to have tests.) The IEND case is indeed an invalid APNG file, but like you said, it would fail in in parseSize. I'll add a test for this, and for more cases of interchanged or missing chunks for that matter. When an fcTL chunk appears before the IDAT chunk it indicates that the IDAT is part of the animation. That is a valid PNG encoding. This is already tested with the png-animated-idat-part-of-animation image.
Patchset #10 (id:240001) has been deleted
https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:498: // Change the width and height of the frame so it falls outside the image. On 2016/10/28 18:41:25, joostouwerling wrote: > On 2016/10/28 14:20:32, scroggo_chromium wrote: > > On 2016/10/26 18:25:58, scroggo_chromium wrote: > > > I don't think this is a realistic test. Although you've changed the encoded > > > width and height, presumably the data still represents the original frame, > > which > > > is a subset of the image dimensions. > > > > > > A more interesting test would be a PNG with a frame that is truly bigger > than > > > the image getting clipped to the image dimensions. > > > > Maybe it's silly of me to say "this is what a broken image will look like". > > That's what we've seen in GIF, and we try to correct for it. But there could > > certainly be other errors that we don't correct for. I guess I just find this > > one unlikely - the frame reports dimensions that don't match its actual data, > > but the actual data matches a correctly clipped frame. > > I don't completely understand what you mean with "this is what a broken image > will look like", but I agree with your general point and added this improvement > to my todo list. My original comment was that I would expect a broken image to look a certain way, based on how we see some broken images in GIF. My follow up comment was that there's no reason to assume broken images would look a certain way - they're broken. So while I don't expect to see a broken image that looks like this, we could, and it doesn't hurt to test it.* Have you looked at other implementations (e.g. FireFox) to see what kinds of tests they have, and what kinds of errors they correct for? * But this seems like a more general problem. If the reported dimensions do not match the image data, they won't necessarily match what would clip to the full image size. In the more general case I'm not sure how we'd handle it - i.e. whether we'd even know there was a problem. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:588: // For explanation of this test, see the definition of On 2016/10/28 18:41:25, joostouwerling wrote: > On 2016/10/28 14:20:33, scroggo_chromium wrote: > > I think this comment is unnecessary. > > Done. It looks like these comments are still here. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:181: m_decodeOffset += processData( On 2016/10/28 18:41:25, joostouwerling wrote: > On 2016/10/28 14:20:33, scroggo_chromium wrote: > > Why not continue to use m_frameInfo[0].readOffset? > > > > That's more clear to me. I find it confusing that m_decodeOffset only applies > in > > some cases. (But maybe there's a good reason to separate them, which I still > > don't understand?) > > I use m_decodeOffset to keep track how far the first frame is decoded > progressively. I did not create the variable before since I can reuse the frame > offset for non animated images, but since it was necessary to have it for > animated images, I decided to use it here for consistency as well. > > I will rename it to m_progressiveDecodeOffset to make its usage more clear. I figured out why I'm confused. PNGImageReader::m_readOffset is how far we've read into the input data - it changes when we read more data. But FrameInfo::readOffset is only set once (or I think twice - once just to initialize it to zero as a sentinel) - it means the start of the data. How about changing FrameInfo::readOffset to something that better reflects that, like initialOffset? (I'm not a huge fan of that name, but I cannot think of anything better, and that's at least analogous to m_initialOffset, which means something similar.) And please add a comment explaining it. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:186: // Progressive decoding is only done if: On 2016/10/28 18:41:26, joostouwerling wrote: > On 2016/10/28 14:20:33, scroggo_chromium wrote: > > I find the term "progressive decoding" a little bit confusing. We're still > using > > libpng's progressive read function. The difference is we'll never have to > resume > > in the case of later frames. > > Right. But I think we still need to use the progressive read functions to work > with the APNG chunks, or at least, if we don't want to copy the image data, > since we need to modify small parts the data stream before it is fed to libpng > (i.e the fdAT chunks). Oh I'm not suggesting that we shouldn't use the progressive read functions. I'm suggesting that we should think of some better nomenclature. > > > It occurs to me that there's a potential downside to only decoding later > frames > > when they're complete (i.e. have all the data they report to have). Let's say > we > > supported progressively decoding later frames. The client could begin decoding > > frame i (where i > 0) in a background thread or if it has spare cycles as soon > > as some data becomes available, even though they won't use that frame yet (our > > client already handles this, for the case of an animated gif). Since we don't > > allow this, when the frame is actually complete, the client will have to wait > > longer for the decode when they need it. > > > > That doesn't necessarily mean it's the wrong decision, but we should consider > > that. (As I stated, the GIF decoder made a different decision, but I think > WEBP > > made the same decision as we have for APNG.) cblume@, any thoughts? > https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:186: // Progressive decoding is only done if: On 2016/10/28 18:41:26, joostouwerling wrote: > On 2016/10/28 17:29:17, cblume wrote: > > On 2016/10/28 14:20:33, scroggo_chromium wrote: > > > I find the term "progressive decoding" a little bit confusing. We're still > > using > > > libpng's progressive read function. The difference is we'll never have to > > resume > > > in the case of later frames. > > > > > > It occurs to me that there's a potential downside to only decoding later > > frames > > > when they're complete (i.e. have all the data they report to have). Let's > say > > we > > > supported progressively decoding later frames. The client could begin > decoding > > > frame i (where i > 0) in a background thread or if it has spare cycles as > soon > > > as some data becomes available, even though they won't use that frame yet > (our > > > client already handles this, for the case of an animated gif). Since we > don't > > > allow this, when the frame is actually complete, the client will have to > wait > > > longer for the decode when they need it. > > > > > > That doesn't necessarily mean it's the wrong decision, but we should > consider > > > that. (As I stated, the GIF decoder made a different decision, but I think > > WEBP > > > made the same decision as we have for APNG.) cblume@, any thoughts? > > > > If it isn't too much trouble, I would love the ability to decode in advance. > > > > Right now Chrome does lazy decoding when it urgently needs a frame. But with > > things like big.LITTLE I would have preferred to have been decoding in advance > > on a core that takes less energy overall. > > > > It would take us a while to implement this in Chrome. But when I step back, > that > > is definitely something I want. > > > > Because we won't be able to use it for a while, and because it may make > landing > > this CL harder, I'm fine with putting it off until later. > > It would not be too difficult to implement progressive decoding for each frame, > I guess. We can report a frame when the frame control is seen, instead of all > data, and we need to keep track of which frame we were decoding before and how > far in the stream we've came. But the infrastructure for the actual decoding is > not different than for frame 0, as far as I can see it now. > > But I am fine with postponing it to make the landing process smoother. FWIW, I think it will make the landing process (or more to the point - the reviewing process) easier if we decode all frames progressively. It means that more code will be shared and we can remove some code around deciding which path to take. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:190: bool firstFrameIncomplete = m_frameInfo[0].byteLength == kFirstFrameIndicator; On 2016/10/28 18:41:25, joostouwerling wrote: > On 2016/10/28 14:20:33, scroggo_chromium wrote: > > I find the word "Incomplete" here confusing, since we have > > FrameStatus::FrameComplete. > > > > I started to suggest something like > > > > bool firstFrameNotFullyReceived > > > > (kind of long and hard to read...) > > > > or > > > > bool firstFrameFullyReceived = byteLength != kFirstFrameInd > > > > that's a little easier to read, but non-intuitive since you'll later say > > !firstFrameFullyReceived. Even worse, though, I think that's not even true. It > > doesn't tell us whether we have all the data for the first frame. It just > tells > > whether we *know* that we have all the data for the first frame. (Right?) > > Right, if the client would have supplied more data without having called > parse(PNGMetaDataQuery) we would have all the data but don't know about it. I > choose firstFrameLengthKnown since it implies what we know at this point. sgtm https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:198: startFrameDecoding(data, index); On 2016/10/28 18:41:26, joostouwerling wrote: > On 2016/10/28 14:20:33, scroggo_chromium wrote: > > If the first frame fills the image size, can we skip recreating the png > struct? > > We could, but there needs to be a check that this is the original png struct, > and not one used for a different frame. That could happen if this frame was > decoded a second time. I think the common case is that it *is* the original png struct. I don't think it would be too hard to check for, but perhaps at least add a FIXME? https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:200: // By default, a frame will be considered to be decoded completely, unless On 2016/10/28 18:41:26, joostouwerling wrote: > On 2016/10/28 14:20:33, scroggo_chromium wrote: > > Try to keep comments focused on why, rather than what. I don't think this > > comment really explains anything, but it tells us what the next few lines say. > > How about the following: > > > > bool decodedFrameCompletely; > > if (progressiveDecode) > > decodedFrameCompletely = progressivelyDecodeFirstFrame(data) > > else { > > decodeFrame(data, index); > > // For a non-progressive decode, we already have all the data we are going > to > > get, > > // so consider the frame complete. > > decodedFrameCompletely = true; > > } > > Acknowledged. > > > Bigger picture issue, though - if frame 2 is incomplete, in the sense that it > > only has half the data that it should, should we indicate that in some way to > > the client? Maybe they don't want to bother with decoding frame 3 in that > case? > > I think this will result in marking frame 2 as FrameComplete, so they'll > assume > > that frame 3 will composite correctly with it. > > You mean a frame that says it has |n| rows of |m| columns, but provides less > data than that? Yes. > I commented elsewhere on this as well, but my take is that the > decoder can take this into account if it receives the complete() call before it > has received all the data it anticipated. i.e. in PNGImageDecoder::complete()? It looks like the decoder does not currently do this? https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:307: // Scenario 1: |m_decodeOffset| is ahead of the chunk tag. On 2016/10/28 18:41:26, joostouwerling wrote: > On 2016/10/28 14:20:34, scroggo_chromium wrote: > > Why does this happen? I guess we didn't update readOffset to account for > having > > read further? Why not? > > This can happen if a chunk is partially decoded. We can't set |offset| to where > we stopped in the previous call since we won't know how many bytes are left in > this chunk. That is necessary to know, to make sure we (possibly) convert the > next fdAT chunk to an IDAT chunk. A downside of this method is that we need to > skip over the chunks that are already decoded before we get to the required > offset. The advantage is that we don't need to store something like > |m_bytesLeftInChunkForProgressiveDecode| and can omit more complex logic in this > method. The nice thing about this structure is that we're always at the > beginning of a chunk in each loop. > > For APNG we can't use m_frameInfo[0].readOffset to keep track of how far we've > decoded the frame, since it may be necessary to re-decode the frame when it has > been cleared. I have a comment about this elsewhere - I misunderstood FrameInfo::readOffset entirely. I think giving it a name that doesn't sound like PNGImageReader::m_readOffset will provide some clarity. https://codereview.chromium.org/2386453003/diff/220001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/220001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:415: m_frameBufferCache[m_currentFrame].setStatus(ImageFrame::FrameComplete); From https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... you stated: "You mean a frame that says it has |n| rows of |m| columns, but provides less data than that? I commented elsewhere on this as well, but my take is that the decoder can take this into account if it receives the complete() call before it has received all the data it anticipated." I assume here is the place to account for that? I think we should probably do at least one of two things: 1) call setFailed() - but keeping in mind that failure currently means to stop, but it should probably mean to loop the already decoded frames (if looping is indicated) - see crbug.com/267883 (again). 2) set the status to FramePartial - but the decoder may try to continue decoding later [1], and which would redecode the frame based on how this class is written. [1] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... https://codereview.chromium.org/2386453003/diff/220001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/220001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:554: // clippedi, not just report larger dimensions. clipped* https://codereview.chromium.org/2386453003/diff/220001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/220001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:308: // Otherwise, store how far we've came in |m_progressiveDecodeOffset| and return we've come*
Patchset #10 (id:260001) has been deleted
Description was changed from ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. TODO: - alpha blending - disposal operations BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. TODO: - alpha blending - disposal operations BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
Patchset #11 (id:300001) has been deleted
https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:411: // ... and then decode frames from |reallocatedData|. On 2016/10/26 18:25:58, scroggo_chromium wrote: > On 2016/10/26 15:44:17, joostouwerling wrote: > > On 2016/10/25 14:59:06, scroggo_chromium wrote: > > > What is interesting about this test? > > > > > > (Will PNGImageDecoder even do anything here? Or will it still have the > frames > > > cached?) > > > > It should have the frame meta data cached, not the actual frames. It tests > > whether decoding still works when supplying data from a different location in > > memory, since it does not explicitly parses the stream again. This is a test > > that I found in the WebP test suite and I *think* it emulates what's happening > > with the DeferredImageDecoder's. My reasoning was here, that if this is a > > reasonable scenario for webp, it would also be realistic for apng. > > Ok, that makes sense. You asked me a question in person about comments I made on > code that was copied from somewhere else. (i.e. should we make changes in > response to my comments in both places?) I think the real answer is we should > consolidate the code. ImageDecoderTestHelpers has a bunch of methods that are > called in multiple tests. If this is the same as in a WEBP test, we should put a > method in that file to share. I will consolidate the tests when this CL is done, or if I have some time available before that. https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:498: // Change the width and height of the frame so it falls outside the image. On 2016/10/31 13:35:11, scroggo_chromium wrote: > On 2016/10/28 18:41:25, joostouwerling wrote: > > On 2016/10/28 14:20:32, scroggo_chromium wrote: > > > On 2016/10/26 18:25:58, scroggo_chromium wrote: > > > > I don't think this is a realistic test. Although you've changed the > encoded > > > > width and height, presumably the data still represents the original frame, > > > which > > > > is a subset of the image dimensions. > > > > > > > > A more interesting test would be a PNG with a frame that is truly bigger > > than > > > > the image getting clipped to the image dimensions. > > > > > > Maybe it's silly of me to say "this is what a broken image will look like". > > > That's what we've seen in GIF, and we try to correct for it. But there could > > > certainly be other errors that we don't correct for. I guess I just find > this > > > one unlikely - the frame reports dimensions that don't match its actual > data, > > > but the actual data matches a correctly clipped frame. > > > > I don't completely understand what you mean with "this is what a broken image > > will look like", but I agree with your general point and added this > improvement > > to my todo list. > > My original comment was that I would expect a broken image to look a certain > way, based on how we see some broken images in GIF. My follow up comment was > that there's no reason to assume broken images would look a certain way - > they're broken. So while I don't expect to see a broken image that looks like > this, we could, and it doesn't hurt to test it.* Have you looked at other > implementations (e.g. FireFox) to see what kinds of tests they have, and what > kinds of errors they correct for? I could not find any explicit tests for corrupt PNG images in the Firefox image tests. They have some layout tests for specific bugs but nothing related to this issue. For decoding they use libpng with the animation patch. I assume that patch will throw an error, and thereby set the decoder to the failed state, if the reported dimensions exceed the image size, or if not enough data is available. It's just a guess, though, but I would think the patch adheres strictly to the specification. WebKit explicitly falls back to a non-animated image when the indicated frame dimensions exceed the image size. They don't have any tests nor explicit code that deals with respectively more or less frame data. > * But this seems like a more general problem. If the reported dimensions do not > match the image data, they won't necessarily match what would clip to the full > image size. In the more general case I'm not sure how we'd handle it - i.e. > whether we'd even know there was a problem. I'm not sure how libpng handles less or more frame data either, and I can't find any answer to it in the documentation. It may very well throw an error but it could also silently report less or more rows. I added a todo to figure this out so we can design our code accordingly. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:588: // For explanation of this test, see the definition of On 2016/10/31 13:35:11, scroggo_chromium wrote: > On 2016/10/28 18:41:25, joostouwerling wrote: > > On 2016/10/28 14:20:33, scroggo_chromium wrote: > > > I think this comment is unnecessary. > > > > Done. > > It looks like these comments are still here. Error from my side. Is fixed in patch set 11. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:181: m_decodeOffset += processData( On 2016/10/31 13:35:11, scroggo_chromium wrote: > On 2016/10/28 18:41:25, joostouwerling wrote: > > On 2016/10/28 14:20:33, scroggo_chromium wrote: > > > Why not continue to use m_frameInfo[0].readOffset? > > > > > > That's more clear to me. I find it confusing that m_decodeOffset only > applies > > in > > > some cases. (But maybe there's a good reason to separate them, which I still > > > don't understand?) > > > > I use m_decodeOffset to keep track how far the first frame is decoded > > progressively. I did not create the variable before since I can reuse the > frame > > offset for non animated images, but since it was necessary to have it for > > animated images, I decided to use it here for consistency as well. > > > > I will rename it to m_progressiveDecodeOffset to make its usage more clear. > > I figured out why I'm confused. PNGImageReader::m_readOffset is how far we've > read into the input data - it changes when we read more data. But > FrameInfo::readOffset is only set once (or I think twice - once just to > initialize it to zero as a sentinel) - it means the start of the data. How about > changing FrameInfo::readOffset to something that better reflects that, like > initialOffset? (I'm not a huge fan of that name, but I cannot think of anything > better, and that's at least analogous to m_initialOffset, which means something > similar.) And please add a comment explaining it. How about startOffset? When glancing over the code, I guess initialOffset could also cause confusion if it is interpreted as the start of the PNG stream. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:186: // Progressive decoding is only done if: On 2016/10/31 13:35:11, scroggo_chromium wrote: > On 2016/10/28 18:41:26, joostouwerling wrote: > > On 2016/10/28 17:29:17, cblume wrote: > > > On 2016/10/28 14:20:33, scroggo_chromium wrote: > > > > I find the term "progressive decoding" a little bit confusing. We're still > > > using > > > > libpng's progressive read function. The difference is we'll never have to > > > resume > > > > in the case of later frames. > > > > > > > > It occurs to me that there's a potential downside to only decoding later > > > frames > > > > when they're complete (i.e. have all the data they report to have). Let's > > say > > > we > > > > supported progressively decoding later frames. The client could begin > > decoding > > > > frame i (where i > 0) in a background thread or if it has spare cycles as > > soon > > > > as some data becomes available, even though they won't use that frame yet > > (our > > > > client already handles this, for the case of an animated gif). Since we > > don't > > > > allow this, when the frame is actually complete, the client will have to > > wait > > > > longer for the decode when they need it. > > > > > > > > That doesn't necessarily mean it's the wrong decision, but we should > > consider > > > > that. (As I stated, the GIF decoder made a different decision, but I think > > > WEBP > > > > made the same decision as we have for APNG.) cblume@, any thoughts? > > > > > > If it isn't too much trouble, I would love the ability to decode in advance. > > > > > > Right now Chrome does lazy decoding when it urgently needs a frame. But with > > > things like big.LITTLE I would have preferred to have been decoding in > advance > > > on a core that takes less energy overall. > > > > > > It would take us a while to implement this in Chrome. But when I step back, > > that > > > is definitely something I want. > > > > > > Because we won't be able to use it for a while, and because it may make > > landing > > > this CL harder, I'm fine with putting it off until later. > > > > It would not be too difficult to implement progressive decoding for each > frame, > > I guess. We can report a frame when the frame control is seen, instead of all > > data, and we need to keep track of which frame we were decoding before and how > > far in the stream we've came. But the infrastructure for the actual decoding > is > > not different than for frame 0, as far as I can see it now. > > > > But I am fine with postponing it to make the landing process smoother. > > FWIW, I think it will make the landing process (or more to the point - the > reviewing process) easier if we decode all frames progressively. It means that > more code will be shared and we can remove some code around deciding which path > to take. As agreed upon in person: for now, only the first frame is decoded progressively, and later frames are decoded in one call. We can revisit this before trying to land this CL. Another thing that sprung to mind is that progressively decoding because we have some cycles left is something different that progressively decoding because not all data has arrived yet. AFAIK there is nothing from stopping the client to decode a frame in advance, before it needs to be shown. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:198: startFrameDecoding(data, index); On 2016/10/31 13:35:12, scroggo_chromium wrote: > On 2016/10/28 18:41:26, joostouwerling wrote: > > On 2016/10/28 14:20:33, scroggo_chromium wrote: > > > If the first frame fills the image size, can we skip recreating the png > > struct? > > > > We could, but there needs to be a check that this is the original png struct, > > and not one used for a different frame. That could happen if this frame was > > decoded a second time. > > I think the common case is that it *is* the original png struct. I don't think > it would be too hard to check for, but perhaps at least add a FIXME? Done. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:200: // By default, a frame will be considered to be decoded completely, unless On 2016/10/31 13:35:11, scroggo_chromium wrote: > On 2016/10/28 18:41:26, joostouwerling wrote: > > On 2016/10/28 14:20:33, scroggo_chromium wrote: > > > Try to keep comments focused on why, rather than what. I don't think this > > > comment really explains anything, but it tells us what the next few lines > say. > > > How about the following: > > > > > > bool decodedFrameCompletely; > > > if (progressiveDecode) > > > decodedFrameCompletely = progressivelyDecodeFirstFrame(data) > > > else { > > > decodeFrame(data, index); > > > // For a non-progressive decode, we already have all the data we are going > > to > > > get, > > > // so consider the frame complete. > > > decodedFrameCompletely = true; > > > } > > > > Acknowledged. > > > > > Bigger picture issue, though - if frame 2 is incomplete, in the sense that > it > > > only has half the data that it should, should we indicate that in some way > to > > > the client? Maybe they don't want to bother with decoding frame 3 in that > > case? > > > I think this will result in marking frame 2 as FrameComplete, so they'll > > assume > > > that frame 3 will composite correctly with it. > > > > You mean a frame that says it has |n| rows of |m| columns, but provides less > > data than that? > > Yes. > > > I commented elsewhere on this as well, but my take is that the > > decoder can take this into account if it receives the complete() call before > it > > has received all the data it anticipated. > > i.e. in PNGImageDecoder::complete()? It looks like the decoder does not > currently do this? I added a todo, which depends on the result of the investigation in libpng's behavior I mentioned at PGNImageDecoderTest [1]. [1]: https://codereview.chromium.org/2386453003/diff/160001/third_party/WebKit/Sou... https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:307: // Scenario 1: |m_decodeOffset| is ahead of the chunk tag. On 2016/10/31 13:35:12, scroggo_chromium wrote: > On 2016/10/28 18:41:26, joostouwerling wrote: > > On 2016/10/28 14:20:34, scroggo_chromium wrote: > > > Why does this happen? I guess we didn't update readOffset to account for > > having > > > read further? Why not? > > > > This can happen if a chunk is partially decoded. We can't set |offset| to > where > > we stopped in the previous call since we won't know how many bytes are left in > > this chunk. That is necessary to know, to make sure we (possibly) convert the > > next fdAT chunk to an IDAT chunk. A downside of this method is that we need to > > skip over the chunks that are already decoded before we get to the required > > offset. The advantage is that we don't need to store something like > > |m_bytesLeftInChunkForProgressiveDecode| and can omit more complex logic in > this > > method. The nice thing about this structure is that we're always at the > > beginning of a chunk in each loop. > > > > For APNG we can't use m_frameInfo[0].readOffset to keep track of how far we've > > decoded the frame, since it may be necessary to re-decode the frame when it > has > > been cleared. > > I have a comment about this elsewhere - I misunderstood FrameInfo::readOffset > entirely. I think giving it a name that doesn't sound like > PNGImageReader::m_readOffset will provide some clarity. Done. https://codereview.chromium.org/2386453003/diff/220001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/220001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:415: m_frameBufferCache[m_currentFrame].setStatus(ImageFrame::FrameComplete); On 2016/10/31 13:35:12, scroggo_chromium wrote: > From > https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... > you stated: > > "You mean a frame that says it has |n| rows of |m| columns, but provides > less data than that? I commented elsewhere on this as well, but my take > is that the decoder can take this into account if it receives the > complete() call before it has received all the data it anticipated." > > I assume here is the place to account for that? I think we should probably do at > least one of two things: > > 1) call setFailed() - but keeping in mind that failure currently means to stop, > but it should probably mean to loop the already decoded frames (if looping is > indicated) - see crbug.com/267883 (again). > > 2) set the status to FramePartial - but the decoder may try to continue decoding > later [1], and which would redecode the frame based on how this class is > written. > > [1] > https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... Acknowledged, that is the approach I wanted to take. The implementation will depend on what libpng does in the case of more/less data, as I've explained at [1]. [1]: https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... https://codereview.chromium.org/2386453003/diff/220001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/220001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:554: // clippedi, not just report larger dimensions. On 2016/10/31 13:35:12, scroggo_chromium wrote: > clipped* Done. https://codereview.chromium.org/2386453003/diff/220001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/220001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:308: // Otherwise, store how far we've came in |m_progressiveDecodeOffset| and return On 2016/10/31 13:35:12, scroggo_chromium wrote: > we've come* Done.
Changes so far look to be on the right track. https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:181: m_decodeOffset += processData( On 2016/10/31 18:40:19, joostouwerling wrote: > On 2016/10/31 13:35:11, scroggo_chromium wrote: > > On 2016/10/28 18:41:25, joostouwerling wrote: > > > On 2016/10/28 14:20:33, scroggo_chromium wrote: > > > > Why not continue to use m_frameInfo[0].readOffset? > > > > > > > > That's more clear to me. I find it confusing that m_decodeOffset only > > applies > > > in > > > > some cases. (But maybe there's a good reason to separate them, which I > still > > > > don't understand?) > > > > > > I use m_decodeOffset to keep track how far the first frame is decoded > > > progressively. I did not create the variable before since I can reuse the > > frame > > > offset for non animated images, but since it was necessary to have it for > > > animated images, I decided to use it here for consistency as well. > > > > > > I will rename it to m_progressiveDecodeOffset to make its usage more clear. > > > > I figured out why I'm confused. PNGImageReader::m_readOffset is how far we've > > read into the input data - it changes when we read more data. But > > FrameInfo::readOffset is only set once (or I think twice - once just to > > initialize it to zero as a sentinel) - it means the start of the data. How > about > > changing FrameInfo::readOffset to something that better reflects that, like > > initialOffset? (I'm not a huge fan of that name, but I cannot think of > anything > > better, and that's at least analogous to m_initialOffset, which means > something > > similar.) And please add a comment explaining it. > > How about startOffset? When glancing over the code, I guess initialOffset could > also cause confusion if it is interpreted as the start of the PNG stream. sgtm https://codereview.chromium.org/2386453003/diff/200001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:186: // Progressive decoding is only done if: On 2016/10/31 18:40:19, joostouwerling wrote: > On 2016/10/31 13:35:11, scroggo_chromium wrote: > > On 2016/10/28 18:41:26, joostouwerling wrote: > > > On 2016/10/28 17:29:17, cblume wrote: > > > > On 2016/10/28 14:20:33, scroggo_chromium wrote: > > > > > I find the term "progressive decoding" a little bit confusing. We're > still > > > > using > > > > > libpng's progressive read function. The difference is we'll never have > to > > > > resume > > > > > in the case of later frames. > > > > > > > > > > It occurs to me that there's a potential downside to only decoding later > > > > frames > > > > > when they're complete (i.e. have all the data they report to have). > Let's > > > say > > > > we > > > > > supported progressively decoding later frames. The client could begin > > > decoding > > > > > frame i (where i > 0) in a background thread or if it has spare cycles > as > > > soon > > > > > as some data becomes available, even though they won't use that frame > yet > > > (our > > > > > client already handles this, for the case of an animated gif). Since we > > > don't > > > > > allow this, when the frame is actually complete, the client will have to > > > wait > > > > > longer for the decode when they need it. > > > > > > > > > > That doesn't necessarily mean it's the wrong decision, but we should > > > consider > > > > > that. (As I stated, the GIF decoder made a different decision, but I > think > > > > WEBP > > > > > made the same decision as we have for APNG.) cblume@, any thoughts? > > > > > > > > If it isn't too much trouble, I would love the ability to decode in > advance. > > > > > > > > Right now Chrome does lazy decoding when it urgently needs a frame. But > with > > > > things like big.LITTLE I would have preferred to have been decoding in > > advance > > > > on a core that takes less energy overall. > > > > > > > > It would take us a while to implement this in Chrome. But when I step > back, > > > that > > > > is definitely something I want. > > > > > > > > Because we won't be able to use it for a while, and because it may make > > > landing > > > > this CL harder, I'm fine with putting it off until later. > > > > > > It would not be too difficult to implement progressive decoding for each > > frame, > > > I guess. We can report a frame when the frame control is seen, instead of > all > > > data, and we need to keep track of which frame we were decoding before and > how > > > far in the stream we've came. But the infrastructure for the actual decoding > > is > > > not different than for frame 0, as far as I can see it now. > > > > > > But I am fine with postponing it to make the landing process smoother. > > > > FWIW, I think it will make the landing process (or more to the point - the > > reviewing process) easier if we decode all frames progressively. It means that > > more code will be shared and we can remove some code around deciding which > path > > to take. > > As agreed upon in person: for now, only the first frame is decoded > progressively, and later frames are decoded in one call. We can revisit this > before trying to land this CL. > > Another thing that sprung to mind is that progressively decoding because we have > some cycles left is something different that progressively decoding because not > all data has arrived yet. AFAIK there is nothing from stopping the client to > decode a frame in advance, before it needs to be shown. Sure, it's just that allowing progressive decoding for later frames means that the client can decode a frame in advance, even if it hasn't been fully received yet. https://codereview.chromium.org/2386453003/diff/320001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/320001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:427: // @TODO(joostouwerling) if necessary, do a check if all expected data has been Could you add some context? This is because we artificially send the IEND chunk. (Also, I think this makes more sense just above the line that sets the status.)
Patchset #12 (id:340001) has been deleted
Patchset #12 (id:360001) has been deleted
Description was changed from ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. TODO: - alpha blending - disposal operations BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. Frame disposal is implemented. TODO: - alpha blending BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
Patchset #12 (id:380001) has been deleted
Implemented frame disposal. For alpha blending, I plan on pulling some functions from WEBPImageDecoder up to ImageFrame, so they can share that code. Some ->setAlpha statements may need in this patch to be revisited based on the alpha blending implementation. https://codereview.chromium.org/2386453003/diff/320001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/320001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:427: // @TODO(joostouwerling) if necessary, do a check if all expected data has been On 2016/10/31 19:34:06, scroggo_chromium wrote: > Could you add some context? This is because we artificially send the IEND chunk. > > (Also, I think this makes more sense just above the line that sets the status.) Done.
https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:71: if (!m_reader || !m_reader->parseCompleted()) It's somewhat noticeable that this method checks parseCompleted before calling parse, but the next method (decode) does not. I'm guessing that parse will check parseCompleted first anyway? https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:89: // status of frame |index| is not ImageFrane::FrameComplete. Therefore, it is ImageFrame* https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:91: // without checking for it's status. its* https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:202: // should be changed to stick with m_repetitionCount to match other browsers. I don't think you want m_repetitionCount - that could include frames that had errors, right? https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:262: // PNGImageDecoder::complete(). This prevents overwriting previous frames I don't see why this is necessary. Let's take frame 1. If it depends on frame 0, we'll start by copying frame 0 into frame 1's frame buffer. Are you saying you want to leave frame 1 looking like frame 0 until it's complete? I would think the correct way to not show partial frames is somewhere else - i.e. just don't show a frame that is FramePartial. (But if this *was* the right place, we should make a couple of changes anyway - this is unnecessary if frame 1 is independent - the interlaceBuffer should be renamed, since that name no longer reflects its use) Edit: I think I understand - I forgot about takeBitmapDataIfWritable, which is a recent addition. Maybe instead we should skip that, if we're afraid of stealing frame 0's data while frame 1 may still be incomplete? (On another note, GIFImageDecoder doesn't do this trick of writing to an interlaceBuffer first. Does it have a bug? Or does it accomplish this differently?) https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:278: // fully transparant black after initialisation. transparent* https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:294: // We want to clear the previous frame to transparant, without affecting transparent* https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:277: static inline bool isChunk(const png_byte* chunk, const char* tag) I like this :) https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:298: if (isChunk(chunk, "fcTL") || isChunk(chunk, "IEND")) Previously, you were comparing the first four bytes, and now you're comparing the second four bytes? (I know the check against IEND was wrong, but I thought the one for fcTL was correct?) https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:401: bool PNGImageReader::parse(SegmentReader& data, I think early on in this method you want to check for m_parseCompleted and return if it's true.
Patchset #13 (id:420001) has been deleted
Patchset #13 (id:440001) has been deleted
Patchset #13 (id:460001) has been deleted
https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:71: if (!m_reader || !m_reader->parseCompleted()) On 2016/11/07 13:02:16, scroggo_chromium wrote: > It's somewhat noticeable that this method checks parseCompleted before calling > parse, but the next method (decode) does not. I'm guessing that parse will check > parseCompleted first anyway? I put the check for parseCompleted at the beginning of PNGImageReader::parse() (per your suggestion) so the calls to PNGImageDecoder::parse(), and PNGImageDecoder::parse() itself, don't need to check for this anymore. https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:89: // status of frame |index| is not ImageFrane::FrameComplete. Therefore, it is On 2016/11/07 13:02:16, scroggo_chromium wrote: > ImageFrame* Done. https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:91: // without checking for it's status. On 2016/11/07 13:02:16, scroggo_chromium wrote: > its* Done. https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:202: // should be changed to stick with m_repetitionCount to match other browsers. On 2016/11/07 13:02:16, scroggo_chromium wrote: > I don't think you want m_repetitionCount - that could include frames that had > errors, right? I think you are confusing the repetition count with the frame count? I would say that we want to loop the frames we were able to decode |m_repetitionCount| times. https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:262: // PNGImageDecoder::complete(). This prevents overwriting previous frames On 2016/11/07 13:02:16, scroggo_chromium wrote: > I don't see why this is necessary. Let's take frame 1. If it depends on frame 0, > we'll start by copying frame 0 into frame 1's frame buffer. Are you saying you > want to leave frame 1 looking like frame 0 until it's complete? I would think > the correct way to not show partial frames is somewhere else - i.e. just don't > show a frame that is FramePartial. I don't think that the FramePartial check is enforced higher up. At [1], it will make an SkImage from the frame bitmap that exists for FramePartial, instead of finalizing the complete frame. Following the call hierarchy up to [2], I don't see a check for the frame status. In BitmapImage::startAnimation() there is a check for frameIsCompleteAtIndex() but that reports true when the frame is fully received, not when it is fully decoded [3]. And sometimes, at least for the first frames, it makes sense to show partial frames. The partial overlap of frames can only be caused by slow decoding, since we only report on the frames when they're fully received. I'm not sure if that delay is noticeable. If possible, this extra buffer should be avoided since it'll use 4 * width * height bytes in extra memory for decoding the image. [1] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/graph... [2] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/graph... [3] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/graph... > (But if this *was* the right place, we should make a couple of changes anyway > - this is unnecessary if frame 1 is independent > - the interlaceBuffer should be renamed, since that name no > longer reflects its use) Done. > Edit: I think I understand - I forgot about takeBitmapDataIfWritable, which is a > recent addition. Maybe instead we should skip that, if we're afraid of stealing > frame 0's data while frame 1 may still be incomplete? I don't think that makes any difference, since it'll still write row per row to the frame buffer. > (On another note, GIFImageDecoder doesn't do this trick of writing to an > interlaceBuffer first. Does it have a bug? Or does it accomplish this > differently?) I can't find any other mechanism that would do this, so that'd mean its a bug (or a non-issue). But in all fairness, I am not entirely sure of my reasoning at this point. https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:278: // fully transparant black after initialisation. On 2016/11/07 13:02:16, scroggo_chromium wrote: > transparent* Done. https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:294: // We want to clear the previous frame to transparant, without affecting On 2016/11/07 13:02:16, scroggo_chromium wrote: > transparent* Done. https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:277: static inline bool isChunk(const png_byte* chunk, const char* tag) On 2016/11/07 13:02:16, scroggo_chromium wrote: > I like this :) Acknowledged. https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:298: if (isChunk(chunk, "fcTL") || isChunk(chunk, "IEND")) On 2016/11/07 13:02:16, scroggo_chromium wrote: > Previously, you were comparing the first four bytes, and now you're comparing > the second four bytes? (I know the check against IEND was wrong, but I thought > the one for fcTL was correct?) No, that one was wrong as well, since the first four bytes contain the length. The previous if-condition would never evaluate to true, since the first memcmp would always compare a number with "fcTL" and the second memcmp would always return 0 since zero bytes were compared. This was not caught by the tests since, frankly, nothing broke. This loop would continue to decode chunks, skipping all unknown fcTL and fdAT chunks (PNGImageReader::startFrameDecoding() does not specify to keep unknown chunks), until it ends up at the IEND chunk, and through scenario 3, would process it normally. This would invoke the frameComplete callback and thus the status of the frame buffer would be set to FrameComplete. PNGImageReader::progressivelyDecodeFirstFrame() would return false, but that only results in PNGImageReader::endFrameDecoding() not being called, but the effect of that method was already achieved by processing the IEND chunk. https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:330: } else if (isChunk(chunk, "fdAT")) { Same holds here, as for what you mentioned above. The postmortem analysis here is that my tests didn't catch this because I was only testing an image where the first frame is an IDAT chunk. https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:401: bool PNGImageReader::parse(SegmentReader& data, On 2016/11/07 13:02:16, scroggo_chromium wrote: > I think early on in this method you want to check for m_parseCompleted and > return if it's true. Done.
Patchset #13 (id:480001) has been deleted
https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:202: // should be changed to stick with m_repetitionCount to match other browsers. On 2016/11/08 21:58:17, joostouwerling wrote: > On 2016/11/07 13:02:16, scroggo_chromium wrote: > > I don't think you want m_repetitionCount - that could include frames that had > > errors, right? > > I think you are confusing the repetition count with the frame count? I would say > that we want to loop the frames we were able to decode |m_repetitionCount| > times. Haha, yes, I was confused. https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:262: // PNGImageDecoder::complete(). This prevents overwriting previous frames On 2016/11/08 21:58:17, joostouwerling wrote: > On 2016/11/07 13:02:16, scroggo_chromium wrote: > > I don't see why this is necessary. Let's take frame 1. If it depends on frame > 0, > > we'll start by copying frame 0 into frame 1's frame buffer. Are you saying you > > want to leave frame 1 looking like frame 0 until it's complete? I would think > > the correct way to not show partial frames is somewhere else - i.e. just don't > > show a frame that is FramePartial. > > I don't think that the FramePartial check is enforced higher up. At [1], it will > make an SkImage from the frame bitmap that exists for FramePartial, instead of > finalizing the complete frame. Following the call hierarchy up to [2], I don't > see a check for the frame status. In BitmapImage::startAnimation() there is a > check for frameIsCompleteAtIndex() but that reports true when the frame is fully > received, not when it is fully decoded [3]. Agreed, but those two will typically be the same - if the frame is fully received, it will also be Complete, unless there is an error. Below you talk about slow decoding, but in the current architecture we will fully decode all the data that has been received, so if you ask for frame i, and it is fully received, it will be completely decoded before drawing, even if it's slow. (It could be FramePartial if there is an error, but that shouldn't be the typical case. In fact, I think you mark it FrameComplete when you run out of data, so it could only be Partial if you called setFailed(), in which case I think we'll avoid showing that frame in another way?) > And sometimes, at least for the > first frames, it makes sense to show partial frames. I agree that it makes sense to show the first frame even if it's partial. But I don't see how that's relevant here. The question is, for frame i, where i > 0 and depends on a prior frame (say i-1), should we draw directly into m_frameBufferCache[i], or should we leave m_frameBufferCache[i] looking like i-1 and draw into a temporary buffer and then copy it when it's complete? But I don't think we'll ever hit the case where i is incomplete. Since you only start decoding i when all of its data has been received, it is going to receive the IEND chunk unless there's an error, in which case I think you'll call setFailed(), so I don't think there's an issue of i being Partial. In the typical case, you'll draw into your temporary buffer and then copy immediately into m_frameBufferCache[i]. So there's no sense in preserving the contents of i-1 in i. > > The partial overlap of frames can only be caused by slow decoding, since we only > report on the frames when they're fully received. I'm not sure if that delay is > noticeable. > If possible, this extra buffer should be avoided since it'll use 4 * > width * height bytes in extra memory for decoding the image. Agreed. This is precisely what I'm pushing for here. I'm not convinced you need to use this extra memory. > > [1] > https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/graph... > [2] > https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/graph... > [3] > https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/graph... > > > (But if this *was* the right place, we should make a couple of changes anyway > > - this is unnecessary if frame 1 is independent > > - the interlaceBuffer should be renamed, since that name no > > longer reflects its use) > > Done. > > > Edit: I think I understand - I forgot about takeBitmapDataIfWritable, which is > a > > recent addition. Maybe instead we should skip that, if we're afraid of > stealing > > frame 0's data while frame 1 may still be incomplete? > > I don't think that makes any difference, since it'll still write row per row to > the frame buffer. Here's the difference I was talking about: Let's say you're decoding frame i, which depends on frame i-1. Your use of the extra buffer (interlaceBuffer/imageBuffer) preserves the contents of frame i-1 in the ImageFrame for i. The only reason I think you'd want to do that is because you want to keep showing frame i-1. Assuming we took the old case of copying frame i-1 into i when we initialize i, if the client really wanted to keep showing i-1, they could just keep showing ImageFrame i-1. But if takeBitmapDataIfWritable succeeded frame i-1 is now empty, so the client could not show i-1 if they wanted to. (IIUC, takeBitmapDataIfWritable will only succeed if we skipped displaying frame i-1, since I'm assuming that if we're decoding i, we must have all the data for i-1, and if we didn't skip i-1, we would have called the code in your link at [1], which calls finalizePixelsAndGetImage(), which makes future calls to takeBitmapDataIfWritable fail.) But ultimately I think you're correct that it doesn't make a difference - as I point out above, we'll only decode i if we have all the data, so we shouldn't end up with a partial frame anyway. > > > (On another note, GIFImageDecoder doesn't do this trick of writing to an > > interlaceBuffer first. Does it have a bug? Or does it accomplish this > > differently?) > > I can't find any other mechanism that would do this, so that'd mean its a bug > (or a non-issue). But in all fairness, I am not entirely sure of my reasoning at > this point. This is definitely a complicated system that is hard to reason about, but I think your link at [3] takes care of this. https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:298: if (isChunk(chunk, "fcTL") || isChunk(chunk, "IEND")) On 2016/11/08 21:58:17, joostouwerling wrote: > On 2016/11/07 13:02:16, scroggo_chromium wrote: > > Previously, you were comparing the first four bytes, and now you're comparing > > the second four bytes? (I know the check against IEND was wrong, but I thought > > the one for fcTL was correct?) > > No, that one was wrong as well, since the first four bytes contain the length. > The previous if-condition would never evaluate to true, since the first memcmp > would always compare a number with "fcTL" and the second memcmp would always > return 0 since zero bytes were compared. > > This was not caught by the tests since, frankly, nothing broke. This loop would > continue to decode chunks, skipping all unknown fcTL and fdAT chunks > (PNGImageReader::startFrameDecoding() does not specify to keep unknown chunks), > until it ends up at the IEND chunk, and through scenario 3, would process it > normally. This would invoke the frameComplete callback and thus the status of > the frame buffer would be set to FrameComplete. > PNGImageReader::progressivelyDecodeFirstFrame() would return false, but that > only results in PNGImageReader::endFrameDecoding() not being called, but the > effect of that method was already achieved by processing the IEND chunk. Please add a test that verifies you got this correct. https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:451: // Tell libpng to send us rows for interlaced pngs. Make sure the interlace image* buffer https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:531: // For non-first frames, don't write rows incrementally to the buffer, since This comment is no longer correct. This now applies to frames that depend on other frames. https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h (right): https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:75: // Non-first frames that depend on previous frames should not be decoded row I don't think "Non-first" is necessary here. The first frame cannot depend on a previous frame, so "that depend on previous frames" makes it clear you're not talking about the first one. https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:404: // The IEND chunk marks the end of the PNG image data. When the chunk is seen, I don't think this comment is necessary. I think it's self-explanatory that if the parse is completed there is no need to parse. https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h (right): https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:105: // 2) to store the pixel data while decoding non-first frames, so the decoder No longer non-first frames - this is frames that depend on other frames. https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:137: std::unique_ptr<png_byte[]> m_interlaceBuffer; I think you no longer need this one?
Patchset #14 (id:520001) has been deleted
I revisited the decision to write the pixels to the frame buffer in the complete() callback for dependent non-first frames in patch set 14. https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:262: // PNGImageDecoder::complete(). This prevents overwriting previous frames On 2016/11/09 13:42:50, scroggo_chromium wrote: > Agreed, but those two will typically be the same - if the frame is fully > received, it will also be Complete, unless there is an error. Below you talk > about slow decoding, but in the current architecture we will fully decode all > the data that has been received, so if you ask for frame i, and it is fully > received, it will be completely decoded before drawing, even if it's slow. (It > could be FramePartial if there is an error, but that shouldn't be the typical > case. In fact, I think you mark it FrameComplete when you run out of data, so it > could only be Partial if you called setFailed(), in which case I think we'll > avoid showing that frame in another way?) Yes, I agree with you now. Since the decode call will only return once it has decoded all data, we shouldn't worry about partial frames due to slow decoding. In the case of a failure during decoding, I was planning on clearing the frame and reducing the frame count to |i| if decoding of frame |i| fails. (that is, reducing the frame count to 2 if decoding of frame 3, with index 2, fails). https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/400001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:298: if (isChunk(chunk, "fcTL") || isChunk(chunk, "IEND")) On 2016/11/09 13:42:50, scroggo_chromium wrote: > On 2016/11/08 21:58:17, joostouwerling wrote: > > On 2016/11/07 13:02:16, scroggo_chromium wrote: > > > Previously, you were comparing the first four bytes, and now you're > comparing > > > the second four bytes? (I know the check against IEND was wrong, but I > thought > > > the one for fcTL was correct?) > > > > No, that one was wrong as well, since the first four bytes contain the length. > > The previous if-condition would never evaluate to true, since the first memcmp > > would always compare a number with "fcTL" and the second memcmp would always > > return 0 since zero bytes were compared. > > > > This was not caught by the tests since, frankly, nothing broke. This loop > would > > continue to decode chunks, skipping all unknown fcTL and fdAT chunks > > (PNGImageReader::startFrameDecoding() does not specify to keep unknown > chunks), > > until it ends up at the IEND chunk, and through scenario 3, would process it > > normally. This would invoke the frameComplete callback and thus the status of > > the frame buffer would be set to FrameComplete. > > PNGImageReader::progressivelyDecodeFirstFrame() would return false, but that > > only results in PNGImageReader::endFrameDecoding() not being called, but the > > effect of that method was already achieved by processing the IEND chunk. > > Please add a test that verifies you got this correct. As discussed in person, this is hard to test without exposing the private parts of PNGImageReader. https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:451: // Tell libpng to send us rows for interlaced pngs. Make sure the interlace On 2016/11/09 13:42:50, scroggo_chromium wrote: > image* buffer In the revisited patch, this is correct. https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:531: // For non-first frames, don't write rows incrementally to the buffer, since On 2016/11/09 13:42:50, scroggo_chromium wrote: > This comment is no longer correct. This now applies to frames that depend on > other frames. This is now removed in the revisited patch. https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h (right): https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:75: // Non-first frames that depend on previous frames should not be decoded row On 2016/11/09 13:42:50, scroggo_chromium wrote: > I don't think "Non-first" is necessary here. The first frame cannot depend on a > previous frame, so "that depend on previous frames" makes it clear you're not > talking about the first one. Acknowledged. https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:404: // The IEND chunk marks the end of the PNG image data. When the chunk is seen, On 2016/11/09 13:42:51, scroggo_chromium wrote: > I don't think this comment is necessary. I think it's self-explanatory that if > the parse is completed there is no need to parse. Done. https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h (right): https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:105: // 2) to store the pixel data while decoding non-first frames, so the decoder On 2016/11/09 13:42:51, scroggo_chromium wrote: > No longer non-first frames - this is frames that depend on other frames. This is removed. https://codereview.chromium.org/2386453003/diff/500001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:137: std::unique_ptr<png_byte[]> m_interlaceBuffer; On 2016/11/09 13:42:51, scroggo_chromium wrote: > I think you no longer need this one? In the revisited patch, it is :) But yeah, here it should've been removed.
https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (left): https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:313: if (m_reader->decodingSizeOnly()) { Why is this no longer necessary? It looks to me like we'll keep parsing even if we asked for the size only. Maybe I missed something? https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:189: setFailed(); Should we also return here? Do we want to read the frame count after we setFailed? https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:205: m_reader->frameCount() == 1) This seems complicated. Don't we know long before isAllDataReceived that there is no animation? https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:270: buffer->setHasAlpha(false); Most of our buffers are set to true initially, since a partially decoded frame displays transparent where it hasn't been decoded. Why did you choose false here? https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:415: setColorSpaceAndComputeTransform(colorSpace); This method will be called for each frame, right? Do we need to do this for each frame? (Can there be a different color space per frame?) https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:444: // Tell libpng to send us rows for interlaced pngs. Is this added whitespace? https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:462: ImageFrame& buffer = m_frameBufferCache[m_currentFrame]; Move this below the if check, which does not use it? https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:538: bool PNGImageReader::parseSize(SegmentReader &data) SegmentReader& data https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h (right): https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:83: // a) the png is determined to be non-animated, if no acTL chunk is found, or no acTL chunk is found prior to <IDAT, I think?> https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:85: bool parseCompleted() { return m_parseCompleted; }; nit: This method can be const. https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:127: // the IDAT is ignored. ignored for an animated image.
Patchset #15 (id:560001) has been deleted
Three new patches: Patch set 15 implements alpha blending. Patch set 16 fixes feedback from scroggo@ in patch set 14. Patch set 17 revisits the alpha setting of the framebuffer for APNG. My idea of it on paper corresponds to the implementation for GIF. If you agree as well, I will refactor the common code up to ImageDecoder. WebP could *probably* use the same code, but the way they decode their images is somewhat different from the approach GIF and APNG take (WebP checks the previous' frame alpha status before decoding, and GIF/APNG afterwards), so I need to take a more careful look at that. After this, there's a bunch of small things that need to be pushed before we can go to the final review: - Merge all refactor CL's I made into this code, - Frame count adjustment after failure, - Handle in- and overcomplete frame data, and a number of tests that check for erroneous images. I hope to add all of this by the end of this week. https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (left): https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:313: if (m_reader->decodingSizeOnly()) { On 2016/11/11 21:31:06, scroggo_chromium wrote: > Why is this no longer necessary? > > It looks to me like we'll keep parsing even if we asked for the size only. Maybe > I missed something? As discussed in person: since I'm structurally parsing chunk by chunk I process exactly as much data as necessary to decode the size, so it is unnecessary to rewind. See PNGImageReader::parseSize. https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:189: setFailed(); On 2016/11/11 21:31:06, scroggo_chromium wrote: > Should we also return here? Do we want to read the frame count after we > setFailed? I think this is better. Say the parse call returns false because the IEND chunk is missing. In that case, we can (and want to!) show all frames that were completely received, right? So if this call detected three new frames, we still want to add those to the frame count. https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:205: m_reader->frameCount() == 1) On 2016/11/11 21:31:06, scroggo_chromium wrote: > This seems complicated. Don't we know long before isAllDataReceived that there > is no animation? Yeah, you're right - once parseCompleted() is true we know the frame count won't be changed anymore. And for non-animated images, that is as soon as the IDAT chunk has been seen. https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:270: buffer->setHasAlpha(false); On 2016/11/11 21:31:06, scroggo_chromium wrote: > Most of our buffers are set to true initially, since a partially decoded frame > displays transparent where it hasn't been decoded. Why did you choose false > here? The original PNG implementation used it in this way - it only sets setHasAlpha to true when it actually has seen transparent pixels. I was planning on looking into this after alpha blending. Your reasoning sounds logical to me, and it could be that the original implementation was able to get away with it because of [1], which only sets the alpha type of the bitmap to opaque when the frame is fully decoded (for single frame images). The resulting code is a little simpler in this case, since you only need to set hasAlpha to true when you encounter a transparent pixel, instead of tracking for the whole image if there has been only opaque pixels. But this does not work anymore for animated images, since a later frame is considered complete when it is fully received, instead of decoded. In patch set 17 I revisited the alpha settings, PTAL. [1]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:415: setColorSpaceAndComputeTransform(colorSpace); On 2016/11/11 21:31:06, scroggo_chromium wrote: > This method will be called for each frame, right? Do we need to do this for each > frame? (Can there be a different color space per frame?) Afaik there is only color space information in the header chunks, so once should be enough. https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:444: // Tell libpng to send us rows for interlaced pngs. On 2016/11/11 21:31:06, scroggo_chromium wrote: > Is this added whitespace? Yes. Removed in ps 16. https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:462: ImageFrame& buffer = m_frameBufferCache[m_currentFrame]; On 2016/11/11 21:31:06, scroggo_chromium wrote: > Move this below the if check, which does not use it? Done. https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:538: bool PNGImageReader::parseSize(SegmentReader &data) On 2016/11/11 21:31:06, scroggo_chromium wrote: > SegmentReader& data Done. https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h (right): https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:83: // a) the png is determined to be non-animated, if no acTL chunk is found, or On 2016/11/11 21:31:06, scroggo_chromium wrote: > no acTL chunk is found prior to <IDAT, I think?> It does not check for an acTL after the IDAT anymore, yes :) (That'd be against the APNG spec). Clarified it. https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:85: bool parseCompleted() { return m_parseCompleted; }; On 2016/11/11 21:31:06, scroggo_chromium wrote: > nit: This method can be const. Done. https://codereview.chromium.org/2386453003/diff/540001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:127: // the IDAT is ignored. On 2016/11/11 21:31:06, scroggo_chromium wrote: > ignored for an animated image. Done.
Description was changed from ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. Frame disposal is implemented. TODO: - alpha blending BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
Description was changed from ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. Failures are handled differently, based on the image and state of the decoder: 1) When a non-animated PNG, or the first frame of an animated PNG, can't be decoded or parsed, set the decoder to the failed state. Also set the state to failed if a parse error occurs before any frames were received. 2) When a decoding failure occurs for non-first frames, we still want to show earlier frames. This means the frame count needs to be adjusted. 3) When a parsing failure occurs, we still want to show all frames that were succesfully parsed. BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
Description was changed from ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. Failures are handled differently, based on the image and state of the decoder: 1) When a non-animated PNG, or the first frame of an animated PNG, can't be decoded or parsed, set the decoder to the failed state. Also set the state to failed if a parse error occurs before any frames were received. 2) When a decoding failure occurs for non-first frames, we still want to show earlier frames. This means the frame count needs to be adjusted. 3) When a parsing failure occurs, we still want to show all frames that were succesfully parsed. BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. Failures are handled differently, based on the image and state of the decoder: 1) When a non-animated PNG, or the first frame of an animated PNG, can't be decoded or parsed, set the decoder to the failed state. Also set the state to failed if a parse error occurs before any frames were received. 2) When a decoding failure occurs for non-first frames, we still want to show earlier frames. This means the frame count needs to be adjusted. 3) When a parsing failure occurs, we still want to show all frames that were successfully parsed. BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
Description was changed from ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. Failures are handled differently, based on the image and state of the decoder: 1) When a non-animated PNG, or the first frame of an animated PNG, can't be decoded or parsed, set the decoder to the failed state. Also set the state to failed if a parse error occurs before any frames were received. 2) When a decoding failure occurs for non-first frames, we still want to show earlier frames. This means the frame count needs to be adjusted. 3) When a parsing failure occurs, we still want to show all frames that were successfully parsed. BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. Failures are handled differently, based on the image and state of the decoder: 1) When a non-animated PNG, or the first frame of an animated PNG, can't be decoded or parsed, set the decoder to the failed state, because there are no frames we can show to the client. Also set the state to failed if a parse error occurs before any frames were received. 2) When a decoding failure occurs for non-first frames, we still want to show earlier frames. This means the frame count needs to be adjusted. 3) When a parsing failure occurs, the frame count is adjusted to the number of successfully parsed frames, since we can still show those. BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
Description was changed from ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. Failures are handled differently, based on the image and state of the decoder: 1) When a non-animated PNG, or the first frame of an animated PNG, can't be decoded or parsed, set the decoder to the failed state, because there are no frames we can show to the client. Also set the state to failed if a parse error occurs before any frames were received. 2) When a decoding failure occurs for non-first frames, we still want to show earlier frames. This means the frame count needs to be adjusted. 3) When a parsing failure occurs, the frame count is adjusted to the number of successfully parsed frames, since we can still show those. BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. Failures during decoding or parsing are handled differently, based on the image and state of the decoder: 1) When a non-animated PNG, or the first frame of an animated PNG, can't be decoded or parsed, set the decoder to the failed state, because there are no frames we can show to the client. Also set the state to failed if a parse error occurs before any frames were received. 2) When a decoding failure occurs for non-first frames, we still want to show earlier frames. This means the frame count needs to be adjusted. 3) When a parsing failure occurs, the frame count is adjusted to the number of successfully parsed frames, since we can still show those. BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
Patchset #18 (id:640001) has been deleted
I implemented different behavior for failures during decoding or parsing, and added tests to verify that behavior. There are three scenarios that are considered with the new code: 1) When a non-animated PNG, or the first frame of an animated PNG, can't be decoded or parsed, set the decoder to the failed state, because there are no frames we can show to the client. Also set the state to failed if a parse error occurs before any frames were received. 2) When a decoding failure occurs for non-first frames, we still want to show earlier frames. This means the frame count needs to be adjusted to |m_currentFrameIndex|. 3) When a parsing failure occurs, the frame count is adjusted to the number of successfully parsed frames, since we can still try to decode those.
Description was changed from ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. Failures during decoding or parsing are handled differently, based on the image and state of the decoder: 1) When a non-animated PNG, or the first frame of an animated PNG, can't be decoded or parsed, set the decoder to the failed state, because there are no frames we can show to the client. Also set the state to failed if a parse error occurs before any frames were received. 2) When a decoding failure occurs for non-first frames, we still want to show earlier frames. This means the frame count needs to be adjusted. 3) When a parsing failure occurs, the frame count is adjusted to the number of successfully parsed frames, since we can still show those. BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. Failures during decoding or parsing are handled differently, based on the image and state of the decoder: 1) When a non-animated PNG, or the first frame of an animated PNG, can't be decoded or parsed, set the decoder to the failed state, because there are no frames we can show to the client. Also set the state to failed if a parse error occurs before any frames were received. 2) When a decoding failure occurs for non-first frames, we still want to show earlier frames. This means, if frame n has a failure during decoding, the frame count is adjusted to n - 1. 3) When a parsing failure occurs, the frame count is adjusted to the number of successfully parsed frames, since we can still show those. BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
Patchset #20 (id:700001) has been deleted
Patchset #19 (id:680001) has been deleted
I've added tests to verify the behavior for in- and overcomplete data. There is no change in behavior. Extra rows of pixels are ignored. If the image does not provide pixels for the whole frame, nothing is done - there is no informed decision possible about the intention of the author, and the image can still be shown, although it may look different from how the author intended.
https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:400: return ImageDecoder::frameIsCompleteAtIndex(index); Revisiting this because your test (VerifyFrameCompleteBehavior) checks that the first frame only reports that it is complete when it is fully decoded, but I do not think that's the right behavior, and your comment sounds like you agree: On 2016/10/26 15:44:16, joostouwerling wrote: > On 2016/10/25 14:59:05, scroggo_chromium wrote: > > This is probably the right thing to do if it's non-animated, but for an > animated > > image, I think you want to know whether we've received all the data for this > > frame - not whether we've fully decoded it. > > > > (I'm on the record for disliking this method - it means different things > > depending on whether it is an animated image versus a non-animated image. Sort > > of ... GIF treats it the same for animated versus non-animated, but WEBP > treats > > them differently. ICO also uses what I think of as the animated version (is > all > > data received)). > > If GIF is able to get away (that is, since a decoded frame is a step further > down the road than having received all data) with returning true when it has > just received all data, for non-animated GIF's, does that not imply that is all > the client needs to know? I agree that this is all the client needs to know. But ImageDecoder's implementation, which you call here, returns something else - whether it is fully decoded. > As far as I can see in the call hierarchy of > frameIsCompleteAtIndex() it never depends on actually having decoded the frame > data. But I may be wrong in this since the call hierarchy is pretty deep and it > is used in several local variables, so please correct me if I'm wrong. No, I think you are correct. FWIW, WEBPImageDecoder::frameIsCompleteAtIndex returns ImageDecoder::frameIsCompleteAtIndex iff the image is not animated. This is different from GIF and ICO, so I think you could go either way for a non-animated PNG, but I do think it's more correct to return whether the (only) frame is fully received. (It matches the comment [1], if not the name. DeferredImageDecoder also queries m_actualDecoder [2], which is only used for parsing, so it will never return true using the base class implementation. There was a CL to correct the confusion in [3], but the author stopped working on it and it never got submitted.) [1] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... [2] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/graph... [3] crrev.com/1962563002 https://codereview.chromium.org/2386453003/diff/600001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/600001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:407: !m_colorSpaceSet) { It's too bad we had to introduce a new variable for this. Since we are providing chunks directly to the reader, my first thought was you could move this block outside of headerAvailable, and just call it once when you need to. But then this code would get executed *after* the below block (if (!hasEmbeddedColorSpace)), which expects this to have already happened. Up above, you used isDecodedSizeAvailable to determine whether this is the first frame. Should we use that same check here? https://codereview.chromium.org/2386453003/diff/600001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:556: for (auto *dstPixel = dstRow; dstPixel < dstRow + width; nit: auto* instead of auto * (here and below) https://codereview.chromium.org/2386453003/diff/600001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:569: } else { Maybe add a comment that this is ImageFrame::BlendAtopPreviousFrame? (Or make it a switch statement?) https://codereview.chromium.org/2386453003/diff/620001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/620001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:303: m_currentBufferSawAlpha = false; We already know the frame rectangle here. Could we (should we) set this to true if it does not cover the full frame? I guess not necessarily, since we also need to check the required frame if it has one? https://codereview.chromium.org/2386453003/diff/620001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:645: if (!m_currentBufferSawAlpha) { This boolean corresponds to the current frame, but is used by PNGImageDecoder for all frames. I think there is a corner case that might break this: e.g. - partially decode frame A, which has no alpha (complete() is not called) - decode frame B, which does not depend on A and has alpha - finish decoding frame A decoding B will set m_currentBufferSawAlpha to true. resuming decoding frame A will not reset it to false, so when we call complete() on A the value will be incorrect. We only (currently) progressively decode frame 0, so I think this could only happen if frame A = frame 0, and frame B is a later frame. Maybe it would be safer to drop m_currentBufferSawAlpha and store alpha on the ImageFrame the whole time (and rely on the code you referenced earlier, which checks for completeness)? https://codereview.chromium.org/2386453003/diff/620001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:661: // Now, if we're at a DisposeNotSpecified or DisposeKeep frame, then These comments make it sound like the first block does something (check the alpha of the prior frame) and the second block does something different, but really the first just initializes a pointer, and the second does all the checks. Maybe merge the comments together? On another note, when you say "we" on this line, it is not clear to me which frame you mean. (I think you mean prevBuffer?) https://codereview.chromium.org/2386453003/diff/620001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:668: // rect, we know the current frame has no alpha. FWIW, I think you could take this further - if prevBuffer *does* have alpha, but only in the rectangle which is covered by this one frame, you could still say that this one has no alpha. But I think determining that would be tricky (-er). https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:79: if (m_isParsing) It seems awkward that the reader calls a method that behaves differently depending on the state of the decoder. Would it be possible to instead call a different method? https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:109: void PNGImageDecoder::decode(size_t index) { If a client calls frameBufferAtIndex() without calling frameCount(), will we still parse? https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:222: else if (query == PNGParseQuery::PNGMetaDataQuery) I guess you changed this to an else because we will have already set m_frameCount to m_reader->frameCount() inside setFailed? https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:649: // below the reported frame count. Maybe add a comment explaining why we cannot use m_frameBufferCache? https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h (right): https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:57: // 1) When a non-animated PNG, or the first frame of an animated PNG, can't nit: these commas are unnecessary https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:59: // are no frames we can show to the client. Also set the state to failed if For a partial first frame (or partial only frame, for non-animated), we *could* show what we've decoded so far, but I think showing nothing matches the current behavior? https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:12: // The image /LT/f/i/r/png-animated-idat-part-of-animation.png is modified in Is this abbreviation standard? If not, I think it would be better to spell out the path. https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:443: // succesfull parse, since frame data is not analyzed in that step, but successful* (two "s"s, one "l") https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:174: // wrong data. Since we are reusing |m_png|, we can call setjmp before I find this comment confusing. It describes why you deleted the above code / why we're not sharing a single setjmp. But it doesn't describe what you're doing here, and viewing this code later on doesn't provide the same context (that you deleted the above lines). I think I'd rather you remove this comment rather than try to explain it better. Down below we should make sure it's obvious why we have to use different setjmp calls. https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:204: // struct. This can't be done in one call for animated and non-animated PNGs, I think "animated and non-animated PNGs" reduces clarity. How about saying something like: "... with the current |m_png| struct. This has to be done after resetPNGStructPreDecode(), which will have replaced |m_png|." https://codereview.chromium.org/2386453003/diff/760001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/760001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:877: // The first frame is only considered complete if it has been fully decoded. I think this behavior is wrong. I've gone back to look at our comments in the CL [1], and it sounds like you agree with me. [1] https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... https://codereview.chromium.org/2386453003/diff/760001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:881: EXPECT_TRUE(decoder->frameIsCompleteAtIndex(1)); I think you meant to pass index to frameIsCompleteAtIndex? https://codereview.chromium.org/2386453003/diff/760001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:886: // Also test that a progressive decode of frame 0 only returns true on Again, I think this is the wrong behavior.
https://codereview.chromium.org/2386453003/diff/620001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/620001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:661: // Now, if we're at a DisposeNotSpecified or DisposeKeep frame, then On 2016/11/29 16:30:52, scroggo_chromium wrote: > These comments make it sound like the first block does something (check the > alpha of the prior frame) and the second block does something different, but > really the first just initializes a pointer, and the second does all the checks. > Maybe merge the comments together? > > On another note, when you say "we" on this line, it is not clear to me which > frame you mean. (I think you mean prevBuffer?) I was just looking at GIFImageDecoder and I realized that this is copied directly from there. (Another opportunity to share code!)
Patchset #24 (id:820001) has been deleted
https://codereview.chromium.org/2386453003/diff/600001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/600001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:407: !m_colorSpaceSet) { On 2016/11/29 16:30:51, scroggo_chromium wrote: > It's too bad we had to introduce a new variable for this. Since we are providing > chunks directly to the reader, my first thought was you could move this block > outside of headerAvailable, and just call it once when you need to. But then > this code would get executed *after* the below block (if > (!hasEmbeddedColorSpace)), which expects this to have already happened. > > Up above, you used isDecodedSizeAvailable to determine whether this is the first > frame. Should we use that same check here? I think that it works fine when some blocks are moved around - see the new patch. Since we can only check for isDecodedSizeAvailable() once (without introducing an extra variable, which would not be so much of an issue, but better try to prevent it), I moved the isDecodedSizeAvailable block to after PNG_COLOR_TYPE_GRAY setting. I don't think that is a problem since the code in between does not rely on the size. The same holds for failing when the image is too large - there is not much harm in the extra png getters and setters that are now done before the size check. The width and height variables are now read through png_get_IHDR instead of png_get_image_width/height, but according to [1], that is not different, since we're using png_uint_32 for these variables. [1] https://github.com/glennrp/libpng/blob/d65a92b951079d315e17e20ba9e7b8423d1939... https://codereview.chromium.org/2386453003/diff/600001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:556: for (auto *dstPixel = dstRow; dstPixel < dstRow + width; On 2016/11/29 16:30:51, scroggo_chromium wrote: > nit: auto* instead of auto * (here and below) Done. https://codereview.chromium.org/2386453003/diff/600001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:569: } else { On 2016/11/29 16:30:51, scroggo_chromium wrote: > Maybe add a comment that this is ImageFrame::BlendAtopPreviousFrame? (Or make it > a switch statement?) Done. I prefer the if-else since there are only two options, and the default case for a switch statement is a little awkward, though the if-else has an implicit default as well. What is in general the approach within Blink, for a default that you'd never expect? A DCHECK(false) to assert we should not reach that case? https://codereview.chromium.org/2386453003/diff/620001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/620001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:303: m_currentBufferSawAlpha = false; On 2016/11/29 16:30:52, scroggo_chromium wrote: > We already know the frame rectangle here. Could we (should we) set this to true > if it does not cover the full frame? I guess not necessarily, since we also need > to check the required frame if it has one? This variable is semantically used to store whether there are (semi-) transparent pixels in the the frame data. The logic of whether this means the frame is actually transparent is done in complete(). And indeed, the required frame could be opaque outside this frame's rect, which could result in an opaque frame if the frame data does not have any transparent pixels. https://codereview.chromium.org/2386453003/diff/620001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:645: if (!m_currentBufferSawAlpha) { On 2016/11/29 16:30:52, scroggo_chromium wrote: > This boolean corresponds to the current frame, but is used by PNGImageDecoder > for all frames. I think there is a corner case that might break this: > > e.g. > - partially decode frame A, which has no alpha (complete() is not called) > - decode frame B, which does not depend on A and has alpha > - finish decoding frame A > > decoding B will set m_currentBufferSawAlpha to true. resuming decoding frame A > will not reset it to false, so when we call complete() on A the value will be > incorrect. > > We only (currently) progressively decode frame 0, so I think this could only > happen if frame A = frame 0, and frame B is a later frame. > > Maybe it would be safer to drop m_currentBufferSawAlpha and store alpha on the > ImageFrame the whole time (and rely on the code you referenced earlier, which > checks for completeness)? I think your example would falsely set frame A's alpha to true, yes. Moreover, this scenario would have invalidated the decoder as is - since it would try to continue progressively decoding a frame when it can't: the png struct has already been reset for frame B. This new png struct has already received the IEND chunk and thus does not expect any new frame data. The easy solution for this issue is to reset |m_progressiveDecodeOffset| in PNGImageReader when a non-first frame is decoded, and completely re-decode first frames when they're decoded later on. And now that I've looked more carefully at it, this is probably the best solution. Trying to continue decoding where we left off is tricky because: - We may have partially decoded a row at the last decode call. So we have to find out where that row started to re-decode that row, but I don't immediately see how. - We have to keep track in PNGImageReader that we need to create a new png struct when |m_progressiveDecodeOffset| is > 0, but another frame has been decoded already. - Libpng has to re-decode the header data anyway, since the new struct needs to learn what's in the frame. - We can keep a different png struct for the first frame for this scenario, but I don't think that is worth the extra class overhead and implementation obscurity for this edge case. The aforementioned solution is implemented in the new patch and I added a test to make sure it correctly decodes the first frame. In regard to the alpha issue, we can solve this by resetting |m_currentBufferSawAlpha| in headerAvailable, with the change described above in mind, since it will be called before *each* frame is decoded. https://codereview.chromium.org/2386453003/diff/620001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:661: // Now, if we're at a DisposeNotSpecified or DisposeKeep frame, then On 2016/11/29 16:30:52, scroggo_chromium wrote: > These comments make it sound like the first block does something (check the > alpha of the prior frame) and the second block does something different, but > really the first just initializes a pointer, and the second does all the checks. > Maybe merge the comments together? > > On another note, when you say "we" on this line, it is not clear to me which > frame you mean. (I think you mean prevBuffer?) Yes, this is somewhat confusion. I've merged and clarified the comments. https://codereview.chromium.org/2386453003/diff/620001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:668: // rect, we know the current frame has no alpha. On 2016/11/29 16:30:52, scroggo_chromium wrote: > FWIW, I think you could take this further - if prevBuffer *does* have alpha, but > only in the rectangle which is covered by this one frame, you could still say > that this one has no alpha. But I think determining that would be tricky (-er). It is not necessarily tricky, but computationally expensive, since the runtime is O(width * height). I don't think that is worth it. https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:79: if (m_isParsing) On 2016/11/29 16:30:52, scroggo_chromium wrote: > It seems awkward that the reader calls a method that behaves differently > depending on the state of the decoder. Would it be possible to instead call a > different method? I considered this, but this implementation is a little shorter in code and does not add two public methods to PNGImageDecoder. However, I do not have a strong preference for either implementation, and your suggestions nicely separates decoding and parsing failures. So if you feel this is awkward, I'm happy to change it. https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:109: void PNGImageDecoder::decode(size_t index) { On 2016/11/29 16:30:52, scroggo_chromium wrote: > If a client calls frameBufferAtIndex() without calling frameCount(), will we > still parse? Yes, that is done already in ImageDecoder::frameBufferAtIndex() [1], so there's no need to make the call again. [1]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:222: else if (query == PNGParseQuery::PNGMetaDataQuery) On 2016/11/29 16:30:52, scroggo_chromium wrote: > I guess you changed this to an else because we will have already set > m_frameCount to m_reader->frameCount() inside setFailed? Yes. https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:649: // below the reported frame count. On 2016/11/29 16:30:52, scroggo_chromium wrote: > Maybe add a comment explaining why we cannot use m_frameBufferCache? Done. https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h (right): https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:57: // 1) When a non-animated PNG, or the first frame of an animated PNG, can't On 2016/11/29 16:30:52, scroggo_chromium wrote: > nit: these commas are unnecessary Done. https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:59: // are no frames we can show to the client. Also set the state to failed if On 2016/11/29 16:30:52, scroggo_chromium wrote: > For a partial first frame (or partial only frame, for non-animated), we *could* > show what we've decoded so far, but I think showing nothing matches the current > behavior? Yes, the current behavior is that any failure invalidates the decoder. You can stretch your idea even further and show *any* partial frame, but I think it is safer to not do this. I would guess web authors never intend to show partial frames, and we can only guess why the image decoding failed, so I would err on the safe side and not show any frame that can't be completely decoded. https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:12: // The image /LT/f/i/r/png-animated-idat-part-of-animation.png is modified in On 2016/11/29 16:30:52, scroggo_chromium wrote: > Is this abbreviation standard? If not, I think it would be better to spell out > the path. Vim uses this way of abbreviation, but besides that, no. Changed it. https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:443: // succesfull parse, since frame data is not analyzed in that step, but On 2016/11/29 16:30:52, scroggo_chromium wrote: > successful* (two "s"s, one "l") Done. https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:174: // wrong data. Since we are reusing |m_png|, we can call setjmp before On 2016/11/29 16:30:52, scroggo_chromium wrote: > I find this comment confusing. It describes why you deleted the above code / why > we're not sharing a single setjmp. But it doesn't describe what you're doing > here, and viewing this code later on doesn't provide the same context (that you > deleted the above lines). > > I think I'd rather you remove this comment rather than try to explain it better. > Down below we should make sure it's obvious why we have to use different setjmp > calls. Done. https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:204: // struct. This can't be done in one call for animated and non-animated PNGs, On 2016/11/29 16:30:52, scroggo_chromium wrote: > I think "animated and non-animated PNGs" reduces clarity. How about saying > something like: > > "... with the current |m_png| struct. This has to be done after > resetPNGStructPreDecode(), which will have replaced |m_png|." Done. https://codereview.chromium.org/2386453003/diff/760001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/760001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:877: // The first frame is only considered complete if it has been fully decoded. On 2016/11/29 16:30:52, scroggo_chromium wrote: > I think this behavior is wrong. I've gone back to look at our comments in the CL > [1], and it sounds like you agree with me. > > [1] > https://codereview.chromium.org/2386453003/diff/140001/third_party/WebKit/Sou... Yes, I agree. I think it is for the best to return what the comment specifies at ImageDecoder.h, but keep the behavior for static PNGs, which is returning whether the frame status is FrameComplete. https://codereview.chromium.org/2386453003/diff/760001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:881: EXPECT_TRUE(decoder->frameIsCompleteAtIndex(1)); On 2016/11/29 16:30:52, scroggo_chromium wrote: > I think you meant to pass index to frameIsCompleteAtIndex? Done. https://codereview.chromium.org/2386453003/diff/760001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:886: // Also test that a progressive decode of frame 0 only returns true on On 2016/11/29 16:30:52, scroggo_chromium wrote: > Again, I think this is the wrong behavior. Acknowledged.
On 2016/12/02 15:55:36, scroggo_chromium wrote: > https://codereview.chromium.org/2386453003/diff/620001/third_party/WebKit/Sou... > File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp > (right): > > https://codereview.chromium.org/2386453003/diff/620001/third_party/WebKit/Sou... > third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:661: > // Now, if we're at a DisposeNotSpecified or DisposeKeep frame, then > On 2016/11/29 16:30:52, scroggo_chromium wrote: > > These comments make it sound like the first block does something (check the > > alpha of the prior frame) and the second block does something different, but > > really the first just initializes a pointer, and the second does all the > checks. > > Maybe merge the comments together? > > > > On another note, when you say "we" on this line, it is not clear to me which > > frame you mean. (I think you mean prevBuffer?) > > I was just looking at GIFImageDecoder and I realized that this is copied > directly from there. (Another opportunity to share code!) It is :) I will upload a CL for this later today.
Changes 1gtm. I'll want to take another look over it once you rebase on top of your refactoring changes. https://codereview.chromium.org/2386453003/diff/600001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/600001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:407: !m_colorSpaceSet) { On 2016/12/02 16:08:41, joostouwerling wrote: > On 2016/11/29 16:30:51, scroggo_chromium wrote: > > It's too bad we had to introduce a new variable for this. Since we are > providing > > chunks directly to the reader, my first thought was you could move this block > > outside of headerAvailable, and just call it once when you need to. But then > > this code would get executed *after* the below block (if > > (!hasEmbeddedColorSpace)), which expects this to have already happened. > > > > Up above, you used isDecodedSizeAvailable to determine whether this is the > first > > frame. Should we use that same check here? > > I think that it works fine when some blocks are moved around - see the new > patch. Since we can only check for isDecodedSizeAvailable() once (without > introducing an extra variable, which would not be so much of an issue, but > better try to prevent it), I moved the isDecodedSizeAvailable block to after > PNG_COLOR_TYPE_GRAY setting. I don't think that is a problem since the code in > between does not rely on the size. The same holds for failing when the image is > too large - there is not much harm in the extra png getters and setters that are > now done before the size check. > > The width and height variables are now read through png_get_IHDR instead of > png_get_image_width/height, but according to [1], that is not different, since > we're using png_uint_32 for these variables. > > [1] > https://github.com/glennrp/libpng/blob/d65a92b951079d315e17e20ba9e7b8423d1939... Exactly what I was thinking :) https://codereview.chromium.org/2386453003/diff/600001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:569: } else { On 2016/12/02 16:08:41, joostouwerling wrote: > On 2016/11/29 16:30:51, scroggo_chromium wrote: > > Maybe add a comment that this is ImageFrame::BlendAtopPreviousFrame? (Or make > it > > a switch statement?) > > Done. I prefer the if-else since there are only two options, and the default > case for a switch statement is a little awkward, though the if-else has an > implicit default as well. Sgtm. > What is in general the approach within Blink, for a > default that you'd never expect? A DCHECK(false) to assert we should not reach > that case? It looks like (would, if you used a switch statement) you want UNREACHABLE(). https://cs.chromium.org/chromium/src/third_party/WebKit/Source/wtf/dtoa/strto... https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:79: if (m_isParsing) On 2016/12/02 16:08:42, joostouwerling wrote: > On 2016/11/29 16:30:52, scroggo_chromium wrote: > > It seems awkward that the reader calls a method that behaves differently > > depending on the state of the decoder. Would it be possible to instead call a > > different method? > > I considered this, but this implementation is a little shorter in code and does > not add two public methods to PNGImageDecoder. However, I do not have a strong > preference for either implementation, and your suggestions nicely separates > decoding and parsing failures. So if you feel this is awkward, I'm happy to > change it. I'm curious to see what that looks like.
I will rebase it later today. https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/660001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:79: if (m_isParsing) On 2016/12/02 21:28:49, scroggo_chromium wrote: > On 2016/12/02 16:08:42, joostouwerling wrote: > > On 2016/11/29 16:30:52, scroggo_chromium wrote: > > > It seems awkward that the reader calls a method that behaves differently > > > depending on the state of the decoder. Would it be possible to instead call > a > > > different method? > > > > I considered this, but this implementation is a little shorter in code and > does > > not add two public methods to PNGImageDecoder. However, I do not have a strong > > preference for either implementation, and your suggestions nicely separates > > decoding and parsing failures. So if you feel this is awkward, I'm happy to > > change it. > > I'm curious to see what that looks like. As discussed in person, one obstacle for this implementation comes with a refactor I made in another CL [1]. ImageDecoder::initFrameBuffer calls ImageDecoder::setFailed whereas with your suggested implementation it would need to call PNGImageDecoder::failureDuringDecode (or so). But that is incompatible with the GIF and WebP decoders. [1] https://codereview.chromium.org/2495183002/
Patchset #25 (id:860001) has been deleted
Patchset #25 (id:880001) has been deleted
Patchset #25 (id:900001) has been deleted
PTAL at patch set 25. This is a rebase of the current master branch which incorporates the latest work on color correction, but more importantly for this CL, my refactors on ImageDecoder and ImageDecoderTestHelpers. There are 800 LoC less in comparison to patch 24, so that effort was well worth it. :) This is also the point where we can start looking at landing this CL, so we all need to take a good careful look to make sure we haven't missed anything.
https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:49: sk_sp<SkColorSpace> colorSpace, I'm guessing colorSpace changed since you touched code around here. Please change this back, since you didn't deliberately change it from targetColorSpace. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:52: : ImageDecoder(alphaOption, colorOptions, colorSpace, maxDecodedBytes), std::move(targetColorSpace) https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:110: break; It is interesting that these two exit conditions are handled differently (return versus break), but in practice, they do the same thing, since nothing follows the loop. I notice that both GIF [1] and WEBP [2] follow the loops with a check to see if the file is truncated. That's probably why they have similar loops with a return and a break. I'm guessing that section is not here because of crbug.com/267883 ? It's a little weird that this one is different, but I also don't want you to add the wrong behavior here, and it's probably more complicated than just removing that block from the others... [1] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... [2] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:116: m_reader->clearDecodeState(frameIndex); I think it's safe to always call this method, instead of checking the frame's status. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:123: png_structp png = m_reader->pngPtr(); Why did you create a local variable that would only be used once? Was it to fin the if statement on one line? Edit - looks like this code was moved from elsewhere. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h (right): https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:89: // When the disposal method of the frame is DisposeOverwritePrevious, the Is this not already explained in the base class? Clients won't call this method directly, so I don't think the explanation is necessary here. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:97: size_t clearCacheExceptTwoFrames(size_t, size_t); I thought this was moved to the base class? https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:115: // This flag prevents from calling parse() again, which could change the frame What if instead we set the reader's m_parseCompleted to true? Then do we still need this variable? https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:96: #if USE(QCMSLIB) QCMSLIB has been removed. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:549: // does not need to be bothered with parsing the contents. This also enables I don't think we should speculate about whether the code is bothered. How about "does not need to parse the contents" https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:598: if (isChunk(chunk, "fcTL")) I think it cannot be both acTL and fcTL. Make this an else if? https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h (right): https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:84: // This is a callback for libpng, when it encounters an unknown chunk. nit: technically this is called by the callback, not a callback itself. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:151: void resetPNGStructPreDecode(); I find this name long and awkward, and I'm not sure "PreDecode" adds useful information. Why not call it "resetPNGStruct"?
https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:49: sk_sp<SkColorSpace> colorSpace, On 2016/12/07 13:44:44, scroggo_chromium wrote: > I'm guessing colorSpace changed since you touched code around here. Please > change this back, since you didn't deliberately change it from targetColorSpace. Done. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:52: : ImageDecoder(alphaOption, colorOptions, colorSpace, maxDecodedBytes), On 2016/12/07 13:44:44, scroggo_chromium wrote: > std::move(targetColorSpace) Done. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:110: break; On 2016/12/07 13:44:44, scroggo_chromium wrote: > It is interesting that these two exit conditions are handled differently (return > versus break), but in practice, they do the same thing, since nothing follows > the loop. > > I notice that both GIF [1] and WEBP [2] follow the loops with a check to see if > the file is truncated. That's probably why they have similar loops with a return > and a break. > > I'm guessing that section is not here because of crbug.com/267883 ? It's a > little weird that this one is different, but I also don't want you to add the > wrong behavior here, and it's probably more complicated than just removing that > block from the others... > > [1] > https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... > [2] > https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... I considered those checks, but even without relating it to the bug you referred, I thought it was not the behavior we intended for APNG. My arguing was that, if we were able to successfully decode all frames, why would we invalidate the complete image if there is no end-of-image indicated? The image is broken but there are frames that were successfully decoded, and we can still show those. And with the current implementation of PNGImageDecoder::setFailed the net result of that check would be very similar to not checking it - on the first glance, only the last frame would be discarded in that case. We could, of course, call ImageDecoder::setFailed, but as I've explained, I don't think that is the right behavior. I improved the code to return with a single if. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:116: m_reader->clearDecodeState(frameIndex); On 2016/12/07 13:44:44, scroggo_chromium wrote: > I think it's safe to always call this method, instead of checking the frame's > status. Done. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:123: png_structp png = m_reader->pngPtr(); On 2016/12/07 13:44:44, scroggo_chromium wrote: > Why did you create a local variable that would only be used once? Was it to fin > the if statement on one line? > > Edit - looks like this code was moved from elsewhere. It was copied from initFrameBuffer, yes. But we don't need the separate variable, so I removed it. The code does not look really bad when it is spread over two lines. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h (right): https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:89: // When the disposal method of the frame is DisposeOverwritePrevious, the On 2016/12/07 13:44:44, scroggo_chromium wrote: > Is this not already explained in the base class? Clients won't call this method > directly, so I don't think the explanation is necessary here. The base class explains how this method is used (i.e. by initFrameBuffer to determine if it can be taken over), and this comment explains why this condition is as it is for APNG. I think that adds value. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:97: size_t clearCacheExceptTwoFrames(size_t, size_t); On 2016/12/07 13:44:44, scroggo_chromium wrote: > I thought this was moved to the base class? Yes. Removed it. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.h:115: // This flag prevents from calling parse() again, which could change the frame On 2016/12/07 13:44:44, scroggo_chromium wrote: > What if instead we set the reader's m_parseCompleted to true? Then do we still > need this variable? That'd work for parsing errors, but not for decoding errors, since parseCompleted may already be true at that point. Edit: if the check for parseCompleted() is moved to the first line of PNGImageDecoder::parse(), it'll work. What we try to prevent is getting to is else if (query == PNGParseQuery::PNGMetaDataQuery) m_frameCount = m_reader->frameCount(); and if we'd return already at the beginning of parse(), that'll be avoided. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp (right): https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:96: #if USE(QCMSLIB) On 2016/12/07 13:44:44, scroggo_chromium wrote: > QCMSLIB has been removed. Done. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:549: // does not need to be bothered with parsing the contents. This also enables On 2016/12/07 13:44:44, scroggo_chromium wrote: > I don't think we should speculate about whether the code is bothered. How about > > "does not need to parse the contents" Done. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp:598: if (isChunk(chunk, "fcTL")) On 2016/12/07 13:44:44, scroggo_chromium wrote: > I think it cannot be both acTL and fcTL. Make this an else if? Done. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h (right): https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:84: // This is a callback for libpng, when it encounters an unknown chunk. On 2016/12/07 13:44:44, scroggo_chromium wrote: > nit: technically this is called by the callback, not a callback itself. Done. https://codereview.chromium.org/2386453003/diff/920001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:151: void resetPNGStructPreDecode(); On 2016/12/07 13:44:44, scroggo_chromium wrote: > I find this name long and awkward, and I'm not sure "PreDecode" adds useful > information. Why not call it "resetPNGStruct"? Done.
I've moved the initialization of PNGImageDecoder::m_reader to the constructor in patch set 27. Since we don't delete it anymore, because we need it to decode a single frame, we can remove PNGImageDecoder::m_offset and don't need to check for the existence of |m_reader| at two points in the code.
https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:61: m_reader = wrapUnique(new PNGImageReader(this, offset)); Now that m_reader has the same lifetime as PNGImageDecoder, why not make it a full member, instead of holding a pointer to it? https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:68: // of frames there were parsed. We don't want to set the decoder to the failed nit: I think this sounds better if you switch "there" to "that" https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:118: m_frameBufferCache[frameIndex].clearPixelData(); For consistency with the other decoders, you might call ImageDecoder::clearFrameBuffer(frameIndex) instead of this call. It does the exact same thing. I don't have a strong preference though. https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:513: if (failed()) I just noticed that the other implementations do not check for failure, although GIF indirectly checks for it (it checks whether it has an m_reader, which it deletes in setFailed). Why is this one different? https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h (right): https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:94: void setParseCompleted(bool completed) { m_parseCompleted = completed; }; nit: this is only called with true, and I think it would be weird to call it with false. Maybe change it to setParseCompleted() { m_parseCompleted = true; }
https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:61: m_reader = wrapUnique(new PNGImageReader(this, offset)); On 2016/12/08 13:59:09, scroggo_chromium wrote: > Now that m_reader has the same lifetime as PNGImageDecoder, why not make it a > full member, instead of holding a pointer to it? That's difficult since they cross reference each other, so you can't include each others header files. PNGImageReader uses PNGImageDecoder as a pointer but I can't forward declare it since it specifies PNGImageDecoder::PNGParseQuery in the header file. https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:68: // of frames there were parsed. We don't want to set the decoder to the failed On 2016/12/08 13:59:09, scroggo_chromium wrote: > nit: I think this sounds better if you switch "there" to "that" Done. https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:118: m_frameBufferCache[frameIndex].clearPixelData(); On 2016/12/08 13:59:09, scroggo_chromium wrote: > For consistency with the other decoders, you might call > ImageDecoder::clearFrameBuffer(frameIndex) instead of this call. It does the > exact same thing. I don't have a strong preference though. Calling ImageDecoder::clearFrameBuffer may be the better option, in the case code or requirements change later on, so we don't need to make modifications in multiple places. https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:513: if (failed()) On 2016/12/08 13:59:09, scroggo_chromium wrote: > I just noticed that the other implementations do not check for failure, although > GIF indirectly checks for it (it checks whether it has an m_reader, which it > deletes in setFailed). Why is this one different? I'm not entirely sure why it was there in the first place, and I don't think it is necessary. The implementation in the base class does not check for failed(), and since the current implementation of setFailed only invalidates the decoder when the image is not animated or when there are no frames to show, this is check has merely the same effect. That is, ImageDecoder::frameIsCompleteAtIndex only returns false when the frame is not successfully decoded, and if that happens through a failure, it would also return false. But since we're already calling ImageDecoder::frameIsCompleteAtIndex for non-animated images, the net effect is equivalent when failed() returns true. https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h (right): https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:94: void setParseCompleted(bool completed) { m_parseCompleted = completed; }; On 2016/12/08 13:59:09, scroggo_chromium wrote: > nit: this is only called with true, and I think it would be weird to call it > with false. Maybe change it to > > setParseCompleted() { m_parseCompleted = true; } Done.
https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:61: m_reader = wrapUnique(new PNGImageReader(this, offset)); On 2016/12/08 16:28:39, joostouwerling wrote: > On 2016/12/08 13:59:09, scroggo_chromium wrote: > > Now that m_reader has the same lifetime as PNGImageDecoder, why not make it a > > full member, instead of holding a pointer to it? > > That's difficult since they cross reference each other, so you can't include > each others header files. PNGImageReader uses PNGImageDecoder as a pointer but I > can't forward declare it since it specifies PNGImageDecoder::PNGParseQuery in > the header file. What about if you move PNGParseQuery to PNGImageReader? That is probably more appropriate anyway.
Patchset #29 (id:1000001) has been deleted
https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/960001/third_party/WebKit/Sou... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:61: m_reader = wrapUnique(new PNGImageReader(this, offset)); On 2016/12/08 16:33:25, scroggo_chromium wrote: > On 2016/12/08 16:28:39, joostouwerling wrote: > > On 2016/12/08 13:59:09, scroggo_chromium wrote: > > > Now that m_reader has the same lifetime as PNGImageDecoder, why not make it > a > > > full member, instead of holding a pointer to it? > > > > That's difficult since they cross reference each other, so you can't include > > each others header files. PNGImageReader uses PNGImageDecoder as a pointer but > I > > can't forward declare it since it specifies PNGImageDecoder::PNGParseQuery in > > the header file. > > What about if you move PNGParseQuery to PNGImageReader? That is probably more > appropriate anyway. Done.
Some nits on the commit message: > Implement an internal query system in PNGImageDecoder to parse the size and > frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageReader::PNGParseQuery* > With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This > will parse the complete data stream it has so far and reports frames when > they're fully seen, Make a note about an exception for the first frame? > Frame decoding for animated images. For now, only "simple" frame decoding is > implemented, alpha blending and disposal operations are yet to be completed. This is no longer true. > Single frames are decoded by mocking them as complete PNG images. This is > necessary since libpng does not support animated PNG. This works by drecreating recreating* Otherwise, the code lgtm. Let's hold off on submission until we figure out the right way to introduce it. You'll still need to get approval for the extra images as well.
The CQ bit was checked by joostouwerling@google.com to run a CQ dry run
Dry run: CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
The CQ bit was unchecked by commit-bot@chromium.org
Dry run: Try jobs failed on following builders: mac_chromium_compile_dbg_ng on master.tryserver.chromium.mac (JOB_FAILED, http://build.chromium.org/p/tryserver.chromium.mac/builders/mac_chromium_comp...) mac_chromium_rel_ng on master.tryserver.chromium.mac (JOB_FAILED, http://build.chromium.org/p/tryserver.chromium.mac/builders/mac_chromium_rel_...) win_chromium_compile_dbg_ng on master.tryserver.chromium.win (JOB_FAILED, http://build.chromium.org/p/tryserver.chromium.win/builders/win_chromium_comp...)
The CQ bit was checked by joostouwerling@google.com to run a CQ dry run
Dry run: CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
The CQ bit was unchecked by commit-bot@chromium.org
Dry run: Try jobs failed on following builders: cast_shell_android on master.tryserver.chromium.android (JOB_FAILED, https://build.chromium.org/p/tryserver.chromium.android/builders/cast_shell_a...) mac_chromium_rel_ng on master.tryserver.chromium.mac (JOB_FAILED, http://build.chromium.org/p/tryserver.chromium.mac/builders/mac_chromium_rel_...) win_chromium_compile_dbg_ng on master.tryserver.chromium.win (JOB_FAILED, http://build.chromium.org/p/tryserver.chromium.win/builders/win_chromium_comp...)
The CQ bit was checked by joostouwerling@google.com to run a CQ dry run
Dry run: CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
The CQ bit was unchecked by commit-bot@chromium.org
Dry run: Try jobs failed on following builders: win_chromium_x64_rel_ng on master.tryserver.chromium.win (JOB_FAILED, http://build.chromium.org/p/tryserver.chromium.win/builders/win_chromium_x64_...)
The CQ bit was checked by joostouwerling@google.com to run a CQ dry run
Dry run: CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
The CQ bit was unchecked by commit-bot@chromium.org
Dry run: Try jobs failed on following builders: linux_chromium_rel_ng on master.tryserver.chromium.linux (JOB_FAILED, http://build.chromium.org/p/tryserver.chromium.linux/builders/linux_chromium_...)
The CQ bit was checked by joostouwerling@google.com to run a CQ dry run
Dry run: CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
The CQ bit was unchecked by commit-bot@chromium.org
Dry run: Try jobs failed on following builders: chromeos_daisy_chromium_compile_only_ng on master.tryserver.chromium.linux (JOB_TIMED_OUT, no build URL)
https://codereview.chromium.org/2386453003/diff/1080001/third_party/WebKit/So... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/1080001/third_party/WebKit/So... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:165: case 0: Nit: I like that there is a link there describing these magic numbers in "case 0:" for example, I would rather those magic number have a label. Like case APNGDisposeOpNone: Then the link can go by those definitions.
The CQ bit was checked by joostouwerling@google.com to run a CQ dry run
Dry run: CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
The CQ bit was unchecked by commit-bot@chromium.org
Dry run: Try jobs failed on following builders: chromium_presubmit on master.tryserver.chromium.linux (JOB_FAILED, http://build.chromium.org/p/tryserver.chromium.linux/builders/chromium_presub...)
https://codereview.chromium.org/2386453003/diff/1080001/third_party/WebKit/So... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp (right): https://codereview.chromium.org/2386453003/diff/1080001/third_party/WebKit/So... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp:165: case 0: On 2016/12/11 22:08:26, cblume wrote: > Nit: I like that there is a link there describing these magic numbers in "case > 0:" for example, I would rather those magic number have a label. > > Like > case APNGDisposeOpNone: > > Then the link can go by those definitions. Thanks! I agree that it is better. There was also an opportunity to use them in PNGImageReader.
lgtm https://codereview.chromium.org/2386453003/diff/1100001/third_party/WebKit/So... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h (right): https://codereview.chromium.org/2386453003/diff/1100001/third_party/WebKit/So... third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.h:43: const uint8_t kAPNGDisposeKeep = 0; nit: Maybe leave a blank line in between the Blend modes and Dispose modes?
Disabled the LayoutTest 2d.drawImage.animated.apng. Stated the why's as a comment in the test.
https://codereview.chromium.org/2386453003/diff/1120001/third_party/WebKit/La... File third_party/WebKit/LayoutTests/canvas/philip/tests/2d.drawImage.animated.poster.html (right): https://codereview.chromium.org/2386453003/diff/1120001/third_party/WebKit/La... third_party/WebKit/LayoutTests/canvas/philip/tests/2d.drawImage.animated.poster.html:15: // This test verifies that the poster frame of an animated PNG is drawn on a Can we instead delete the test?
https://codereview.chromium.org/2386453003/diff/1120001/third_party/WebKit/La... File third_party/WebKit/LayoutTests/canvas/philip/tests/2d.drawImage.animated.poster.html (right): https://codereview.chromium.org/2386453003/diff/1120001/third_party/WebKit/La... third_party/WebKit/LayoutTests/canvas/philip/tests/2d.drawImage.animated.poster.html:15: // This test verifies that the poster frame of an animated PNG is drawn on a On 2016/12/12 16:06:39, scroggo_chromium wrote: > Can we instead delete the test? I'm hesitant about that since it is part of a larger suite [1] so it may be merged in again at a later point in time, if it is deleted. This method of "disabling" more clearly states why we don't want this to be tested. But if you feel that that is not a problem, I'll happily delete it - the test does not really do anything now. [1] https://cs.chromium.org/chromium/src/third_party/WebKit/LayoutTests/canvas/ph...
The CQ bit was checked by joostouwerling@google.com
The patchset sent to the CQ was uploaded after l-g-t-m from scroggo@chromium.org Link to the patchset: https://codereview.chromium.org/2386453003/#ps1140001 (title: "Change test to prevent access to possible invalid memory.")
The CQ bit was unchecked by joostouwerling@google.com
The CQ bit was checked by joostouwerling@google.com to run a CQ dry run
Dry run: CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
non-owner LGTM
Description was changed from ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageDecoder::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images. For now, only "simple" frame decoding is implemented, alpha blending and disposal operations are yet to be completed. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by drecreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. Failures during decoding or parsing are handled differently, based on the image and state of the decoder: 1) When a non-animated PNG, or the first frame of an animated PNG, can't be decoded or parsed, set the decoder to the failed state, because there are no frames we can show to the client. Also set the state to failed if a parse error occurs before any frames were received. 2) When a decoding failure occurs for non-first frames, we still want to show earlier frames. This means, if frame n has a failure during decoding, the frame count is adjusted to n - 1. 3) When a parsing failure occurs, the frame count is adjusted to the number of successfully parsed frames, since we can still show those. BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ========== to ========== Implement Animated PNG. Implement an internal query system in PNGImageDecoder to parse the size and frame data, by passing a PNGImageReader::PNGParseQuery to PNGImageDecoder::parse(). This utilizes the PNGImageReader to answer the query. For a PNGSizeQuery, PNGImageReader processes the stream until the first IDAT frame chunk. At this point, the size should have been decoded from the IHDR chunk. With a PNGFrameCountQuery, the frame data is parsed by the PNGImageReader. This will parse the complete data stream it has so far and reports frames when they're fully seen, so PNGImageDecoder won't report information on partial frames. This prevents decoding half of the frame on top of the other half of the previous frame, in case of blending and/or disposal. The expection for this is the first frame, which is reported as soon as the starting point of the frame is received, so it can be decoded progressively. When the reader does not encounter an acTL chunk before the first IDAT chunk, the image is considered to be non-animated, and frame count parsing is stopped, even if frames may appear after the IDAT chunk(s). The reason for this is a) that it is required by the specification, so most APNG files should have it, and b) that it does not slow down decoding for non-animated PNG's, which is the something like 99.9% of the use cases for now. The following behavioral decisions are implemented: - An fcTL with the wrong chunk length results in a failed decoder, since the decoder can't make any sensible predicitions about missing data. - An acTL with the wrong chunk length is ignored, so the resulting image will be non animated. - The sequence numbers in fcTL and fdAT chunks are ignored. - The frame count in the acTL chunk is ignored. Instead, the actual number of complete frames that are in the stream is reported. - A frame with a frame rect that falls outside of the original image's rect is clipped, so it fits within the image. The frame data outside of the clipped rectangle is ignored. Reimplement frame decoding for non-animated PNGs. When the decoder reads a non-animated PNG, it will reuse the existing libpng pointers and continue processing the data stream from the first IDAT chunk, where it ended after a PNGFrameCountQuery. This is similar to the old approach and is still used to retain performance. Progressive decoding is supported. Frame decoding for animated images is implemented. Single frames are decoded by mocking them as complete PNG images. This is necessary since libpng does not support animated PNG. This works by recreating libpng pointers, and reprocess the PNG header data, but with the frame's width and height. Then, the frame data is processed, by converting fdAT chunks to IDAT chunks. With this approach, the existing infrastructure in PNGImageDecoder to write rows into an ImageFrame does not need many changes. Progressive decoding is supported for non-animated PNGs and for the first frame of animated PNGs. Failures during decoding or parsing are handled differently, based on the image and state of the decoder: 1) When a non-animated PNG, or the first frame of an animated PNG, can't be decoded or parsed, set the decoder to the failed state, because there are no frames we can show to the client. Also set the state to failed if a parse error occurs before any frames were received. 2) When a decoding failure occurs for non-first frames, we still want to show earlier frames. This means, if frame n has a failure during decoding, the frame count is adjusted to n - 1. 3) When a parsing failure occurs, the frame count is adjusted to the number of successfully parsed frames, since we can still show those. BUG=1171 BUG=437662 R=scroggo@google.com,cblume@google.com ==========
The CQ bit was unchecked by commit-bot@chromium.org
Dry run: Try jobs failed on following builders: mac_chromium_rel_ng on master.tryserver.chromium.mac (JOB_FAILED, http://build.chromium.org/p/tryserver.chromium.mac/builders/mac_chromium_rel_...)
Patchset #39 (id:1220001) has been deleted
Patchset #38 (id:1200001) has been deleted
The CQ bit was checked by joostouwerling@google.com to run a CQ dry run
Dry run: CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
I've changed PNGImageReader so it passes all tests - more specifically, it was leaking memory through setjmp calls that did not unwind the stack. By that, it did not destroy the FastSharedBufferReaders, which kept a reference to the byte stream - which would therefore never be deleted.
The CQ bit was unchecked by commit-bot@chromium.org
Dry run: This issue passed the CQ dry run.
On 2016/12/13 20:13:08, joostouwerling wrote: > I've changed PNGImageReader so it passes all tests - more specifically, it was > leaking memory through setjmp calls that did not unwind the stack. By that, it > did not destroy the FastSharedBufferReaders, which kept a reference to the byte > stream - which would therefore never be deleted. I'm glad to hear it. If I'm honest, I really dislike all the setjmp calls we have. There are still a few. But I believe that's just part of how this decoder works? Is there any way we can get rid of them?
On 2016/12/13 21:09:55, cblume wrote: > On 2016/12/13 20:13:08, joostouwerling wrote: > > I've changed PNGImageReader so it passes all tests - more specifically, it was > > leaking memory through setjmp calls that did not unwind the stack. By that, it > > did not destroy the FastSharedBufferReaders, which kept a reference to the > byte > > stream - which would therefore never be deleted. > > I'm glad to hear it. > > If I'm honest, I really dislike all the setjmp calls we have. There are still a > few. > But I believe that's just part of how this decoder works? > Is there any way we can get rid of them? I agree with you, they're very prone to error and this bug was a good example of why we should avoid it. They grew into this solution because they were in the original PNGImageDecoder, but I should have resolved this more proactively. But yeah, I have been thinking about a workaround for the past few days as well. The problem is that png_process_data does not return or throw an error - it will call an error callback function. We can work around this by having this callback call something like "m_reader->notifyPngError()". This is possible since libpng can keep a pointer to an object, which can be the reader. Then every call to png_process_data must be followed by something along the lines of "if (wasNotifiedOfPngError()) { ... error handling ... }". This will add some code clutter but I don't see something that would stop us from implementing it for now. I will try to code an example in the next three days...
On 2016/12/13 21:42:08, joostouwerling wrote: > On 2016/12/13 21:09:55, cblume wrote: > > On 2016/12/13 20:13:08, joostouwerling wrote: > > > I've changed PNGImageReader so it passes all tests - more specifically, it > was > > > leaking memory through setjmp calls that did not unwind the stack. By that, > it > > > did not destroy the FastSharedBufferReaders, which kept a reference to the > > byte > > > stream - which would therefore never be deleted. > > > > I'm glad to hear it. > > > > If I'm honest, I really dislike all the setjmp calls we have. There are still > a > > few. > > But I believe that's just part of how this decoder works? > > Is there any way we can get rid of them? > > I agree with you, they're very prone to error and this bug was a good example of > why we should avoid it. They grew into this solution because they were in the > original PNGImageDecoder, but I should have resolved this more proactively. > > But yeah, I have been thinking about a workaround for the past few days as well. > The problem is that png_process_data does not return or throw an error - it will > call an error callback function. We can work around this by having this callback > call something like "m_reader->notifyPngError()". This is possible since libpng > can keep a pointer to an object, which can be the reader. Then every call to > png_process_data must be followed by something along the lines of "if > (wasNotifiedOfPngError()) { ... error handling ... }". This will add some code > clutter but I don't see something that would stop us from implementing it for > now. > > I will try to code an example in the next three days... Reverting the above.. Had a chat with scroggo@ about this and libpng calls longjmp if we don't do it [1], which means we have to handle the jumps anyway. [1] https://cs.chromium.org/chromium/src/third_party/skia/third_party/libpng/pnge...
https://codereview.chromium.org/2386453003/diff/1120001/third_party/WebKit/La... File third_party/WebKit/LayoutTests/canvas/philip/tests/2d.drawImage.animated.poster.html (right): https://codereview.chromium.org/2386453003/diff/1120001/third_party/WebKit/La... third_party/WebKit/LayoutTests/canvas/philip/tests/2d.drawImage.animated.poster.html:15: // This test verifies that the poster frame of an animated PNG is drawn on a On 2016/12/12 17:53:20, joostouwerling wrote: > On 2016/12/12 16:06:39, scroggo_chromium wrote: > > Can we instead delete the test? > > I'm hesitant about that since it is part of a larger suite [1] so it may be > merged in again at a later point in time, if it is deleted. This method of > "disabling" more clearly states why we don't want this to be tested. But if you > feel that that is not a problem, I'll happily delete it - the test does not > really do anything now. > > [1] > https://cs.chromium.org/chromium/src/third_party/WebKit/LayoutTests/canvas/ph... Are you saying that we copy those tests here from time to time, and your comment will prevent someone from re-adding it? I find the comment confusing. It starts off by explaining what the test does (maybe in too much detail), and then eventually states that the test is disabled. I would make it more explicit that the test is bad, and start off with that. Something like: This test is disabled because it is wrong. It expects <this behavior>, which should only happen if we do *not* support APNG. Now that we support APNG, this test fails. https://codereview.chromium.org/2386453003/diff/1240001/third_party/WebKit/So... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/1240001/third_party/WebKit/So... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:290: EXPECT_EQ(nullptr, decoder->frameBufferAtIndex(frameIndex)); If expectFailure is true, should perform a different test? e.g. check the status of the frame? https://codereview.chromium.org/2386453003/diff/1240001/third_party/WebKit/So... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:770: ImageFrame* frame0 = decoder->frameBufferAtIndex(0); nit: Now that you do not hang onto frame0 (was that the memory concern?), you do not need to have this local variable. https://codereview.chromium.org/2386453003/diff/1240001/third_party/WebKit/So... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:777: ASSERT_EQ(kNotFound, Maybe a comment here that the point is that this did not decode frame 0?
https://codereview.chromium.org/2386453003/diff/1120001/third_party/WebKit/La... File third_party/WebKit/LayoutTests/canvas/philip/tests/2d.drawImage.animated.poster.html (right): https://codereview.chromium.org/2386453003/diff/1120001/third_party/WebKit/La... third_party/WebKit/LayoutTests/canvas/philip/tests/2d.drawImage.animated.poster.html:15: // This test verifies that the poster frame of an animated PNG is drawn on a On 2016/12/14 14:18:06, scroggo_chromium wrote: > On 2016/12/12 17:53:20, joostouwerling wrote: > > On 2016/12/12 16:06:39, scroggo_chromium wrote: > > > Can we instead delete the test? > > > > I'm hesitant about that since it is part of a larger suite [1] so it may be > > merged in again at a later point in time, if it is deleted. This method of > > "disabling" more clearly states why we don't want this to be tested. But if > you > > feel that that is not a problem, I'll happily delete it - the test does not > > really do anything now. > > > > [1] > > > https://cs.chromium.org/chromium/src/third_party/WebKit/LayoutTests/canvas/ph... > > Are you saying that we copy those tests here from time to time, and your comment > will prevent someone from re-adding it? That could be the case. I'm not sure if it is synced with the test repository. This more clearly states why we don't think this is a good test, even though it is imported from another test suite. > I find the comment confusing. It starts off by explaining what the test does > (maybe in too much detail), and then eventually states that the test is > disabled. I would make it more explicit that the test is bad, and start off with > that. Something like: > > This test is disabled because it is wrong. It expects <this behavior>, which > should only happen if we do *not* support APNG. Now that we support APNG, this > test fails. Improved it per your suggestion. https://codereview.chromium.org/2386453003/diff/1240001/third_party/WebKit/So... File third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp (right): https://codereview.chromium.org/2386453003/diff/1240001/third_party/WebKit/So... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:290: EXPECT_EQ(nullptr, decoder->frameBufferAtIndex(frameIndex)); On 2016/12/14 14:18:06, scroggo_chromium wrote: > If expectFailure is true, should perform a different test? e.g. check the status > of the frame? Hmm, I don't think that is necessary if we already verify that it is in the failed state. I made that more explicit with an ASSERT. https://codereview.chromium.org/2386453003/diff/1240001/third_party/WebKit/So... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:770: ImageFrame* frame0 = decoder->frameBufferAtIndex(0); On 2016/12/14 14:18:06, scroggo_chromium wrote: > nit: Now that you do not hang onto frame0 (was that the memory concern?), you do > not need to have this local variable. Done, and yes -- frame 0 was used later on, after more frames were discovered. https://codereview.chromium.org/2386453003/diff/1240001/third_party/WebKit/So... third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp:777: ASSERT_EQ(kNotFound, On 2016/12/14 14:18:06, scroggo_chromium wrote: > Maybe a comment here that the point is that this did not decode frame 0? Done.
Calling this this method for every frame feels a bit unsafe. void createInterlaceBuffer(int size) { m_interlaceBuffer = wrapArrayUnique(new png_byte[size]); } You'll have to be extra careful to avoid a memory leak. I think allocating this buffer just once and re-using it for all frames would be safer, faster, and it should work just fine.
On 2016/12/27 06:51:37, MaxStepin wrote: > Calling this this method for every frame feels a bit unsafe. > > void createInterlaceBuffer(int size) { > m_interlaceBuffer = wrapArrayUnique(new png_byte[size]); > } > > You'll have to be extra careful to avoid a memory leak. > > I think allocating this buffer just once and re-using it for all frames would be > safer, faster, and it should work just fine. Hello Max. :) Thank you for your input. It looks like this CL doesn't change the behavior of createInterlaceBuffer. If we want to do some work there, we should probably do it in a separate bug / CL. But at a cursory glance, I think I agree. createInterlaceBuffer seems to only be called from one location, PNGImageDecoder::rowAvailable https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... And PNGImageDecoder::rowAvailable is only called from pngRowAvailable https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... pngRowAvailable is then passed into libpng's png_set_progressive_read_fn https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... pngRowAvailable is called every time a new row is ready. But we only call createInterlaceBuffer if the frame status is empty. On the first row callback, this is true. After we allocate, we set the frame status to partial. So even though this is called for every row, it only allocates once per frame like you mentioned. After being allocated once per frame, it is only ever read in future row callbacks. When we hit the next frame, the frame status is once again empty. So we allocate again. After we allocate, we assign to the member unique_ptr, which ends up freeing the old frame. This probably wasn't much of a concern before APNG, since there was only ever one frame. It wouldn't end up ever deallocating until the reader is done. But now that we have multiple frames, it will deallocate and reallocate each frame.
On 2017/01/04 01:08:05, cblume wrote: > On 2016/12/27 06:51:37, MaxStepin wrote: > > Calling this this method for every frame feels a bit unsafe. > > > > void createInterlaceBuffer(int size) { > > m_interlaceBuffer = wrapArrayUnique(new png_byte[size]); > > } > > > > You'll have to be extra careful to avoid a memory leak. > > > > I think allocating this buffer just once and re-using it for all frames would > be > > safer, faster, and it should work just fine. > > Hello Max. :) Thank you for your input. > > It looks like this CL doesn't change the behavior of createInterlaceBuffer. > > If we want to do some work there, we should probably do it in a separate bug / > CL. > > > > But at a cursory glance, I think I agree. > > createInterlaceBuffer seems to only be called from one location, > PNGImageDecoder::rowAvailable > https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... > > And PNGImageDecoder::rowAvailable is only called from pngRowAvailable > https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... > > pngRowAvailable is then passed into libpng's png_set_progressive_read_fn > https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... > > pngRowAvailable is called every time a new row is ready. > But we only call createInterlaceBuffer if the frame status is empty. On the > first row callback, this is true. > After we allocate, we set the frame status to partial. > So even though this is called for every row, it only allocates once per frame > like you mentioned. > > > After being allocated once per frame, it is only ever read in future row > callbacks. > When we hit the next frame, the frame status is once again empty. So we allocate > again. > After we allocate, we assign to the member unique_ptr, which ends up freeing the > old frame. > > > This probably wasn't much of a concern before APNG, since there was only ever > one frame. > It wouldn't end up ever deallocating until the reader is done. > But now that we have multiple frames, it will deallocate and reallocate each > frame. As cblume@ mentions, we're not leaking memory because m_interlaceBuffer is a smart pointer. But you are right, we could reuse the buffer from frame to frame, which would avoid the cost of reallocating. There are a few different ways we could handle this, which have their own tradeoffs: - This method is simple, and if the client never asks for another frame, we freed the buffer early. (I just realized that if the decoder fails we do *not* free the buffer. In the existing implementation (i.e. no APNG support), a failed decode deletes the PNGImageReader, which also frees the buffer, so this is a change.) - On the other extreme, we could create the buffer only once, and keep it until the client is done with the PNGImageDecoder. This would mean we pay the cost of allocating only once, but we would be holding onto memory that we do not (at least currently) need. - Another option would be to create it in PNGImageDecoder::decode(), prior to decoding any dependencies, and clear it after the loop (unless the frame was partial). We would still clear it unnecessarily sometimes, but we would only hang on to it when we were confident we still need it.
On 2017/01/06 15:08:40, scroggo_chromium wrote: > On 2017/01/04 01:08:05, cblume wrote: > > On 2016/12/27 06:51:37, MaxStepin wrote: > > > Calling this this method for every frame feels a bit unsafe. > > > > > > void createInterlaceBuffer(int size) { > > > m_interlaceBuffer = wrapArrayUnique(new png_byte[size]); > > > } > > > > > > You'll have to be extra careful to avoid a memory leak. > > > > > > I think allocating this buffer just once and re-using it for all frames > would > > be > > > safer, faster, and it should work just fine. > > > > Hello Max. :) Thank you for your input. > > > > It looks like this CL doesn't change the behavior of createInterlaceBuffer. > > > > If we want to do some work there, we should probably do it in a separate bug / > > CL. > > > > > > > > But at a cursory glance, I think I agree. > > > > createInterlaceBuffer seems to only be called from one location, > > PNGImageDecoder::rowAvailable > > > https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... > > > > And PNGImageDecoder::rowAvailable is only called from pngRowAvailable > > > https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... > > > > pngRowAvailable is then passed into libpng's png_set_progressive_read_fn > > > https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/image... > > > > pngRowAvailable is called every time a new row is ready. > > But we only call createInterlaceBuffer if the frame status is empty. On the > > first row callback, this is true. > > After we allocate, we set the frame status to partial. > > So even though this is called for every row, it only allocates once per frame > > like you mentioned. > > > > > > After being allocated once per frame, it is only ever read in future row > > callbacks. > > When we hit the next frame, the frame status is once again empty. So we > allocate > > again. > > After we allocate, we assign to the member unique_ptr, which ends up freeing > the > > old frame. > > > > > > This probably wasn't much of a concern before APNG, since there was only ever > > one frame. > > It wouldn't end up ever deallocating until the reader is done. > > But now that we have multiple frames, it will deallocate and reallocate each > > frame. > > As cblume@ mentions, we're not leaking memory because m_interlaceBuffer is a > smart pointer. > > But you are right, we could reuse the buffer from frame to frame, which would > avoid the cost of reallocating. There are a few different ways we could handle > this, which have their own tradeoffs: > > - This method is simple, and if the client never asks for another frame, we > freed the buffer early. (I just realized that if the decoder fails we do *not* > free the buffer. In the existing implementation (i.e. no APNG support), a failed > decode deletes the PNGImageReader, which also frees the buffer, so this is a > change.) > - On the other extreme, we could create the buffer only once, and keep it until > the client is done with the PNGImageDecoder. This would mean we pay the cost of > allocating only once, but we would be holding onto memory that we do not (at > least currently) need. > - Another option would be to create it in PNGImageDecoder::decode(), prior to > decoding any dependencies, and clear it after the loop (unless the frame was > partial). We would still clear it unnecessarily sometimes, but we would only > hang on to it when we were confident we still need it. filed crbug.com/701779 |