Skip to content

Commit

Permalink
Bug 567426: Make image encoders implement nsIAsyncInputStream. r=joed…
Browse files Browse the repository at this point in the history
…rew sr=bz
  • Loading branch information
khuey committed Jun 23, 2010
1 parent 1021f12 commit 1b22082
Show file tree
Hide file tree
Showing 6 changed files with 371 additions and 110 deletions.
89 changes: 82 additions & 7 deletions modules/libpr0n/encoders/jpeg/nsJPEGEncoder.cpp
Expand Up @@ -45,10 +45,7 @@
#include <setjmp.h>
#include "jerror.h"

// Input streams that do not implement nsIAsyncInputStream should be threadsafe
// so that they may be used with nsIInputStreamPump and nsIInputStreamChannel,
// which read such a stream on a background thread.
NS_IMPL_THREADSAFE_ISUPPORTS2(nsJPEGEncoder, imgIEncoder, nsIInputStream)
NS_IMPL_THREADSAFE_ISUPPORTS3(nsJPEGEncoder, imgIEncoder, nsIInputStream, nsIAsyncInputStream)

// used to pass error info through the JPEG library
struct encoder_error_mgr {
Expand All @@ -57,7 +54,10 @@ struct encoder_error_mgr {
};

nsJPEGEncoder::nsJPEGEncoder() : mImageBuffer(nsnull), mImageBufferSize(0),
mImageBufferUsed(0), mImageBufferReadPoint(0)
mImageBufferUsed(0), mImageBufferReadPoint(0),
mFinished(PR_FALSE), mCallback(nsnull),
mCallbackTarget(nsnull), mNotifyThreshold(0),
mMonitor("JPEG Encoder Monitor")
{
}

Expand Down Expand Up @@ -197,6 +197,9 @@ NS_IMETHODIMP nsJPEGEncoder::InitFromData(const PRUint8* aData,
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);

mFinished = PR_TRUE;
NotifyListener();

// if output callback can't get enough memory, it will free our buffer
if (!mImageBuffer)
return NS_ERROR_OUT_OF_MEMORY;
Expand Down Expand Up @@ -263,10 +266,13 @@ NS_IMETHODIMP nsJPEGEncoder::Read(char * aBuf, PRUint32 aCount,
/* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, in voidPtr aClosure, in unsigned long aCount); */
NS_IMETHODIMP nsJPEGEncoder::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, PRUint32 aCount, PRUint32 *_retval)
{
// Avoid another thread reallocing the buffer underneath us
mozilla::MonitorAutoEnter autoEnter(mMonitor);

PRUint32 maxCount = mImageBufferUsed - mImageBufferReadPoint;
if (maxCount == 0) {
*_retval = 0;
return NS_OK;
return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
}

if (aCount > maxCount)
Expand All @@ -286,10 +292,42 @@ NS_IMETHODIMP nsJPEGEncoder::ReadSegments(nsWriteSegmentFun aWriter, void *aClos
/* boolean isNonBlocking (); */
NS_IMETHODIMP nsJPEGEncoder::IsNonBlocking(PRBool *_retval)
{
*_retval = PR_FALSE; // We don't implement nsIAsyncInputStream
*_retval = PR_TRUE;
return NS_OK;
}

NS_IMETHODIMP nsJPEGEncoder::AsyncWait(nsIInputStreamCallback *aCallback,
PRUint32 aFlags,
PRUint32 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; // 1 KB seems good. 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 nsJPEGEncoder::CloseWithStatus(nsresult aStatus)
{
return Close();
}

// nsJPEGEncoder::ConvertHostARGBRow
//
Expand Down Expand Up @@ -374,6 +412,10 @@ nsJPEGEncoder::emptyOutputBuffer(jpeg_compress_struct* cinfo)
nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data);
NS_ASSERTION(that->mImageBuffer, "No buffer to empty!");

// 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
mozilla::MonitorAutoEnter autoEnter(that->mMonitor);

that->mImageBufferUsed = that->mImageBufferSize;

// expand buffer, just double size each time
Expand Down Expand Up @@ -416,6 +458,7 @@ nsJPEGEncoder::termDestination(jpeg_compress_struct* cinfo)
that->mImageBufferUsed = cinfo->dest->next_output_byte - that->mImageBuffer;
NS_ASSERTION(that->mImageBufferUsed < that->mImageBufferSize,
"JPEG library busted, got a bad image buffer size");
that->NotifyListener();
}


Expand All @@ -442,3 +485,35 @@ nsJPEGEncoder::errorExit(jpeg_common_struct* cinfo)
// Return control to the setjmp point.
longjmp(err->setjmp_buffer, error_code);
}

void
nsJPEGEncoder::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).
mozilla::MonitorAutoEnter autoEnter(mMonitor);

if (mCallback &&
(mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold ||
mFinished)) {
nsCOMPtr<nsIInputStreamCallback> callback;
if (mCallbackTarget) {
NS_NewInputStreamReadyEvent(getter_AddRefs(callback),
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 = nsnull;
mCallbackTarget = nsnull;
mNotifyThreshold = 0;

callback->OnInputStreamReady(this);
}
}
22 changes: 22 additions & 0 deletions modules/libpr0n/encoders/jpeg/nsJPEGEncoder.h
Expand Up @@ -38,6 +38,10 @@

#include "imgIEncoder.h"

#include "mozilla/Monitor.h"

#include "nsCOMPtr.h"

// needed for JPEG library
#include <stdio.h>

Expand All @@ -58,10 +62,12 @@ extern "C" {

class nsJPEGEncoder : public imgIEncoder
{
typedef mozilla::Monitor Monitor;
public:
NS_DECL_ISUPPORTS
NS_DECL_IMGIENCODER
NS_DECL_NSIINPUTSTREAM
NS_DECL_NSIASYNCINPUTSTREAM

nsJPEGEncoder();

Expand All @@ -80,10 +86,26 @@ class nsJPEGEncoder : public imgIEncoder

static void errorExit(jpeg_common_struct* cinfo);

void NotifyListener();

PRPackedBool mFinished;

// image buffer
PRUint8* mImageBuffer;
PRUint32 mImageBufferSize;
PRUint32 mImageBufferUsed;

PRUint32 mImageBufferReadPoint;

nsCOMPtr<nsIInputStreamCallback> mCallback;
nsCOMPtr<nsIEventTarget> mCallbackTarget;
PRUint32 mNotifyThreshold;

/*
nsJPEGEncoder is designed to allow one thread to pump data into it while another
reads from it. We lock to ensure that the buffer remains append-only while
we read from it (that it is not realloced) and to ensure that only one thread
dispatches a callback for each call to AsyncWait.
*/
Monitor mMonitor;
};
89 changes: 82 additions & 7 deletions modules/libpr0n/encoders/png/nsPNGEncoder.cpp
Expand Up @@ -46,15 +46,15 @@
#include "nsString.h"
#include "nsStreamUtils.h"

// Input streams that do not implement nsIAsyncInputStream should be threadsafe
// so that they may be used with nsIInputStreamPump and nsIInputStreamChannel,
// which read such a stream on a background thread.
NS_IMPL_THREADSAFE_ISUPPORTS2(nsPNGEncoder, imgIEncoder, nsIInputStream)
NS_IMPL_THREADSAFE_ISUPPORTS3(nsPNGEncoder, imgIEncoder, nsIInputStream, nsIAsyncInputStream)

nsPNGEncoder::nsPNGEncoder() : mPNG(nsnull), mPNGinfo(nsnull),
mIsAnimation(PR_FALSE),
mImageBuffer(nsnull), mImageBufferSize(0),
mImageBufferUsed(0), mImageBufferReadPoint(0)
mImageBufferUsed(0), mImageBufferReadPoint(0),
mFinished(PR_FALSE), mCallback(nsnull),
mCallbackTarget(nsnull), mNotifyThreshold(0),
mMonitor("PNG Encoder Monitor")
{
}

Expand Down Expand Up @@ -334,6 +334,9 @@ NS_IMETHODIMP nsPNGEncoder::EndImageEncode()
png_write_end(mPNG, mPNGinfo);
png_destroy_write_struct(&mPNG, &mPNGinfo);

mFinished = PR_TRUE;
NotifyListener();

// if output callback can't get enough memory, it will free our buffer
if (!mImageBuffer)
return NS_ERROR_OUT_OF_MEMORY;
Expand Down Expand Up @@ -526,10 +529,13 @@ NS_IMETHODIMP nsPNGEncoder::ReadSegments(nsWriteSegmentFun aWriter,
void *aClosure, PRUint32 aCount,
PRUint32 *_retval)
{
// Avoid another thread reallocing the buffer underneath us
mozilla::MonitorAutoEnter autoEnter(mMonitor);

PRUint32 maxCount = mImageBufferUsed - mImageBufferReadPoint;
if (maxCount == 0) {
*_retval = 0;
return NS_OK;
return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
}

if (aCount > maxCount)
Expand All @@ -550,10 +556,42 @@ NS_IMETHODIMP nsPNGEncoder::ReadSegments(nsWriteSegmentFun aWriter,
/* boolean isNonBlocking (); */
NS_IMETHODIMP nsPNGEncoder::IsNonBlocking(PRBool *_retval)
{
*_retval = PR_FALSE; // We don't implement nsIAsyncInputStream
*_retval = PR_TRUE;
return NS_OK;
}

NS_IMETHODIMP nsPNGEncoder::AsyncWait(nsIInputStreamCallback *aCallback,
PRUint32 aFlags,
PRUint32 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
//
Expand Down Expand Up @@ -629,6 +667,10 @@ nsPNGEncoder::WriteCallback(png_structp png, png_bytep data,
return;

if (that->mImageBufferUsed + size > 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
mozilla::MonitorAutoEnter autoEnter(that->mMonitor);

// expand buffer, just double each time
that->mImageBufferSize *= 2;
PRUint8* newBuf = (PRUint8*)PR_Realloc(that->mImageBuffer,
Expand All @@ -644,4 +686,37 @@ nsPNGEncoder::WriteCallback(png_structp png, png_bytep data,
}
memcpy(&that->mImageBuffer[that->mImageBufferUsed], data, size);
that->mImageBufferUsed += size;
that->NotifyListener();
}

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).
mozilla::MonitorAutoEnter autoEnter(mMonitor);

if (mCallback &&
(mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold ||
mFinished)) {
nsCOMPtr<nsIInputStreamCallback> callback;
if (mCallbackTarget) {
NS_NewInputStreamReadyEvent(getter_AddRefs(callback),
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 = nsnull;
mCallbackTarget = nsnull;
mNotifyThreshold = 0;

callback->OnInputStreamReady(this);
}
}
22 changes: 21 additions & 1 deletion modules/libpr0n/encoders/png/nsPNGEncoder.h
Expand Up @@ -37,6 +37,10 @@

#include "imgIEncoder.h"

#include "mozilla/Monitor.h"

#include "nsCOMPtr.h"

#include <png.h>

#define NS_PNGENCODER_CID \
Expand All @@ -52,10 +56,12 @@

class nsPNGEncoder : public imgIEncoder
{
typedef mozilla::Monitor Monitor;
public:
NS_DECL_ISUPPORTS
NS_DECL_IMGIENCODER
NS_DECL_NSIINPUTSTREAM
NS_DECL_NSIASYNCINPUTSTREAM

nsPNGEncoder();

Expand All @@ -79,16 +85,30 @@ class nsPNGEncoder : public imgIEncoder
PRUint32 aPixelWidth);
static void ErrorCallback(png_structp png_ptr, png_const_charp warning_msg);
static void WriteCallback(png_structp png, png_bytep data, png_size_t size);
void NotifyListener();

png_struct* mPNG;
png_info* mPNGinfo;

PRBool mIsAnimation;
PRPackedBool mIsAnimation;
PRPackedBool mFinished;

// image buffer
PRUint8* mImageBuffer;
PRUint32 mImageBufferSize;
PRUint32 mImageBufferUsed;

PRUint32 mImageBufferReadPoint;

nsCOMPtr<nsIInputStreamCallback> mCallback;
nsCOMPtr<nsIEventTarget> mCallbackTarget;
PRUint32 mNotifyThreshold;

/*
nsPNGEncoder is designed to allow one thread to pump data into it while another
reads from it. We lock to ensure that the buffer remains append-only while
we read from it (that it is not realloced) and to ensure that only one thread
dispatches a callback for each call to AsyncWait.
*/
Monitor mMonitor;
};

0 comments on commit 1b22082

Please sign in to comment.