Bug 1191114 (Part 1) - Always detect HAS_TRANSPARENCY during the metadata decode. r=tn
authorSeth Fowler <mark.seth.fowler@gmail.com>
Wed, 12 Aug 2015 10:41:02 -0700
changeset 290115 3db1ac46dacc9db0dcafdec5b9314dcd14be2237
parent 290114 009a2d6aa52c3fa1bd78462654234b27ed0a6636
child 290116 fce34ae035239a2014509c8262424dcdccd07e9f
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn
bugs1191114
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1191114 (Part 1) - Always detect HAS_TRANSPARENCY during the metadata decode. r=tn
image/decoders/nsBMPDecoder.cpp
image/decoders/nsGIFDecoder2.cpp
image/decoders/nsGIFDecoder2.h
image/decoders/nsPNGDecoder.cpp
image/decoders/nsPNGDecoder.h
image/test/mochitest/test_has_transparency.html
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -364,16 +364,25 @@ nsBMPDecoder::WriteInternal(const char* 
 
       // Post our size to the superclass
       PostSize(mBIH.width, real_height);
       if (HasError()) {
         // Setting the size led to an error.
         return;
       }
 
+      // We treat BMPs as transparent if they're 32bpp and alpha is enabled, but
+      // also if they use RLE encoding, because the 'delta' mode can skip pixels
+      // and cause implicit transparency.
+      if ((mBIH.compression == BMPINFOHEADER::RLE8) ||
+          (mBIH.compression == BMPINFOHEADER::RLE4) ||
+          (mBIH.bpp == 32 && mUseAlphaData)) {
+        PostHasTransparency();
+      }
+
       // We have the size. If we're doing a metadata decode, we're done.
       if (IsMetadataDecode()) {
         return;
       }
 
       // We're doing a real decode.
       mOldLine = mCurLine = real_height;
 
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -45,16 +45,18 @@ mailing address.
 #include "RasterImage.h"
 
 #include "gfxColor.h"
 #include "gfxPlatform.h"
 #include "qcms.h"
 #include <algorithm>
 #include "mozilla/Telemetry.h"
 
+using namespace mozilla::gfx;
+
 namespace mozilla {
 namespace image {
 
 // GETN(n, s) requests at least 'n' bytes available from 'q', at start of state
 // 's'. Colormaps are directly copied in the resp. global_colormap or the
 // local_colormap of the PAL image frame So a fixed buffer in gif_struct is
 // good enough. This buffer is only needed to copy left-over data from one
 // GifWrite call to the next
@@ -156,47 +158,63 @@ nsGIFDecoder2::BeginGIF()
     return;
   }
 
   mGIFOpen = true;
 
   PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height);
 }
 
+void
+nsGIFDecoder2::CheckForTransparency(IntRect aFrameRect)
+{
+  // Check if the image has a transparent color in its palette.
+  if (mGIFStruct.is_transparent) {
+    PostHasTransparency();
+    return;
+  }
+
+  if (mGIFStruct.images_decoded > 0) {
+    return;  // We only care about first frame padding below.
+  }
+
+  // If we need padding on the first frame, that means we don't draw into part
+  // of the image at all. Report that as transparency.
+  IntRect imageRect(0, 0, mGIFStruct.screen_width, mGIFStruct.screen_height);
+  if (!imageRect.IsEqualEdges(aFrameRect)) {
+    PostHasTransparency();
+  }
+}
+
 //******************************************************************************
 nsresult
 nsGIFDecoder2::BeginImageFrame(uint16_t aDepth)
 {
   MOZ_ASSERT(HasSize());
 
   gfx::SurfaceFormat format;
   if (mGIFStruct.is_transparent) {
     format = gfx::SurfaceFormat::B8G8R8A8;
-    PostHasTransparency();
   } else {
     format = gfx::SurfaceFormat::B8G8R8X8;
   }
 
-  nsIntRect frameRect(mGIFStruct.x_offset, mGIFStruct.y_offset,
-                      mGIFStruct.width, mGIFStruct.height);
+  IntRect frameRect(mGIFStruct.x_offset, mGIFStruct.y_offset,
+                    mGIFStruct.width, mGIFStruct.height);
+
+  CheckForTransparency(frameRect);
 
   // Use correct format, RGB for first frame, PAL for following frames
   // and include transparency to allow for optimization of opaque images
   nsresult rv = NS_OK;
   if (mGIFStruct.images_decoded) {
     // Image data is stored with original depth and palette.
     rv = AllocateFrame(mGIFStruct.images_decoded, GetSize(),
                        frameRect, format, aDepth);
   } else {
-    if (!nsIntRect(nsIntPoint(), GetSize()).IsEqualEdges(frameRect)) {
-      // We need padding on the first frame, which means that we don't draw into
-      // part of the image at all. Report that as transparency.
-      PostHasTransparency();
-    }
-
     // Regardless of depth of input, the first frame is decoded into 24bit RGB.
     rv = AllocateFrame(mGIFStruct.images_decoded, GetSize(),
                        frameRect, format);
   }
 
   mCurrentFrameIndex = mGIFStruct.images_decoded;
 
   return rv;
@@ -684,22 +702,16 @@ nsGIFDecoder2::WriteInternal(const char*
       // individual images can be smaller than the
       // screen size and located with an origin anywhere
       // within the screen.
 
       mGIFStruct.screen_width = GETINT16(q);
       mGIFStruct.screen_height = GETINT16(q + 2);
       mGIFStruct.global_colormap_depth = (q[4]&0x07) + 1;
 
-      if (IsMetadataDecode()) {
-        MOZ_ASSERT(!mGIFOpen, "Gif should not be open at this point");
-        PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height);
-        return;
-      }
-
       // screen_bgcolor is not used
       //mGIFStruct.screen_bgcolor = q[5];
       // q[6] = Pixel Aspect Ratio
       //   Not used
       //   float aspect = (float)((q[6] + 15) / 64.0);
 
       if (q[4] & 0x80) {
         // Get the global colormap
@@ -726,16 +738,19 @@ nsGIFDecoder2::WriteInternal(const char*
       ConvertColormap(mGIFStruct.global_colormap,
                       1<<mGIFStruct.global_colormap_depth);
       GETN(1, gif_image_start);
       break;
 
     case gif_image_start:
       switch (*q) {
         case GIF_TRAILER:
+          if (IsMetadataDecode()) {
+            return;
+          }
           mGIFStruct.state = gif_done;
           break;
 
         case GIF_EXTENSION_INTRODUCER:
           GETN(2, gif_extension);
           break;
 
         case GIF_IMAGE_SEPARATOR:
@@ -938,16 +953,19 @@ nsGIFDecoder2::WriteInternal(const char*
         if (HasError()) {
           // Setting the size led to an error.
           mGIFStruct.state = gif_error;
           return;
         }
 
         // If we were doing a metadata decode, we're done.
         if (IsMetadataDecode()) {
+          IntRect frameRect(mGIFStruct.x_offset, mGIFStruct.y_offset,
+                            mGIFStruct.width, mGIFStruct.height);
+          CheckForTransparency(frameRect);
           return;
         }
       }
 
       // Work around more broken GIF files that have zero image width or height
       if (!mGIFStruct.height || !mGIFStruct.width) {
         mGIFStruct.height = mGIFStruct.screen_height;
         mGIFStruct.width = mGIFStruct.screen_width;
--- a/image/decoders/nsGIFDecoder2.h
+++ b/image/decoders/nsGIFDecoder2.h
@@ -42,16 +42,17 @@ private:
   void      FlushImageData();
   void      FlushImageData(uint32_t fromRow, uint32_t rows);
 
   nsresult  GifWrite(const uint8_t* buf, uint32_t numbytes);
   uint32_t  OutputRow();
   bool      DoLzw(const uint8_t* q);
   bool      SetHold(const uint8_t* buf, uint32_t count,
                     const uint8_t* buf2 = nullptr, uint32_t count2 = 0);
+  void      CheckForTransparency(gfx::IntRect aFrameRect);
 
   inline int ClearCode() const { return 1 << mGIFStruct.datasize; }
 
   int32_t mCurrentRow;
   int32_t mLastFlushedRow;
 
   uint32_t mOldColor;        // The old value of the transparent pixel
 
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -14,16 +14,18 @@
 #include "nsRect.h"
 #include "nspr.h"
 #include "png.h"
 #include "RasterImage.h"
 #include "mozilla/Telemetry.h"
 
 #include <algorithm>
 
+using namespace mozilla::gfx;
+
 namespace mozilla {
 namespace image {
 
 static PRLogModuleInfo*
 GetPNGLog()
 {
   static PRLogModuleInfo* sPNGLog;
   if (!sPNGLog) {
@@ -131,36 +133,41 @@ nsPNGDecoder::~nsPNGDecoder()
 
     // mTransform belongs to us only if mInProfile is non-null
     if (mTransform) {
       qcms_transform_release(mTransform);
     }
   }
 }
 
+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));
+}
+
 // 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)
 {
   MOZ_ASSERT(HasSize());
   MOZ_ASSERT(!IsMetadataDecode());
 
-  if (aFormat == gfx::SurfaceFormat::B8G8R8A8) {
-    PostHasTransparency();
-  }
-
-  nsIntRect frameRect(aXOffset, aYOffset, aWidth, aHeight);
-  if (mNumFrames == 0 &&
-      !nsIntRect(nsIntPoint(), GetSize()).IsEqualEdges(frameRect)) {
-    // We need padding on the first frame, which means that we don't draw into
-    // part of the image at all. Report that as transparency.
-    PostHasTransparency();
-  }
+  IntRect frameRect(aXOffset, aYOffset, aWidth, aHeight);
+  CheckForTransparency(aFormat, frameRect);
 
   // XXX(seth): Some tests depend on the first frame of PNGs being B8G8R8A8.
   // This is something we should fix.
   gfx::SurfaceFormat format = aFormat;
   if (mNumFrames == 0) {
     format = gfx::SurfaceFormat::B8G8R8A8;
   }
 
@@ -263,17 +270,17 @@ nsPNGDecoder::InitInternal()
   if (!mInfo) {
     PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
     png_destroy_read_struct(&mPNG, nullptr, nullptr);
     return;
   }
 
 #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
   // Ignore unused chunks
-  if (mCMSMode == eCMSMode_Off) {
+  if (mCMSMode == eCMSMode_Off || IsMetadataDecode()) {
     png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2);
   }
 
   png_set_keep_unknown_chunks(mPNG, 1, unused_chunks,
                               (int)sizeof(unused_chunks)/5);
 #endif
 
 #ifdef PNG_SET_USER_LIMITS_SUPPORTED
@@ -481,22 +488,16 @@ nsPNGDecoder::info_callback(png_structp 
 
   // Post our size to the superclass
   decoder->PostSize(width, height);
   if (decoder->HasError()) {
     // Setting the size led to an error.
     png_longjmp(decoder->mPNG, 1);
   }
 
-  if (decoder->IsMetadataDecode()) {
-    // We have the size, so we don't need to decode any further.
-    decoder->mSuccessfulEarlyFinish = true;
-    png_longjmp(decoder->mPNG, 1);
-  }
-
   if (color_type == PNG_COLOR_TYPE_PALETTE) {
     png_set_expand(png_ptr);
   }
 
   if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
     png_set_expand(png_ptr);
   }
 
@@ -589,16 +590,26 @@ nsPNGDecoder::info_callback(png_structp 
   if (channels == 1 || channels == 3) {
     decoder->format = gfx::SurfaceFormat::B8G8R8X8;
   } else if (channels == 2 || channels == 4) {
     decoder->format = gfx::SurfaceFormat::B8G8R8A8;
   } else {
     png_longjmp(decoder->mPNG, 1); // invalid number of channels
   }
 
+  if (decoder->IsMetadataDecode()) {
+    decoder->CheckForTransparency(decoder->format,
+                                  IntRect(0, 0, width, height));
+
+    // We have the size and transparency information we're looking for, so we
+    // don't need to decode any further.
+    decoder->mSuccessfulEarlyFinish = true;
+    png_longjmp(decoder->mPNG, 1);
+  }
+
 #ifdef PNG_APNG_SUPPORTED
   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) {
     png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback,
                                  nullptr);
   }
 
   if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) {
     decoder->mFrameIsHidden = true;
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -30,16 +30,19 @@ public:
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override;
   virtual Telemetry::ID SpeedHistogram() override;
 
   nsresult CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset,
                        int32_t aWidth, int32_t aHeight,
                        gfx::SurfaceFormat aFormat);
   void EndImageFrame();
 
+  void CheckForTransparency(gfx::SurfaceFormat aFormat,
+                            const gfx::IntRect& aFrameRect);
+
   // Check if PNG is valid ICO (32bpp RGBA)
   // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
   bool IsValidICO() const
   {
     // If there are errors in the call to png_get_IHDR, the error_callback in
     // nsPNGDecoder.cpp is called.  In this error callback we do a longjmp, so
     // we need to save the jump buffer here. Oterwise we'll end up without a
     // proper callstack.
--- a/image/test/mochitest/test_has_transparency.html
+++ b/image/test/mochitest/test_has_transparency.html
@@ -43,18 +43,22 @@ function testFiles() {
   yield ["red.png", false];
   yield ["transparent.png", true];
   yield ["red.gif", false];
   yield ["transparent.gif", true];
 
   // GIFs with padding on the first frame are always transparent.
   yield ["first-frame-padding.gif", true];
 
-  // JPEGs and BMPs are never transparent.
+  // JPEGs are never transparent.
   yield ["damon.jpg", false];
+
+  // Most BMPs are not transparent. (The TestMetadata GTest, which will
+  // eventually replace this test totally, has coverage for the kinds that can be
+  // transparent.)
   yield ["opaque.bmp", false];
 
   // ICO files which contain BMPs have an additional type of transparency - the
   // AND mask - that warrants separate testing.
   yield ["ico-bmp-opaque.ico", false];
   yield ["ico-bmp-transparent.ico", true];
 
   // SVGs are always transparent.