Bug 1249576 - If the first frame of an APNG doesn't cover the whole image mark the image as transparent. r=edwin, a=lizzard
authorTimothy Nikkel <tnikkel@gmail.com>
Tue, 23 Feb 2016 20:22:00 +0100
changeset 304263 a41f3b09e1218a925889fd24b765c542040d17ff
parent 304262 0dcdb53c422a1c43e9d66092d22a7db292ce8562
child 304264 2b369b9835ad4f979ebfa57f662dbaf4d8399c62
push id9148
push usercbook@mozilla.com
push dateMon, 29 Feb 2016 08:27:20 +0000
treeherdermozilla-aurora@10e1774de80e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersedwin, lizzard
bugs1249576
milestone46.0a2
Bug 1249576 - If the first frame of an APNG doesn't cover the whole image mark the image as transparent. r=edwin, a=lizzard In (non-animated) PNGs the image data is contained in IDAT chunks. In APNGs there are IDAT chunks, which contain the default image, and fDAT chunks, which contain frames of the animation. The default image is sometimes part of the animation (as the first frame), and sometimes not (displayed only by non-APNG aware viewers). The default image must have the same size as in the PNG header chunk. But the fDAT images can be any (smaller) size. So the first frame of a PNG is allowed to be smaller than the whole image size so long as we are in an APNG and the first frame is from an fDAT chunk, not an IDAT chunk. We post transparency if we encounter this case because we don't draw into those pixels on at least the first frame.
image/decoders/nsPNGDecoder.cpp
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -128,19 +128,22 @@ void
 nsPNGDecoder::CheckForTransparency(SurfaceFormat aFormat,
                                    const IntRect& aFrameRect)
 {
   // Check if the image has a transparent color in its palette.
   if (aFormat == SurfaceFormat::B8G8R8A8) {
     PostHasTransparency();
   }
 
-  // PNGs shouldn't have first-frame padding.
-  MOZ_ASSERT_IF(mNumFrames == 0,
-                IntRect(IntPoint(), GetSize()).IsEqualEdges(aFrameRect));
+  // If the first frame of animated image doesn't draw into the whole image,
+  // then record that it is transparent.
+  if (mNumFrames == 0 && !IntRect(IntPoint(), GetSize()).IsEqualEdges(aFrameRect)) {
+    MOZ_ASSERT(HasAnimation());
+    PostHasTransparency();
+  }
 }
 
 // CreateFrame() is used for both simple and animated images
 nsresult
 nsPNGDecoder::CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset,
                           int32_t aWidth, int32_t aHeight,
                           gfx::SurfaceFormat aFormat)
 {
@@ -599,16 +602,22 @@ nsPNGDecoder::info_callback(png_structp 
       MOZ_ASSERT_UNREACHABLE("Doing downscale-during-decode "
                              "for an animated image?");
       decoder->mDownscaler.reset();
     }
   }
 #endif
 
   if (decoder->IsMetadataDecode()) {
+    // If we are animated then the first frame rect is either: 1) the whole image
+    // if the IDAT chunk is part of the animation 2) the frame rect of the first
+    // fDAT chunk otherwise. If we are not animated then we want to make sure to
+    // call PostHasTransparency in the metadata decode if we need to. So it's okay
+    // to pass IntRect(0, 0, width, height) here for animated images; they will
+    // call with the proper first frame rect in the full decode.
     decoder->CheckForTransparency(decoder->format,
                                   IntRect(0, 0, width, height));
 
     // We have the metadata we're looking for, so we don't need to decode any
     // further.
     decoder->mSuccessfulEarlyFinish = true;
     png_longjmp(decoder->mPNG, 1);
   }