1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/image/encoders/jpeg/nsJPEGEncoder.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,505 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nsJPEGEncoder.h" 1.10 +#include "prprf.h" 1.11 +#include "nsString.h" 1.12 +#include "nsStreamUtils.h" 1.13 +#include "gfxColor.h" 1.14 + 1.15 +#include <setjmp.h> 1.16 +#include "jerror.h" 1.17 + 1.18 +using namespace mozilla; 1.19 + 1.20 +NS_IMPL_ISUPPORTS(nsJPEGEncoder, imgIEncoder, nsIInputStream, nsIAsyncInputStream) 1.21 + 1.22 +// used to pass error info through the JPEG library 1.23 +struct encoder_error_mgr { 1.24 + jpeg_error_mgr pub; 1.25 + jmp_buf setjmp_buffer; 1.26 +}; 1.27 + 1.28 +nsJPEGEncoder::nsJPEGEncoder() : mFinished(false), 1.29 + mImageBuffer(nullptr), mImageBufferSize(0), 1.30 + mImageBufferUsed(0), mImageBufferReadPoint(0), 1.31 + mCallback(nullptr), 1.32 + mCallbackTarget(nullptr), mNotifyThreshold(0), 1.33 + mReentrantMonitor("nsJPEGEncoder.mReentrantMonitor") 1.34 +{ 1.35 +} 1.36 + 1.37 +nsJPEGEncoder::~nsJPEGEncoder() 1.38 +{ 1.39 + if (mImageBuffer) { 1.40 + moz_free(mImageBuffer); 1.41 + mImageBuffer = nullptr; 1.42 + } 1.43 +} 1.44 + 1.45 + 1.46 +// nsJPEGEncoder::InitFromData 1.47 +// 1.48 +// One output option is supported: "quality=X" where X is an integer in the 1.49 +// range 0-100. Higher values for X give better quality. 1.50 +// 1.51 +// Transparency is always discarded. 1.52 + 1.53 +NS_IMETHODIMP nsJPEGEncoder::InitFromData(const uint8_t* aData, 1.54 + uint32_t aLength, // (unused, req'd by JS) 1.55 + uint32_t aWidth, 1.56 + uint32_t aHeight, 1.57 + uint32_t aStride, 1.58 + uint32_t aInputFormat, 1.59 + const nsAString& aOutputOptions) 1.60 +{ 1.61 + NS_ENSURE_ARG(aData); 1.62 + 1.63 + // validate input format 1.64 + if (aInputFormat != INPUT_FORMAT_RGB && 1.65 + aInputFormat != INPUT_FORMAT_RGBA && 1.66 + aInputFormat != INPUT_FORMAT_HOSTARGB) 1.67 + return NS_ERROR_INVALID_ARG; 1.68 + 1.69 + // Stride is the padded width of each row, so it better be longer (I'm afraid 1.70 + // people will not understand what stride means, so check it well) 1.71 + if ((aInputFormat == INPUT_FORMAT_RGB && 1.72 + aStride < aWidth * 3) || 1.73 + ((aInputFormat == INPUT_FORMAT_RGBA || aInputFormat == INPUT_FORMAT_HOSTARGB) && 1.74 + aStride < aWidth * 4)) { 1.75 + NS_WARNING("Invalid stride for InitFromData"); 1.76 + return NS_ERROR_INVALID_ARG; 1.77 + } 1.78 + 1.79 + // can't initialize more than once 1.80 + if (mImageBuffer != nullptr) 1.81 + return NS_ERROR_ALREADY_INITIALIZED; 1.82 + 1.83 + // options: we only have one option so this is easy 1.84 + int quality = 92; 1.85 + if (aOutputOptions.Length() > 0) { 1.86 + // have options string 1.87 + const nsString qualityPrefix(NS_LITERAL_STRING("quality=")); 1.88 + if (aOutputOptions.Length() > qualityPrefix.Length() && 1.89 + StringBeginsWith(aOutputOptions, qualityPrefix)) { 1.90 + // have quality string 1.91 + nsCString value = NS_ConvertUTF16toUTF8(Substring(aOutputOptions, 1.92 + qualityPrefix.Length())); 1.93 + int newquality = -1; 1.94 + if (PR_sscanf(value.get(), "%d", &newquality) == 1) { 1.95 + if (newquality >= 0 && newquality <= 100) { 1.96 + quality = newquality; 1.97 + } else { 1.98 + NS_WARNING("Quality value out of range, should be 0-100, using default"); 1.99 + } 1.100 + } else { 1.101 + NS_WARNING("Quality value invalid, should be integer 0-100, using default"); 1.102 + } 1.103 + } 1.104 + else { 1.105 + return NS_ERROR_INVALID_ARG; 1.106 + } 1.107 + } 1.108 + 1.109 + jpeg_compress_struct cinfo; 1.110 + 1.111 + // We set up the normal JPEG error routines, then override error_exit. 1.112 + // This must be done before the call to create_compress 1.113 + encoder_error_mgr errmgr; 1.114 + cinfo.err = jpeg_std_error(&errmgr.pub); 1.115 + errmgr.pub.error_exit = errorExit; 1.116 + // Establish the setjmp return context for my_error_exit to use. 1.117 + if (setjmp(errmgr.setjmp_buffer)) { 1.118 + // If we get here, the JPEG code has signaled an error. 1.119 + // We need to clean up the JPEG object, close the input file, and return. 1.120 + return NS_ERROR_FAILURE; 1.121 + } 1.122 + 1.123 + jpeg_create_compress(&cinfo); 1.124 + cinfo.image_width = aWidth; 1.125 + cinfo.image_height = aHeight; 1.126 + cinfo.input_components = 3; 1.127 + cinfo.in_color_space = JCS_RGB; 1.128 + cinfo.data_precision = 8; 1.129 + 1.130 + jpeg_set_defaults(&cinfo); 1.131 + jpeg_set_quality(&cinfo, quality, 1); // quality here is 0-100 1.132 + if (quality >= 90) { 1.133 + int i; 1.134 + for (i=0; i < MAX_COMPONENTS; i++) { 1.135 + cinfo.comp_info[i].h_samp_factor=1; 1.136 + cinfo.comp_info[i].v_samp_factor=1; 1.137 + } 1.138 + } 1.139 + 1.140 + // set up the destination manager 1.141 + jpeg_destination_mgr destmgr; 1.142 + destmgr.init_destination = initDestination; 1.143 + destmgr.empty_output_buffer = emptyOutputBuffer; 1.144 + destmgr.term_destination = termDestination; 1.145 + cinfo.dest = &destmgr; 1.146 + cinfo.client_data = this; 1.147 + 1.148 + jpeg_start_compress(&cinfo, 1); 1.149 + 1.150 + // feed it the rows 1.151 + if (aInputFormat == INPUT_FORMAT_RGB) { 1.152 + while (cinfo.next_scanline < cinfo.image_height) { 1.153 + const uint8_t* row = &aData[cinfo.next_scanline * aStride]; 1.154 + jpeg_write_scanlines(&cinfo, const_cast<uint8_t**>(&row), 1); 1.155 + } 1.156 + } else if (aInputFormat == INPUT_FORMAT_RGBA) { 1.157 + uint8_t* row = new uint8_t[aWidth * 3]; 1.158 + while (cinfo.next_scanline < cinfo.image_height) { 1.159 + ConvertRGBARow(&aData[cinfo.next_scanline * aStride], row, aWidth); 1.160 + jpeg_write_scanlines(&cinfo, &row, 1); 1.161 + } 1.162 + delete[] row; 1.163 + } else if (aInputFormat == INPUT_FORMAT_HOSTARGB) { 1.164 + uint8_t* row = new uint8_t[aWidth * 3]; 1.165 + while (cinfo.next_scanline < cinfo.image_height) { 1.166 + ConvertHostARGBRow(&aData[cinfo.next_scanline * aStride], row, aWidth); 1.167 + jpeg_write_scanlines(&cinfo, &row, 1); 1.168 + } 1.169 + delete[] row; 1.170 + } 1.171 + 1.172 + jpeg_finish_compress(&cinfo); 1.173 + jpeg_destroy_compress(&cinfo); 1.174 + 1.175 + mFinished = true; 1.176 + NotifyListener(); 1.177 + 1.178 + // if output callback can't get enough memory, it will free our buffer 1.179 + if (!mImageBuffer) 1.180 + return NS_ERROR_OUT_OF_MEMORY; 1.181 + 1.182 + return NS_OK; 1.183 +} 1.184 + 1.185 + 1.186 +NS_IMETHODIMP nsJPEGEncoder::StartImageEncode(uint32_t aWidth, 1.187 + uint32_t aHeight, 1.188 + uint32_t aInputFormat, 1.189 + const nsAString& aOutputOptions) 1.190 +{ 1.191 + return NS_ERROR_NOT_IMPLEMENTED; 1.192 +} 1.193 + 1.194 +// Returns the number of bytes in the image buffer used. 1.195 +NS_IMETHODIMP nsJPEGEncoder::GetImageBufferUsed(uint32_t *aOutputSize) 1.196 +{ 1.197 + NS_ENSURE_ARG_POINTER(aOutputSize); 1.198 + *aOutputSize = mImageBufferUsed; 1.199 + return NS_OK; 1.200 +} 1.201 + 1.202 +// Returns a pointer to the start of the image buffer 1.203 +NS_IMETHODIMP nsJPEGEncoder::GetImageBuffer(char **aOutputBuffer) 1.204 +{ 1.205 + NS_ENSURE_ARG_POINTER(aOutputBuffer); 1.206 + *aOutputBuffer = reinterpret_cast<char*>(mImageBuffer); 1.207 + return NS_OK; 1.208 +} 1.209 + 1.210 +NS_IMETHODIMP nsJPEGEncoder::AddImageFrame(const uint8_t* aData, 1.211 + uint32_t aLength, 1.212 + uint32_t aWidth, 1.213 + uint32_t aHeight, 1.214 + uint32_t aStride, 1.215 + uint32_t aFrameFormat, 1.216 + const nsAString& aFrameOptions) 1.217 +{ 1.218 + return NS_ERROR_NOT_IMPLEMENTED; 1.219 +} 1.220 + 1.221 +NS_IMETHODIMP nsJPEGEncoder::EndImageEncode() 1.222 +{ 1.223 + return NS_ERROR_NOT_IMPLEMENTED; 1.224 +} 1.225 + 1.226 + 1.227 +/* void close (); */ 1.228 +NS_IMETHODIMP nsJPEGEncoder::Close() 1.229 +{ 1.230 + if (mImageBuffer != nullptr) { 1.231 + moz_free(mImageBuffer); 1.232 + mImageBuffer = nullptr; 1.233 + mImageBufferSize = 0; 1.234 + mImageBufferUsed = 0; 1.235 + mImageBufferReadPoint = 0; 1.236 + } 1.237 + return NS_OK; 1.238 +} 1.239 + 1.240 +/* unsigned long available (); */ 1.241 +NS_IMETHODIMP nsJPEGEncoder::Available(uint64_t *_retval) 1.242 +{ 1.243 + if (!mImageBuffer) 1.244 + return NS_BASE_STREAM_CLOSED; 1.245 + 1.246 + *_retval = mImageBufferUsed - mImageBufferReadPoint; 1.247 + return NS_OK; 1.248 +} 1.249 + 1.250 +/* [noscript] unsigned long read (in charPtr aBuf, in unsigned long aCount); */ 1.251 +NS_IMETHODIMP nsJPEGEncoder::Read(char * aBuf, uint32_t aCount, 1.252 + uint32_t *_retval) 1.253 +{ 1.254 + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); 1.255 +} 1.256 + 1.257 +/* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, in voidPtr aClosure, in unsigned long aCount); */ 1.258 +NS_IMETHODIMP nsJPEGEncoder::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, uint32_t aCount, uint32_t *_retval) 1.259 +{ 1.260 + // Avoid another thread reallocing the buffer underneath us 1.261 + ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); 1.262 + 1.263 + uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint; 1.264 + if (maxCount == 0) { 1.265 + *_retval = 0; 1.266 + return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; 1.267 + } 1.268 + 1.269 + if (aCount > maxCount) 1.270 + aCount = maxCount; 1.271 + nsresult rv = aWriter(this, aClosure, 1.272 + reinterpret_cast<const char*>(mImageBuffer+mImageBufferReadPoint), 1.273 + 0, aCount, _retval); 1.274 + if (NS_SUCCEEDED(rv)) { 1.275 + NS_ASSERTION(*_retval <= aCount, "bad write count"); 1.276 + mImageBufferReadPoint += *_retval; 1.277 + } 1.278 + 1.279 + // errors returned from the writer end here! 1.280 + return NS_OK; 1.281 +} 1.282 + 1.283 +/* boolean isNonBlocking (); */ 1.284 +NS_IMETHODIMP nsJPEGEncoder::IsNonBlocking(bool *_retval) 1.285 +{ 1.286 + *_retval = true; 1.287 + return NS_OK; 1.288 +} 1.289 + 1.290 +NS_IMETHODIMP nsJPEGEncoder::AsyncWait(nsIInputStreamCallback *aCallback, 1.291 + uint32_t aFlags, 1.292 + uint32_t aRequestedCount, 1.293 + nsIEventTarget *aTarget) 1.294 +{ 1.295 + if (aFlags != 0) 1.296 + return NS_ERROR_NOT_IMPLEMENTED; 1.297 + 1.298 + if (mCallback || mCallbackTarget) 1.299 + return NS_ERROR_UNEXPECTED; 1.300 + 1.301 + mCallbackTarget = aTarget; 1.302 + // 0 means "any number of bytes except 0" 1.303 + mNotifyThreshold = aRequestedCount; 1.304 + if (!aRequestedCount) 1.305 + mNotifyThreshold = 1024; // 1 KB seems good. We don't want to notify incessantly 1.306 + 1.307 + // We set the callback absolutely last, because NotifyListener uses it to 1.308 + // determine if someone needs to be notified. If we don't set it last, 1.309 + // NotifyListener might try to fire off a notification to a null target 1.310 + // which will generally cause non-threadsafe objects to be used off the main thread 1.311 + mCallback = aCallback; 1.312 + 1.313 + // What we are being asked for may be present already 1.314 + NotifyListener(); 1.315 + return NS_OK; 1.316 +} 1.317 + 1.318 +NS_IMETHODIMP nsJPEGEncoder::CloseWithStatus(nsresult aStatus) 1.319 +{ 1.320 + return Close(); 1.321 +} 1.322 + 1.323 + 1.324 + 1.325 +// nsJPEGEncoder::ConvertHostARGBRow 1.326 +// 1.327 +// Our colors are stored with premultiplied alphas, but we need 1.328 +// an output with no alpha in machine-independent byte order. 1.329 +// 1.330 +// See gfx/cairo/cairo/src/cairo-png.c 1.331 +void 1.332 +nsJPEGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest, 1.333 + uint32_t aPixelWidth) 1.334 +{ 1.335 + for (uint32_t x = 0; x < aPixelWidth; x++) { 1.336 + const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x]; 1.337 + uint8_t *pixelOut = &aDest[x * 3]; 1.338 + 1.339 + pixelOut[0] = (pixelIn & 0xff0000) >> 16; 1.340 + pixelOut[1] = (pixelIn & 0x00ff00) >> 8; 1.341 + pixelOut[2] = (pixelIn & 0x0000ff) >> 0; 1.342 + } 1.343 +} 1.344 + 1.345 +/** 1.346 + * nsJPEGEncoder::ConvertRGBARow 1.347 + * 1.348 + * Input is RGBA, output is RGB, so we should alpha-premultiply. 1.349 + */ 1.350 +void 1.351 +nsJPEGEncoder::ConvertRGBARow(const uint8_t* aSrc, uint8_t* aDest, 1.352 + uint32_t aPixelWidth) 1.353 +{ 1.354 + for (uint32_t x = 0; x < aPixelWidth; x++) { 1.355 + const uint8_t* pixelIn = &aSrc[x * 4]; 1.356 + uint8_t* pixelOut = &aDest[x * 3]; 1.357 + 1.358 + uint8_t alpha = pixelIn[3]; 1.359 + pixelOut[0] = gfxPreMultiply(pixelIn[0], alpha); 1.360 + pixelOut[1] = gfxPreMultiply(pixelIn[1], alpha); 1.361 + pixelOut[2] = gfxPreMultiply(pixelIn[2], alpha); 1.362 + } 1.363 +} 1.364 + 1.365 +// nsJPEGEncoder::initDestination 1.366 +// 1.367 +// Initialize destination. This is called by jpeg_start_compress() before 1.368 +// any data is actually written. It must initialize next_output_byte and 1.369 +// free_in_buffer. free_in_buffer must be initialized to a positive value. 1.370 + 1.371 +void // static 1.372 +nsJPEGEncoder::initDestination(jpeg_compress_struct* cinfo) 1.373 +{ 1.374 + nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data); 1.375 + NS_ASSERTION(! that->mImageBuffer, "Image buffer already initialized"); 1.376 + 1.377 + that->mImageBufferSize = 8192; 1.378 + that->mImageBuffer = (uint8_t*)moz_malloc(that->mImageBufferSize); 1.379 + that->mImageBufferUsed = 0; 1.380 + 1.381 + cinfo->dest->next_output_byte = that->mImageBuffer; 1.382 + cinfo->dest->free_in_buffer = that->mImageBufferSize; 1.383 +} 1.384 + 1.385 + 1.386 +// nsJPEGEncoder::emptyOutputBuffer 1.387 +// 1.388 +// This is called whenever the buffer has filled (free_in_buffer reaches 1.389 +// zero). In typical applications, it should write out the *entire* buffer 1.390 +// (use the saved start address and buffer length; ignore the current state 1.391 +// of next_output_byte and free_in_buffer). Then reset the pointer & count 1.392 +// to the start of the buffer, and return TRUE indicating that the buffer 1.393 +// has been dumped. free_in_buffer must be set to a positive value when 1.394 +// TRUE is returned. A FALSE return should only be used when I/O suspension 1.395 +// is desired (this operating mode is discussed in the next section). 1.396 + 1.397 +boolean // static 1.398 +nsJPEGEncoder::emptyOutputBuffer(jpeg_compress_struct* cinfo) 1.399 +{ 1.400 + nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data); 1.401 + NS_ASSERTION(that->mImageBuffer, "No buffer to empty!"); 1.402 + 1.403 + // When we're reallocing the buffer we need to take the lock to ensure 1.404 + // that nobody is trying to read from the buffer we are destroying 1.405 + ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor); 1.406 + 1.407 + that->mImageBufferUsed = that->mImageBufferSize; 1.408 + 1.409 + // expand buffer, just double size each time 1.410 + that->mImageBufferSize *= 2; 1.411 + 1.412 + uint8_t* newBuf = (uint8_t*)moz_realloc(that->mImageBuffer, 1.413 + that->mImageBufferSize); 1.414 + if (! newBuf) { 1.415 + // can't resize, just zero (this will keep us from writing more) 1.416 + moz_free(that->mImageBuffer); 1.417 + that->mImageBuffer = nullptr; 1.418 + that->mImageBufferSize = 0; 1.419 + that->mImageBufferUsed = 0; 1.420 + 1.421 + // This seems to be the only way to do errors through the JPEG library. We 1.422 + // pass an nsresult masquerading as an int, which works because the 1.423 + // setjmp() caller casts it back. 1.424 + longjmp(((encoder_error_mgr*)(cinfo->err))->setjmp_buffer, 1.425 + static_cast<int>(NS_ERROR_OUT_OF_MEMORY)); 1.426 + } 1.427 + that->mImageBuffer = newBuf; 1.428 + 1.429 + cinfo->dest->next_output_byte = &that->mImageBuffer[that->mImageBufferUsed]; 1.430 + cinfo->dest->free_in_buffer = that->mImageBufferSize - that->mImageBufferUsed; 1.431 + return 1; 1.432 +} 1.433 + 1.434 + 1.435 +// nsJPEGEncoder::termDestination 1.436 +// 1.437 +// Terminate destination --- called by jpeg_finish_compress() after all data 1.438 +// has been written. In most applications, this must flush any data 1.439 +// remaining in the buffer. Use either next_output_byte or free_in_buffer 1.440 +// to determine how much data is in the buffer. 1.441 + 1.442 +void // static 1.443 +nsJPEGEncoder::termDestination(jpeg_compress_struct* cinfo) 1.444 +{ 1.445 + nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data); 1.446 + if (! that->mImageBuffer) 1.447 + return; 1.448 + that->mImageBufferUsed = cinfo->dest->next_output_byte - that->mImageBuffer; 1.449 + NS_ASSERTION(that->mImageBufferUsed < that->mImageBufferSize, 1.450 + "JPEG library busted, got a bad image buffer size"); 1.451 + that->NotifyListener(); 1.452 +} 1.453 + 1.454 + 1.455 +// nsJPEGEncoder::errorExit 1.456 +// 1.457 +// Override the standard error method in the IJG JPEG decoder code. This 1.458 +// was mostly copied from nsJPEGDecoder.cpp 1.459 + 1.460 +void // static 1.461 +nsJPEGEncoder::errorExit(jpeg_common_struct* cinfo) 1.462 +{ 1.463 + nsresult error_code; 1.464 + encoder_error_mgr *err = (encoder_error_mgr *) cinfo->err; 1.465 + 1.466 + // Convert error to a browser error code 1.467 + switch (cinfo->err->msg_code) { 1.468 + case JERR_OUT_OF_MEMORY: 1.469 + error_code = NS_ERROR_OUT_OF_MEMORY; 1.470 + break; 1.471 + default: 1.472 + error_code = NS_ERROR_FAILURE; 1.473 + } 1.474 + 1.475 + // Return control to the setjmp point. We pass an nsresult masquerading as 1.476 + // an int, which works because the setjmp() caller casts it back. 1.477 + longjmp(err->setjmp_buffer, static_cast<int>(error_code)); 1.478 +} 1.479 + 1.480 +void 1.481 +nsJPEGEncoder::NotifyListener() 1.482 +{ 1.483 + // We might call this function on multiple threads (any threads that call 1.484 + // AsyncWait and any that do encoding) so we lock to avoid notifying the 1.485 + // listener twice about the same data (which generally leads to a truncated 1.486 + // image). 1.487 + ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); 1.488 + 1.489 + if (mCallback && 1.490 + (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold || 1.491 + mFinished)) { 1.492 + nsCOMPtr<nsIInputStreamCallback> callback; 1.493 + if (mCallbackTarget) { 1.494 + callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); 1.495 + } else { 1.496 + callback = mCallback; 1.497 + } 1.498 + 1.499 + NS_ASSERTION(callback, "Shouldn't fail to make the callback"); 1.500 + // Null the callback first because OnInputStreamReady could reenter 1.501 + // AsyncWait 1.502 + mCallback = nullptr; 1.503 + mCallbackTarget = nullptr; 1.504 + mNotifyThreshold = 0; 1.505 + 1.506 + callback->OnInputStreamReady(this); 1.507 + } 1.508 +}