nsWebPEncoder.cpp (11057B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 // #include "ImageLogging.h" 7 #include "nsCRT.h" 8 #include "nsWebPEncoder.h" 9 #include "nsStreamUtils.h" 10 #include "nsString.h" 11 #include "prprf.h" 12 #include "mozilla/CheckedInt.h" 13 #include "mozilla/UniquePtrExtensions.h" 14 15 using namespace mozilla; 16 17 // static LazyLogModule sWEBPEncoderLog("WEBPEncoder"); 18 19 NS_IMPL_ISUPPORTS(nsWebPEncoder, imgIEncoder, nsIInputStream, 20 nsIAsyncInputStream) 21 22 nsWebPEncoder::nsWebPEncoder() 23 : mFinished(false), 24 mImageBuffer(nullptr), 25 mImageBufferSize(0), 26 mImageBufferUsed(0), 27 mImageBufferReadPoint(0), 28 mCallback(nullptr), 29 mCallbackTarget(nullptr), 30 mNotifyThreshold(0), 31 mReentrantMonitor("nsWebPEncoder.mReentrantMonitor") {} 32 33 nsWebPEncoder::~nsWebPEncoder() { 34 if (mImageBuffer) { 35 WebPFree(mImageBuffer); 36 mImageBuffer = nullptr; 37 mImageBufferSize = 0; 38 mImageBufferUsed = 0; 39 mImageBufferReadPoint = 0; 40 } 41 } 42 43 // nsWebPEncoder::InitFromData 44 // 45 // One output option is supported: "quality=X" where X is an integer in the 46 // range 0-100. Higher values for X give better quality. 47 48 NS_IMETHODIMP 49 nsWebPEncoder::InitFromData(const uint8_t* aData, 50 uint32_t aLength, // (unused, req'd by JS) 51 uint32_t aWidth, uint32_t aHeight, uint32_t aStride, 52 uint32_t aInputFormat, 53 const nsAString& aOutputOptions, 54 const nsACString& aRandomizationKey) { 55 NS_ENSURE_ARG(aData); 56 57 // validate input format 58 if (aInputFormat != INPUT_FORMAT_RGB && aInputFormat != INPUT_FORMAT_RGBA && 59 aInputFormat != INPUT_FORMAT_HOSTARGB) 60 return NS_ERROR_INVALID_ARG; 61 62 // Stride is the padded width of each row, so it better be longer (I'm afraid 63 // people will not understand what stride means, so check it well) 64 if ((aInputFormat == INPUT_FORMAT_RGB && aStride < aWidth * 3) || 65 ((aInputFormat == INPUT_FORMAT_RGBA || 66 aInputFormat == INPUT_FORMAT_HOSTARGB) && 67 aStride < aWidth * 4)) { 68 NS_WARNING("Invalid stride for InitFromData"); 69 return NS_ERROR_INVALID_ARG; 70 } 71 72 // can't initialize more than once 73 if (mImageBuffer != nullptr) { 74 return NS_ERROR_ALREADY_INITIALIZED; 75 } 76 77 // options: we only have one option so this is easy 78 int quality = 92; 79 if (aOutputOptions.Length() > 0) { 80 // have options string 81 const nsString qualityPrefix(u"quality="_ns); 82 if (aOutputOptions.Length() > qualityPrefix.Length() && 83 StringBeginsWith(aOutputOptions, qualityPrefix)) { 84 // have quality string 85 nsCString value = NS_ConvertUTF16toUTF8( 86 Substring(aOutputOptions, qualityPrefix.Length())); 87 int newquality = -1; 88 if (PR_sscanf(value.get(), "%d", &newquality) == 1) { 89 if (newquality >= 0 && newquality <= 100) { 90 quality = newquality; 91 } else { 92 NS_WARNING( 93 "Quality value out of range, should be 0-100," 94 " using default"); 95 } 96 } else { 97 NS_WARNING( 98 "Quality value invalid, should be integer 0-100," 99 " using default"); 100 } 101 } else { 102 return NS_ERROR_INVALID_ARG; 103 } 104 } 105 106 size_t size = 0; 107 108 CheckedInt32 width = CheckedInt32(aWidth); 109 CheckedInt32 height = CheckedInt32(aHeight); 110 CheckedInt32 stride = CheckedInt32(aStride); 111 if (!width.isValid() || !height.isValid() || !stride.isValid() || 112 !(CheckedUint32(aStride) * CheckedUint32(aHeight)).isValid()) { 113 return NS_ERROR_INVALID_ARG; 114 } 115 116 if (aInputFormat == INPUT_FORMAT_RGB) { 117 size = quality == 100 118 ? WebPEncodeLosslessRGB(aData, width.value(), height.value(), 119 stride.value(), &mImageBuffer) 120 : WebPEncodeRGB(aData, width.value(), height.value(), 121 stride.value(), quality, &mImageBuffer); 122 } else if (aInputFormat == INPUT_FORMAT_RGBA) { 123 size = quality == 100 124 ? WebPEncodeLosslessRGBA(aData, width.value(), height.value(), 125 stride.value(), &mImageBuffer) 126 : WebPEncodeRGBA(aData, width.value(), height.value(), 127 stride.value(), quality, &mImageBuffer); 128 } else if (aInputFormat == INPUT_FORMAT_HOSTARGB) { 129 UniquePtr<uint8_t[]> aDest = 130 MakeUniqueFallible<uint8_t[]>(aStride * aHeight); 131 if (NS_WARN_IF(!aDest)) { 132 return NS_ERROR_OUT_OF_MEMORY; 133 } 134 135 for (uint32_t y = 0; y < aHeight; y++) { 136 for (uint32_t x = 0; x < aWidth; x++) { 137 const uint32_t& pixelIn = 138 ((const uint32_t*)(aData))[y * aStride / 4 + x]; 139 uint8_t* pixelOut = &aDest[y * aStride + x * 4]; 140 141 uint8_t alpha = (pixelIn & 0xff000000) >> 24; 142 pixelOut[3] = alpha; 143 if (alpha == 255) { 144 pixelOut[0] = (pixelIn & 0xff0000) >> 16; 145 pixelOut[1] = (pixelIn & 0x00ff00) >> 8; 146 pixelOut[2] = (pixelIn & 0x0000ff); 147 } else if (alpha == 0) { 148 pixelOut[0] = pixelOut[1] = pixelOut[2] = 0; 149 } else { 150 pixelOut[0] = 151 (((pixelIn & 0xff0000) >> 16) * 255 + alpha / 2) / alpha; 152 pixelOut[1] = (((pixelIn & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha; 153 pixelOut[2] = (((pixelIn & 0x0000ff)) * 255 + alpha / 2) / alpha; 154 } 155 } 156 } 157 158 size = 159 quality == 100 160 ? WebPEncodeLosslessRGBA(aDest.get(), width.value(), height.value(), 161 stride.value(), &mImageBuffer) 162 : WebPEncodeRGBA(aDest.get(), width.value(), height.value(), 163 stride.value(), quality, &mImageBuffer); 164 } 165 166 mFinished = true; 167 168 if (size == 0) { 169 return NS_ERROR_FAILURE; 170 } 171 172 mImageBufferUsed = size; 173 174 return NS_OK; 175 } 176 177 // nsWebPEncoder::StartImageEncode 178 // 179 // 180 // See ::InitFromData for other info. 181 NS_IMETHODIMP 182 nsWebPEncoder::StartImageEncode(uint32_t aWidth, uint32_t aHeight, 183 uint32_t aInputFormat, 184 const nsAString& aOutputOptions) { 185 return NS_ERROR_NOT_IMPLEMENTED; 186 } 187 188 // Returns the number of bytes in the image buffer used. 189 NS_IMETHODIMP 190 nsWebPEncoder::GetImageBufferUsed(uint32_t* aOutputSize) { 191 NS_ENSURE_ARG_POINTER(aOutputSize); 192 *aOutputSize = mImageBufferUsed; 193 return NS_OK; 194 } 195 196 // Returns a pointer to the start of the image buffer 197 NS_IMETHODIMP 198 nsWebPEncoder::GetImageBuffer(char** aOutputBuffer) { 199 NS_ENSURE_ARG_POINTER(aOutputBuffer); 200 *aOutputBuffer = reinterpret_cast<char*>(mImageBuffer); 201 return NS_OK; 202 } 203 204 NS_IMETHODIMP 205 nsWebPEncoder::AddImageFrame(const uint8_t* aData, 206 uint32_t aLength, // (unused, req'd by JS) 207 uint32_t aWidth, uint32_t aHeight, 208 uint32_t aStride, uint32_t aInputFormat, 209 const nsAString& aFrameOptions) { 210 return NS_ERROR_NOT_IMPLEMENTED; 211 } 212 213 NS_IMETHODIMP 214 nsWebPEncoder::EndImageEncode() { return NS_ERROR_NOT_IMPLEMENTED; } 215 216 NS_IMETHODIMP 217 nsWebPEncoder::Close() { 218 if (mImageBuffer) { 219 WebPFree(mImageBuffer); 220 mImageBuffer = nullptr; 221 mImageBufferSize = 0; 222 mImageBufferUsed = 0; 223 mImageBufferReadPoint = 0; 224 } 225 return NS_OK; 226 } 227 228 NS_IMETHODIMP 229 nsWebPEncoder::Available(uint64_t* _retval) { 230 if (!mImageBuffer) { 231 return NS_BASE_STREAM_CLOSED; 232 } 233 234 *_retval = mImageBufferUsed - mImageBufferReadPoint; 235 return NS_OK; 236 } 237 238 NS_IMETHODIMP 239 nsWebPEncoder::StreamStatus() { 240 return mImageBuffer ? NS_OK : NS_BASE_STREAM_CLOSED; 241 } 242 243 NS_IMETHODIMP 244 nsWebPEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) { 245 return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); 246 } 247 248 NS_IMETHODIMP 249 nsWebPEncoder::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, 250 uint32_t aCount, uint32_t* _retval) { 251 // Avoid another thread reallocing the buffer underneath us 252 ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); 253 254 uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint; 255 if (maxCount == 0) { 256 *_retval = 0; 257 return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; 258 } 259 260 if (aCount > maxCount) { 261 aCount = maxCount; 262 } 263 nsresult rv = aWriter( 264 this, aClosure, 265 reinterpret_cast<const char*>(mImageBuffer + mImageBufferReadPoint), 0, 266 aCount, _retval); 267 if (NS_SUCCEEDED(rv)) { 268 NS_ASSERTION(*_retval <= aCount, "bad write count"); 269 mImageBufferReadPoint += *_retval; 270 } 271 272 // errors returned from the writer end here! 273 return NS_OK; 274 } 275 276 NS_IMETHODIMP 277 nsWebPEncoder::IsNonBlocking(bool* _retval) { 278 *_retval = true; 279 return NS_OK; 280 } 281 282 NS_IMETHODIMP 283 nsWebPEncoder::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, 284 uint32_t aRequestedCount, nsIEventTarget* aTarget) { 285 if (aFlags != 0) { 286 return NS_ERROR_NOT_IMPLEMENTED; 287 } 288 289 if (mCallback || mCallbackTarget) { 290 return NS_ERROR_UNEXPECTED; 291 } 292 293 mCallbackTarget = aTarget; 294 // 0 means "any number of bytes except 0" 295 mNotifyThreshold = aRequestedCount; 296 if (!aRequestedCount) { 297 mNotifyThreshold = 1024; // 1 KB seems good. We don't want to 298 // notify incessantly 299 } 300 301 // We set the callback absolutely last, because NotifyListener uses it to 302 // determine if someone needs to be notified. If we don't set it last, 303 // NotifyListener might try to fire off a notification to a null target 304 // which will generally cause non-threadsafe objects to be used off the 305 // main thread 306 mCallback = aCallback; 307 308 // What we are being asked for may be present already 309 NotifyListener(); 310 return NS_OK; 311 } 312 313 NS_IMETHODIMP 314 nsWebPEncoder::CloseWithStatus(nsresult aStatus) { return Close(); } 315 316 void nsWebPEncoder::NotifyListener() { 317 // We might call this function on multiple threads (any threads that call 318 // AsyncWait and any that do encoding) so we lock to avoid notifying the 319 // listener twice about the same data (which generally leads to a truncated 320 // image). 321 ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); 322 323 if (mCallback && 324 (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold || 325 mFinished)) { 326 nsCOMPtr<nsIInputStreamCallback> callback; 327 if (mCallbackTarget) { 328 callback = NS_NewInputStreamReadyEvent("nsWebPEncoder::NotifyListener", 329 mCallback, mCallbackTarget); 330 } else { 331 callback = mCallback; 332 } 333 334 NS_ASSERTION(callback, "Shouldn't fail to make the callback"); 335 // Null the callback first because OnInputStreamReady could reenter 336 // AsyncWait 337 mCallback = nullptr; 338 mCallbackTarget = nullptr; 339 mNotifyThreshold = 0; 340 341 callback->OnInputStreamReady(this); 342 } 343 }