b=622184; implement webgl colour conversion/premultiplication semantics; r=joe, a=b

This commit is contained in:
Vladimir Vukicevic 2011-01-12 17:45:13 -08:00
parent ba2c53afe2
commit 44d1f1a0b6
13 changed files with 208 additions and 29 deletions

View File

@ -3198,9 +3198,17 @@ WebGLContext::DOMElementToImageSurface(nsIDOMElement *imageOrCanvas,
{
gfxImageSurface *surf = nsnull;
PRUint32 flags =
nsLayoutUtils::SFE_WANT_NEW_SURFACE |
nsLayoutUtils::SFE_WANT_IMAGE_SURFACE;
if (mPixelStoreColorspaceConversion == LOCAL_GL_NONE)
flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION;
if (!mPixelStorePremultiplyAlpha)
flags |= nsLayoutUtils::SFE_NO_PREMULTIPLY_ALPHA;
nsLayoutUtils::SurfaceFromElementResult res =
nsLayoutUtils::SurfaceFromElement(imageOrCanvas,
nsLayoutUtils::SFE_WANT_NEW_SURFACE | nsLayoutUtils::SFE_WANT_IMAGE_SURFACE);
nsLayoutUtils::SurfaceFromElement(imageOrCanvas, flags);
if (!res.mSurface)
return NS_ERROR_FAILURE;
@ -4011,7 +4019,7 @@ WebGLContext::TexImage2D_dom(WebGLenum target, WebGLint level, WebGLenum interna
isurf->Width(), isurf->Height(), isurf->Stride(), 0,
format, type,
isurf->Data(), byteLength,
srcFormat, PR_TRUE);
srcFormat, mPixelStorePremultiplyAlpha);
}
NS_IMETHODIMP

View File

@ -148,6 +148,13 @@
(GFX_PREMULTIPLY(g,a) << 8) | \
(GFX_PREMULTIPLY(b,a))
/**
* Macro to pack the 4 8-bit channels (A,R,G,B)
* into a 32-bit packed NON-premultiplied pixel.
*/
#define GFX_PACKED_PIXEL_NO_PREMULTIPLY(a,r,g,b) \
(((a) << 24) | ((r) << 16) | ((g) << 8) | (b))
/**
* A color value, storing red, green, blue and alpha components.

View File

@ -3567,6 +3567,11 @@ nsLayoutUtils::SurfaceFromElement(nsIDOMElement *aElement,
PRBool forceCopy = (aSurfaceFlags & SFE_WANT_NEW_SURFACE) != 0;
PRBool wantImageSurface = (aSurfaceFlags & SFE_WANT_IMAGE_SURFACE) != 0;
if (aSurfaceFlags & SFE_NO_PREMULTIPLY_ALPHA) {
forceCopy = PR_TRUE;
wantImageSurface = PR_TRUE;
}
// If it's a <canvas>, we may be able to just grab its internal surface
nsCOMPtr<nsIDOMHTMLCanvasElement> domCanvas = do_QueryInterface(aElement);
if (node && domCanvas) {
@ -3601,6 +3606,12 @@ nsLayoutUtils::SurfaceFromElement(nsIDOMElement *aElement,
return result;
}
if (aSurfaceFlags & SFE_NO_PREMULTIPLY_ALPHA) {
// we can modify this surface since we force a copy above when
// when NO_PREMULTIPLY_ALPHA is set
gfxUtils::UnpremultiplyImageSurface(static_cast<gfxImageSurface*>(surf.get()));
}
nsCOMPtr<nsIPrincipal> principal = node->NodePrincipal();
result.mSurface = surf;
@ -3714,9 +3725,14 @@ nsLayoutUtils::SurfaceFromElement(nsIDOMElement *aElement,
PRUint32 whichFrame = (aSurfaceFlags & SFE_WANT_FIRST_FRAME)
? (PRUint32) imgIContainer::FRAME_FIRST
: (PRUint32) imgIContainer::FRAME_CURRENT;
PRUint32 frameFlags = imgIContainer::FLAG_SYNC_DECODE;
if (aSurfaceFlags & SFE_NO_COLORSPACE_CONVERSION)
frameFlags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION;
if (aSurfaceFlags & SFE_NO_PREMULTIPLY_ALPHA)
frameFlags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
nsRefPtr<gfxASurface> framesurf;
rv = imgContainer->GetFrame(whichFrame,
imgIContainer::FLAG_SYNC_DECODE,
frameFlags,
getter_AddRefs(framesurf));
if (NS_FAILED(rv))
return result;

View File

@ -1209,7 +1209,13 @@ public:
SFE_WANT_IMAGE_SURFACE = 1 << 1,
/* Whether to extract the first frame (as opposed to the
current frame) in the case that the element is an image. */
SFE_WANT_FIRST_FRAME = 1 << 2
SFE_WANT_FIRST_FRAME = 1 << 2,
/* Whether we should skip colorspace/gamma conversion */
SFE_NO_COLORSPACE_CONVERSION = 1 << 3,
/* Whether we should skip premultiplication -- the resulting
image will always be an image surface, and must not be given to
Thebes for compositing! */
SFE_NO_PREMULTIPLY_ALPHA = 1 << 4
};
struct SurfaceFromElementResult {

View File

@ -115,6 +115,8 @@ nsJPEGDecoder::nsJPEGDecoder()
mInProfile = nsnull;
mTransform = nsnull;
mCMSMode = 0;
PR_LOG(gJPEGDecoderAccountingLog, PR_LOG_DEBUG,
("nsJPEGDecoder::nsJPEGDecoder: Creating JPEG decoder %p",
this));
@ -141,6 +143,10 @@ nsJPEGDecoder::~nsJPEGDecoder()
void
nsJPEGDecoder::InitInternal()
{
mCMSMode = gfxPlatform::GetCMSMode();
if ((mDecodeFlags & DECODER_NO_COLORSPACE_CONVERSION) != 0)
mCMSMode = eCMSMode_Off;
/* We set up the normal JPEG error routines, then override error_exit. */
mInfo.err = jpeg_std_error(&mErr.pub);
/* mInfo.err = jpeg_std_error(&mErr.pub); */
@ -244,9 +250,8 @@ nsJPEGDecoder::WriteInternal(const char *aBuffer, PRUint32 aCount)
/* We're doing a full decode. */
JOCTET *profile;
PRUint32 profileLength;
eCMSMode cmsMode = gfxPlatform::GetCMSMode();
if ((cmsMode != eCMSMode_Off) &&
if ((mCMSMode != eCMSMode_Off) &&
read_icc_profile(&mInfo, &profile, &profileLength) &&
(mInProfile = qcms_profile_from_memory(profile, profileLength)) != NULL) {
free(profile);
@ -412,7 +417,7 @@ nsJPEGDecoder::WriteInternal(const char *aBuffer, PRUint32 aCount)
}
/* Force to use our YCbCr to Packed RGB converter when possible */
if (!mTransform && (gfxPlatform::GetCMSMode() != eCMSMode_All) &&
if (!mTransform && (mCMSMode != eCMSMode_All) &&
mInfo.jpeg_color_space == JCS_YCbCr && mInfo.out_color_space == JCS_RGB) {
/* Special case for the most common case: transform from YCbCr direct into packed ARGB */
mInfo.out_color_components = 4; /* Packed ARGB pixels are always 4 bytes...*/
@ -607,7 +612,7 @@ nsJPEGDecoder::OutputScanlines(PRBool* suspend)
cmyk_convert_rgb((JSAMPROW)imageRow, mInfo.output_width);
sampleRow += mInfo.output_width;
}
if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
if (mCMSMode == eCMSMode_All) {
/* No embedded ICC profile - treat as sRGB */
qcms_transform *transform = gfxPlatform::GetCMSRGBTransform();
if (transform) {

View File

@ -123,6 +123,8 @@ public:
qcms_transform *mTransform;
PRPackedBool mReading;
PRUint32 mCMSMode;
};
} // namespace imagelib

View File

@ -86,7 +86,8 @@ nsPNGDecoder::nsPNGDecoder() :
mCMSLine(nsnull), interlacebuf(nsnull),
mInProfile(nsnull), mTransform(nsnull),
mHeaderBuf(nsnull), mHeaderBytesRead(0),
mChannels(0), mFrameIsHidden(PR_FALSE)
mChannels(0), mFrameIsHidden(PR_FALSE),
mCMSMode(0), mDisablePremultipliedAlpha(PR_FALSE)
{
}
@ -216,6 +217,10 @@ void nsPNGDecoder::EndImageFrame()
void
nsPNGDecoder::InitInternal()
{
mCMSMode = gfxPlatform::GetCMSMode();
if ((mDecodeFlags & DECODER_NO_COLORSPACE_CONVERSION) != 0)
mCMSMode = eCMSMode_Off;
mDisablePremultipliedAlpha = (mDecodeFlags & DECODER_NO_PREMULTIPLY_ALPHA) != 0;
#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
static png_byte color_chunks[]=
@ -264,7 +269,7 @@ nsPNGDecoder::InitInternal()
#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
/* Ignore unused chunks */
if (gfxPlatform::GetCMSMode() == eCMSMode_Off)
if (mCMSMode == eCMSMode_Off)
png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2);
png_set_keep_unknown_chunks(mPNG, 1, unused_chunks,
@ -272,7 +277,7 @@ nsPNGDecoder::InitInternal()
#endif
#ifdef PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED
if (gfxPlatform::GetCMSMode() != eCMSMode_Off)
if (mCMSMode != eCMSMode_Off)
png_set_chunk_malloc_max(mPNG, 4000000L);
#endif
@ -531,7 +536,7 @@ nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr)
qcms_data_type inType;
PRUint32 intent = -1;
PRUint32 pIntent;
if (gfxPlatform::GetCMSMode() != eCMSMode_Off) {
if (decoder->mCMSMode != eCMSMode_Off) {
intent = gfxPlatform::GetRenderingIntent();
decoder->mInProfile = PNGGetColorProfile(png_ptr, info_ptr,
color_type, &inType, &pIntent);
@ -554,9 +559,12 @@ nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr)
(qcms_intent)intent);
} else {
png_set_gray_to_rgb(png_ptr);
PNGDoGammaCorrection(png_ptr, info_ptr);
if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
// only do gamma correction if CMS isn't entirely disabled
if (decoder->mCMSMode != eCMSMode_Off)
PNGDoGammaCorrection(png_ptr, info_ptr);
if (decoder->mCMSMode == eCMSMode_All) {
if (color_type & PNG_COLOR_MASK_ALPHA || num_trans)
decoder->mTransform = gfxPlatform::GetCMSRGBATransform();
else
@ -745,11 +753,20 @@ nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row,
break;
case gfxASurface::ImageFormatARGB32:
{
for (PRUint32 x=width; x>0; --x) {
*cptr32++ = GFX_PACKED_PIXEL(line[3], line[0], line[1], line[2]);
if (line[3] != 0xff)
rowHasNoAlpha = PR_FALSE;
line += 4;
if (!decoder->mDisablePremultipliedAlpha) {
for (PRUint32 x=width; x>0; --x) {
*cptr32++ = GFX_PACKED_PIXEL(line[3], line[0], line[1], line[2]);
if (line[3] != 0xff)
rowHasNoAlpha = PR_FALSE;
line += 4;
}
} else {
for (PRUint32 x=width; x>0; --x) {
*cptr32++ = GFX_PACKED_PIXEL_NO_PREMULTIPLY(line[3], line[0], line[1], line[2]);
if (line[3] != 0xff)
rowHasNoAlpha = PR_FALSE;
line += 4;
}
}
}
break;

View File

@ -92,6 +92,10 @@ public:
PRPackedBool mFrameHasNoAlpha;
PRPackedBool mFrameIsHidden;
// whether CMS or premultiplied alpha are forced off
PRUint32 mCMSMode;
PRPackedBool mDisablePremultipliedAlpha;
/*
* libpng callbacks
*

View File

@ -133,10 +133,19 @@ interface imgIContainer : nsISupports
* available data before the call returns. It is an error to pass this flag
* from a call stack that originates in a decoder (ie, from a decoder
* observer event).
*
* FLAG_DECODE_NO_PREMULTIPLY_ALPHA: Do not premultiply alpha if
* it's not already premultiplied in the image data.
*
* FLAG_DECODE_NO_COLORSPACE_CONVERSION: Do not do any colorspace conversion;
* ignore any embedded profiles, and don't convert to any particular destination
* space.
*/
const long FLAG_NONE = 0x0;
const long FLAG_SYNC_DECODE = 0x1;
const long FLAG_DECODE_NO_PREMULTIPLY_ALPHA = 0x2;
const long FLAG_DECODE_NO_COLORSPACE_CONVERSION = 0x4;
/**
* Constants for specifying various "special" frames.

View File

@ -45,7 +45,8 @@ namespace mozilla {
namespace imagelib {
Decoder::Decoder()
: mFrameCount(0)
: mDecodeFlags(0)
, mFrameCount(0)
, mFailCode(NS_OK)
, mInitialized(false)
, mSizeDecode(false)

View File

@ -122,6 +122,16 @@ public:
bool HasDecoderError() { return NS_FAILED(mFailCode); };
nsresult GetDecoderError() { return mFailCode; };
// flags. Keep these in sync with imgIContainer.idl.
// SetDecodeFlags must be called before Init(), otherwise
// default flags are assumed.
enum {
DECODER_NO_PREMULTIPLY_ALPHA = 0x2,
DECODER_NO_COLORSPACE_CONVERSION = 0x4
};
void SetDecodeFlags(PRUint32 aFlags) { mDecodeFlags = aFlags; }
PRUint32 GetDecodeFlags() { return mDecodeFlags; }
protected:
/*
@ -166,6 +176,8 @@ protected:
*/
nsRefPtr<RasterImage> mImage;
PRUint32 mDecodeFlags;
private:
nsCOMPtr<imgIDecoderObserver> mObserver;

View File

@ -69,6 +69,10 @@
using namespace mozilla::imagelib;
// a mask for flags that will affect the decoding
#define DECODE_FLAGS_MASK (imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA | imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION)
#define DECODE_FLAGS_DEFAULT 0
/* Accounting for compressed data */
#if defined(PR_LOGGING)
static PRLogModuleInfo *gCompressedImageAccountingLog = PR_NewLogModule ("CompressedImageAccounting");
@ -178,6 +182,7 @@ NS_IMPL_ISUPPORTS5(RasterImage, imgIContainer, nsITimerCallback, nsIProperties,
RasterImage::RasterImage(imgStatusTracker* aStatusTracker) :
Image(aStatusTracker), // invoke superclass's constructor
mSize(0,0),
mFrameDecodeFlags(DECODE_FLAGS_DEFAULT),
mAnim(nsnull),
mLoopCount(-1),
mObserver(nsnull),
@ -340,7 +345,21 @@ RasterImage::ExtractFrame(PRUint32 aWhichFrame,
img->SetSize(aRegion.width, aRegion.height);
img->mDecoded = PR_TRUE; // Also, we need to mark the image as decoded
img->mHasBeenDecoded = PR_TRUE;
img->mFrameDecodeFlags = aFlags & DECODE_FLAGS_MASK;
if (img->mFrameDecodeFlags != mFrameDecodeFlags) {
// if we can't discard, then we're screwed; we have no way
// to re-decode. Similarly if we aren't allowed to do a sync
// decode.
if (!(aFlags & FLAG_SYNC_DECODE))
return NS_ERROR_NOT_AVAILABLE;
if (!CanForciblyDiscard() || mDecoder || mAnim)
return NS_ERROR_NOT_AVAILABLE;
ForceDiscard();
mFrameDecodeFlags = img->mFrameDecodeFlags;
}
// If a synchronous decode was requested, do it
if (aFlags & FLAG_SYNC_DECODE) {
rv = SyncDecode();
@ -590,6 +609,20 @@ RasterImage::CopyFrame(PRUint32 aWhichFrame,
nsresult rv;
PRUint32 desiredDecodeFlags = aFlags & DECODE_FLAGS_MASK;
if (desiredDecodeFlags != mFrameDecodeFlags) {
// if we can't discard, then we're screwed; we have no way
// to re-decode. Similarly if we aren't allowed to do a sync
// decode.
if (!(aFlags & FLAG_SYNC_DECODE))
return NS_ERROR_NOT_AVAILABLE;
if (!CanForciblyDiscard() || mDecoder || mAnim)
return NS_ERROR_NOT_AVAILABLE;
ForceDiscard();
mFrameDecodeFlags = desiredDecodeFlags;
}
// If requested, synchronously flush any data we have lying around to the decoder
if (aFlags & FLAG_SYNC_DECODE) {
rv = SyncDecode();
@ -648,6 +681,21 @@ RasterImage::GetFrame(PRUint32 aWhichFrame,
nsresult rv = NS_OK;
PRUint32 desiredDecodeFlags = aFlags & DECODE_FLAGS_MASK;
if (desiredDecodeFlags != mFrameDecodeFlags) {
// if we can't discard, then we're screwed; we have no way
// to re-decode. Similarly if we aren't allowed to do a sync
// decode.
if (!(aFlags & FLAG_SYNC_DECODE))
return NS_ERROR_NOT_AVAILABLE;
if (!CanForciblyDiscard() || mDecoder || mAnim)
return NS_ERROR_NOT_AVAILABLE;
ForceDiscard();
mFrameDecodeFlags = desiredDecodeFlags;
}
// If the caller requested a synchronous decode, do it
if (aFlags & FLAG_SYNC_DECODE) {
rv = SyncDecode();
@ -1996,10 +2044,10 @@ RasterImage::GetKeys(PRUint32 *count, char ***keys)
}
void
RasterImage::Discard()
RasterImage::Discard(bool force)
{
// We should be ok for discard
NS_ABORT_IF_FALSE(CanDiscard(), "Asked to discard but can't!");
NS_ABORT_IF_FALSE(force ? CanForciblyDiscard() : CanDiscard(), "Asked to discard but can't!");
// We should never discard when we have an active decoder
NS_ABORT_IF_FALSE(!mDecoder, "Asked to discard with open decoder!");
@ -2024,6 +2072,9 @@ RasterImage::Discard()
if (observer)
observer->OnDiscard(nsnull);
if (force)
DiscardTracker::Remove(&mDiscardTrackerNode);
// Log
PR_LOG(gCompressedImageAccountingLog, PR_LOG_DEBUG,
("CompressedImageAccounting: discarded uncompressed image "
@ -2050,6 +2101,13 @@ RasterImage::CanDiscard() {
mDecoded); // ...and have something to discard.
}
PRBool
RasterImage::CanForciblyDiscard() {
return (mDiscardable && // ...Enabled at creation time...
mHasSourceData && // ...have the source data...
mDecoded); // ...and have something to discard.
}
// Helper method to tell us whether the clock is currently running for
// discarding this image. Mainly for assertions.
PRBool
@ -2110,6 +2168,7 @@ RasterImage::InitDecoder(bool aDoSizeDecode)
// Initialize the decoder
nsCOMPtr<imgIDecoderObserver> observer(do_QueryReferent(mObserver));
mDecoder->SetSizeDecode(aDoSizeDecode);
mDecoder->SetDecodeFlags(mFrameDecodeFlags);
mDecoder->Init(this, observer);
CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError());
@ -2273,8 +2332,11 @@ RasterImage::RequestDecode()
}
// If we have a size decode open, interrupt it and shut it down
if (mDecoder && mDecoder->IsSizeDecode()) {
// If we have a size decode open, interrupt it and shut it down; or if
// the decoder has different flags than what we need
if (mDecoder &&
(mDecoder->IsSizeDecode() || mDecoder->GetDecodeFlags() != mFrameDecodeFlags))
{
rv = ShutdownDecoder(eShutdownIntent_Interrupted);
CONTAINER_ENSURE_SUCCESS(rv);
}
@ -2324,8 +2386,11 @@ RasterImage::SyncDecode()
// disallow this type of call in the API, and check for it in API methods.
NS_ABORT_IF_FALSE(!mInDecoder, "Yikes, forcing sync in reentrant call!");
// If we have a size decode open, shut it down
if (mDecoder && mDecoder->IsSizeDecode()) {
// If we have a size decoder open, or one with different flags than
// what we need, shut it down
if (mDecoder &&
(mDecoder->IsSizeDecode() || mDecoder->GetDecodeFlags() != mFrameDecodeFlags))
{
rv = ShutdownDecoder(eShutdownIntent_Interrupted);
CONTAINER_ENSURE_SUCCESS(rv);
}
@ -2384,8 +2449,23 @@ RasterImage::Draw(gfxContext *aContext,
if (mInDecoder && (aFlags & imgIContainer::FLAG_SYNC_DECODE))
return NS_ERROR_FAILURE;
// Illegal -- you can't draw with non-default decode flags.
// (Disabling colorspace conversion might make sense to allow, but
// we don't currently.)
if ((aFlags & DECODE_FLAGS_MASK) != DECODE_FLAGS_DEFAULT)
return NS_ERROR_FAILURE;
NS_ENSURE_ARG_POINTER(aContext);
// We can only draw with the default decode flags
if (mFrameDecodeFlags != DECODE_FLAGS_DEFAULT) {
if (!CanForciblyDiscard())
return NS_ERROR_NOT_AVAILABLE;
ForceDiscard();
mFrameDecodeFlags = DECODE_FLAGS_DEFAULT;
}
// If a synchronous draw is requested, flush anything that might be sitting around
if (aFlags & FLAG_SYNC_DECODE) {
nsresult rv = SyncDecode();

View File

@ -213,7 +213,8 @@ public:
PRUint32 GetSourceDataSize();
/* Triggers discarding. */
void Discard();
void Discard(bool force = false);
void ForceDiscard() { Discard(/* force = */ true); }
/* Callbacks for decoders */
nsresult SetFrameDisposalMethod(PRUint32 aFrameNum,
@ -450,7 +451,17 @@ private:
private: // data
nsIntSize mSize;
// Whether mFrames below were decoded using any special flags.
// Some flags (e.g. unpremultiplied data) may not be compatible
// with the browser's needs for displaying the image to the user.
// As such, we may need to redecode if we're being asked for
// a frame with different flags. 0 indicates default flags.
//
// Valid flag bits are imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA
// and imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION.
PRUint32 mFrameDecodeFlags;
//! All the frames of the image
// IMPORTANT: if you use mFrames in a method, call EnsureImageIsDecoded() first
// to ensure that the frames actually exist (they may have been discarded to save
@ -530,6 +541,7 @@ private: // data
// Helpers
void DoError();
PRBool CanDiscard();
PRBool CanForciblyDiscard();
PRBool DiscardingActive();
PRBool StoringSourceData();