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