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