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