Bug 1206836 - When downscaling ICOs, downscale the AND mask as well. r=tn a=KWierso
authorSeth Fowler <mark.seth.fowler@gmail.com>
Mon, 21 Sep 2015 19:52:31 -0700
changeset 263532 2235e56c94cf
parent 263531 197af2fb7e29
child 263563 2782233685c3
push id29412
push usermfowler@mozilla.com
push date2015-09-22 02:52 +0000
treeherdermozilla-central@2235e56c94cf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn, KWierso
bugs1206836
milestone44.0a1
first release with
nightly linux32
2235e56c94cf / 44.0a1 / 20150922030204 / files
nightly linux64
2235e56c94cf / 44.0a1 / 20150922030204 / files
nightly mac
2235e56c94cf / 44.0a1 / 20150922030204 / files
nightly win32
2235e56c94cf / 44.0a1 / 20150922030204 / files
nightly win64
2235e56c94cf / 44.0a1 / 20150922030204 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1206836 - When downscaling ICOs, downscale the AND mask as well. r=tn a=KWierso
image/decoders/nsBMPDecoder.h
image/decoders/nsICODecoder.cpp
image/decoders/nsICODecoder.h
--- a/image/decoders/nsBMPDecoder.h
+++ b/image/decoders/nsBMPDecoder.h
@@ -35,16 +35,17 @@ public:
     // Obtains the width from the internal BIH header
     int32_t GetWidth() const;
 
     // Obtains the abs-value of the height from the internal BIH header
     int32_t GetHeight() const;
 
     // Obtains the internal output image buffer
     uint32_t* GetImageData();
+    size_t GetImageDataLength() const { return mImageDataLength; }
 
     // Obtains the size of the compressed image resource
     int32_t GetCompressedImageSize() const;
 
     // Obtains whether or not a BMP file had alpha data in its 4th byte
     // for 32BPP bitmaps.  Only use after the bitmap has been processed.
     bool HasAlphaData() const { return mHaveAlphaData; }
 
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -64,16 +64,18 @@ nsICODecoder::nsICODecoder(RasterImage* 
   , mBiggestResourceColorDepth(0)
   , mBestResourceDelta(INT_MIN)
   , mBestResourceColorDepth(0)
   , mNumIcons(0)
   , mCurrIcon(0)
   , mBPP(0)
   , mMaskRowSize(0)
   , mCurrMaskLine(0)
+  , mIsCursor(false)
+  , mHasMaskAlpha(false)
 { }
 
 void
 nsICODecoder::FinishInternal()
 {
   // We shouldn't be called in error cases
   MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
 
@@ -537,68 +539,137 @@ nsICODecoder::PrepareForMask()
 
   // If the expected size of the AND mask is larger than its actual size, then
   // we must have a truncated (and therefore corrupt) AND mask.
   uint32_t expectedLength = mMaskRowSize * GetRealHeight();
   if (maskLength < expectedLength) {
     return Transition::Terminate(ICOState::FAILURE);
   }
 
+  // If we're downscaling, the mask is the wrong size for the surface we've
+  // produced, so we need to downscale the mask into a temporary buffer and then
+  // combine the mask's alpha values with the color values from the image.
+  if (mDownscaler) {
+    MOZ_ASSERT(bmpDecoder->GetImageDataLength() ==
+                 mDownscaler->TargetSize().width *
+                 mDownscaler->TargetSize().height *
+                 sizeof(uint32_t));
+    mMaskBuffer = MakeUnique<uint8_t[]>(bmpDecoder->GetImageDataLength());
+    nsresult rv = mDownscaler->BeginFrame(GetRealSize(),
+                                          mMaskBuffer.get(),
+                                          /* aHasAlpha = */ true,
+                                          /* aFlipVertically = */ true);
+    if (NS_FAILED(rv)) {
+      return Transition::Terminate(ICOState::FAILURE);
+    }
+  }
+
   mCurrMaskLine = GetRealHeight();
   return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
 }
 
 
 LexerTransition<ICOState>
 nsICODecoder::ReadMaskRow(const char* aData)
 {
   mCurrMaskLine--;
 
-  nsRefPtr<nsBMPDecoder> bmpDecoder =
-    static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+  uint8_t sawTransparency = 0;
+
+  // Get the mask row we're reading.
+  const uint8_t* mask = reinterpret_cast<const uint8_t*>(aData);
+  const uint8_t* maskRowEnd = mask + mMaskRowSize;
 
-  uint32_t* imageData = bmpDecoder->GetImageData();
-  if (!imageData) {
-    return Transition::Terminate(ICOState::FAILURE);
+  // Get the corresponding row of the mask buffer (if we're downscaling) or the
+  // decoded image data (if we're not).
+  uint32_t* decoded = nullptr;
+  if (mDownscaler) {
+    // Initialize the row to all white and fully opaque.
+    memset(mDownscaler->RowBuffer(), 0xFF, GetRealWidth() * sizeof(uint32_t));
+
+    decoded = reinterpret_cast<uint32_t*>(mDownscaler->RowBuffer());
+  } else {
+    nsRefPtr<nsBMPDecoder> bmpDecoder =
+      static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+    uint32_t* imageData = bmpDecoder->GetImageData();
+    if (!imageData) {
+      return Transition::Terminate(ICOState::FAILURE);
+    }
+
+    decoded = imageData + mCurrMaskLine * GetRealWidth();
   }
 
-  uint8_t sawTransparency = 0;
-  uint32_t* decoded = imageData + mCurrMaskLine * GetRealWidth();
+  MOZ_ASSERT(decoded);
   uint32_t* decodedRowEnd = decoded + GetRealWidth();
-  const uint8_t* mask = reinterpret_cast<const uint8_t*>(aData);
-  const uint8_t* maskRowEnd = mask + mMaskRowSize;
 
   // Iterate simultaneously through the AND mask and the image data.
   while (mask < maskRowEnd) {
     uint8_t idx = *mask++;
     sawTransparency |= idx;
     for (uint8_t bit = 0x80; bit && decoded < decodedRowEnd; bit >>= 1) {
       // Clear pixel completely for transparency.
       if (idx & bit) {
         *decoded = 0;
       }
       decoded++;
     }
   }
 
+  if (mDownscaler) {
+    mDownscaler->CommitRow();
+  }
+
   // If any bits are set in sawTransparency, then we know at least one pixel was
   // transparent.
   if (sawTransparency) {
-    PostHasTransparency();
-    bmpDecoder->SetHasAlphaData();
+    mHasMaskAlpha = true;
   }
 
   if (mCurrMaskLine == 0) {
-    return Transition::To(ICOState::FINISHED_RESOURCE, 0);
+    return Transition::To(ICOState::FINISH_MASK, 0);
   }
 
   return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
 }
 
 LexerTransition<ICOState>
+nsICODecoder::FinishMask()
+{
+  // If we're downscaling, we now have the appropriate alpha values in
+  // mMaskBuffer. We just need to transfer them to the image.
+  if (mDownscaler) {
+    // Retrieve the image data.
+    nsRefPtr<nsBMPDecoder> bmpDecoder =
+      static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+    uint8_t* imageData = reinterpret_cast<uint8_t*>(bmpDecoder->GetImageData());
+    if (!imageData) {
+      return Transition::Terminate(ICOState::FAILURE);
+    }
+
+    // Iterate through the alpha values, copying from mask to image.
+    MOZ_ASSERT(mMaskBuffer);
+    MOZ_ASSERT(bmpDecoder->GetImageDataLength() > 0);
+    for (size_t i = 3 ; i < bmpDecoder->GetImageDataLength() ; i += 4) {
+      imageData[i] = mMaskBuffer[i];
+    }
+  }
+
+  // If the mask contained any transparent pixels, record that fact.
+  if (mHasMaskAlpha) {
+    PostHasTransparency();
+
+    nsRefPtr<nsBMPDecoder> bmpDecoder =
+      static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+    bmpDecoder->SetHasAlphaData();
+  }
+
+  return Transition::To(ICOState::FINISHED_RESOURCE, 0);
+}
+
+LexerTransition<ICOState>
 nsICODecoder::FinishResource()
 {
   // Make sure the actual size of the resource matches the size in the directory
   // entry. If not, we consider the image corrupt.
   if (mContainedDecoder->HasSize() &&
       mContainedDecoder->GetSize() != GetRealSize()) {
     return Transition::Terminate(ICOState::FAILURE);
   }
@@ -632,16 +703,18 @@ nsICODecoder::WriteInternal(const char* 
         case ICOState::READ_BIH:
           return ReadBIH(aData);
         case ICOState::READ_BMP:
           return ReadBMP(aData, aLength);
         case ICOState::PREPARE_FOR_MASK:
           return PrepareForMask();
         case ICOState::READ_MASK_ROW:
           return ReadMaskRow(aData);
+        case ICOState::FINISH_MASK:
+          return FinishMask();
         case ICOState::SKIP_MASK:
           return Transition::ContinueUnbuffered(ICOState::SKIP_MASK);
         case ICOState::FINISHED_RESOURCE:
           return FinishResource();
         default:
           MOZ_ASSERT_UNREACHABLE("Unknown ICOState");
           return Transition::Terminate(ICOState::FAILURE);
       }
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -29,16 +29,17 @@ enum class ICOState
   SKIP_TO_RESOURCE,
   FOUND_RESOURCE,
   SNIFF_RESOURCE,
   READ_PNG,
   READ_BIH,
   READ_BMP,
   PREPARE_FOR_MASK,
   READ_MASK_ROW,
+  FINISH_MASK,
   SKIP_MASK,
   FINISHED_RESOURCE
 };
 
 class nsICODecoder : public Decoder
 {
 public:
   virtual ~nsICODecoder() { }
@@ -109,31 +110,34 @@ private:
   LexerTransition<ICOState> ReadHeader(const char* aData);
   LexerTransition<ICOState> ReadDirEntry(const char* aData);
   LexerTransition<ICOState> SniffResource(const char* aData);
   LexerTransition<ICOState> ReadPNG(const char* aData, uint32_t aLen);
   LexerTransition<ICOState> ReadBIH(const char* aData);
   LexerTransition<ICOState> ReadBMP(const char* aData, uint32_t aLen);
   LexerTransition<ICOState> PrepareForMask();
   LexerTransition<ICOState> ReadMaskRow(const char* aData);
+  LexerTransition<ICOState> FinishMask();
   LexerTransition<ICOState> FinishResource();
 
   StreamingLexer<ICOState, 32> mLexer; // The lexer.
   nsRefPtr<Decoder> mContainedDecoder; // Either a BMP or PNG decoder.
+  UniquePtr<uint8_t[]> mMaskBuffer;    // A temporary buffer for the alpha mask.
   char mBIHraw[40];                    // The bitmap information header.
   IconDirEntry mDirEntry;              // The dir entry for the selected resource.
   IntSize mBiggestResourceSize;        // Used to select the intrinsic size.
   IntSize mBiggestResourceHotSpot;     // Used to select the intrinsic size.
   uint16_t mBiggestResourceColorDepth; // Used to select the intrinsic size.
   int32_t mBestResourceDelta;          // Used to select the best resource.
   uint16_t mBestResourceColorDepth;    // Used to select the best resource.
   uint16_t mNumIcons; // Stores the number of icons in the ICO file.
   uint16_t mCurrIcon; // Stores the current dir entry index we are processing.
   uint16_t mBPP;      // The BPP of the resource we're decoding.
   uint32_t mMaskRowSize;  // The size in bytes of each row in the BMP alpha mask.
   uint32_t mCurrMaskLine; // The line of the BMP alpha mask we're processing.
   bool mIsCursor;         // Is this ICO a cursor?
+  bool mHasMaskAlpha;     // Did the BMP alpha mask have any transparency?
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_decoders_nsICODecoder_h