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 "nsCRT.h" |
michael@0 | 7 | #include "nsPNGEncoder.h" |
michael@0 | 8 | #include "prprf.h" |
michael@0 | 9 | #include "nsString.h" |
michael@0 | 10 | #include "nsStreamUtils.h" |
michael@0 | 11 | |
michael@0 | 12 | using namespace mozilla; |
michael@0 | 13 | |
michael@0 | 14 | NS_IMPL_ISUPPORTS(nsPNGEncoder, imgIEncoder, nsIInputStream, nsIAsyncInputStream) |
michael@0 | 15 | |
michael@0 | 16 | nsPNGEncoder::nsPNGEncoder() : mPNG(nullptr), mPNGinfo(nullptr), |
michael@0 | 17 | mIsAnimation(false), |
michael@0 | 18 | mFinished(false), |
michael@0 | 19 | mImageBuffer(nullptr), mImageBufferSize(0), |
michael@0 | 20 | mImageBufferUsed(0), mImageBufferReadPoint(0), |
michael@0 | 21 | mCallback(nullptr), |
michael@0 | 22 | mCallbackTarget(nullptr), mNotifyThreshold(0), |
michael@0 | 23 | mReentrantMonitor("nsPNGEncoder.mReentrantMonitor") |
michael@0 | 24 | { |
michael@0 | 25 | } |
michael@0 | 26 | |
michael@0 | 27 | nsPNGEncoder::~nsPNGEncoder() |
michael@0 | 28 | { |
michael@0 | 29 | if (mImageBuffer) { |
michael@0 | 30 | moz_free(mImageBuffer); |
michael@0 | 31 | mImageBuffer = nullptr; |
michael@0 | 32 | } |
michael@0 | 33 | // don't leak if EndImageEncode wasn't called |
michael@0 | 34 | if (mPNG) |
michael@0 | 35 | png_destroy_write_struct(&mPNG, &mPNGinfo); |
michael@0 | 36 | } |
michael@0 | 37 | |
michael@0 | 38 | // nsPNGEncoder::InitFromData |
michael@0 | 39 | // |
michael@0 | 40 | // One output option is supported: "transparency=none" means that the |
michael@0 | 41 | // output PNG will not have an alpha channel, even if the input does. |
michael@0 | 42 | // |
michael@0 | 43 | // Based partially on gfx/cairo/cairo/src/cairo-png.c |
michael@0 | 44 | // See also modules/libimg/png/libpng.txt |
michael@0 | 45 | |
michael@0 | 46 | NS_IMETHODIMP nsPNGEncoder::InitFromData(const uint8_t* aData, |
michael@0 | 47 | uint32_t aLength, // (unused, |
michael@0 | 48 | // req'd by JS) |
michael@0 | 49 | uint32_t aWidth, |
michael@0 | 50 | uint32_t aHeight, |
michael@0 | 51 | uint32_t aStride, |
michael@0 | 52 | uint32_t aInputFormat, |
michael@0 | 53 | const nsAString& aOutputOptions) |
michael@0 | 54 | { |
michael@0 | 55 | NS_ENSURE_ARG(aData); |
michael@0 | 56 | nsresult rv; |
michael@0 | 57 | |
michael@0 | 58 | rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions); |
michael@0 | 59 | if (!NS_SUCCEEDED(rv)) |
michael@0 | 60 | return rv; |
michael@0 | 61 | |
michael@0 | 62 | rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride, |
michael@0 | 63 | aInputFormat, aOutputOptions); |
michael@0 | 64 | if (!NS_SUCCEEDED(rv)) |
michael@0 | 65 | return rv; |
michael@0 | 66 | |
michael@0 | 67 | rv = EndImageEncode(); |
michael@0 | 68 | |
michael@0 | 69 | return rv; |
michael@0 | 70 | } |
michael@0 | 71 | |
michael@0 | 72 | |
michael@0 | 73 | // nsPNGEncoder::StartImageEncode |
michael@0 | 74 | // |
michael@0 | 75 | // |
michael@0 | 76 | // See ::InitFromData for other info. |
michael@0 | 77 | NS_IMETHODIMP nsPNGEncoder::StartImageEncode(uint32_t aWidth, |
michael@0 | 78 | uint32_t aHeight, |
michael@0 | 79 | uint32_t aInputFormat, |
michael@0 | 80 | const nsAString& aOutputOptions) |
michael@0 | 81 | { |
michael@0 | 82 | bool useTransparency = true, skipFirstFrame = false; |
michael@0 | 83 | uint32_t numFrames = 1; |
michael@0 | 84 | uint32_t numPlays = 0; // For animations, 0 == forever |
michael@0 | 85 | |
michael@0 | 86 | // can't initialize more than once |
michael@0 | 87 | if (mImageBuffer != nullptr) |
michael@0 | 88 | return NS_ERROR_ALREADY_INITIALIZED; |
michael@0 | 89 | |
michael@0 | 90 | // validate input format |
michael@0 | 91 | if (aInputFormat != INPUT_FORMAT_RGB && |
michael@0 | 92 | aInputFormat != INPUT_FORMAT_RGBA && |
michael@0 | 93 | aInputFormat != INPUT_FORMAT_HOSTARGB) |
michael@0 | 94 | return NS_ERROR_INVALID_ARG; |
michael@0 | 95 | |
michael@0 | 96 | // parse and check any provided output options |
michael@0 | 97 | nsresult rv = ParseOptions(aOutputOptions, &useTransparency, &skipFirstFrame, |
michael@0 | 98 | &numFrames, &numPlays, nullptr, nullptr, |
michael@0 | 99 | nullptr, nullptr, nullptr); |
michael@0 | 100 | if (rv != NS_OK) |
michael@0 | 101 | return rv; |
michael@0 | 102 | |
michael@0 | 103 | #ifdef PNG_APNG_SUPPORTED |
michael@0 | 104 | if (numFrames > 1) |
michael@0 | 105 | mIsAnimation = true; |
michael@0 | 106 | |
michael@0 | 107 | #endif |
michael@0 | 108 | |
michael@0 | 109 | // initialize |
michael@0 | 110 | mPNG = png_create_write_struct(PNG_LIBPNG_VER_STRING, |
michael@0 | 111 | nullptr, |
michael@0 | 112 | ErrorCallback, |
michael@0 | 113 | WarningCallback); |
michael@0 | 114 | if (! mPNG) |
michael@0 | 115 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 116 | |
michael@0 | 117 | mPNGinfo = png_create_info_struct(mPNG); |
michael@0 | 118 | if (! mPNGinfo) { |
michael@0 | 119 | png_destroy_write_struct(&mPNG, nullptr); |
michael@0 | 120 | return NS_ERROR_FAILURE; |
michael@0 | 121 | } |
michael@0 | 122 | |
michael@0 | 123 | // libpng's error handler jumps back here upon an error. |
michael@0 | 124 | // Note: It's important that all png_* callers do this, or errors |
michael@0 | 125 | // will result in a corrupt time-warped stack. |
michael@0 | 126 | if (setjmp(png_jmpbuf(mPNG))) { |
michael@0 | 127 | png_destroy_write_struct(&mPNG, &mPNGinfo); |
michael@0 | 128 | return NS_ERROR_FAILURE; |
michael@0 | 129 | } |
michael@0 | 130 | |
michael@0 | 131 | // Set up to read the data into our image buffer, start out with an 8K |
michael@0 | 132 | // estimated size. Note: we don't have to worry about freeing this data |
michael@0 | 133 | // in this function. It will be freed on object destruction. |
michael@0 | 134 | mImageBufferSize = 8192; |
michael@0 | 135 | mImageBuffer = (uint8_t*)moz_malloc(mImageBufferSize); |
michael@0 | 136 | if (!mImageBuffer) { |
michael@0 | 137 | png_destroy_write_struct(&mPNG, &mPNGinfo); |
michael@0 | 138 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 139 | } |
michael@0 | 140 | mImageBufferUsed = 0; |
michael@0 | 141 | |
michael@0 | 142 | // set our callback for libpng to give us the data |
michael@0 | 143 | png_set_write_fn(mPNG, this, WriteCallback, nullptr); |
michael@0 | 144 | |
michael@0 | 145 | // include alpha? |
michael@0 | 146 | int colorType; |
michael@0 | 147 | if ((aInputFormat == INPUT_FORMAT_HOSTARGB || |
michael@0 | 148 | aInputFormat == INPUT_FORMAT_RGBA) && |
michael@0 | 149 | useTransparency) |
michael@0 | 150 | colorType = PNG_COLOR_TYPE_RGB_ALPHA; |
michael@0 | 151 | else |
michael@0 | 152 | colorType = PNG_COLOR_TYPE_RGB; |
michael@0 | 153 | |
michael@0 | 154 | png_set_IHDR(mPNG, mPNGinfo, aWidth, aHeight, 8, colorType, |
michael@0 | 155 | PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, |
michael@0 | 156 | PNG_FILTER_TYPE_DEFAULT); |
michael@0 | 157 | |
michael@0 | 158 | #ifdef PNG_APNG_SUPPORTED |
michael@0 | 159 | if (mIsAnimation) { |
michael@0 | 160 | png_set_first_frame_is_hidden(mPNG, mPNGinfo, skipFirstFrame); |
michael@0 | 161 | png_set_acTL(mPNG, mPNGinfo, numFrames, numPlays); |
michael@0 | 162 | } |
michael@0 | 163 | #endif |
michael@0 | 164 | |
michael@0 | 165 | // XXX: support PLTE, gAMA, tRNS, bKGD? |
michael@0 | 166 | |
michael@0 | 167 | png_write_info(mPNG, mPNGinfo); |
michael@0 | 168 | |
michael@0 | 169 | return NS_OK; |
michael@0 | 170 | } |
michael@0 | 171 | |
michael@0 | 172 | // Returns the number of bytes in the image buffer used. |
michael@0 | 173 | NS_IMETHODIMP nsPNGEncoder::GetImageBufferUsed(uint32_t *aOutputSize) |
michael@0 | 174 | { |
michael@0 | 175 | NS_ENSURE_ARG_POINTER(aOutputSize); |
michael@0 | 176 | *aOutputSize = mImageBufferUsed; |
michael@0 | 177 | return NS_OK; |
michael@0 | 178 | } |
michael@0 | 179 | |
michael@0 | 180 | // Returns a pointer to the start of the image buffer |
michael@0 | 181 | NS_IMETHODIMP nsPNGEncoder::GetImageBuffer(char **aOutputBuffer) |
michael@0 | 182 | { |
michael@0 | 183 | NS_ENSURE_ARG_POINTER(aOutputBuffer); |
michael@0 | 184 | *aOutputBuffer = reinterpret_cast<char*>(mImageBuffer); |
michael@0 | 185 | return NS_OK; |
michael@0 | 186 | } |
michael@0 | 187 | |
michael@0 | 188 | NS_IMETHODIMP nsPNGEncoder::AddImageFrame(const uint8_t* aData, |
michael@0 | 189 | uint32_t aLength, // (unused, |
michael@0 | 190 | // req'd by JS) |
michael@0 | 191 | uint32_t aWidth, |
michael@0 | 192 | uint32_t aHeight, |
michael@0 | 193 | uint32_t aStride, |
michael@0 | 194 | uint32_t aInputFormat, |
michael@0 | 195 | const nsAString& aFrameOptions) |
michael@0 | 196 | { |
michael@0 | 197 | bool useTransparency= true; |
michael@0 | 198 | uint32_t delay_ms = 500; |
michael@0 | 199 | #ifdef PNG_APNG_SUPPORTED |
michael@0 | 200 | uint32_t dispose_op = PNG_DISPOSE_OP_NONE; |
michael@0 | 201 | uint32_t blend_op = PNG_BLEND_OP_SOURCE; |
michael@0 | 202 | #else |
michael@0 | 203 | uint32_t dispose_op; |
michael@0 | 204 | uint32_t blend_op; |
michael@0 | 205 | #endif |
michael@0 | 206 | uint32_t x_offset = 0, y_offset = 0; |
michael@0 | 207 | |
michael@0 | 208 | // must be initialized |
michael@0 | 209 | if (mImageBuffer == nullptr) |
michael@0 | 210 | return NS_ERROR_NOT_INITIALIZED; |
michael@0 | 211 | |
michael@0 | 212 | // EndImageEncode was done, or some error occurred earlier |
michael@0 | 213 | if (!mPNG) |
michael@0 | 214 | return NS_BASE_STREAM_CLOSED; |
michael@0 | 215 | |
michael@0 | 216 | // validate input format |
michael@0 | 217 | if (aInputFormat != INPUT_FORMAT_RGB && |
michael@0 | 218 | aInputFormat != INPUT_FORMAT_RGBA && |
michael@0 | 219 | aInputFormat != INPUT_FORMAT_HOSTARGB) |
michael@0 | 220 | return NS_ERROR_INVALID_ARG; |
michael@0 | 221 | |
michael@0 | 222 | // libpng's error handler jumps back here upon an error. |
michael@0 | 223 | if (setjmp(png_jmpbuf(mPNG))) { |
michael@0 | 224 | png_destroy_write_struct(&mPNG, &mPNGinfo); |
michael@0 | 225 | return NS_ERROR_FAILURE; |
michael@0 | 226 | } |
michael@0 | 227 | |
michael@0 | 228 | // parse and check any provided output options |
michael@0 | 229 | nsresult rv = ParseOptions(aFrameOptions, &useTransparency, nullptr, |
michael@0 | 230 | nullptr, nullptr, &dispose_op, &blend_op, |
michael@0 | 231 | &delay_ms, &x_offset, &y_offset); |
michael@0 | 232 | if (rv != NS_OK) |
michael@0 | 233 | return rv; |
michael@0 | 234 | |
michael@0 | 235 | #ifdef PNG_APNG_SUPPORTED |
michael@0 | 236 | if (mIsAnimation) { |
michael@0 | 237 | // XXX the row pointers arg (#3) is unused, can it be removed? |
michael@0 | 238 | png_write_frame_head(mPNG, mPNGinfo, nullptr, |
michael@0 | 239 | aWidth, aHeight, x_offset, y_offset, |
michael@0 | 240 | delay_ms, 1000, dispose_op, blend_op); |
michael@0 | 241 | } |
michael@0 | 242 | #endif |
michael@0 | 243 | |
michael@0 | 244 | // Stride is the padded width of each row, so it better be longer |
michael@0 | 245 | // (I'm afraid people will not understand what stride means, so |
michael@0 | 246 | // check it well) |
michael@0 | 247 | if ((aInputFormat == INPUT_FORMAT_RGB && |
michael@0 | 248 | aStride < aWidth * 3) || |
michael@0 | 249 | ((aInputFormat == INPUT_FORMAT_RGBA || |
michael@0 | 250 | aInputFormat == INPUT_FORMAT_HOSTARGB) && |
michael@0 | 251 | aStride < aWidth * 4)) { |
michael@0 | 252 | NS_WARNING("Invalid stride for InitFromData/AddImageFrame"); |
michael@0 | 253 | return NS_ERROR_INVALID_ARG; |
michael@0 | 254 | } |
michael@0 | 255 | |
michael@0 | 256 | #ifdef PNG_WRITE_FILTER_SUPPORTED |
michael@0 | 257 | png_set_filter(mPNG, PNG_FILTER_TYPE_BASE, PNG_FILTER_VALUE_NONE); |
michael@0 | 258 | #endif |
michael@0 | 259 | |
michael@0 | 260 | // write each row: if we add more input formats, we may want to |
michael@0 | 261 | // generalize the conversions |
michael@0 | 262 | if (aInputFormat == INPUT_FORMAT_HOSTARGB) { |
michael@0 | 263 | // PNG requires RGBA with post-multiplied alpha, so we need to |
michael@0 | 264 | // convert |
michael@0 | 265 | uint8_t* row = new uint8_t[aWidth * 4]; |
michael@0 | 266 | for (uint32_t y = 0; y < aHeight; y ++) { |
michael@0 | 267 | ConvertHostARGBRow(&aData[y * aStride], row, aWidth, useTransparency); |
michael@0 | 268 | png_write_row(mPNG, row); |
michael@0 | 269 | } |
michael@0 | 270 | delete[] row; |
michael@0 | 271 | |
michael@0 | 272 | } else if (aInputFormat == INPUT_FORMAT_RGBA && ! useTransparency) { |
michael@0 | 273 | // RBGA, but we need to strip the alpha |
michael@0 | 274 | uint8_t* row = new uint8_t[aWidth * 4]; |
michael@0 | 275 | for (uint32_t y = 0; y < aHeight; y ++) { |
michael@0 | 276 | StripAlpha(&aData[y * aStride], row, aWidth); |
michael@0 | 277 | png_write_row(mPNG, row); |
michael@0 | 278 | } |
michael@0 | 279 | delete[] row; |
michael@0 | 280 | |
michael@0 | 281 | } else if (aInputFormat == INPUT_FORMAT_RGB || |
michael@0 | 282 | aInputFormat == INPUT_FORMAT_RGBA) { |
michael@0 | 283 | // simple RBG(A), no conversion needed |
michael@0 | 284 | for (uint32_t y = 0; y < aHeight; y ++) { |
michael@0 | 285 | png_write_row(mPNG, (uint8_t*)&aData[y * aStride]); |
michael@0 | 286 | } |
michael@0 | 287 | |
michael@0 | 288 | } else { |
michael@0 | 289 | NS_NOTREACHED("Bad format type"); |
michael@0 | 290 | return NS_ERROR_INVALID_ARG; |
michael@0 | 291 | } |
michael@0 | 292 | |
michael@0 | 293 | #ifdef PNG_APNG_SUPPORTED |
michael@0 | 294 | if (mIsAnimation) { |
michael@0 | 295 | png_write_frame_tail(mPNG, mPNGinfo); |
michael@0 | 296 | } |
michael@0 | 297 | #endif |
michael@0 | 298 | |
michael@0 | 299 | return NS_OK; |
michael@0 | 300 | } |
michael@0 | 301 | |
michael@0 | 302 | |
michael@0 | 303 | NS_IMETHODIMP nsPNGEncoder::EndImageEncode() |
michael@0 | 304 | { |
michael@0 | 305 | // must be initialized |
michael@0 | 306 | if (mImageBuffer == nullptr) |
michael@0 | 307 | return NS_ERROR_NOT_INITIALIZED; |
michael@0 | 308 | |
michael@0 | 309 | // EndImageEncode has already been called, or some error |
michael@0 | 310 | // occurred earlier |
michael@0 | 311 | if (!mPNG) |
michael@0 | 312 | return NS_BASE_STREAM_CLOSED; |
michael@0 | 313 | |
michael@0 | 314 | // libpng's error handler jumps back here upon an error. |
michael@0 | 315 | if (setjmp(png_jmpbuf(mPNG))) { |
michael@0 | 316 | png_destroy_write_struct(&mPNG, &mPNGinfo); |
michael@0 | 317 | return NS_ERROR_FAILURE; |
michael@0 | 318 | } |
michael@0 | 319 | |
michael@0 | 320 | png_write_end(mPNG, mPNGinfo); |
michael@0 | 321 | png_destroy_write_struct(&mPNG, &mPNGinfo); |
michael@0 | 322 | |
michael@0 | 323 | mFinished = true; |
michael@0 | 324 | NotifyListener(); |
michael@0 | 325 | |
michael@0 | 326 | // if output callback can't get enough memory, it will free our buffer |
michael@0 | 327 | if (!mImageBuffer) |
michael@0 | 328 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 329 | |
michael@0 | 330 | return NS_OK; |
michael@0 | 331 | } |
michael@0 | 332 | |
michael@0 | 333 | |
michael@0 | 334 | nsresult |
michael@0 | 335 | nsPNGEncoder::ParseOptions(const nsAString& aOptions, |
michael@0 | 336 | bool* useTransparency, |
michael@0 | 337 | bool* skipFirstFrame, |
michael@0 | 338 | uint32_t* numFrames, |
michael@0 | 339 | uint32_t* numPlays, |
michael@0 | 340 | uint32_t* frameDispose, |
michael@0 | 341 | uint32_t* frameBlend, |
michael@0 | 342 | uint32_t* frameDelay, |
michael@0 | 343 | uint32_t* offsetX, |
michael@0 | 344 | uint32_t* offsetY) |
michael@0 | 345 | { |
michael@0 | 346 | #ifdef PNG_APNG_SUPPORTED |
michael@0 | 347 | // Make a copy of aOptions, because strtok() will modify it. |
michael@0 | 348 | nsAutoCString optionsCopy; |
michael@0 | 349 | optionsCopy.Assign(NS_ConvertUTF16toUTF8(aOptions)); |
michael@0 | 350 | char* options = optionsCopy.BeginWriting(); |
michael@0 | 351 | |
michael@0 | 352 | while (char* token = nsCRT::strtok(options, ";", &options)) { |
michael@0 | 353 | // If there's an '=' character, split the token around it. |
michael@0 | 354 | char* equals = token, *value = nullptr; |
michael@0 | 355 | while(*equals != '=' && *equals) { |
michael@0 | 356 | ++equals; |
michael@0 | 357 | } |
michael@0 | 358 | if (*equals == '=') |
michael@0 | 359 | value = equals + 1; |
michael@0 | 360 | |
michael@0 | 361 | if (value) |
michael@0 | 362 | *equals = '\0'; // temporary null |
michael@0 | 363 | |
michael@0 | 364 | // transparency=[yes|no|none] |
michael@0 | 365 | if (nsCRT::strcmp(token, "transparency") == 0 && useTransparency) { |
michael@0 | 366 | if (!value) |
michael@0 | 367 | return NS_ERROR_INVALID_ARG; |
michael@0 | 368 | |
michael@0 | 369 | if (nsCRT::strcmp(value, "none") == 0 || |
michael@0 | 370 | nsCRT::strcmp(value, "no") == 0) { |
michael@0 | 371 | *useTransparency = false; |
michael@0 | 372 | } else if (nsCRT::strcmp(value, "yes") == 0) { |
michael@0 | 373 | *useTransparency = true; |
michael@0 | 374 | } else { |
michael@0 | 375 | return NS_ERROR_INVALID_ARG; |
michael@0 | 376 | } |
michael@0 | 377 | |
michael@0 | 378 | // skipfirstframe=[yes|no] |
michael@0 | 379 | } else if (nsCRT::strcmp(token, "skipfirstframe") == 0 && |
michael@0 | 380 | skipFirstFrame) { |
michael@0 | 381 | if (!value) |
michael@0 | 382 | return NS_ERROR_INVALID_ARG; |
michael@0 | 383 | |
michael@0 | 384 | if (nsCRT::strcmp(value, "no") == 0) { |
michael@0 | 385 | *skipFirstFrame = false; |
michael@0 | 386 | } else if (nsCRT::strcmp(value, "yes") == 0) { |
michael@0 | 387 | *skipFirstFrame = true; |
michael@0 | 388 | } else { |
michael@0 | 389 | return NS_ERROR_INVALID_ARG; |
michael@0 | 390 | } |
michael@0 | 391 | |
michael@0 | 392 | // frames=# |
michael@0 | 393 | } else if (nsCRT::strcmp(token, "frames") == 0 && numFrames) { |
michael@0 | 394 | if (!value) |
michael@0 | 395 | return NS_ERROR_INVALID_ARG; |
michael@0 | 396 | |
michael@0 | 397 | if (PR_sscanf(value, "%u", numFrames) != 1) { |
michael@0 | 398 | return NS_ERROR_INVALID_ARG; |
michael@0 | 399 | } |
michael@0 | 400 | |
michael@0 | 401 | // frames=0 is nonsense. |
michael@0 | 402 | if (*numFrames == 0) |
michael@0 | 403 | return NS_ERROR_INVALID_ARG; |
michael@0 | 404 | |
michael@0 | 405 | // plays=# |
michael@0 | 406 | } else if (nsCRT::strcmp(token, "plays") == 0 && numPlays) { |
michael@0 | 407 | if (!value) |
michael@0 | 408 | return NS_ERROR_INVALID_ARG; |
michael@0 | 409 | |
michael@0 | 410 | // plays=0 to loop forever, otherwise play sequence specified |
michael@0 | 411 | // number of times |
michael@0 | 412 | if (PR_sscanf(value, "%u", numPlays) != 1) |
michael@0 | 413 | return NS_ERROR_INVALID_ARG; |
michael@0 | 414 | |
michael@0 | 415 | // dispose=[none|background|previous] |
michael@0 | 416 | } else if (nsCRT::strcmp(token, "dispose") == 0 && frameDispose) { |
michael@0 | 417 | if (!value) |
michael@0 | 418 | return NS_ERROR_INVALID_ARG; |
michael@0 | 419 | |
michael@0 | 420 | if (nsCRT::strcmp(value, "none") == 0) { |
michael@0 | 421 | *frameDispose = PNG_DISPOSE_OP_NONE; |
michael@0 | 422 | } else if (nsCRT::strcmp(value, "background") == 0) { |
michael@0 | 423 | *frameDispose = PNG_DISPOSE_OP_BACKGROUND; |
michael@0 | 424 | } else if (nsCRT::strcmp(value, "previous") == 0) { |
michael@0 | 425 | *frameDispose = PNG_DISPOSE_OP_PREVIOUS; |
michael@0 | 426 | } else { |
michael@0 | 427 | return NS_ERROR_INVALID_ARG; |
michael@0 | 428 | } |
michael@0 | 429 | |
michael@0 | 430 | // blend=[source|over] |
michael@0 | 431 | } else if (nsCRT::strcmp(token, "blend") == 0 && frameBlend) { |
michael@0 | 432 | if (!value) |
michael@0 | 433 | return NS_ERROR_INVALID_ARG; |
michael@0 | 434 | |
michael@0 | 435 | if (nsCRT::strcmp(value, "source") == 0) { |
michael@0 | 436 | *frameBlend = PNG_BLEND_OP_SOURCE; |
michael@0 | 437 | } else if (nsCRT::strcmp(value, "over") == 0) { |
michael@0 | 438 | *frameBlend = PNG_BLEND_OP_OVER; |
michael@0 | 439 | } else { |
michael@0 | 440 | return NS_ERROR_INVALID_ARG; |
michael@0 | 441 | } |
michael@0 | 442 | |
michael@0 | 443 | // delay=# (in ms) |
michael@0 | 444 | } else if (nsCRT::strcmp(token, "delay") == 0 && frameDelay) { |
michael@0 | 445 | if (!value) |
michael@0 | 446 | return NS_ERROR_INVALID_ARG; |
michael@0 | 447 | |
michael@0 | 448 | if (PR_sscanf(value, "%u", frameDelay) != 1) |
michael@0 | 449 | return NS_ERROR_INVALID_ARG; |
michael@0 | 450 | |
michael@0 | 451 | // xoffset=# |
michael@0 | 452 | } else if (nsCRT::strcmp(token, "xoffset") == 0 && offsetX) { |
michael@0 | 453 | if (!value) |
michael@0 | 454 | return NS_ERROR_INVALID_ARG; |
michael@0 | 455 | |
michael@0 | 456 | if (PR_sscanf(value, "%u", offsetX) != 1) |
michael@0 | 457 | return NS_ERROR_INVALID_ARG; |
michael@0 | 458 | |
michael@0 | 459 | // yoffset=# |
michael@0 | 460 | } else if (nsCRT::strcmp(token, "yoffset") == 0 && offsetY) { |
michael@0 | 461 | if (!value) |
michael@0 | 462 | return NS_ERROR_INVALID_ARG; |
michael@0 | 463 | |
michael@0 | 464 | if (PR_sscanf(value, "%u", offsetY) != 1) |
michael@0 | 465 | return NS_ERROR_INVALID_ARG; |
michael@0 | 466 | |
michael@0 | 467 | // unknown token name |
michael@0 | 468 | } else |
michael@0 | 469 | return NS_ERROR_INVALID_ARG; |
michael@0 | 470 | |
michael@0 | 471 | if (value) |
michael@0 | 472 | *equals = '='; // restore '=' so strtok doesn't get lost |
michael@0 | 473 | } |
michael@0 | 474 | |
michael@0 | 475 | #endif |
michael@0 | 476 | return NS_OK; |
michael@0 | 477 | } |
michael@0 | 478 | |
michael@0 | 479 | |
michael@0 | 480 | /* void close (); */ |
michael@0 | 481 | NS_IMETHODIMP nsPNGEncoder::Close() |
michael@0 | 482 | { |
michael@0 | 483 | if (mImageBuffer != nullptr) { |
michael@0 | 484 | moz_free(mImageBuffer); |
michael@0 | 485 | mImageBuffer = nullptr; |
michael@0 | 486 | mImageBufferSize = 0; |
michael@0 | 487 | mImageBufferUsed = 0; |
michael@0 | 488 | mImageBufferReadPoint = 0; |
michael@0 | 489 | } |
michael@0 | 490 | return NS_OK; |
michael@0 | 491 | } |
michael@0 | 492 | |
michael@0 | 493 | /* unsigned long available (); */ |
michael@0 | 494 | NS_IMETHODIMP nsPNGEncoder::Available(uint64_t *_retval) |
michael@0 | 495 | { |
michael@0 | 496 | if (!mImageBuffer) |
michael@0 | 497 | return NS_BASE_STREAM_CLOSED; |
michael@0 | 498 | |
michael@0 | 499 | *_retval = mImageBufferUsed - mImageBufferReadPoint; |
michael@0 | 500 | return NS_OK; |
michael@0 | 501 | } |
michael@0 | 502 | |
michael@0 | 503 | /* [noscript] unsigned long read (in charPtr aBuf, |
michael@0 | 504 | in unsigned long aCount); */ |
michael@0 | 505 | NS_IMETHODIMP nsPNGEncoder::Read(char * aBuf, uint32_t aCount, |
michael@0 | 506 | uint32_t *_retval) |
michael@0 | 507 | { |
michael@0 | 508 | return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); |
michael@0 | 509 | } |
michael@0 | 510 | |
michael@0 | 511 | /* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, |
michael@0 | 512 | in voidPtr aClosure, |
michael@0 | 513 | in unsigned long aCount); */ |
michael@0 | 514 | NS_IMETHODIMP nsPNGEncoder::ReadSegments(nsWriteSegmentFun aWriter, |
michael@0 | 515 | void *aClosure, uint32_t aCount, |
michael@0 | 516 | uint32_t *_retval) |
michael@0 | 517 | { |
michael@0 | 518 | // Avoid another thread reallocing the buffer underneath us |
michael@0 | 519 | ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); |
michael@0 | 520 | |
michael@0 | 521 | uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint; |
michael@0 | 522 | if (maxCount == 0) { |
michael@0 | 523 | *_retval = 0; |
michael@0 | 524 | return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; |
michael@0 | 525 | } |
michael@0 | 526 | |
michael@0 | 527 | if (aCount > maxCount) |
michael@0 | 528 | aCount = maxCount; |
michael@0 | 529 | nsresult rv = |
michael@0 | 530 | aWriter(this, aClosure, |
michael@0 | 531 | reinterpret_cast<const char*>(mImageBuffer+mImageBufferReadPoint), |
michael@0 | 532 | 0, aCount, _retval); |
michael@0 | 533 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 534 | NS_ASSERTION(*_retval <= aCount, "bad write count"); |
michael@0 | 535 | mImageBufferReadPoint += *_retval; |
michael@0 | 536 | } |
michael@0 | 537 | |
michael@0 | 538 | // errors returned from the writer end here! |
michael@0 | 539 | return NS_OK; |
michael@0 | 540 | } |
michael@0 | 541 | |
michael@0 | 542 | /* boolean isNonBlocking (); */ |
michael@0 | 543 | NS_IMETHODIMP nsPNGEncoder::IsNonBlocking(bool *_retval) |
michael@0 | 544 | { |
michael@0 | 545 | *_retval = true; |
michael@0 | 546 | return NS_OK; |
michael@0 | 547 | } |
michael@0 | 548 | |
michael@0 | 549 | NS_IMETHODIMP nsPNGEncoder::AsyncWait(nsIInputStreamCallback *aCallback, |
michael@0 | 550 | uint32_t aFlags, |
michael@0 | 551 | uint32_t aRequestedCount, |
michael@0 | 552 | nsIEventTarget *aTarget) |
michael@0 | 553 | { |
michael@0 | 554 | if (aFlags != 0) |
michael@0 | 555 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 556 | |
michael@0 | 557 | if (mCallback || mCallbackTarget) |
michael@0 | 558 | return NS_ERROR_UNEXPECTED; |
michael@0 | 559 | |
michael@0 | 560 | mCallbackTarget = aTarget; |
michael@0 | 561 | // 0 means "any number of bytes except 0" |
michael@0 | 562 | mNotifyThreshold = aRequestedCount; |
michael@0 | 563 | if (!aRequestedCount) |
michael@0 | 564 | mNotifyThreshold = 1024; // We don't want to notify incessantly |
michael@0 | 565 | |
michael@0 | 566 | // We set the callback absolutely last, because NotifyListener uses it to |
michael@0 | 567 | // determine if someone needs to be notified. If we don't set it last, |
michael@0 | 568 | // NotifyListener might try to fire off a notification to a null target |
michael@0 | 569 | // which will generally cause non-threadsafe objects to be used off the main thread |
michael@0 | 570 | mCallback = aCallback; |
michael@0 | 571 | |
michael@0 | 572 | // What we are being asked for may be present already |
michael@0 | 573 | NotifyListener(); |
michael@0 | 574 | return NS_OK; |
michael@0 | 575 | } |
michael@0 | 576 | |
michael@0 | 577 | NS_IMETHODIMP nsPNGEncoder::CloseWithStatus(nsresult aStatus) |
michael@0 | 578 | { |
michael@0 | 579 | return Close(); |
michael@0 | 580 | } |
michael@0 | 581 | |
michael@0 | 582 | // nsPNGEncoder::ConvertHostARGBRow |
michael@0 | 583 | // |
michael@0 | 584 | // Our colors are stored with premultiplied alphas, but PNGs use |
michael@0 | 585 | // post-multiplied alpha. This swaps to PNG-style alpha. |
michael@0 | 586 | // |
michael@0 | 587 | // Copied from gfx/cairo/cairo/src/cairo-png.c |
michael@0 | 588 | |
michael@0 | 589 | void |
michael@0 | 590 | nsPNGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest, |
michael@0 | 591 | uint32_t aPixelWidth, |
michael@0 | 592 | bool aUseTransparency) |
michael@0 | 593 | { |
michael@0 | 594 | uint32_t pixelStride = aUseTransparency ? 4 : 3; |
michael@0 | 595 | for (uint32_t x = 0; x < aPixelWidth; x ++) { |
michael@0 | 596 | const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x]; |
michael@0 | 597 | uint8_t *pixelOut = &aDest[x * pixelStride]; |
michael@0 | 598 | |
michael@0 | 599 | uint8_t alpha = (pixelIn & 0xff000000) >> 24; |
michael@0 | 600 | if (alpha == 0) { |
michael@0 | 601 | pixelOut[0] = pixelOut[1] = pixelOut[2] = pixelOut[3] = 0; |
michael@0 | 602 | } else { |
michael@0 | 603 | pixelOut[0] = (((pixelIn & 0xff0000) >> 16) * 255 + alpha / 2) / alpha; |
michael@0 | 604 | pixelOut[1] = (((pixelIn & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha; |
michael@0 | 605 | pixelOut[2] = (((pixelIn & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha; |
michael@0 | 606 | if (aUseTransparency) |
michael@0 | 607 | pixelOut[3] = alpha; |
michael@0 | 608 | } |
michael@0 | 609 | } |
michael@0 | 610 | } |
michael@0 | 611 | |
michael@0 | 612 | |
michael@0 | 613 | // nsPNGEncoder::StripAlpha |
michael@0 | 614 | // |
michael@0 | 615 | // Input is RGBA, output is RGB |
michael@0 | 616 | |
michael@0 | 617 | void |
michael@0 | 618 | nsPNGEncoder::StripAlpha(const uint8_t* aSrc, uint8_t* aDest, |
michael@0 | 619 | uint32_t aPixelWidth) |
michael@0 | 620 | { |
michael@0 | 621 | for (uint32_t x = 0; x < aPixelWidth; x ++) { |
michael@0 | 622 | const uint8_t* pixelIn = &aSrc[x * 4]; |
michael@0 | 623 | uint8_t* pixelOut = &aDest[x * 3]; |
michael@0 | 624 | pixelOut[0] = pixelIn[0]; |
michael@0 | 625 | pixelOut[1] = pixelIn[1]; |
michael@0 | 626 | pixelOut[2] = pixelIn[2]; |
michael@0 | 627 | } |
michael@0 | 628 | } |
michael@0 | 629 | |
michael@0 | 630 | |
michael@0 | 631 | // nsPNGEncoder::WarningCallback |
michael@0 | 632 | |
michael@0 | 633 | void // static |
michael@0 | 634 | nsPNGEncoder::WarningCallback(png_structp png_ptr, |
michael@0 | 635 | png_const_charp warning_msg) |
michael@0 | 636 | { |
michael@0 | 637 | #ifdef DEBUG |
michael@0 | 638 | // XXX: these messages are probably useful callers... |
michael@0 | 639 | // use nsIConsoleService? |
michael@0 | 640 | PR_fprintf(PR_STDERR, "PNG Encoder: %s\n", warning_msg);; |
michael@0 | 641 | #endif |
michael@0 | 642 | } |
michael@0 | 643 | |
michael@0 | 644 | |
michael@0 | 645 | // nsPNGEncoder::ErrorCallback |
michael@0 | 646 | |
michael@0 | 647 | void // static |
michael@0 | 648 | nsPNGEncoder::ErrorCallback(png_structp png_ptr, |
michael@0 | 649 | png_const_charp error_msg) |
michael@0 | 650 | { |
michael@0 | 651 | #ifdef DEBUG |
michael@0 | 652 | // XXX: these messages are probably useful callers... |
michael@0 | 653 | // use nsIConsoleService? |
michael@0 | 654 | PR_fprintf(PR_STDERR, "PNG Encoder: %s\n", error_msg);; |
michael@0 | 655 | #endif |
michael@0 | 656 | #if PNG_LIBPNG_VER < 10500 |
michael@0 | 657 | longjmp(png_ptr->jmpbuf, 1); |
michael@0 | 658 | #else |
michael@0 | 659 | png_longjmp(png_ptr, 1); |
michael@0 | 660 | #endif |
michael@0 | 661 | } |
michael@0 | 662 | |
michael@0 | 663 | |
michael@0 | 664 | // nsPNGEncoder::WriteCallback |
michael@0 | 665 | |
michael@0 | 666 | void // static |
michael@0 | 667 | nsPNGEncoder::WriteCallback(png_structp png, png_bytep data, |
michael@0 | 668 | png_size_t size) |
michael@0 | 669 | { |
michael@0 | 670 | nsPNGEncoder* that = static_cast<nsPNGEncoder*>(png_get_io_ptr(png)); |
michael@0 | 671 | if (! that->mImageBuffer) |
michael@0 | 672 | return; |
michael@0 | 673 | |
michael@0 | 674 | if (that->mImageBufferUsed + size > that->mImageBufferSize) { |
michael@0 | 675 | // When we're reallocing the buffer we need to take the lock to ensure |
michael@0 | 676 | // that nobody is trying to read from the buffer we are destroying |
michael@0 | 677 | ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor); |
michael@0 | 678 | |
michael@0 | 679 | // expand buffer, just double each time |
michael@0 | 680 | that->mImageBufferSize *= 2; |
michael@0 | 681 | uint8_t* newBuf = (uint8_t*)moz_realloc(that->mImageBuffer, |
michael@0 | 682 | that->mImageBufferSize); |
michael@0 | 683 | if (! newBuf) { |
michael@0 | 684 | // can't resize, just zero (this will keep us from writing more) |
michael@0 | 685 | moz_free(that->mImageBuffer); |
michael@0 | 686 | that->mImageBuffer = nullptr; |
michael@0 | 687 | that->mImageBufferSize = 0; |
michael@0 | 688 | that->mImageBufferUsed = 0; |
michael@0 | 689 | return; |
michael@0 | 690 | } |
michael@0 | 691 | that->mImageBuffer = newBuf; |
michael@0 | 692 | } |
michael@0 | 693 | memcpy(&that->mImageBuffer[that->mImageBufferUsed], data, size); |
michael@0 | 694 | that->mImageBufferUsed += size; |
michael@0 | 695 | that->NotifyListener(); |
michael@0 | 696 | } |
michael@0 | 697 | |
michael@0 | 698 | void |
michael@0 | 699 | nsPNGEncoder::NotifyListener() |
michael@0 | 700 | { |
michael@0 | 701 | // We might call this function on multiple threads (any threads that call |
michael@0 | 702 | // AsyncWait and any that do encoding) so we lock to avoid notifying the |
michael@0 | 703 | // listener twice about the same data (which generally leads to a truncated |
michael@0 | 704 | // image). |
michael@0 | 705 | ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); |
michael@0 | 706 | |
michael@0 | 707 | if (mCallback && |
michael@0 | 708 | (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold || |
michael@0 | 709 | mFinished)) { |
michael@0 | 710 | nsCOMPtr<nsIInputStreamCallback> callback; |
michael@0 | 711 | if (mCallbackTarget) { |
michael@0 | 712 | callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); |
michael@0 | 713 | } else { |
michael@0 | 714 | callback = mCallback; |
michael@0 | 715 | } |
michael@0 | 716 | |
michael@0 | 717 | NS_ASSERTION(callback, "Shouldn't fail to make the callback"); |
michael@0 | 718 | // Null the callback first because OnInputStreamReady could reenter |
michael@0 | 719 | // AsyncWait |
michael@0 | 720 | mCallback = nullptr; |
michael@0 | 721 | mCallbackTarget = nullptr; |
michael@0 | 722 | mNotifyThreshold = 0; |
michael@0 | 723 | |
michael@0 | 724 | callback->OnInputStreamReady(this); |
michael@0 | 725 | } |
michael@0 | 726 | } |