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"
8 #include "nsWebPEncoder.h"
9 #include "nsStreamUtils.h"
12 #include "mozilla/CheckedInt.h"
14 using namespace mozilla
;
16 // static LazyLogModule sWEBPEncoderLog("WEBPEncoder");
18 NS_IMPL_ISUPPORTS(nsWebPEncoder
, imgIEncoder
, nsIInputStream
,
21 nsWebPEncoder::nsWebPEncoder()
23 mImageBuffer(nullptr),
26 mImageBufferReadPoint(0),
28 mCallbackTarget(nullptr),
30 mReentrantMonitor("nsWebPEncoder.mReentrantMonitor") {}
32 nsWebPEncoder::~nsWebPEncoder() {
34 WebPFree(mImageBuffer
);
35 mImageBuffer
= nullptr;
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.
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
) {
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
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()));
86 if (PR_sscanf(value
.get(), "%d", &newquality
) == 1) {
87 if (newquality
>= 0 && newquality
<= 100) {
91 "Quality value out of range, should be 0-100,"
96 "Quality value invalid, should be integer 0-100,"
100 return NS_ERROR_INVALID_ARG
;
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;
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;
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
;
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
);
163 return NS_ERROR_FAILURE
;
166 mImageBufferUsed
= size
;
171 // nsWebPEncoder::StartImageEncode
174 // See ::InitFromData for other info.
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.
184 nsWebPEncoder::GetImageBufferUsed(uint32_t* aOutputSize
) {
185 NS_ENSURE_ARG_POINTER(aOutputSize
);
186 *aOutputSize
= mImageBufferUsed
;
190 // Returns a pointer to the start of the image buffer
192 nsWebPEncoder::GetImageBuffer(char** aOutputBuffer
) {
193 NS_ENSURE_ARG_POINTER(aOutputBuffer
);
194 *aOutputBuffer
= reinterpret_cast<char*>(mImageBuffer
);
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
;
208 nsWebPEncoder::EndImageEncode() { return NS_ERROR_NOT_IMPLEMENTED
; }
211 nsWebPEncoder::Close() {
213 WebPFree(mImageBuffer
);
214 mImageBuffer
= nullptr;
215 mImageBufferSize
= 0;
216 mImageBufferUsed
= 0;
217 mImageBufferReadPoint
= 0;
223 nsWebPEncoder::Available(uint64_t* _retval
) {
225 return NS_BASE_STREAM_CLOSED
;
228 *_retval
= mImageBufferUsed
- mImageBufferReadPoint
;
233 nsWebPEncoder::StreamStatus() {
234 return mImageBuffer
? NS_OK
: NS_BASE_STREAM_CLOSED
;
238 nsWebPEncoder::Read(char* aBuf
, uint32_t aCount
, uint32_t* _retval
) {
239 return ReadSegments(NS_CopySegmentToBuffer
, aBuf
, aCount
, _retval
);
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
;
251 return mFinished
? NS_OK
: NS_BASE_STREAM_WOULD_BLOCK
;
254 if (aCount
> maxCount
) {
257 nsresult rv
= aWriter(
259 reinterpret_cast<const char*>(mImageBuffer
+ mImageBufferReadPoint
), 0,
261 if (NS_SUCCEEDED(rv
)) {
262 NS_ASSERTION(*_retval
<= aCount
, "bad write count");
263 mImageBufferReadPoint
+= *_retval
;
266 // errors returned from the writer end here!
271 nsWebPEncoder::IsNonBlocking(bool* _retval
) {
277 nsWebPEncoder::AsyncWait(nsIInputStreamCallback
* aCallback
, uint32_t aFlags
,
278 uint32_t aRequestedCount
, nsIEventTarget
* aTarget
) {
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
300 mCallback
= aCallback
;
302 // What we are being asked for may be present already
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
315 ReentrantMonitorAutoEnter
autoEnter(mReentrantMonitor
);
318 (mImageBufferUsed
- mImageBufferReadPoint
>= mNotifyThreshold
||
320 nsCOMPtr
<nsIInputStreamCallback
> callback
;
321 if (mCallbackTarget
) {
322 callback
= NS_NewInputStreamReadyEvent("nsWebPEncoder::NotifyListener",
323 mCallback
, mCallbackTarget
);
325 callback
= mCallback
;
328 NS_ASSERTION(callback
, "Shouldn't fail to make the callback");
329 // Null the callback first because OnInputStreamReady could reenter
332 mCallbackTarget
= nullptr;
333 mNotifyThreshold
= 0;
335 callback
->OnInputStreamReady(this);