/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ImageLogging.h" #include "nsCRT.h" #include "nsPNGEncoder.h" #include "nsStreamUtils.h" #include "nsString.h" #include "prprf.h" #include "mozilla/CheckedInt.h" #include "mozilla/UniquePtrExtensions.h" using namespace mozilla; static LazyLogModule sPNGEncoderLog("PNGEncoder"); NS_IMPL_ISUPPORTS(nsPNGEncoder, imgIEncoder, nsIInputStream, nsIAsyncInputStream) #define DEFAULT_ZLIB_LEVEL 3 #define DEFAULT_FILTERS PNG_FILTER_SUB nsPNGEncoder::nsPNGEncoder() : mPNG(nullptr), mPNGinfo(nullptr), mIsAnimation(false), mFinished(false), mImageBuffer(nullptr), mImageBufferSize(0), mImageBufferUsed(0), mImageBufferReadPoint(0), mCallback(nullptr), mCallbackTarget(nullptr), mNotifyThreshold(0), mReentrantMonitor("nsPNGEncoder.mReentrantMonitor") {} nsPNGEncoder::~nsPNGEncoder() { if (mImageBuffer) { free(mImageBuffer); mImageBuffer = nullptr; } // don't leak if EndImageEncode wasn't called if (mPNG) { png_destroy_write_struct(&mPNG, &mPNGinfo); } } // nsPNGEncoder::InitFromData // // One output option is supported: "transparency=none" means that the // output PNG will not have an alpha channel, even if the input does. // // Based partially on gfx/cairo/cairo/src/cairo-png.c // See also media/libpng/libpng-manual.txt NS_IMETHODIMP nsPNGEncoder::InitFromData(const uint8_t* aData, uint32_t aLength, // (unused, req'd by JS) uint32_t aWidth, uint32_t aHeight, uint32_t aStride, uint32_t aInputFormat, const nsAString& aOutputOptions) { NS_ENSURE_ARG(aData); nsresult rv; rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions); if (!NS_SUCCEEDED(rv)) { return rv; } rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride, aInputFormat, aOutputOptions); if (!NS_SUCCEEDED(rv)) { return rv; } rv = EndImageEncode(); return rv; } // nsPNGEncoder::StartImageEncode // // // See ::InitFromData for other info. NS_IMETHODIMP nsPNGEncoder::StartImageEncode(uint32_t aWidth, uint32_t aHeight, uint32_t aInputFormat, const nsAString& aOutputOptions) { bool useTransparency = true, skipFirstFrame = false; uint32_t numFrames = 1; uint32_t numPlays = 0; // For animations, 0 == forever int zlibLevel = DEFAULT_ZLIB_LEVEL; int filters = DEFAULT_FILTERS; // can't initialize more than once if (mImageBuffer != nullptr) { return NS_ERROR_ALREADY_INITIALIZED; } // validate input format if (aInputFormat != INPUT_FORMAT_RGB && aInputFormat != INPUT_FORMAT_RGBA && aInputFormat != INPUT_FORMAT_HOSTARGB) return NS_ERROR_INVALID_ARG; // parse and check any provided output options nsresult rv = ParseOptions(aOutputOptions, &useTransparency, &skipFirstFrame, &numFrames, &numPlays, &zlibLevel, &filters, nullptr, nullptr, nullptr, nullptr, nullptr); if (rv != NS_OK) { return rv; } #ifdef PNG_APNG_SUPPORTED if (numFrames > 1) { mIsAnimation = true; } #endif // initialize mPNG = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, ErrorCallback, WarningCallback); if (!mPNG) { return NS_ERROR_OUT_OF_MEMORY; } mPNGinfo = png_create_info_struct(mPNG); if (!mPNGinfo) { png_destroy_write_struct(&mPNG, nullptr); return NS_ERROR_FAILURE; } // libpng's error handler jumps back here upon an error. // Note: It's important that all png_* callers do this, or errors // will result in a corrupt time-warped stack. if (setjmp(png_jmpbuf(mPNG))) { png_destroy_write_struct(&mPNG, &mPNGinfo); return NS_ERROR_FAILURE; } #ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED png_set_compression_level(mPNG, zlibLevel); #endif #ifdef PNG_WRITE_FILTER_SUPPORTED png_set_filter(mPNG, PNG_FILTER_TYPE_BASE, filters); #endif // Set up to read the data into our image buffer, start out with an 8K // estimated size. Note: we don't have to worry about freeing this data // in this function. It will be freed on object destruction. mImageBufferSize = 8192; mImageBuffer = (uint8_t*)malloc(mImageBufferSize); if (!mImageBuffer) { png_destroy_write_struct(&mPNG, &mPNGinfo); return NS_ERROR_OUT_OF_MEMORY; } mImageBufferUsed = 0; // set our callback for libpng to give us the data png_set_write_fn(mPNG, this, WriteCallback, nullptr); // include alpha? int colorType; if ((aInputFormat == INPUT_FORMAT_HOSTARGB || aInputFormat == INPUT_FORMAT_RGBA) && useTransparency) colorType = PNG_COLOR_TYPE_RGB_ALPHA; else colorType = PNG_COLOR_TYPE_RGB; png_set_IHDR(mPNG, mPNGinfo, aWidth, aHeight, 8, colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); #ifdef PNG_APNG_SUPPORTED if (mIsAnimation) { png_set_first_frame_is_hidden(mPNG, mPNGinfo, skipFirstFrame); png_set_acTL(mPNG, mPNGinfo, numFrames, numPlays); } #endif // XXX: support PLTE, gAMA, tRNS, bKGD? png_write_info(mPNG, mPNGinfo); return NS_OK; } // Returns the number of bytes in the image buffer used. NS_IMETHODIMP nsPNGEncoder::GetImageBufferUsed(uint32_t* aOutputSize) { NS_ENSURE_ARG_POINTER(aOutputSize); *aOutputSize = mImageBufferUsed; return NS_OK; } // Returns a pointer to the start of the image buffer NS_IMETHODIMP nsPNGEncoder::GetImageBuffer(char** aOutputBuffer) { NS_ENSURE_ARG_POINTER(aOutputBuffer); *aOutputBuffer = reinterpret_cast(mImageBuffer); return NS_OK; } NS_IMETHODIMP nsPNGEncoder::AddImageFrame(const uint8_t* aData, uint32_t aLength, // (unused, req'd by JS) uint32_t aWidth, uint32_t aHeight, uint32_t aStride, uint32_t aInputFormat, const nsAString& aFrameOptions) { bool useTransparency = true; int filters = DEFAULT_FILTERS; uint32_t delay_ms = 500; #ifdef PNG_APNG_SUPPORTED uint32_t dispose_op = PNG_DISPOSE_OP_NONE; uint32_t blend_op = PNG_BLEND_OP_SOURCE; #else uint32_t dispose_op; uint32_t blend_op; #endif uint32_t x_offset = 0, y_offset = 0; // must be initialized if (mImageBuffer == nullptr) { return NS_ERROR_NOT_INITIALIZED; } // EndImageEncode was done, or some error occurred earlier if (!mPNG) { return NS_BASE_STREAM_CLOSED; } // validate input format if (aInputFormat != INPUT_FORMAT_RGB && aInputFormat != INPUT_FORMAT_RGBA && aInputFormat != INPUT_FORMAT_HOSTARGB) return NS_ERROR_INVALID_ARG; // libpng's error handler jumps back here upon an error. if (setjmp(png_jmpbuf(mPNG))) { png_destroy_write_struct(&mPNG, &mPNGinfo); return NS_ERROR_FAILURE; } // parse and check any provided output options nsresult rv = ParseOptions(aFrameOptions, &useTransparency, nullptr, nullptr, nullptr, nullptr, &filters, &dispose_op, &blend_op, &delay_ms, &x_offset, &y_offset); if (rv != NS_OK) { return rv; } #ifdef PNG_APNG_SUPPORTED if (mIsAnimation) { // XXX the row pointers arg (#3) is unused, can it be removed? png_write_frame_head(mPNG, mPNGinfo, nullptr, aWidth, aHeight, x_offset, y_offset, delay_ms, 1000, dispose_op, blend_op); } #endif // Stride is the padded width of each row, so it better be longer // (I'm afraid people will not understand what stride means, so // check it well) if ((aInputFormat == INPUT_FORMAT_RGB && aStride < aWidth * 3) || ((aInputFormat == INPUT_FORMAT_RGBA || aInputFormat == INPUT_FORMAT_HOSTARGB) && aStride < aWidth * 4)) { NS_WARNING("Invalid stride for InitFromData/AddImageFrame"); return NS_ERROR_INVALID_ARG; } #ifdef PNG_WRITE_FILTER_SUPPORTED png_set_filter(mPNG, PNG_FILTER_TYPE_BASE, filters); #endif // write each row: if we add more input formats, we may want to // generalize the conversions if (aInputFormat == INPUT_FORMAT_HOSTARGB) { // PNG requires RGBA with post-multiplied alpha, so we need to // convert UniquePtr row = MakeUniqueFallible(aWidth * 4); if (NS_WARN_IF(!row)) { return NS_ERROR_OUT_OF_MEMORY; } for (uint32_t y = 0; y < aHeight; y++) { ConvertHostARGBRow(&aData[y * aStride], row.get(), aWidth, useTransparency); png_write_row(mPNG, row.get()); } } else if (aInputFormat == INPUT_FORMAT_RGBA && !useTransparency) { // RBGA, but we need to strip the alpha UniquePtr row = MakeUniqueFallible(aWidth * 4); if (NS_WARN_IF(!row)) { return NS_ERROR_OUT_OF_MEMORY; } for (uint32_t y = 0; y < aHeight; y++) { StripAlpha(&aData[y * aStride], row.get(), aWidth); png_write_row(mPNG, row.get()); } } else if (aInputFormat == INPUT_FORMAT_RGB || aInputFormat == INPUT_FORMAT_RGBA) { // simple RBG(A), no conversion needed for (uint32_t y = 0; y < aHeight; y++) { png_write_row(mPNG, (uint8_t*)&aData[y * aStride]); } } else { MOZ_ASSERT_UNREACHABLE("Bad format type"); return NS_ERROR_INVALID_ARG; } #ifdef PNG_APNG_SUPPORTED if (mIsAnimation) { png_write_frame_tail(mPNG, mPNGinfo); } #endif return NS_OK; } NS_IMETHODIMP nsPNGEncoder::EndImageEncode() { // must be initialized if (mImageBuffer == nullptr) { return NS_ERROR_NOT_INITIALIZED; } // EndImageEncode has already been called, or some error // occurred earlier if (!mPNG) { return NS_BASE_STREAM_CLOSED; } // libpng's error handler jumps back here upon an error. if (setjmp(png_jmpbuf(mPNG))) { png_destroy_write_struct(&mPNG, &mPNGinfo); return NS_ERROR_FAILURE; } png_write_end(mPNG, mPNGinfo); png_destroy_write_struct(&mPNG, &mPNGinfo); mFinished = true; NotifyListener(); // if output callback can't get enough memory, it will free our buffer if (!mImageBuffer) { return NS_ERROR_OUT_OF_MEMORY; } return NS_OK; } nsresult nsPNGEncoder::ParseOptions(const nsAString& aOptions, bool* useTransparency, bool* skipFirstFrame, uint32_t* numFrames, uint32_t* numPlays, int* zlibLevel, int* filters, uint32_t* frameDispose, uint32_t* frameBlend, uint32_t* frameDelay, uint32_t* offsetX, uint32_t* offsetY) { #ifdef PNG_APNG_SUPPORTED // Make a copy of aOptions, because strtok() will modify it. nsAutoCString optionsCopy; optionsCopy.Assign(NS_ConvertUTF16toUTF8(aOptions)); char* options = optionsCopy.BeginWriting(); while (char* token = nsCRT::strtok(options, ";", &options)) { // If there's an '=' character, split the token around it. char* equals = token; char* value = nullptr; while (*equals != '=' && *equals) { ++equals; } if (*equals == '=') { value = equals + 1; } if (value) { *equals = '\0'; // temporary null } // transparency=[yes|no|none] if (nsCRT::strcmp(token, "transparency") == 0 && useTransparency) { if (!value) { return NS_ERROR_INVALID_ARG; } if (nsCRT::strcmp(value, "none") == 0 || nsCRT::strcmp(value, "no") == 0) { *useTransparency = false; } else if (nsCRT::strcmp(value, "yes") == 0) { *useTransparency = true; } else { return NS_ERROR_INVALID_ARG; } // skipfirstframe=[yes|no] } else if (nsCRT::strcmp(token, "skipfirstframe") == 0 && skipFirstFrame) { if (!value) { return NS_ERROR_INVALID_ARG; } if (nsCRT::strcmp(value, "no") == 0) { *skipFirstFrame = false; } else if (nsCRT::strcmp(value, "yes") == 0) { *skipFirstFrame = true; } else { return NS_ERROR_INVALID_ARG; } // frames=# } else if (nsCRT::strcmp(token, "frames") == 0 && numFrames) { if (!value) { return NS_ERROR_INVALID_ARG; } if (PR_sscanf(value, "%u", numFrames) != 1) { return NS_ERROR_INVALID_ARG; } // frames=0 is nonsense. if (*numFrames == 0) { return NS_ERROR_INVALID_ARG; } // plays=# } else if (nsCRT::strcmp(token, "plays") == 0 && numPlays) { if (!value) { return NS_ERROR_INVALID_ARG; } // plays=0 to loop forever, otherwise play sequence specified // number of times if (PR_sscanf(value, "%u", numPlays) != 1) { return NS_ERROR_INVALID_ARG; } // png-zlib-level=# } else if (nsCRT::strcmp(token, "png-zlib-level") == 0) { if (!value) { return NS_ERROR_INVALID_ARG; } int localZlibLevel = DEFAULT_ZLIB_LEVEL; if (PR_sscanf(value, "%d", &localZlibLevel) != 1) { return NS_ERROR_INVALID_ARG; } // zlib-level 0-9 are the only valid values if (localZlibLevel < 0 || localZlibLevel > 9) { return NS_ERROR_INVALID_ARG; } if (zlibLevel) { *zlibLevel = localZlibLevel; } // png-filter=[no_filters|none|sub|up|avg|paeth|fast|all] } else if (nsCRT::strcmp(token, "png-filter") == 0) { if (!value) { return NS_ERROR_INVALID_ARG; } if (nsCRT::strcmp(value, "no_filters") == 0) { if (filters) { *filters = PNG_NO_FILTERS; } } else if (nsCRT::strcmp(value, "none") == 0) { if (filters) { *filters = PNG_FILTER_NONE; } } else if (nsCRT::strcmp(value, "sub") == 0) { if (filters) { *filters = PNG_FILTER_SUB; } } else if (nsCRT::strcmp(value, "up") == 0) { if (filters) { *filters = PNG_FILTER_UP; } } else if (nsCRT::strcmp(value, "avg") == 0) { if (filters) { *filters = PNG_FILTER_AVG; } } else if (nsCRT::strcmp(value, "paeth") == 0) { if (filters) { *filters = PNG_FILTER_PAETH; } } else if (nsCRT::strcmp(value, "fast") == 0) { if (filters) { *filters = PNG_FAST_FILTERS; } } else if (nsCRT::strcmp(value, "all") == 0) { if (filters) { *filters = PNG_ALL_FILTERS; } } else { return NS_ERROR_INVALID_ARG; } // dispose=[none|background|previous] } else if (nsCRT::strcmp(token, "dispose") == 0 && frameDispose) { if (!value) { return NS_ERROR_INVALID_ARG; } if (nsCRT::strcmp(value, "none") == 0) { *frameDispose = PNG_DISPOSE_OP_NONE; } else if (nsCRT::strcmp(value, "background") == 0) { *frameDispose = PNG_DISPOSE_OP_BACKGROUND; } else if (nsCRT::strcmp(value, "previous") == 0) { *frameDispose = PNG_DISPOSE_OP_PREVIOUS; } else { return NS_ERROR_INVALID_ARG; } // blend=[source|over] } else if (nsCRT::strcmp(token, "blend") == 0 && frameBlend) { if (!value) { return NS_ERROR_INVALID_ARG; } if (nsCRT::strcmp(value, "source") == 0) { *frameBlend = PNG_BLEND_OP_SOURCE; } else if (nsCRT::strcmp(value, "over") == 0) { *frameBlend = PNG_BLEND_OP_OVER; } else { return NS_ERROR_INVALID_ARG; } // delay=# (in ms) } else if (nsCRT::strcmp(token, "delay") == 0 && frameDelay) { if (!value) { return NS_ERROR_INVALID_ARG; } if (PR_sscanf(value, "%u", frameDelay) != 1) { return NS_ERROR_INVALID_ARG; } // xoffset=# } else if (nsCRT::strcmp(token, "xoffset") == 0 && offsetX) { if (!value) { return NS_ERROR_INVALID_ARG; } if (PR_sscanf(value, "%u", offsetX) != 1) { return NS_ERROR_INVALID_ARG; } // yoffset=# } else if (nsCRT::strcmp(token, "yoffset") == 0 && offsetY) { if (!value) { return NS_ERROR_INVALID_ARG; } if (PR_sscanf(value, "%u", offsetY) != 1) { return NS_ERROR_INVALID_ARG; } // unknown token name } else return NS_ERROR_INVALID_ARG; if (value) { *equals = '='; // restore '=' so strtok doesn't get lost } } #endif return NS_OK; } NS_IMETHODIMP nsPNGEncoder::Close() { if (mImageBuffer != nullptr) { free(mImageBuffer); mImageBuffer = nullptr; mImageBufferSize = 0; mImageBufferUsed = 0; mImageBufferReadPoint = 0; } return NS_OK; } NS_IMETHODIMP nsPNGEncoder::Available(uint64_t* _retval) { if (!mImageBuffer) { return NS_BASE_STREAM_CLOSED; } *_retval = mImageBufferUsed - mImageBufferReadPoint; return NS_OK; } NS_IMETHODIMP nsPNGEncoder::StreamStatus() { return mImageBuffer ? NS_OK : NS_BASE_STREAM_CLOSED; } NS_IMETHODIMP nsPNGEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) { return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); } NS_IMETHODIMP nsPNGEncoder::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, uint32_t* _retval) { // Avoid another thread reallocing the buffer underneath us ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint; if (maxCount == 0) { *_retval = 0; return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; } if (aCount > maxCount) { aCount = maxCount; } nsresult rv = aWriter( this, aClosure, reinterpret_cast(mImageBuffer + mImageBufferReadPoint), 0, aCount, _retval); if (NS_SUCCEEDED(rv)) { NS_ASSERTION(*_retval <= aCount, "bad write count"); mImageBufferReadPoint += *_retval; } // errors returned from the writer end here! return NS_OK; } NS_IMETHODIMP nsPNGEncoder::IsNonBlocking(bool* _retval) { *_retval = true; return NS_OK; } NS_IMETHODIMP nsPNGEncoder::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, uint32_t aRequestedCount, nsIEventTarget* aTarget) { if (aFlags != 0) { return NS_ERROR_NOT_IMPLEMENTED; } if (mCallback || mCallbackTarget) { return NS_ERROR_UNEXPECTED; } mCallbackTarget = aTarget; // 0 means "any number of bytes except 0" mNotifyThreshold = aRequestedCount; if (!aRequestedCount) { mNotifyThreshold = 1024; // We don't want to notify incessantly } // We set the callback absolutely last, because NotifyListener uses it to // determine if someone needs to be notified. If we don't set it last, // NotifyListener might try to fire off a notification to a null target // which will generally cause non-threadsafe objects to be used off the main // thread mCallback = aCallback; // What we are being asked for may be present already NotifyListener(); return NS_OK; } NS_IMETHODIMP nsPNGEncoder::CloseWithStatus(nsresult aStatus) { return Close(); } // nsPNGEncoder::ConvertHostARGBRow // // Our colors are stored with premultiplied alphas, but PNGs use // post-multiplied alpha. This swaps to PNG-style alpha. // // Copied from gfx/cairo/cairo/src/cairo-png.c void nsPNGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest, uint32_t aPixelWidth, bool aUseTransparency) { uint32_t pixelStride = aUseTransparency ? 4 : 3; for (uint32_t x = 0; x < aPixelWidth; x++) { const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x]; uint8_t* pixelOut = &aDest[x * pixelStride]; uint8_t alpha = (pixelIn & 0xff000000) >> 24; pixelOut[pixelStride - 1] = alpha; // overwritten below if pixelStride == 3 if (alpha == 255) { pixelOut[0] = (pixelIn & 0xff0000) >> 16; pixelOut[1] = (pixelIn & 0x00ff00) >> 8; pixelOut[2] = (pixelIn & 0x0000ff); } else if (alpha == 0) { pixelOut[0] = pixelOut[1] = pixelOut[2] = 0; } else { pixelOut[0] = (((pixelIn & 0xff0000) >> 16) * 255 + alpha / 2) / alpha; pixelOut[1] = (((pixelIn & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha; pixelOut[2] = (((pixelIn & 0x0000ff)) * 255 + alpha / 2) / alpha; } } } // nsPNGEncoder::StripAlpha // // Input is RGBA, output is RGB void nsPNGEncoder::StripAlpha(const uint8_t* aSrc, uint8_t* aDest, uint32_t aPixelWidth) { for (uint32_t x = 0; x < aPixelWidth; x++) { const uint8_t* pixelIn = &aSrc[x * 4]; uint8_t* pixelOut = &aDest[x * 3]; pixelOut[0] = pixelIn[0]; pixelOut[1] = pixelIn[1]; pixelOut[2] = pixelIn[2]; } } // nsPNGEncoder::WarningCallback void nsPNGEncoder::WarningCallback(png_structp png_ptr, png_const_charp warning_msg) { MOZ_LOG(sPNGEncoderLog, LogLevel::Warning, ("libpng warning: %s\n", warning_msg)); } // nsPNGEncoder::ErrorCallback void nsPNGEncoder::ErrorCallback(png_structp png_ptr, png_const_charp error_msg) { MOZ_LOG(sPNGEncoderLog, LogLevel::Error, ("libpng error: %s\n", error_msg)); png_longjmp(png_ptr, 1); } // nsPNGEncoder::WriteCallback void // static nsPNGEncoder::WriteCallback(png_structp png, png_bytep data, png_size_t size) { nsPNGEncoder* that = static_cast(png_get_io_ptr(png)); if (!that->mImageBuffer) { return; } CheckedUint32 sizeNeeded = CheckedUint32(that->mImageBufferUsed) + size; if (!sizeNeeded.isValid()) { // Take the lock to ensure that nobody is trying to read from the buffer // we are destroying ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor); that->NullOutImageBuffer(); return; } if (sizeNeeded.value() > that->mImageBufferSize) { // When we're reallocing the buffer we need to take the lock to ensure // that nobody is trying to read from the buffer we are destroying ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor); while (sizeNeeded.value() > that->mImageBufferSize) { // expand buffer, just double each time CheckedUint32 bufferSize = CheckedUint32(that->mImageBufferSize) * 2; if (!bufferSize.isValid()) { that->NullOutImageBuffer(); return; } that->mImageBufferSize *= 2; uint8_t* newBuf = (uint8_t*)realloc(that->mImageBuffer, that->mImageBufferSize); if (!newBuf) { // can't resize, just zero (this will keep us from writing more) that->NullOutImageBuffer(); return; } that->mImageBuffer = newBuf; } } memcpy(&that->mImageBuffer[that->mImageBufferUsed], data, size); that->mImageBufferUsed += size; that->NotifyListener(); } void nsPNGEncoder::NullOutImageBuffer() { mReentrantMonitor.AssertCurrentThreadIn(); free(mImageBuffer); mImageBuffer = nullptr; mImageBufferSize = 0; mImageBufferUsed = 0; } void nsPNGEncoder::NotifyListener() { // We might call this function on multiple threads (any threads that call // AsyncWait and any that do encoding) so we lock to avoid notifying the // listener twice about the same data (which generally leads to a truncated // image). ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); if (mCallback && (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold || mFinished)) { nsCOMPtr callback; if (mCallbackTarget) { callback = NS_NewInputStreamReadyEvent("nsPNGEncoder::NotifyListener", mCallback, mCallbackTarget); } else { callback = mCallback; } NS_ASSERTION(callback, "Shouldn't fail to make the callback"); // Null the callback first because OnInputStreamReady could reenter // AsyncWait mCallback = nullptr; mCallbackTarget = nullptr; mNotifyThreshold = 0; callback->OnInputStreamReady(this); } }