Bug 1060609 (Part 2) - Add downscale-during-decode support for the PNG decoder. r=tn,f=glennrp
authorSeth Fowler <mark.seth.fowler@gmail.com>
Tue, 01 Sep 2015 14:13:17 -0700
changeset 293024 d6ee72faf4fb071dbd8e0e93fd04a9a59f31a3b5
parent 293023 e177bf9013974fd0f3d07b2360f2b96e04a5e68b
child 293025 40101a28f532d4bd5e7310905994a457e45236e3
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
bugs1060609
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 1060609 (Part 2) - Add downscale-during-decode support for the PNG decoder. r=tn,f=glennrp
image/ImageFactory.cpp
image/decoders/nsPNGDecoder.cpp
image/decoders/nsPNGDecoder.h
--- a/image/ImageFactory.cpp
+++ b/image/ImageFactory.cpp
@@ -30,19 +30,18 @@ namespace image {
 
 /*static*/ void
 ImageFactory::Initialize()
 { }
 
 static bool
 ShouldDownscaleDuringDecode(const nsCString& aMimeType)
 {
-  return aMimeType.EqualsLiteral(IMAGE_JPEG) ||
-         aMimeType.EqualsLiteral(IMAGE_JPG) ||
-         aMimeType.EqualsLiteral(IMAGE_PJPEG);
+  DecoderType type = DecoderFactory::GetDecoderType(aMimeType.get());
+  return type == DecoderType::JPEG || type == DecoderType::PNG;
 }
 
 static uint32_t
 ComputeImageFlags(ImageURL* uri, const nsCString& aMimeType, bool isMultiPart)
 {
   nsresult rv;
 
   // We default to the static globals.
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -136,16 +136,30 @@ nsPNGDecoder::~nsPNGDecoder()
 
     // mTransform belongs to us only if mInProfile is non-null
     if (mTransform) {
       qcms_transform_release(mTransform);
     }
   }
 }
 
+nsresult
+nsPNGDecoder::SetTargetSize(const nsIntSize& aSize)
+{
+  // Make sure the size is reasonable.
+  if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Create a downscaler that we'll filter our output through.
+  mDownscaler.emplace(aSize);
+
+  return NS_OK;
+}
+
 void
 nsPNGDecoder::CheckForTransparency(SurfaceFormat aFormat,
                                    const IntRect& aFrameRect)
 {
   // Check if the image has a transparent color in its palette.
   if (aFormat == SurfaceFormat::B8G8R8A8) {
     PostHasTransparency();
   }
@@ -169,17 +183,26 @@ nsPNGDecoder::CreateFrame(png_uint_32 aX
 
   // 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;
   }
 
-  nsresult rv = AllocateFrame(mNumFrames, GetSize(), frameRect, format);
+  // Make sure there's no animation or padding if we're downscaling.
+  MOZ_ASSERT_IF(mDownscaler, !GetImageMetadata().HasAnimation());
+  MOZ_ASSERT_IF(mDownscaler,
+                IntRect(IntPoint(), GetSize()).IsEqualEdges(frameRect));
+
+  IntSize targetSize = mDownscaler ? mDownscaler->TargetSize()
+                                   : GetSize();
+  IntRect targetFrameRect = mDownscaler ? IntRect(IntPoint(), targetSize)
+                                        : frameRect;
+  nsresult rv = AllocateFrame(mNumFrames, targetSize, targetFrameRect, format);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   mFrameRect = frameRect;
 
   MOZ_LOG(GetPNGDecoderAccountingLog(), LogLevel::Debug,
          ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created "
@@ -193,16 +216,24 @@ nsPNGDecoder::CreateFrame(png_uint_32 aX
     if (mAnimInfo.mDispose == DisposalMethod::CLEAR) {
       // We may have to display the background under this image during
       // animation playback, so we regard it as transparent.
       PostHasTransparency();
     }
   }
 #endif
 
+  if (mDownscaler) {
+    bool hasAlpha = aFormat != SurfaceFormat::B8G8R8X8;
+    rv = mDownscaler->BeginFrame(frameRect.Size(), mImageData, hasAlpha);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  }
+
   return NS_OK;
 }
 
 // set timeout and frame disposal method for the current frame
 void
 nsPNGDecoder::EndImageFrame()
 {
   if (mFrameIsHidden) {
@@ -588,16 +619,22 @@ nsPNGDecoder::info_callback(png_structp 
   } else {
     png_longjmp(decoder->mPNG, 1); // invalid number of channels
   }
 
 #ifdef PNG_APNG_SUPPORTED
   bool isAnimated = png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL);
   if (isAnimated) {
     decoder->PostIsAnimated(GetNextFrameDelay(png_ptr, info_ptr));
+
+    if (decoder->mDownscaler && !decoder->IsFirstFrameDecode()) {
+      MOZ_ASSERT_UNREACHABLE("Doing downscale-during-decode "
+                             "for an animated image?");
+      decoder->mDownscaler.reset();
+    }
   }
 #endif
 
   if (decoder->IsMetadataDecode()) {
     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
@@ -641,16 +678,43 @@ nsPNGDecoder::info_callback(png_structp 
     }
     if (!decoder->interlacebuf) {
       png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY
     }
   }
 }
 
 void
+nsPNGDecoder::PostPartialInvalidation(const IntRect& aInvalidRegion)
+{
+  if (!mDownscaler) {
+    PostInvalidation(aInvalidRegion);
+    return;
+  }
+
+  if (!mDownscaler->HasInvalidation()) {
+    return;
+  }
+
+  DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect();
+  PostInvalidation(invalidRect.mOriginalSizeRect,
+                   Some(invalidRect.mTargetSizeRect));
+}
+
+void
+nsPNGDecoder::PostFullInvalidation()
+{
+  PostInvalidation(mFrameRect);
+
+  if (mDownscaler) {
+    mDownscaler->ResetForNextProgressivePass();
+  }
+}
+
+void
 nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row,
                            png_uint_32 row_num, int pass)
 {
   /* libpng comments:
    *
    * this function is called for every row in the image.  If the
    * image is interlacing, and you turned on the interlace handler,
    * this function will be called for every row in every pass.
@@ -683,28 +747,35 @@ nsPNGDecoder::row_callback(png_structp p
   if (decoder->mFrameIsHidden) {
     return;
   }
 
   if (row_num >= static_cast<png_uint_32>(decoder->mFrameRect.height)) {
     return;
   }
 
-  if (new_row) {
+  // If |new_row| is null, that indicates that this is an interlaced image and
+  // |row_callback| is being called for a row that hasn't changed.  Ordinarily
+  // we don't need to do anything in this case, but if we're downscaling, the
+  // downscaler doesn't store the rows from previous passes, so we still need to
+  // process the row.
+  if (new_row || decoder->mDownscaler) {
     int32_t width = decoder->mFrameRect.width;
     uint32_t iwidth = decoder->mFrameRect.width;
 
     png_bytep line = new_row;
     if (decoder->interlacebuf) {
       line = decoder->interlacebuf + (row_num * decoder->mChannels * width);
       png_progressive_combine_row(png_ptr, line, new_row);
     }
 
     uint32_t bpr = width * sizeof(uint32_t);
-    uint32_t* cptr32 = (uint32_t*)(decoder->mImageData + (row_num*bpr));
+    uint32_t* cptr32 = decoder->mDownscaler
+      ? reinterpret_cast<uint32_t*>(decoder->mDownscaler->RowBuffer())
+      : reinterpret_cast<uint32_t*>(decoder->mImageData + (row_num*bpr));
 
     if (decoder->mTransform) {
       if (decoder->mCMSLine) {
         qcms_transform_data(decoder->mTransform, line, decoder->mCMSLine,
                             iwidth);
         // copy alpha over
         uint32_t channels = decoder->mChannels;
         if (channels == 2 || channels == 4) {
@@ -758,24 +829,27 @@ nsPNGDecoder::row_callback(png_structp p
           }
         }
       }
       break;
       default:
         png_longjmp(decoder->mPNG, 1);
     }
 
+    if (decoder->mDownscaler) {
+      decoder->mDownscaler->CommitRow();
+    }
+
     if (!decoder->interlacebuf) {
-      // Do line-by-line partial invalidations for non-interlaced images
-      decoder->PostInvalidation(IntRect(0, row_num, width, 1));
+      // Do line-by-line partial invalidations for non-interlaced images.
+      decoder->PostPartialInvalidation(IntRect(0, row_num, width, 1));
     } else if (row_num ==
                static_cast<png_uint_32>(decoder->mFrameRect.height - 1)) {
-      // Do only one full image invalidation for each pass (Bug 1187569)
-      decoder->PostInvalidation(IntRect(0, 0, width,
-                                decoder->mFrameRect.height));
+      // Do only one full image invalidation for each pass. (Bug 1187569)
+      decoder->PostFullInvalidation();
     }
   }
 }
 
 #ifdef PNG_APNG_SUPPORTED
 // got the header of a new frame that's coming
 void
 nsPNGDecoder::frame_info_callback(png_structp png_ptr, png_uint_32 frame_num)
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -3,16 +3,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_decoders_nsPNGDecoder_h
 #define mozilla_image_decoders_nsPNGDecoder_h
 
 #include "Decoder.h"
+#include "Downscaler.h"
 
 #include "gfxTypes.h"
 
 #include "nsCOMPtr.h"
 
 #include "png.h"
 
 #include "qcms.h"
@@ -21,16 +22,18 @@ namespace mozilla {
 namespace image {
 class RasterImage;
 
 class nsPNGDecoder : public Decoder
 {
 public:
   virtual ~nsPNGDecoder();
 
+  virtual nsresult SetTargetSize(const nsIntSize& aSize) override;
+
   virtual void InitInternal() override;
   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();
@@ -72,18 +75,22 @@ public:
 private:
   friend class DecoderFactory;
   friend class nsICODecoder;
 
   // Decoders should only be instantiated via DecoderFactory.
   // XXX(seth): nsICODecoder is temporarily an exception to this rule.
   explicit nsPNGDecoder(RasterImage* aImage);
 
+  void PostPartialInvalidation(const IntRect& aInvalidRegion);
+  void PostFullInvalidation();
+
 public:
   png_structp mPNG;
+  Maybe<Downscaler> mDownscaler;
   png_infop mInfo;
   nsIntRect mFrameRect;
   uint8_t* mCMSLine;
   uint8_t* interlacebuf;
   qcms_profile* mInProfile;
   qcms_transform* mTransform;
 
   gfx::SurfaceFormat format;