|
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 "nsJPEGEncoder.h" |
|
7 #include "prprf.h" |
|
8 #include "nsString.h" |
|
9 #include "nsStreamUtils.h" |
|
10 #include "gfxColor.h" |
|
11 |
|
12 #include <setjmp.h> |
|
13 #include "jerror.h" |
|
14 |
|
15 using namespace mozilla; |
|
16 |
|
17 NS_IMPL_ISUPPORTS(nsJPEGEncoder, imgIEncoder, nsIInputStream, nsIAsyncInputStream) |
|
18 |
|
19 // used to pass error info through the JPEG library |
|
20 struct encoder_error_mgr { |
|
21 jpeg_error_mgr pub; |
|
22 jmp_buf setjmp_buffer; |
|
23 }; |
|
24 |
|
25 nsJPEGEncoder::nsJPEGEncoder() : mFinished(false), |
|
26 mImageBuffer(nullptr), mImageBufferSize(0), |
|
27 mImageBufferUsed(0), mImageBufferReadPoint(0), |
|
28 mCallback(nullptr), |
|
29 mCallbackTarget(nullptr), mNotifyThreshold(0), |
|
30 mReentrantMonitor("nsJPEGEncoder.mReentrantMonitor") |
|
31 { |
|
32 } |
|
33 |
|
34 nsJPEGEncoder::~nsJPEGEncoder() |
|
35 { |
|
36 if (mImageBuffer) { |
|
37 moz_free(mImageBuffer); |
|
38 mImageBuffer = nullptr; |
|
39 } |
|
40 } |
|
41 |
|
42 |
|
43 // nsJPEGEncoder::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 // Transparency is always discarded. |
|
49 |
|
50 NS_IMETHODIMP nsJPEGEncoder::InitFromData(const uint8_t* aData, |
|
51 uint32_t aLength, // (unused, req'd by JS) |
|
52 uint32_t aWidth, |
|
53 uint32_t aHeight, |
|
54 uint32_t aStride, |
|
55 uint32_t aInputFormat, |
|
56 const nsAString& aOutputOptions) |
|
57 { |
|
58 NS_ENSURE_ARG(aData); |
|
59 |
|
60 // validate input format |
|
61 if (aInputFormat != INPUT_FORMAT_RGB && |
|
62 aInputFormat != INPUT_FORMAT_RGBA && |
|
63 aInputFormat != INPUT_FORMAT_HOSTARGB) |
|
64 return NS_ERROR_INVALID_ARG; |
|
65 |
|
66 // Stride is the padded width of each row, so it better be longer (I'm afraid |
|
67 // people will not understand what stride means, so check it well) |
|
68 if ((aInputFormat == INPUT_FORMAT_RGB && |
|
69 aStride < aWidth * 3) || |
|
70 ((aInputFormat == INPUT_FORMAT_RGBA || aInputFormat == INPUT_FORMAT_HOSTARGB) && |
|
71 aStride < aWidth * 4)) { |
|
72 NS_WARNING("Invalid stride for InitFromData"); |
|
73 return NS_ERROR_INVALID_ARG; |
|
74 } |
|
75 |
|
76 // can't initialize more than once |
|
77 if (mImageBuffer != nullptr) |
|
78 return NS_ERROR_ALREADY_INITIALIZED; |
|
79 |
|
80 // options: we only have one option so this is easy |
|
81 int quality = 92; |
|
82 if (aOutputOptions.Length() > 0) { |
|
83 // have options string |
|
84 const nsString qualityPrefix(NS_LITERAL_STRING("quality=")); |
|
85 if (aOutputOptions.Length() > qualityPrefix.Length() && |
|
86 StringBeginsWith(aOutputOptions, qualityPrefix)) { |
|
87 // have quality string |
|
88 nsCString value = NS_ConvertUTF16toUTF8(Substring(aOutputOptions, |
|
89 qualityPrefix.Length())); |
|
90 int newquality = -1; |
|
91 if (PR_sscanf(value.get(), "%d", &newquality) == 1) { |
|
92 if (newquality >= 0 && newquality <= 100) { |
|
93 quality = newquality; |
|
94 } else { |
|
95 NS_WARNING("Quality value out of range, should be 0-100, using default"); |
|
96 } |
|
97 } else { |
|
98 NS_WARNING("Quality value invalid, should be integer 0-100, using default"); |
|
99 } |
|
100 } |
|
101 else { |
|
102 return NS_ERROR_INVALID_ARG; |
|
103 } |
|
104 } |
|
105 |
|
106 jpeg_compress_struct cinfo; |
|
107 |
|
108 // We set up the normal JPEG error routines, then override error_exit. |
|
109 // This must be done before the call to create_compress |
|
110 encoder_error_mgr errmgr; |
|
111 cinfo.err = jpeg_std_error(&errmgr.pub); |
|
112 errmgr.pub.error_exit = errorExit; |
|
113 // Establish the setjmp return context for my_error_exit to use. |
|
114 if (setjmp(errmgr.setjmp_buffer)) { |
|
115 // If we get here, the JPEG code has signaled an error. |
|
116 // We need to clean up the JPEG object, close the input file, and return. |
|
117 return NS_ERROR_FAILURE; |
|
118 } |
|
119 |
|
120 jpeg_create_compress(&cinfo); |
|
121 cinfo.image_width = aWidth; |
|
122 cinfo.image_height = aHeight; |
|
123 cinfo.input_components = 3; |
|
124 cinfo.in_color_space = JCS_RGB; |
|
125 cinfo.data_precision = 8; |
|
126 |
|
127 jpeg_set_defaults(&cinfo); |
|
128 jpeg_set_quality(&cinfo, quality, 1); // quality here is 0-100 |
|
129 if (quality >= 90) { |
|
130 int i; |
|
131 for (i=0; i < MAX_COMPONENTS; i++) { |
|
132 cinfo.comp_info[i].h_samp_factor=1; |
|
133 cinfo.comp_info[i].v_samp_factor=1; |
|
134 } |
|
135 } |
|
136 |
|
137 // set up the destination manager |
|
138 jpeg_destination_mgr destmgr; |
|
139 destmgr.init_destination = initDestination; |
|
140 destmgr.empty_output_buffer = emptyOutputBuffer; |
|
141 destmgr.term_destination = termDestination; |
|
142 cinfo.dest = &destmgr; |
|
143 cinfo.client_data = this; |
|
144 |
|
145 jpeg_start_compress(&cinfo, 1); |
|
146 |
|
147 // feed it the rows |
|
148 if (aInputFormat == INPUT_FORMAT_RGB) { |
|
149 while (cinfo.next_scanline < cinfo.image_height) { |
|
150 const uint8_t* row = &aData[cinfo.next_scanline * aStride]; |
|
151 jpeg_write_scanlines(&cinfo, const_cast<uint8_t**>(&row), 1); |
|
152 } |
|
153 } else if (aInputFormat == INPUT_FORMAT_RGBA) { |
|
154 uint8_t* row = new uint8_t[aWidth * 3]; |
|
155 while (cinfo.next_scanline < cinfo.image_height) { |
|
156 ConvertRGBARow(&aData[cinfo.next_scanline * aStride], row, aWidth); |
|
157 jpeg_write_scanlines(&cinfo, &row, 1); |
|
158 } |
|
159 delete[] row; |
|
160 } else if (aInputFormat == INPUT_FORMAT_HOSTARGB) { |
|
161 uint8_t* row = new uint8_t[aWidth * 3]; |
|
162 while (cinfo.next_scanline < cinfo.image_height) { |
|
163 ConvertHostARGBRow(&aData[cinfo.next_scanline * aStride], row, aWidth); |
|
164 jpeg_write_scanlines(&cinfo, &row, 1); |
|
165 } |
|
166 delete[] row; |
|
167 } |
|
168 |
|
169 jpeg_finish_compress(&cinfo); |
|
170 jpeg_destroy_compress(&cinfo); |
|
171 |
|
172 mFinished = true; |
|
173 NotifyListener(); |
|
174 |
|
175 // if output callback can't get enough memory, it will free our buffer |
|
176 if (!mImageBuffer) |
|
177 return NS_ERROR_OUT_OF_MEMORY; |
|
178 |
|
179 return NS_OK; |
|
180 } |
|
181 |
|
182 |
|
183 NS_IMETHODIMP nsJPEGEncoder::StartImageEncode(uint32_t aWidth, |
|
184 uint32_t aHeight, |
|
185 uint32_t aInputFormat, |
|
186 const nsAString& aOutputOptions) |
|
187 { |
|
188 return NS_ERROR_NOT_IMPLEMENTED; |
|
189 } |
|
190 |
|
191 // Returns the number of bytes in the image buffer used. |
|
192 NS_IMETHODIMP nsJPEGEncoder::GetImageBufferUsed(uint32_t *aOutputSize) |
|
193 { |
|
194 NS_ENSURE_ARG_POINTER(aOutputSize); |
|
195 *aOutputSize = mImageBufferUsed; |
|
196 return NS_OK; |
|
197 } |
|
198 |
|
199 // Returns a pointer to the start of the image buffer |
|
200 NS_IMETHODIMP nsJPEGEncoder::GetImageBuffer(char **aOutputBuffer) |
|
201 { |
|
202 NS_ENSURE_ARG_POINTER(aOutputBuffer); |
|
203 *aOutputBuffer = reinterpret_cast<char*>(mImageBuffer); |
|
204 return NS_OK; |
|
205 } |
|
206 |
|
207 NS_IMETHODIMP nsJPEGEncoder::AddImageFrame(const uint8_t* aData, |
|
208 uint32_t aLength, |
|
209 uint32_t aWidth, |
|
210 uint32_t aHeight, |
|
211 uint32_t aStride, |
|
212 uint32_t aFrameFormat, |
|
213 const nsAString& aFrameOptions) |
|
214 { |
|
215 return NS_ERROR_NOT_IMPLEMENTED; |
|
216 } |
|
217 |
|
218 NS_IMETHODIMP nsJPEGEncoder::EndImageEncode() |
|
219 { |
|
220 return NS_ERROR_NOT_IMPLEMENTED; |
|
221 } |
|
222 |
|
223 |
|
224 /* void close (); */ |
|
225 NS_IMETHODIMP nsJPEGEncoder::Close() |
|
226 { |
|
227 if (mImageBuffer != nullptr) { |
|
228 moz_free(mImageBuffer); |
|
229 mImageBuffer = nullptr; |
|
230 mImageBufferSize = 0; |
|
231 mImageBufferUsed = 0; |
|
232 mImageBufferReadPoint = 0; |
|
233 } |
|
234 return NS_OK; |
|
235 } |
|
236 |
|
237 /* unsigned long available (); */ |
|
238 NS_IMETHODIMP nsJPEGEncoder::Available(uint64_t *_retval) |
|
239 { |
|
240 if (!mImageBuffer) |
|
241 return NS_BASE_STREAM_CLOSED; |
|
242 |
|
243 *_retval = mImageBufferUsed - mImageBufferReadPoint; |
|
244 return NS_OK; |
|
245 } |
|
246 |
|
247 /* [noscript] unsigned long read (in charPtr aBuf, in unsigned long aCount); */ |
|
248 NS_IMETHODIMP nsJPEGEncoder::Read(char * aBuf, uint32_t aCount, |
|
249 uint32_t *_retval) |
|
250 { |
|
251 return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); |
|
252 } |
|
253 |
|
254 /* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, in voidPtr aClosure, in unsigned long aCount); */ |
|
255 NS_IMETHODIMP nsJPEGEncoder::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, uint32_t aCount, uint32_t *_retval) |
|
256 { |
|
257 // Avoid another thread reallocing the buffer underneath us |
|
258 ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); |
|
259 |
|
260 uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint; |
|
261 if (maxCount == 0) { |
|
262 *_retval = 0; |
|
263 return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; |
|
264 } |
|
265 |
|
266 if (aCount > maxCount) |
|
267 aCount = maxCount; |
|
268 nsresult rv = aWriter(this, aClosure, |
|
269 reinterpret_cast<const char*>(mImageBuffer+mImageBufferReadPoint), |
|
270 0, aCount, _retval); |
|
271 if (NS_SUCCEEDED(rv)) { |
|
272 NS_ASSERTION(*_retval <= aCount, "bad write count"); |
|
273 mImageBufferReadPoint += *_retval; |
|
274 } |
|
275 |
|
276 // errors returned from the writer end here! |
|
277 return NS_OK; |
|
278 } |
|
279 |
|
280 /* boolean isNonBlocking (); */ |
|
281 NS_IMETHODIMP nsJPEGEncoder::IsNonBlocking(bool *_retval) |
|
282 { |
|
283 *_retval = true; |
|
284 return NS_OK; |
|
285 } |
|
286 |
|
287 NS_IMETHODIMP nsJPEGEncoder::AsyncWait(nsIInputStreamCallback *aCallback, |
|
288 uint32_t aFlags, |
|
289 uint32_t aRequestedCount, |
|
290 nsIEventTarget *aTarget) |
|
291 { |
|
292 if (aFlags != 0) |
|
293 return NS_ERROR_NOT_IMPLEMENTED; |
|
294 |
|
295 if (mCallback || mCallbackTarget) |
|
296 return NS_ERROR_UNEXPECTED; |
|
297 |
|
298 mCallbackTarget = aTarget; |
|
299 // 0 means "any number of bytes except 0" |
|
300 mNotifyThreshold = aRequestedCount; |
|
301 if (!aRequestedCount) |
|
302 mNotifyThreshold = 1024; // 1 KB seems good. We don't want to notify incessantly |
|
303 |
|
304 // We set the callback absolutely last, because NotifyListener uses it to |
|
305 // determine if someone needs to be notified. If we don't set it last, |
|
306 // NotifyListener might try to fire off a notification to a null target |
|
307 // which will generally cause non-threadsafe objects to be used off the main thread |
|
308 mCallback = aCallback; |
|
309 |
|
310 // What we are being asked for may be present already |
|
311 NotifyListener(); |
|
312 return NS_OK; |
|
313 } |
|
314 |
|
315 NS_IMETHODIMP nsJPEGEncoder::CloseWithStatus(nsresult aStatus) |
|
316 { |
|
317 return Close(); |
|
318 } |
|
319 |
|
320 |
|
321 |
|
322 // nsJPEGEncoder::ConvertHostARGBRow |
|
323 // |
|
324 // Our colors are stored with premultiplied alphas, but we need |
|
325 // an output with no alpha in machine-independent byte order. |
|
326 // |
|
327 // See gfx/cairo/cairo/src/cairo-png.c |
|
328 void |
|
329 nsJPEGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest, |
|
330 uint32_t aPixelWidth) |
|
331 { |
|
332 for (uint32_t x = 0; x < aPixelWidth; x++) { |
|
333 const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x]; |
|
334 uint8_t *pixelOut = &aDest[x * 3]; |
|
335 |
|
336 pixelOut[0] = (pixelIn & 0xff0000) >> 16; |
|
337 pixelOut[1] = (pixelIn & 0x00ff00) >> 8; |
|
338 pixelOut[2] = (pixelIn & 0x0000ff) >> 0; |
|
339 } |
|
340 } |
|
341 |
|
342 /** |
|
343 * nsJPEGEncoder::ConvertRGBARow |
|
344 * |
|
345 * Input is RGBA, output is RGB, so we should alpha-premultiply. |
|
346 */ |
|
347 void |
|
348 nsJPEGEncoder::ConvertRGBARow(const uint8_t* aSrc, uint8_t* aDest, |
|
349 uint32_t aPixelWidth) |
|
350 { |
|
351 for (uint32_t x = 0; x < aPixelWidth; x++) { |
|
352 const uint8_t* pixelIn = &aSrc[x * 4]; |
|
353 uint8_t* pixelOut = &aDest[x * 3]; |
|
354 |
|
355 uint8_t alpha = pixelIn[3]; |
|
356 pixelOut[0] = gfxPreMultiply(pixelIn[0], alpha); |
|
357 pixelOut[1] = gfxPreMultiply(pixelIn[1], alpha); |
|
358 pixelOut[2] = gfxPreMultiply(pixelIn[2], alpha); |
|
359 } |
|
360 } |
|
361 |
|
362 // nsJPEGEncoder::initDestination |
|
363 // |
|
364 // Initialize destination. This is called by jpeg_start_compress() before |
|
365 // any data is actually written. It must initialize next_output_byte and |
|
366 // free_in_buffer. free_in_buffer must be initialized to a positive value. |
|
367 |
|
368 void // static |
|
369 nsJPEGEncoder::initDestination(jpeg_compress_struct* cinfo) |
|
370 { |
|
371 nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data); |
|
372 NS_ASSERTION(! that->mImageBuffer, "Image buffer already initialized"); |
|
373 |
|
374 that->mImageBufferSize = 8192; |
|
375 that->mImageBuffer = (uint8_t*)moz_malloc(that->mImageBufferSize); |
|
376 that->mImageBufferUsed = 0; |
|
377 |
|
378 cinfo->dest->next_output_byte = that->mImageBuffer; |
|
379 cinfo->dest->free_in_buffer = that->mImageBufferSize; |
|
380 } |
|
381 |
|
382 |
|
383 // nsJPEGEncoder::emptyOutputBuffer |
|
384 // |
|
385 // This is called whenever the buffer has filled (free_in_buffer reaches |
|
386 // zero). In typical applications, it should write out the *entire* buffer |
|
387 // (use the saved start address and buffer length; ignore the current state |
|
388 // of next_output_byte and free_in_buffer). Then reset the pointer & count |
|
389 // to the start of the buffer, and return TRUE indicating that the buffer |
|
390 // has been dumped. free_in_buffer must be set to a positive value when |
|
391 // TRUE is returned. A FALSE return should only be used when I/O suspension |
|
392 // is desired (this operating mode is discussed in the next section). |
|
393 |
|
394 boolean // static |
|
395 nsJPEGEncoder::emptyOutputBuffer(jpeg_compress_struct* cinfo) |
|
396 { |
|
397 nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data); |
|
398 NS_ASSERTION(that->mImageBuffer, "No buffer to empty!"); |
|
399 |
|
400 // When we're reallocing the buffer we need to take the lock to ensure |
|
401 // that nobody is trying to read from the buffer we are destroying |
|
402 ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor); |
|
403 |
|
404 that->mImageBufferUsed = that->mImageBufferSize; |
|
405 |
|
406 // expand buffer, just double size each time |
|
407 that->mImageBufferSize *= 2; |
|
408 |
|
409 uint8_t* newBuf = (uint8_t*)moz_realloc(that->mImageBuffer, |
|
410 that->mImageBufferSize); |
|
411 if (! newBuf) { |
|
412 // can't resize, just zero (this will keep us from writing more) |
|
413 moz_free(that->mImageBuffer); |
|
414 that->mImageBuffer = nullptr; |
|
415 that->mImageBufferSize = 0; |
|
416 that->mImageBufferUsed = 0; |
|
417 |
|
418 // This seems to be the only way to do errors through the JPEG library. We |
|
419 // pass an nsresult masquerading as an int, which works because the |
|
420 // setjmp() caller casts it back. |
|
421 longjmp(((encoder_error_mgr*)(cinfo->err))->setjmp_buffer, |
|
422 static_cast<int>(NS_ERROR_OUT_OF_MEMORY)); |
|
423 } |
|
424 that->mImageBuffer = newBuf; |
|
425 |
|
426 cinfo->dest->next_output_byte = &that->mImageBuffer[that->mImageBufferUsed]; |
|
427 cinfo->dest->free_in_buffer = that->mImageBufferSize - that->mImageBufferUsed; |
|
428 return 1; |
|
429 } |
|
430 |
|
431 |
|
432 // nsJPEGEncoder::termDestination |
|
433 // |
|
434 // Terminate destination --- called by jpeg_finish_compress() after all data |
|
435 // has been written. In most applications, this must flush any data |
|
436 // remaining in the buffer. Use either next_output_byte or free_in_buffer |
|
437 // to determine how much data is in the buffer. |
|
438 |
|
439 void // static |
|
440 nsJPEGEncoder::termDestination(jpeg_compress_struct* cinfo) |
|
441 { |
|
442 nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data); |
|
443 if (! that->mImageBuffer) |
|
444 return; |
|
445 that->mImageBufferUsed = cinfo->dest->next_output_byte - that->mImageBuffer; |
|
446 NS_ASSERTION(that->mImageBufferUsed < that->mImageBufferSize, |
|
447 "JPEG library busted, got a bad image buffer size"); |
|
448 that->NotifyListener(); |
|
449 } |
|
450 |
|
451 |
|
452 // nsJPEGEncoder::errorExit |
|
453 // |
|
454 // Override the standard error method in the IJG JPEG decoder code. This |
|
455 // was mostly copied from nsJPEGDecoder.cpp |
|
456 |
|
457 void // static |
|
458 nsJPEGEncoder::errorExit(jpeg_common_struct* cinfo) |
|
459 { |
|
460 nsresult error_code; |
|
461 encoder_error_mgr *err = (encoder_error_mgr *) cinfo->err; |
|
462 |
|
463 // Convert error to a browser error code |
|
464 switch (cinfo->err->msg_code) { |
|
465 case JERR_OUT_OF_MEMORY: |
|
466 error_code = NS_ERROR_OUT_OF_MEMORY; |
|
467 break; |
|
468 default: |
|
469 error_code = NS_ERROR_FAILURE; |
|
470 } |
|
471 |
|
472 // Return control to the setjmp point. We pass an nsresult masquerading as |
|
473 // an int, which works because the setjmp() caller casts it back. |
|
474 longjmp(err->setjmp_buffer, static_cast<int>(error_code)); |
|
475 } |
|
476 |
|
477 void |
|
478 nsJPEGEncoder::NotifyListener() |
|
479 { |
|
480 // We might call this function on multiple threads (any threads that call |
|
481 // AsyncWait and any that do encoding) so we lock to avoid notifying the |
|
482 // listener twice about the same data (which generally leads to a truncated |
|
483 // image). |
|
484 ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); |
|
485 |
|
486 if (mCallback && |
|
487 (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold || |
|
488 mFinished)) { |
|
489 nsCOMPtr<nsIInputStreamCallback> callback; |
|
490 if (mCallbackTarget) { |
|
491 callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); |
|
492 } else { |
|
493 callback = mCallback; |
|
494 } |
|
495 |
|
496 NS_ASSERTION(callback, "Shouldn't fail to make the callback"); |
|
497 // Null the callback first because OnInputStreamReady could reenter |
|
498 // AsyncWait |
|
499 mCallback = nullptr; |
|
500 mCallbackTarget = nullptr; |
|
501 mNotifyThreshold = 0; |
|
502 |
|
503 callback->OnInputStreamReady(this); |
|
504 } |
|
505 } |