Bug 1839526 [wpt PR 40658] - Update wpt metadata, a=testonly
[gecko.git] / image / encoders / webp / nsWebPEncoder.cpp
blobf1221d1186cb6efc8d2e76feafec5a406ef8faa2
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/. */
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"
14 using namespace mozilla;
16 // static LazyLogModule sWEBPEncoderLog("WEBPEncoder");
18 NS_IMPL_ISUPPORTS(nsWebPEncoder, imgIEncoder, nsIInputStream,
19 nsIAsyncInputStream)
21 nsWebPEncoder::nsWebPEncoder()
22 : mFinished(false),
23 mImageBuffer(nullptr),
24 mImageBufferSize(0),
25 mImageBufferUsed(0),
26 mImageBufferReadPoint(0),
27 mCallback(nullptr),
28 mCallbackTarget(nullptr),
29 mNotifyThreshold(0),
30 mReentrantMonitor("nsWebPEncoder.mReentrantMonitor") {}
32 nsWebPEncoder::~nsWebPEncoder() {
33 if (mImageBuffer) {
34 WebPFree(mImageBuffer);
35 mImageBuffer = nullptr;
36 mImageBufferSize = 0;
37 mImageBufferUsed = 0;
38 mImageBufferReadPoint = 0;
42 // nsWebPEncoder::InitFromData
44 // One output option is supported: "quality=X" where X is an integer in the
45 // range 0-100. Higher values for X give better quality.
47 NS_IMETHODIMP
48 nsWebPEncoder::InitFromData(const uint8_t* aData,
49 uint32_t aLength, // (unused, req'd by JS)
50 uint32_t aWidth, uint32_t aHeight, uint32_t aStride,
51 uint32_t aInputFormat,
52 const nsAString& aOutputOptions) {
53 NS_ENSURE_ARG(aData);
55 // validate input format
56 if (aInputFormat != INPUT_FORMAT_RGB && aInputFormat != INPUT_FORMAT_RGBA &&
57 aInputFormat != INPUT_FORMAT_HOSTARGB)
58 return NS_ERROR_INVALID_ARG;
60 // Stride is the padded width of each row, so it better be longer (I'm afraid
61 // people will not understand what stride means, so check it well)
62 if ((aInputFormat == INPUT_FORMAT_RGB && aStride < aWidth * 3) ||
63 ((aInputFormat == INPUT_FORMAT_RGBA ||
64 aInputFormat == INPUT_FORMAT_HOSTARGB) &&
65 aStride < aWidth * 4)) {
66 NS_WARNING("Invalid stride for InitFromData");
67 return NS_ERROR_INVALID_ARG;
70 // can't initialize more than once
71 if (mImageBuffer != nullptr) {
72 return NS_ERROR_ALREADY_INITIALIZED;
75 // options: we only have one option so this is easy
76 int quality = 92;
77 if (aOutputOptions.Length() > 0) {
78 // have options string
79 const nsString qualityPrefix(u"quality="_ns);
80 if (aOutputOptions.Length() > qualityPrefix.Length() &&
81 StringBeginsWith(aOutputOptions, qualityPrefix)) {
82 // have quality string
83 nsCString value = NS_ConvertUTF16toUTF8(
84 Substring(aOutputOptions, qualityPrefix.Length()));
85 int newquality = -1;
86 if (PR_sscanf(value.get(), "%d", &newquality) == 1) {
87 if (newquality >= 0 && newquality <= 100) {
88 quality = newquality;
89 } else {
90 NS_WARNING(
91 "Quality value out of range, should be 0-100,"
92 " using default");
94 } else {
95 NS_WARNING(
96 "Quality value invalid, should be integer 0-100,"
97 " using default");
99 } else {
100 return NS_ERROR_INVALID_ARG;
104 size_t size = 0;
106 CheckedInt32 width = CheckedInt32(aWidth);
107 CheckedInt32 height = CheckedInt32(aHeight);
108 CheckedInt32 stride = CheckedInt32(aStride);
109 if (!width.isValid() || !height.isValid() || !stride.isValid() ||
110 !(CheckedUint32(aStride) * CheckedUint32(aHeight)).isValid()) {
111 return NS_ERROR_INVALID_ARG;
114 if (aInputFormat == INPUT_FORMAT_RGB) {
115 size = quality == 100
116 ? WebPEncodeLosslessRGB(aData, width.value(), height.value(),
117 stride.value(), &mImageBuffer)
118 : WebPEncodeRGB(aData, width.value(), height.value(),
119 stride.value(), quality, &mImageBuffer);
120 } else if (aInputFormat == INPUT_FORMAT_RGBA) {
121 size = quality == 100
122 ? WebPEncodeLosslessRGBA(aData, width.value(), height.value(),
123 stride.value(), &mImageBuffer)
124 : WebPEncodeRGBA(aData, width.value(), height.value(),
125 stride.value(), quality, &mImageBuffer);
126 } else if (aInputFormat == INPUT_FORMAT_HOSTARGB) {
127 UniquePtr<uint8_t[]> aDest = MakeUnique<uint8_t[]>(aStride * aHeight);
129 for (uint32_t y = 0; y < aHeight; y++) {
130 for (uint32_t x = 0; x < aWidth; x++) {
131 const uint32_t& pixelIn =
132 ((const uint32_t*)(aData))[y * aStride / 4 + x];
133 uint8_t* pixelOut = &aDest[y * aStride + x * 4];
135 uint8_t alpha = (pixelIn & 0xff000000) >> 24;
136 pixelOut[3] = alpha;
137 if (alpha == 255) {
138 pixelOut[0] = (pixelIn & 0xff0000) >> 16;
139 pixelOut[1] = (pixelIn & 0x00ff00) >> 8;
140 pixelOut[2] = (pixelIn & 0x0000ff);
141 } else if (alpha == 0) {
142 pixelOut[0] = pixelOut[1] = pixelOut[2] = 0;
143 } else {
144 pixelOut[0] =
145 (((pixelIn & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
146 pixelOut[1] = (((pixelIn & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha;
147 pixelOut[2] = (((pixelIn & 0x0000ff)) * 255 + alpha / 2) / alpha;
152 size =
153 quality == 100
154 ? WebPEncodeLosslessRGBA(aDest.get(), width.value(), height.value(),
155 stride.value(), &mImageBuffer)
156 : WebPEncodeRGBA(aDest.get(), width.value(), height.value(),
157 stride.value(), quality, &mImageBuffer);
160 mFinished = true;
162 if (size == 0) {
163 return NS_ERROR_FAILURE;
166 mImageBufferUsed = size;
168 return NS_OK;
171 // nsWebPEncoder::StartImageEncode
174 // See ::InitFromData for other info.
175 NS_IMETHODIMP
176 nsWebPEncoder::StartImageEncode(uint32_t aWidth, uint32_t aHeight,
177 uint32_t aInputFormat,
178 const nsAString& aOutputOptions) {
179 return NS_ERROR_NOT_IMPLEMENTED;
182 // Returns the number of bytes in the image buffer used.
183 NS_IMETHODIMP
184 nsWebPEncoder::GetImageBufferUsed(uint32_t* aOutputSize) {
185 NS_ENSURE_ARG_POINTER(aOutputSize);
186 *aOutputSize = mImageBufferUsed;
187 return NS_OK;
190 // Returns a pointer to the start of the image buffer
191 NS_IMETHODIMP
192 nsWebPEncoder::GetImageBuffer(char** aOutputBuffer) {
193 NS_ENSURE_ARG_POINTER(aOutputBuffer);
194 *aOutputBuffer = reinterpret_cast<char*>(mImageBuffer);
195 return NS_OK;
198 NS_IMETHODIMP
199 nsWebPEncoder::AddImageFrame(const uint8_t* aData,
200 uint32_t aLength, // (unused, req'd by JS)
201 uint32_t aWidth, uint32_t aHeight,
202 uint32_t aStride, uint32_t aInputFormat,
203 const nsAString& aFrameOptions) {
204 return NS_ERROR_NOT_IMPLEMENTED;
207 NS_IMETHODIMP
208 nsWebPEncoder::EndImageEncode() { return NS_ERROR_NOT_IMPLEMENTED; }
210 NS_IMETHODIMP
211 nsWebPEncoder::Close() {
212 if (mImageBuffer) {
213 WebPFree(mImageBuffer);
214 mImageBuffer = nullptr;
215 mImageBufferSize = 0;
216 mImageBufferUsed = 0;
217 mImageBufferReadPoint = 0;
219 return NS_OK;
222 NS_IMETHODIMP
223 nsWebPEncoder::Available(uint64_t* _retval) {
224 if (!mImageBuffer) {
225 return NS_BASE_STREAM_CLOSED;
228 *_retval = mImageBufferUsed - mImageBufferReadPoint;
229 return NS_OK;
232 NS_IMETHODIMP
233 nsWebPEncoder::StreamStatus() {
234 return mImageBuffer ? NS_OK : NS_BASE_STREAM_CLOSED;
237 NS_IMETHODIMP
238 nsWebPEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) {
239 return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
242 NS_IMETHODIMP
243 nsWebPEncoder::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
244 uint32_t aCount, uint32_t* _retval) {
245 // Avoid another thread reallocing the buffer underneath us
246 ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
248 uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint;
249 if (maxCount == 0) {
250 *_retval = 0;
251 return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
254 if (aCount > maxCount) {
255 aCount = maxCount;
257 nsresult rv = aWriter(
258 this, aClosure,
259 reinterpret_cast<const char*>(mImageBuffer + mImageBufferReadPoint), 0,
260 aCount, _retval);
261 if (NS_SUCCEEDED(rv)) {
262 NS_ASSERTION(*_retval <= aCount, "bad write count");
263 mImageBufferReadPoint += *_retval;
266 // errors returned from the writer end here!
267 return NS_OK;
270 NS_IMETHODIMP
271 nsWebPEncoder::IsNonBlocking(bool* _retval) {
272 *_retval = true;
273 return NS_OK;
276 NS_IMETHODIMP
277 nsWebPEncoder::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags,
278 uint32_t aRequestedCount, nsIEventTarget* aTarget) {
279 if (aFlags != 0) {
280 return NS_ERROR_NOT_IMPLEMENTED;
283 if (mCallback || mCallbackTarget) {
284 return NS_ERROR_UNEXPECTED;
287 mCallbackTarget = aTarget;
288 // 0 means "any number of bytes except 0"
289 mNotifyThreshold = aRequestedCount;
290 if (!aRequestedCount) {
291 mNotifyThreshold = 1024; // 1 KB seems good. We don't want to
292 // notify incessantly
295 // We set the callback absolutely last, because NotifyListener uses it to
296 // determine if someone needs to be notified. If we don't set it last,
297 // NotifyListener might try to fire off a notification to a null target
298 // which will generally cause non-threadsafe objects to be used off the
299 // main thread
300 mCallback = aCallback;
302 // What we are being asked for may be present already
303 NotifyListener();
304 return NS_OK;
307 NS_IMETHODIMP
308 nsWebPEncoder::CloseWithStatus(nsresult aStatus) { return Close(); }
310 void nsWebPEncoder::NotifyListener() {
311 // We might call this function on multiple threads (any threads that call
312 // AsyncWait and any that do encoding) so we lock to avoid notifying the
313 // listener twice about the same data (which generally leads to a truncated
314 // image).
315 ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
317 if (mCallback &&
318 (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold ||
319 mFinished)) {
320 nsCOMPtr<nsIInputStreamCallback> callback;
321 if (mCallbackTarget) {
322 callback = NS_NewInputStreamReadyEvent("nsWebPEncoder::NotifyListener",
323 mCallback, mCallbackTarget);
324 } else {
325 callback = mCallback;
328 NS_ASSERTION(callback, "Shouldn't fail to make the callback");
329 // Null the callback first because OnInputStreamReady could reenter
330 // AsyncWait
331 mCallback = nullptr;
332 mCallbackTarget = nullptr;
333 mNotifyThreshold = 0;
335 callback->OnInputStreamReady(this);