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