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