tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }