1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/image/encoders/png/nsPNGEncoder.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,726 @@ 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 "nsCRT.h" 1.10 +#include "nsPNGEncoder.h" 1.11 +#include "prprf.h" 1.12 +#include "nsString.h" 1.13 +#include "nsStreamUtils.h" 1.14 + 1.15 +using namespace mozilla; 1.16 + 1.17 +NS_IMPL_ISUPPORTS(nsPNGEncoder, imgIEncoder, nsIInputStream, nsIAsyncInputStream) 1.18 + 1.19 +nsPNGEncoder::nsPNGEncoder() : mPNG(nullptr), mPNGinfo(nullptr), 1.20 + mIsAnimation(false), 1.21 + mFinished(false), 1.22 + mImageBuffer(nullptr), mImageBufferSize(0), 1.23 + mImageBufferUsed(0), mImageBufferReadPoint(0), 1.24 + mCallback(nullptr), 1.25 + mCallbackTarget(nullptr), mNotifyThreshold(0), 1.26 + mReentrantMonitor("nsPNGEncoder.mReentrantMonitor") 1.27 +{ 1.28 +} 1.29 + 1.30 +nsPNGEncoder::~nsPNGEncoder() 1.31 +{ 1.32 + if (mImageBuffer) { 1.33 + moz_free(mImageBuffer); 1.34 + mImageBuffer = nullptr; 1.35 + } 1.36 + // don't leak if EndImageEncode wasn't called 1.37 + if (mPNG) 1.38 + png_destroy_write_struct(&mPNG, &mPNGinfo); 1.39 +} 1.40 + 1.41 +// nsPNGEncoder::InitFromData 1.42 +// 1.43 +// One output option is supported: "transparency=none" means that the 1.44 +// output PNG will not have an alpha channel, even if the input does. 1.45 +// 1.46 +// Based partially on gfx/cairo/cairo/src/cairo-png.c 1.47 +// See also modules/libimg/png/libpng.txt 1.48 + 1.49 +NS_IMETHODIMP nsPNGEncoder::InitFromData(const uint8_t* aData, 1.50 + uint32_t aLength, // (unused, 1.51 + // req'd by JS) 1.52 + uint32_t aWidth, 1.53 + uint32_t aHeight, 1.54 + uint32_t aStride, 1.55 + uint32_t aInputFormat, 1.56 + const nsAString& aOutputOptions) 1.57 +{ 1.58 + NS_ENSURE_ARG(aData); 1.59 + nsresult rv; 1.60 + 1.61 + rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions); 1.62 + if (!NS_SUCCEEDED(rv)) 1.63 + return rv; 1.64 + 1.65 + rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride, 1.66 + aInputFormat, aOutputOptions); 1.67 + if (!NS_SUCCEEDED(rv)) 1.68 + return rv; 1.69 + 1.70 + rv = EndImageEncode(); 1.71 + 1.72 + return rv; 1.73 +} 1.74 + 1.75 + 1.76 +// nsPNGEncoder::StartImageEncode 1.77 +// 1.78 +// 1.79 +// See ::InitFromData for other info. 1.80 +NS_IMETHODIMP nsPNGEncoder::StartImageEncode(uint32_t aWidth, 1.81 + uint32_t aHeight, 1.82 + uint32_t aInputFormat, 1.83 + const nsAString& aOutputOptions) 1.84 +{ 1.85 + bool useTransparency = true, skipFirstFrame = false; 1.86 + uint32_t numFrames = 1; 1.87 + uint32_t numPlays = 0; // For animations, 0 == forever 1.88 + 1.89 + // can't initialize more than once 1.90 + if (mImageBuffer != nullptr) 1.91 + return NS_ERROR_ALREADY_INITIALIZED; 1.92 + 1.93 + // validate input format 1.94 + if (aInputFormat != INPUT_FORMAT_RGB && 1.95 + aInputFormat != INPUT_FORMAT_RGBA && 1.96 + aInputFormat != INPUT_FORMAT_HOSTARGB) 1.97 + return NS_ERROR_INVALID_ARG; 1.98 + 1.99 + // parse and check any provided output options 1.100 + nsresult rv = ParseOptions(aOutputOptions, &useTransparency, &skipFirstFrame, 1.101 + &numFrames, &numPlays, nullptr, nullptr, 1.102 + nullptr, nullptr, nullptr); 1.103 + if (rv != NS_OK) 1.104 + return rv; 1.105 + 1.106 +#ifdef PNG_APNG_SUPPORTED 1.107 + if (numFrames > 1) 1.108 + mIsAnimation = true; 1.109 + 1.110 +#endif 1.111 + 1.112 + // initialize 1.113 + mPNG = png_create_write_struct(PNG_LIBPNG_VER_STRING, 1.114 + nullptr, 1.115 + ErrorCallback, 1.116 + WarningCallback); 1.117 + if (! mPNG) 1.118 + return NS_ERROR_OUT_OF_MEMORY; 1.119 + 1.120 + mPNGinfo = png_create_info_struct(mPNG); 1.121 + if (! mPNGinfo) { 1.122 + png_destroy_write_struct(&mPNG, nullptr); 1.123 + return NS_ERROR_FAILURE; 1.124 + } 1.125 + 1.126 + // libpng's error handler jumps back here upon an error. 1.127 + // Note: It's important that all png_* callers do this, or errors 1.128 + // will result in a corrupt time-warped stack. 1.129 + if (setjmp(png_jmpbuf(mPNG))) { 1.130 + png_destroy_write_struct(&mPNG, &mPNGinfo); 1.131 + return NS_ERROR_FAILURE; 1.132 + } 1.133 + 1.134 + // Set up to read the data into our image buffer, start out with an 8K 1.135 + // estimated size. Note: we don't have to worry about freeing this data 1.136 + // in this function. It will be freed on object destruction. 1.137 + mImageBufferSize = 8192; 1.138 + mImageBuffer = (uint8_t*)moz_malloc(mImageBufferSize); 1.139 + if (!mImageBuffer) { 1.140 + png_destroy_write_struct(&mPNG, &mPNGinfo); 1.141 + return NS_ERROR_OUT_OF_MEMORY; 1.142 + } 1.143 + mImageBufferUsed = 0; 1.144 + 1.145 + // set our callback for libpng to give us the data 1.146 + png_set_write_fn(mPNG, this, WriteCallback, nullptr); 1.147 + 1.148 + // include alpha? 1.149 + int colorType; 1.150 + if ((aInputFormat == INPUT_FORMAT_HOSTARGB || 1.151 + aInputFormat == INPUT_FORMAT_RGBA) && 1.152 + useTransparency) 1.153 + colorType = PNG_COLOR_TYPE_RGB_ALPHA; 1.154 + else 1.155 + colorType = PNG_COLOR_TYPE_RGB; 1.156 + 1.157 + png_set_IHDR(mPNG, mPNGinfo, aWidth, aHeight, 8, colorType, 1.158 + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, 1.159 + PNG_FILTER_TYPE_DEFAULT); 1.160 + 1.161 +#ifdef PNG_APNG_SUPPORTED 1.162 + if (mIsAnimation) { 1.163 + png_set_first_frame_is_hidden(mPNG, mPNGinfo, skipFirstFrame); 1.164 + png_set_acTL(mPNG, mPNGinfo, numFrames, numPlays); 1.165 + } 1.166 +#endif 1.167 + 1.168 + // XXX: support PLTE, gAMA, tRNS, bKGD? 1.169 + 1.170 + png_write_info(mPNG, mPNGinfo); 1.171 + 1.172 + return NS_OK; 1.173 +} 1.174 + 1.175 +// Returns the number of bytes in the image buffer used. 1.176 +NS_IMETHODIMP nsPNGEncoder::GetImageBufferUsed(uint32_t *aOutputSize) 1.177 +{ 1.178 + NS_ENSURE_ARG_POINTER(aOutputSize); 1.179 + *aOutputSize = mImageBufferUsed; 1.180 + return NS_OK; 1.181 +} 1.182 + 1.183 +// Returns a pointer to the start of the image buffer 1.184 +NS_IMETHODIMP nsPNGEncoder::GetImageBuffer(char **aOutputBuffer) 1.185 +{ 1.186 + NS_ENSURE_ARG_POINTER(aOutputBuffer); 1.187 + *aOutputBuffer = reinterpret_cast<char*>(mImageBuffer); 1.188 + return NS_OK; 1.189 +} 1.190 + 1.191 +NS_IMETHODIMP nsPNGEncoder::AddImageFrame(const uint8_t* aData, 1.192 + uint32_t aLength, // (unused, 1.193 + // req'd by JS) 1.194 + uint32_t aWidth, 1.195 + uint32_t aHeight, 1.196 + uint32_t aStride, 1.197 + uint32_t aInputFormat, 1.198 + const nsAString& aFrameOptions) 1.199 +{ 1.200 + bool useTransparency= true; 1.201 + uint32_t delay_ms = 500; 1.202 +#ifdef PNG_APNG_SUPPORTED 1.203 + uint32_t dispose_op = PNG_DISPOSE_OP_NONE; 1.204 + uint32_t blend_op = PNG_BLEND_OP_SOURCE; 1.205 +#else 1.206 + uint32_t dispose_op; 1.207 + uint32_t blend_op; 1.208 +#endif 1.209 + uint32_t x_offset = 0, y_offset = 0; 1.210 + 1.211 + // must be initialized 1.212 + if (mImageBuffer == nullptr) 1.213 + return NS_ERROR_NOT_INITIALIZED; 1.214 + 1.215 + // EndImageEncode was done, or some error occurred earlier 1.216 + if (!mPNG) 1.217 + return NS_BASE_STREAM_CLOSED; 1.218 + 1.219 + // validate input format 1.220 + if (aInputFormat != INPUT_FORMAT_RGB && 1.221 + aInputFormat != INPUT_FORMAT_RGBA && 1.222 + aInputFormat != INPUT_FORMAT_HOSTARGB) 1.223 + return NS_ERROR_INVALID_ARG; 1.224 + 1.225 + // libpng's error handler jumps back here upon an error. 1.226 + if (setjmp(png_jmpbuf(mPNG))) { 1.227 + png_destroy_write_struct(&mPNG, &mPNGinfo); 1.228 + return NS_ERROR_FAILURE; 1.229 + } 1.230 + 1.231 + // parse and check any provided output options 1.232 + nsresult rv = ParseOptions(aFrameOptions, &useTransparency, nullptr, 1.233 + nullptr, nullptr, &dispose_op, &blend_op, 1.234 + &delay_ms, &x_offset, &y_offset); 1.235 + if (rv != NS_OK) 1.236 + return rv; 1.237 + 1.238 +#ifdef PNG_APNG_SUPPORTED 1.239 + if (mIsAnimation) { 1.240 + // XXX the row pointers arg (#3) is unused, can it be removed? 1.241 + png_write_frame_head(mPNG, mPNGinfo, nullptr, 1.242 + aWidth, aHeight, x_offset, y_offset, 1.243 + delay_ms, 1000, dispose_op, blend_op); 1.244 + } 1.245 +#endif 1.246 + 1.247 + // Stride is the padded width of each row, so it better be longer 1.248 + // (I'm afraid people will not understand what stride means, so 1.249 + // check it well) 1.250 + if ((aInputFormat == INPUT_FORMAT_RGB && 1.251 + aStride < aWidth * 3) || 1.252 + ((aInputFormat == INPUT_FORMAT_RGBA || 1.253 + aInputFormat == INPUT_FORMAT_HOSTARGB) && 1.254 + aStride < aWidth * 4)) { 1.255 + NS_WARNING("Invalid stride for InitFromData/AddImageFrame"); 1.256 + return NS_ERROR_INVALID_ARG; 1.257 + } 1.258 + 1.259 +#ifdef PNG_WRITE_FILTER_SUPPORTED 1.260 + png_set_filter(mPNG, PNG_FILTER_TYPE_BASE, PNG_FILTER_VALUE_NONE); 1.261 +#endif 1.262 + 1.263 + // write each row: if we add more input formats, we may want to 1.264 + // generalize the conversions 1.265 + if (aInputFormat == INPUT_FORMAT_HOSTARGB) { 1.266 + // PNG requires RGBA with post-multiplied alpha, so we need to 1.267 + // convert 1.268 + uint8_t* row = new uint8_t[aWidth * 4]; 1.269 + for (uint32_t y = 0; y < aHeight; y ++) { 1.270 + ConvertHostARGBRow(&aData[y * aStride], row, aWidth, useTransparency); 1.271 + png_write_row(mPNG, row); 1.272 + } 1.273 + delete[] row; 1.274 + 1.275 + } else if (aInputFormat == INPUT_FORMAT_RGBA && ! useTransparency) { 1.276 + // RBGA, but we need to strip the alpha 1.277 + uint8_t* row = new uint8_t[aWidth * 4]; 1.278 + for (uint32_t y = 0; y < aHeight; y ++) { 1.279 + StripAlpha(&aData[y * aStride], row, aWidth); 1.280 + png_write_row(mPNG, row); 1.281 + } 1.282 + delete[] row; 1.283 + 1.284 + } else if (aInputFormat == INPUT_FORMAT_RGB || 1.285 + aInputFormat == INPUT_FORMAT_RGBA) { 1.286 + // simple RBG(A), no conversion needed 1.287 + for (uint32_t y = 0; y < aHeight; y ++) { 1.288 + png_write_row(mPNG, (uint8_t*)&aData[y * aStride]); 1.289 + } 1.290 + 1.291 + } else { 1.292 + NS_NOTREACHED("Bad format type"); 1.293 + return NS_ERROR_INVALID_ARG; 1.294 + } 1.295 + 1.296 +#ifdef PNG_APNG_SUPPORTED 1.297 + if (mIsAnimation) { 1.298 + png_write_frame_tail(mPNG, mPNGinfo); 1.299 + } 1.300 +#endif 1.301 + 1.302 + return NS_OK; 1.303 +} 1.304 + 1.305 + 1.306 +NS_IMETHODIMP nsPNGEncoder::EndImageEncode() 1.307 +{ 1.308 + // must be initialized 1.309 + if (mImageBuffer == nullptr) 1.310 + return NS_ERROR_NOT_INITIALIZED; 1.311 + 1.312 + // EndImageEncode has already been called, or some error 1.313 + // occurred earlier 1.314 + if (!mPNG) 1.315 + return NS_BASE_STREAM_CLOSED; 1.316 + 1.317 + // libpng's error handler jumps back here upon an error. 1.318 + if (setjmp(png_jmpbuf(mPNG))) { 1.319 + png_destroy_write_struct(&mPNG, &mPNGinfo); 1.320 + return NS_ERROR_FAILURE; 1.321 + } 1.322 + 1.323 + png_write_end(mPNG, mPNGinfo); 1.324 + png_destroy_write_struct(&mPNG, &mPNGinfo); 1.325 + 1.326 + mFinished = true; 1.327 + NotifyListener(); 1.328 + 1.329 + // if output callback can't get enough memory, it will free our buffer 1.330 + if (!mImageBuffer) 1.331 + return NS_ERROR_OUT_OF_MEMORY; 1.332 + 1.333 + return NS_OK; 1.334 +} 1.335 + 1.336 + 1.337 +nsresult 1.338 +nsPNGEncoder::ParseOptions(const nsAString& aOptions, 1.339 + bool* useTransparency, 1.340 + bool* skipFirstFrame, 1.341 + uint32_t* numFrames, 1.342 + uint32_t* numPlays, 1.343 + uint32_t* frameDispose, 1.344 + uint32_t* frameBlend, 1.345 + uint32_t* frameDelay, 1.346 + uint32_t* offsetX, 1.347 + uint32_t* offsetY) 1.348 +{ 1.349 +#ifdef PNG_APNG_SUPPORTED 1.350 + // Make a copy of aOptions, because strtok() will modify it. 1.351 + nsAutoCString optionsCopy; 1.352 + optionsCopy.Assign(NS_ConvertUTF16toUTF8(aOptions)); 1.353 + char* options = optionsCopy.BeginWriting(); 1.354 + 1.355 + while (char* token = nsCRT::strtok(options, ";", &options)) { 1.356 + // If there's an '=' character, split the token around it. 1.357 + char* equals = token, *value = nullptr; 1.358 + while(*equals != '=' && *equals) { 1.359 + ++equals; 1.360 + } 1.361 + if (*equals == '=') 1.362 + value = equals + 1; 1.363 + 1.364 + if (value) 1.365 + *equals = '\0'; // temporary null 1.366 + 1.367 + // transparency=[yes|no|none] 1.368 + if (nsCRT::strcmp(token, "transparency") == 0 && useTransparency) { 1.369 + if (!value) 1.370 + return NS_ERROR_INVALID_ARG; 1.371 + 1.372 + if (nsCRT::strcmp(value, "none") == 0 || 1.373 + nsCRT::strcmp(value, "no") == 0) { 1.374 + *useTransparency = false; 1.375 + } else if (nsCRT::strcmp(value, "yes") == 0) { 1.376 + *useTransparency = true; 1.377 + } else { 1.378 + return NS_ERROR_INVALID_ARG; 1.379 + } 1.380 + 1.381 + // skipfirstframe=[yes|no] 1.382 + } else if (nsCRT::strcmp(token, "skipfirstframe") == 0 && 1.383 + skipFirstFrame) { 1.384 + if (!value) 1.385 + return NS_ERROR_INVALID_ARG; 1.386 + 1.387 + if (nsCRT::strcmp(value, "no") == 0) { 1.388 + *skipFirstFrame = false; 1.389 + } else if (nsCRT::strcmp(value, "yes") == 0) { 1.390 + *skipFirstFrame = true; 1.391 + } else { 1.392 + return NS_ERROR_INVALID_ARG; 1.393 + } 1.394 + 1.395 + // frames=# 1.396 + } else if (nsCRT::strcmp(token, "frames") == 0 && numFrames) { 1.397 + if (!value) 1.398 + return NS_ERROR_INVALID_ARG; 1.399 + 1.400 + if (PR_sscanf(value, "%u", numFrames) != 1) { 1.401 + return NS_ERROR_INVALID_ARG; 1.402 + } 1.403 + 1.404 + // frames=0 is nonsense. 1.405 + if (*numFrames == 0) 1.406 + return NS_ERROR_INVALID_ARG; 1.407 + 1.408 + // plays=# 1.409 + } else if (nsCRT::strcmp(token, "plays") == 0 && numPlays) { 1.410 + if (!value) 1.411 + return NS_ERROR_INVALID_ARG; 1.412 + 1.413 + // plays=0 to loop forever, otherwise play sequence specified 1.414 + // number of times 1.415 + if (PR_sscanf(value, "%u", numPlays) != 1) 1.416 + return NS_ERROR_INVALID_ARG; 1.417 + 1.418 + // dispose=[none|background|previous] 1.419 + } else if (nsCRT::strcmp(token, "dispose") == 0 && frameDispose) { 1.420 + if (!value) 1.421 + return NS_ERROR_INVALID_ARG; 1.422 + 1.423 + if (nsCRT::strcmp(value, "none") == 0) { 1.424 + *frameDispose = PNG_DISPOSE_OP_NONE; 1.425 + } else if (nsCRT::strcmp(value, "background") == 0) { 1.426 + *frameDispose = PNG_DISPOSE_OP_BACKGROUND; 1.427 + } else if (nsCRT::strcmp(value, "previous") == 0) { 1.428 + *frameDispose = PNG_DISPOSE_OP_PREVIOUS; 1.429 + } else { 1.430 + return NS_ERROR_INVALID_ARG; 1.431 + } 1.432 + 1.433 + // blend=[source|over] 1.434 + } else if (nsCRT::strcmp(token, "blend") == 0 && frameBlend) { 1.435 + if (!value) 1.436 + return NS_ERROR_INVALID_ARG; 1.437 + 1.438 + if (nsCRT::strcmp(value, "source") == 0) { 1.439 + *frameBlend = PNG_BLEND_OP_SOURCE; 1.440 + } else if (nsCRT::strcmp(value, "over") == 0) { 1.441 + *frameBlend = PNG_BLEND_OP_OVER; 1.442 + } else { 1.443 + return NS_ERROR_INVALID_ARG; 1.444 + } 1.445 + 1.446 + // delay=# (in ms) 1.447 + } else if (nsCRT::strcmp(token, "delay") == 0 && frameDelay) { 1.448 + if (!value) 1.449 + return NS_ERROR_INVALID_ARG; 1.450 + 1.451 + if (PR_sscanf(value, "%u", frameDelay) != 1) 1.452 + return NS_ERROR_INVALID_ARG; 1.453 + 1.454 + // xoffset=# 1.455 + } else if (nsCRT::strcmp(token, "xoffset") == 0 && offsetX) { 1.456 + if (!value) 1.457 + return NS_ERROR_INVALID_ARG; 1.458 + 1.459 + if (PR_sscanf(value, "%u", offsetX) != 1) 1.460 + return NS_ERROR_INVALID_ARG; 1.461 + 1.462 + // yoffset=# 1.463 + } else if (nsCRT::strcmp(token, "yoffset") == 0 && offsetY) { 1.464 + if (!value) 1.465 + return NS_ERROR_INVALID_ARG; 1.466 + 1.467 + if (PR_sscanf(value, "%u", offsetY) != 1) 1.468 + return NS_ERROR_INVALID_ARG; 1.469 + 1.470 + // unknown token name 1.471 + } else 1.472 + return NS_ERROR_INVALID_ARG; 1.473 + 1.474 + if (value) 1.475 + *equals = '='; // restore '=' so strtok doesn't get lost 1.476 + } 1.477 + 1.478 +#endif 1.479 + return NS_OK; 1.480 +} 1.481 + 1.482 + 1.483 +/* void close (); */ 1.484 +NS_IMETHODIMP nsPNGEncoder::Close() 1.485 +{ 1.486 + if (mImageBuffer != nullptr) { 1.487 + moz_free(mImageBuffer); 1.488 + mImageBuffer = nullptr; 1.489 + mImageBufferSize = 0; 1.490 + mImageBufferUsed = 0; 1.491 + mImageBufferReadPoint = 0; 1.492 + } 1.493 + return NS_OK; 1.494 +} 1.495 + 1.496 +/* unsigned long available (); */ 1.497 +NS_IMETHODIMP nsPNGEncoder::Available(uint64_t *_retval) 1.498 +{ 1.499 + if (!mImageBuffer) 1.500 + return NS_BASE_STREAM_CLOSED; 1.501 + 1.502 + *_retval = mImageBufferUsed - mImageBufferReadPoint; 1.503 + return NS_OK; 1.504 +} 1.505 + 1.506 +/* [noscript] unsigned long read (in charPtr aBuf, 1.507 + in unsigned long aCount); */ 1.508 +NS_IMETHODIMP nsPNGEncoder::Read(char * aBuf, uint32_t aCount, 1.509 + uint32_t *_retval) 1.510 +{ 1.511 + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); 1.512 +} 1.513 + 1.514 +/* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, 1.515 + in voidPtr aClosure, 1.516 + in unsigned long aCount); */ 1.517 +NS_IMETHODIMP nsPNGEncoder::ReadSegments(nsWriteSegmentFun aWriter, 1.518 + void *aClosure, uint32_t aCount, 1.519 + uint32_t *_retval) 1.520 +{ 1.521 + // Avoid another thread reallocing the buffer underneath us 1.522 + ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); 1.523 + 1.524 + uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint; 1.525 + if (maxCount == 0) { 1.526 + *_retval = 0; 1.527 + return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; 1.528 + } 1.529 + 1.530 + if (aCount > maxCount) 1.531 + aCount = maxCount; 1.532 + nsresult rv = 1.533 + aWriter(this, aClosure, 1.534 + reinterpret_cast<const char*>(mImageBuffer+mImageBufferReadPoint), 1.535 + 0, aCount, _retval); 1.536 + if (NS_SUCCEEDED(rv)) { 1.537 + NS_ASSERTION(*_retval <= aCount, "bad write count"); 1.538 + mImageBufferReadPoint += *_retval; 1.539 + } 1.540 + 1.541 + // errors returned from the writer end here! 1.542 + return NS_OK; 1.543 +} 1.544 + 1.545 +/* boolean isNonBlocking (); */ 1.546 +NS_IMETHODIMP nsPNGEncoder::IsNonBlocking(bool *_retval) 1.547 +{ 1.548 + *_retval = true; 1.549 + return NS_OK; 1.550 +} 1.551 + 1.552 +NS_IMETHODIMP nsPNGEncoder::AsyncWait(nsIInputStreamCallback *aCallback, 1.553 + uint32_t aFlags, 1.554 + uint32_t aRequestedCount, 1.555 + nsIEventTarget *aTarget) 1.556 +{ 1.557 + if (aFlags != 0) 1.558 + return NS_ERROR_NOT_IMPLEMENTED; 1.559 + 1.560 + if (mCallback || mCallbackTarget) 1.561 + return NS_ERROR_UNEXPECTED; 1.562 + 1.563 + mCallbackTarget = aTarget; 1.564 + // 0 means "any number of bytes except 0" 1.565 + mNotifyThreshold = aRequestedCount; 1.566 + if (!aRequestedCount) 1.567 + mNotifyThreshold = 1024; // We don't want to notify incessantly 1.568 + 1.569 + // We set the callback absolutely last, because NotifyListener uses it to 1.570 + // determine if someone needs to be notified. If we don't set it last, 1.571 + // NotifyListener might try to fire off a notification to a null target 1.572 + // which will generally cause non-threadsafe objects to be used off the main thread 1.573 + mCallback = aCallback; 1.574 + 1.575 + // What we are being asked for may be present already 1.576 + NotifyListener(); 1.577 + return NS_OK; 1.578 +} 1.579 + 1.580 +NS_IMETHODIMP nsPNGEncoder::CloseWithStatus(nsresult aStatus) 1.581 +{ 1.582 + return Close(); 1.583 +} 1.584 + 1.585 +// nsPNGEncoder::ConvertHostARGBRow 1.586 +// 1.587 +// Our colors are stored with premultiplied alphas, but PNGs use 1.588 +// post-multiplied alpha. This swaps to PNG-style alpha. 1.589 +// 1.590 +// Copied from gfx/cairo/cairo/src/cairo-png.c 1.591 + 1.592 +void 1.593 +nsPNGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest, 1.594 + uint32_t aPixelWidth, 1.595 + bool aUseTransparency) 1.596 +{ 1.597 + uint32_t pixelStride = aUseTransparency ? 4 : 3; 1.598 + for (uint32_t x = 0; x < aPixelWidth; x ++) { 1.599 + const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x]; 1.600 + uint8_t *pixelOut = &aDest[x * pixelStride]; 1.601 + 1.602 + uint8_t alpha = (pixelIn & 0xff000000) >> 24; 1.603 + if (alpha == 0) { 1.604 + pixelOut[0] = pixelOut[1] = pixelOut[2] = pixelOut[3] = 0; 1.605 + } else { 1.606 + pixelOut[0] = (((pixelIn & 0xff0000) >> 16) * 255 + alpha / 2) / alpha; 1.607 + pixelOut[1] = (((pixelIn & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha; 1.608 + pixelOut[2] = (((pixelIn & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha; 1.609 + if (aUseTransparency) 1.610 + pixelOut[3] = alpha; 1.611 + } 1.612 + } 1.613 +} 1.614 + 1.615 + 1.616 +// nsPNGEncoder::StripAlpha 1.617 +// 1.618 +// Input is RGBA, output is RGB 1.619 + 1.620 +void 1.621 +nsPNGEncoder::StripAlpha(const uint8_t* aSrc, uint8_t* aDest, 1.622 + uint32_t aPixelWidth) 1.623 +{ 1.624 + for (uint32_t x = 0; x < aPixelWidth; x ++) { 1.625 + const uint8_t* pixelIn = &aSrc[x * 4]; 1.626 + uint8_t* pixelOut = &aDest[x * 3]; 1.627 + pixelOut[0] = pixelIn[0]; 1.628 + pixelOut[1] = pixelIn[1]; 1.629 + pixelOut[2] = pixelIn[2]; 1.630 + } 1.631 +} 1.632 + 1.633 + 1.634 +// nsPNGEncoder::WarningCallback 1.635 + 1.636 +void // static 1.637 +nsPNGEncoder::WarningCallback(png_structp png_ptr, 1.638 + png_const_charp warning_msg) 1.639 +{ 1.640 +#ifdef DEBUG 1.641 + // XXX: these messages are probably useful callers... 1.642 + // use nsIConsoleService? 1.643 + PR_fprintf(PR_STDERR, "PNG Encoder: %s\n", warning_msg);; 1.644 +#endif 1.645 +} 1.646 + 1.647 + 1.648 +// nsPNGEncoder::ErrorCallback 1.649 + 1.650 +void // static 1.651 +nsPNGEncoder::ErrorCallback(png_structp png_ptr, 1.652 + png_const_charp error_msg) 1.653 +{ 1.654 +#ifdef DEBUG 1.655 + // XXX: these messages are probably useful callers... 1.656 + // use nsIConsoleService? 1.657 + PR_fprintf(PR_STDERR, "PNG Encoder: %s\n", error_msg);; 1.658 +#endif 1.659 +#if PNG_LIBPNG_VER < 10500 1.660 + longjmp(png_ptr->jmpbuf, 1); 1.661 +#else 1.662 + png_longjmp(png_ptr, 1); 1.663 +#endif 1.664 +} 1.665 + 1.666 + 1.667 +// nsPNGEncoder::WriteCallback 1.668 + 1.669 +void // static 1.670 +nsPNGEncoder::WriteCallback(png_structp png, png_bytep data, 1.671 + png_size_t size) 1.672 +{ 1.673 + nsPNGEncoder* that = static_cast<nsPNGEncoder*>(png_get_io_ptr(png)); 1.674 + if (! that->mImageBuffer) 1.675 + return; 1.676 + 1.677 + if (that->mImageBufferUsed + size > that->mImageBufferSize) { 1.678 + // When we're reallocing the buffer we need to take the lock to ensure 1.679 + // that nobody is trying to read from the buffer we are destroying 1.680 + ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor); 1.681 + 1.682 + // expand buffer, just double each time 1.683 + that->mImageBufferSize *= 2; 1.684 + uint8_t* newBuf = (uint8_t*)moz_realloc(that->mImageBuffer, 1.685 + that->mImageBufferSize); 1.686 + if (! newBuf) { 1.687 + // can't resize, just zero (this will keep us from writing more) 1.688 + moz_free(that->mImageBuffer); 1.689 + that->mImageBuffer = nullptr; 1.690 + that->mImageBufferSize = 0; 1.691 + that->mImageBufferUsed = 0; 1.692 + return; 1.693 + } 1.694 + that->mImageBuffer = newBuf; 1.695 + } 1.696 + memcpy(&that->mImageBuffer[that->mImageBufferUsed], data, size); 1.697 + that->mImageBufferUsed += size; 1.698 + that->NotifyListener(); 1.699 +} 1.700 + 1.701 +void 1.702 +nsPNGEncoder::NotifyListener() 1.703 +{ 1.704 + // We might call this function on multiple threads (any threads that call 1.705 + // AsyncWait and any that do encoding) so we lock to avoid notifying the 1.706 + // listener twice about the same data (which generally leads to a truncated 1.707 + // image). 1.708 + ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); 1.709 + 1.710 + if (mCallback && 1.711 + (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold || 1.712 + mFinished)) { 1.713 + nsCOMPtr<nsIInputStreamCallback> callback; 1.714 + if (mCallbackTarget) { 1.715 + callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); 1.716 + } else { 1.717 + callback = mCallback; 1.718 + } 1.719 + 1.720 + NS_ASSERTION(callback, "Shouldn't fail to make the callback"); 1.721 + // Null the callback first because OnInputStreamReady could reenter 1.722 + // AsyncWait 1.723 + mCallback = nullptr; 1.724 + mCallbackTarget = nullptr; 1.725 + mNotifyThreshold = 0; 1.726 + 1.727 + callback->OnInputStreamReady(this); 1.728 + } 1.729 +}