LCOV - code coverage report
Current view: top level - image - Downscaler.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 147 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 11 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
       2             :  *
       3             :  * This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "Downscaler.h"
       8             : 
       9             : #include <algorithm>
      10             : #include <ctime>
      11             : #include "gfxPrefs.h"
      12             : #include "mozilla/gfx/2D.h"
      13             : 
      14             : using std::max;
      15             : using std::swap;
      16             : 
      17             : namespace mozilla {
      18             : 
      19             : using gfx::IntRect;
      20             : 
      21             : namespace image {
      22             : 
      23           0 : Downscaler::Downscaler(const nsIntSize& aTargetSize)
      24             :   : mTargetSize(aTargetSize)
      25             :   , mOutputBuffer(nullptr)
      26             :   , mWindowCapacity(0)
      27             :   , mHasAlpha(true)
      28           0 :   , mFlipVertically(false)
      29             : {
      30           0 :   MOZ_ASSERT(gfxPrefs::ImageDownscaleDuringDecodeEnabled(),
      31             :              "Downscaling even though downscale-during-decode is disabled?");
      32           0 :   MOZ_ASSERT(mTargetSize.width > 0 && mTargetSize.height > 0,
      33             :              "Invalid target size");
      34           0 : }
      35             : 
      36           0 : Downscaler::~Downscaler()
      37             : {
      38           0 :   ReleaseWindow();
      39           0 : }
      40             : 
      41             : void
      42           0 : Downscaler::ReleaseWindow()
      43             : {
      44           0 :   if (!mWindow) {
      45           0 :     return;
      46             :   }
      47             : 
      48           0 :   for (int32_t i = 0; i < mWindowCapacity; ++i) {
      49           0 :     delete[] mWindow[i];
      50             :   }
      51             : 
      52           0 :   mWindow = nullptr;
      53           0 :   mWindowCapacity = 0;
      54             : }
      55             : 
      56             : nsresult
      57           0 : Downscaler::BeginFrame(const nsIntSize& aOriginalSize,
      58             :                        const Maybe<nsIntRect>& aFrameRect,
      59             :                        uint8_t* aOutputBuffer,
      60             :                        bool aHasAlpha,
      61             :                        bool aFlipVertically /* = false */)
      62             : {
      63           0 :   MOZ_ASSERT(aOutputBuffer);
      64           0 :   MOZ_ASSERT(mTargetSize != aOriginalSize,
      65             :              "Created a downscaler, but not downscaling?");
      66           0 :   MOZ_ASSERT(mTargetSize.width <= aOriginalSize.width,
      67             :              "Created a downscaler, but width is larger");
      68           0 :   MOZ_ASSERT(mTargetSize.height <= aOriginalSize.height,
      69             :              "Created a downscaler, but height is larger");
      70           0 :   MOZ_ASSERT(aOriginalSize.width > 0 && aOriginalSize.height > 0,
      71             :              "Invalid original size");
      72             : 
      73             :   // Only downscale from reasonable sizes to avoid using too much memory/cpu
      74             :   // downscaling and decoding. 1 << 20 == 1,048,576 seems a reasonable limit.
      75           0 :   if (aOriginalSize.width > (1 << 20) || aOriginalSize.height > (1 << 20)) {
      76           0 :     NS_WARNING("Trying to downscale image frame that is too large");
      77           0 :     return NS_ERROR_INVALID_ARG;
      78             :   }
      79             : 
      80           0 :   mFrameRect = aFrameRect.valueOr(nsIntRect(nsIntPoint(), aOriginalSize));
      81           0 :   MOZ_ASSERT(mFrameRect.x >= 0 && mFrameRect.y >= 0 &&
      82             :              mFrameRect.width >= 0 && mFrameRect.height >= 0,
      83             :              "Frame rect must have non-negative components");
      84           0 :   MOZ_ASSERT(nsIntRect(0, 0, aOriginalSize.width, aOriginalSize.height)
      85             :                .Contains(mFrameRect),
      86             :              "Frame rect must fit inside image");
      87           0 :   MOZ_ASSERT_IF(!nsIntRect(0, 0, aOriginalSize.width, aOriginalSize.height)
      88             :                   .IsEqualEdges(mFrameRect),
      89             :                 aHasAlpha);
      90             : 
      91           0 :   mOriginalSize = aOriginalSize;
      92           0 :   mScale = gfxSize(double(mOriginalSize.width) / mTargetSize.width,
      93           0 :                    double(mOriginalSize.height) / mTargetSize.height);
      94           0 :   mOutputBuffer = aOutputBuffer;
      95           0 :   mHasAlpha = aHasAlpha;
      96           0 :   mFlipVertically = aFlipVertically;
      97             : 
      98           0 :   ReleaseWindow();
      99             : 
     100           0 :   auto resizeMethod = gfx::ConvolutionFilter::ResizeMethod::LANCZOS3;
     101           0 :   if (!mXFilter.ComputeResizeFilter(resizeMethod, mOriginalSize.width, mTargetSize.width) ||
     102           0 :       !mYFilter.ComputeResizeFilter(resizeMethod, mOriginalSize.height, mTargetSize.height)) {
     103           0 :     NS_WARNING("Failed to compute filters for image downscaling");
     104           0 :     return NS_ERROR_OUT_OF_MEMORY;
     105             :   }
     106             : 
     107             :   // Allocate the buffer, which contains scanlines of the original image.
     108             :   // pad to handle overreads by the simd code
     109           0 :   size_t bufferLen = gfx::ConvolutionFilter::PadBytesForSIMD(mOriginalSize.width * sizeof(uint32_t));
     110           0 :   mRowBuffer.reset(new (fallible) uint8_t[bufferLen]);
     111           0 :   if (MOZ_UNLIKELY(!mRowBuffer)) {
     112           0 :     return NS_ERROR_OUT_OF_MEMORY;
     113             :   }
     114             : 
     115             :   // Zero buffer to keep valgrind happy.
     116           0 :   memset(mRowBuffer.get(), 0, bufferLen);
     117             : 
     118             :   // Allocate the window, which contains horizontally downscaled scanlines. (We
     119             :   // can store scanlines which are already downscale because our downscaling
     120             :   // filter is separable.)
     121           0 :   mWindowCapacity = mYFilter.MaxFilter();
     122           0 :   mWindow.reset(new (fallible) uint8_t*[mWindowCapacity]);
     123           0 :   if (MOZ_UNLIKELY(!mWindow)) {
     124           0 :     return NS_ERROR_OUT_OF_MEMORY;
     125             :   }
     126             : 
     127           0 :   bool anyAllocationFailed = false;
     128             :   // pad to handle overreads by the simd code
     129           0 :   const size_t rowSize = gfx::ConvolutionFilter::PadBytesForSIMD(mTargetSize.width * sizeof(uint32_t));
     130           0 :   for (int32_t i = 0; i < mWindowCapacity; ++i) {
     131           0 :     mWindow[i] = new (fallible) uint8_t[rowSize];
     132           0 :     anyAllocationFailed = anyAllocationFailed || mWindow[i] == nullptr;
     133             :   }
     134             : 
     135           0 :   if (MOZ_UNLIKELY(anyAllocationFailed)) {
     136             :     // We intentionally iterate through the entire array even if an allocation
     137             :     // fails, to ensure that all the pointers in it are either valid or nullptr.
     138             :     // That in turn ensures that ReleaseWindow() can clean up correctly.
     139           0 :     return NS_ERROR_OUT_OF_MEMORY;
     140             :   }
     141             : 
     142           0 :   ResetForNextProgressivePass();
     143             : 
     144           0 :   return NS_OK;
     145             : }
     146             : 
     147             : void
     148           0 : Downscaler::SkipToRow(int32_t aRow)
     149             : {
     150           0 :   if (mCurrentInLine < aRow) {
     151           0 :     ClearRow();
     152           0 :     do {
     153           0 :       CommitRow();
     154           0 :     } while (mCurrentInLine < aRow);
     155             :   }
     156           0 : }
     157             : 
     158             : void
     159           0 : Downscaler::ResetForNextProgressivePass()
     160             : {
     161           0 :   mPrevInvalidatedLine = 0;
     162           0 :   mCurrentOutLine = 0;
     163           0 :   mCurrentInLine = 0;
     164           0 :   mLinesInBuffer = 0;
     165             : 
     166           0 :   if (mFrameRect.IsEmpty()) {
     167             :     // Our frame rect is zero size; commit rows until the end of the image.
     168           0 :     SkipToRow(mOriginalSize.height - 1);
     169             :   } else {
     170             :     // If we have a vertical offset, commit rows to shift us past it.
     171           0 :     SkipToRow(mFrameRect.y);
     172             :   }
     173           0 : }
     174             : 
     175             : void
     176           0 : Downscaler::ClearRestOfRow(uint32_t aStartingAtCol)
     177             : {
     178           0 :   MOZ_ASSERT(int64_t(aStartingAtCol) <= int64_t(mOriginalSize.width));
     179           0 :   uint32_t bytesToClear = (mOriginalSize.width - aStartingAtCol)
     180           0 :                         * sizeof(uint32_t);
     181           0 :   memset(mRowBuffer.get() + (aStartingAtCol * sizeof(uint32_t)),
     182           0 :          0, bytesToClear);
     183           0 : }
     184             : 
     185             : void
     186           0 : Downscaler::CommitRow()
     187             : {
     188           0 :   MOZ_ASSERT(mOutputBuffer, "Should have a current frame");
     189           0 :   MOZ_ASSERT(mCurrentInLine < mOriginalSize.height, "Past end of input");
     190             : 
     191           0 :   if (mCurrentOutLine < mTargetSize.height) {
     192           0 :     int32_t filterOffset = 0;
     193           0 :     int32_t filterLength = 0;
     194           0 :     mYFilter.GetFilterOffsetAndLength(mCurrentOutLine,
     195           0 :                                       &filterOffset, &filterLength);
     196             : 
     197           0 :     int32_t inLineToRead = filterOffset + mLinesInBuffer;
     198           0 :     MOZ_ASSERT(mCurrentInLine <= inLineToRead, "Reading past end of input");
     199           0 :     if (mCurrentInLine == inLineToRead) {
     200           0 :       mXFilter.ConvolveHorizontally(mRowBuffer.get(), mWindow[mLinesInBuffer++], mHasAlpha);
     201             :     }
     202             : 
     203           0 :     MOZ_ASSERT(mCurrentOutLine < mTargetSize.height,
     204             :                "Writing past end of output");
     205             : 
     206           0 :     while (mLinesInBuffer == filterLength) {
     207           0 :       DownscaleInputLine();
     208             : 
     209           0 :       if (mCurrentOutLine == mTargetSize.height) {
     210           0 :         break;  // We're done.
     211             :       }
     212             : 
     213           0 :       mYFilter.GetFilterOffsetAndLength(mCurrentOutLine,
     214           0 :                                         &filterOffset, &filterLength);
     215             :     }
     216             :   }
     217             : 
     218           0 :   mCurrentInLine += 1;
     219             : 
     220             :   // If we're at the end of the part of the original image that has data, commit
     221             :   // rows to shift us to the end.
     222           0 :   if (mCurrentInLine == (mFrameRect.y + mFrameRect.height)) {
     223           0 :     SkipToRow(mOriginalSize.height - 1);
     224             :   }
     225           0 : }
     226             : 
     227             : bool
     228           0 : Downscaler::HasInvalidation() const
     229             : {
     230           0 :   return mCurrentOutLine > mPrevInvalidatedLine;
     231             : }
     232             : 
     233             : DownscalerInvalidRect
     234           0 : Downscaler::TakeInvalidRect()
     235             : {
     236           0 :   if (MOZ_UNLIKELY(!HasInvalidation())) {
     237           0 :     return DownscalerInvalidRect();
     238             :   }
     239             : 
     240           0 :   DownscalerInvalidRect invalidRect;
     241             : 
     242             :   // Compute the target size invalid rect.
     243           0 :   if (mFlipVertically) {
     244             :     // We need to flip it. This will implicitly flip the original size invalid
     245             :     // rect, since we compute it by scaling this rect.
     246           0 :     invalidRect.mTargetSizeRect =
     247           0 :       IntRect(0, mTargetSize.height - mCurrentOutLine,
     248           0 :               mTargetSize.width, mCurrentOutLine - mPrevInvalidatedLine);
     249             :   } else {
     250           0 :     invalidRect.mTargetSizeRect =
     251           0 :       IntRect(0, mPrevInvalidatedLine,
     252           0 :               mTargetSize.width, mCurrentOutLine - mPrevInvalidatedLine);
     253             :   }
     254             : 
     255           0 :   mPrevInvalidatedLine = mCurrentOutLine;
     256             : 
     257             :   // Compute the original size invalid rect.
     258           0 :   invalidRect.mOriginalSizeRect = invalidRect.mTargetSizeRect;
     259           0 :   invalidRect.mOriginalSizeRect.ScaleRoundOut(mScale.width, mScale.height);
     260             : 
     261           0 :   return invalidRect;
     262             : }
     263             : 
     264             : void
     265           0 : Downscaler::DownscaleInputLine()
     266             : {
     267           0 :   MOZ_ASSERT(mOutputBuffer);
     268           0 :   MOZ_ASSERT(mCurrentOutLine < mTargetSize.height,
     269             :              "Writing past end of output");
     270             : 
     271           0 :   int32_t filterOffset = 0;
     272           0 :   int32_t filterLength = 0;
     273           0 :   mYFilter.GetFilterOffsetAndLength(mCurrentOutLine,
     274           0 :                                     &filterOffset, &filterLength);
     275             : 
     276           0 :   int32_t currentOutLine = mFlipVertically
     277           0 :                          ? mTargetSize.height - (mCurrentOutLine + 1)
     278           0 :                          : mCurrentOutLine;
     279           0 :   MOZ_ASSERT(currentOutLine >= 0);
     280             : 
     281             :   uint8_t* outputLine =
     282           0 :     &mOutputBuffer[currentOutLine * mTargetSize.width * sizeof(uint32_t)];
     283           0 :   mYFilter.ConvolveVertically(mWindow.get(), outputLine, mCurrentOutLine, mXFilter.NumValues(), mHasAlpha);
     284             : 
     285           0 :   mCurrentOutLine += 1;
     286             : 
     287           0 :   if (mCurrentOutLine == mTargetSize.height) {
     288             :     // We're done.
     289           0 :     return;
     290             :   }
     291             : 
     292           0 :   int32_t newFilterOffset = 0;
     293           0 :   int32_t newFilterLength = 0;
     294           0 :   mYFilter.GetFilterOffsetAndLength(mCurrentOutLine,
     295           0 :                                     &newFilterOffset, &newFilterLength);
     296             : 
     297           0 :   int diff = newFilterOffset - filterOffset;
     298           0 :   MOZ_ASSERT(diff >= 0, "Moving backwards in the filter?");
     299             : 
     300             :   // Shift the buffer. We're just moving pointers here, so this is cheap.
     301           0 :   mLinesInBuffer -= diff;
     302           0 :   mLinesInBuffer = max(mLinesInBuffer, 0);
     303           0 :   for (int32_t i = 0; i < mLinesInBuffer; ++i) {
     304           0 :     swap(mWindow[i], mWindow[filterLength - mLinesInBuffer + i]);
     305             :   }
     306             : }
     307             : 
     308             : 
     309             : 
     310             : } // namespace image
     311             : } // namespace mozilla

Generated by: LCOV version 1.13