Bug 696569 - Allow for non-premultiplied data for canvases - r=bjacob,joedrew

This commit is contained in:
Jeff Gilbert 2012-03-23 15:10:50 -07:00
Родитель 680806e579
Коммит f23bc53c69
22 изменённых файлов: 182 добавлений и 36 удалений

Просмотреть файл

@ -67,6 +67,10 @@ class nsICanvasElementExternal : public nsISupports {
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICANVASELEMENTEXTERNAL_IID)
enum {
RenderFlagPremultAlpha = 0x1
};
/**
* Get the size in pixels of this canvas element
*/
@ -77,7 +81,8 @@ public:
* to the given gfxContext at the origin of its coordinate space.
*/
NS_IMETHOD RenderContextsExternal(gfxContext *ctx,
gfxPattern::GraphicsFilter aFilter) = 0;
gfxPattern::GraphicsFilter aFilter,
PRUint32 aFlags = RenderFlagPremultAlpha) = 0;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsICanvasElementExternal, NS_ICANVASELEMENTEXTERNAL_IID)

Просмотреть файл

@ -74,6 +74,10 @@ public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICANVASRENDERINGCONTEXTINTERNAL_IID)
enum {
RenderFlagPremultAlpha = 0x1
};
// This method should NOT hold a ref to aParentCanvas; it will be called
// with nsnull when the element is going away.
NS_IMETHOD SetCanvasElement(nsHTMLCanvasElement* aParentCanvas) = 0;
@ -85,7 +89,9 @@ public:
NS_IMETHOD InitializeWithSurface(nsIDocShell *docShell, gfxASurface *surface, PRInt32 width, PRInt32 height) = 0;
// Render the canvas at the origin of the given gfxContext
NS_IMETHOD Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter) = 0;
NS_IMETHOD Render(gfxContext *ctx,
gfxPattern::GraphicsFilter aFilter,
PRUint32 aFlags = RenderFlagPremultAlpha) = 0;
// Gives you a stream containing the image represented by this context.
// The format is given in aMimeTime, for example "image/png".

Просмотреть файл

@ -561,7 +561,7 @@ WebGLContext::SetDimensions(PRInt32 width, PRInt32 height)
}
NS_IMETHODIMP
WebGLContext::Render(gfxContext *ctx, gfxPattern::GraphicsFilter f)
WebGLContext::Render(gfxContext *ctx, gfxPattern::GraphicsFilter f, PRUint32 aFlags)
{
if (!gl)
return NS_OK;
@ -572,7 +572,15 @@ WebGLContext::Render(gfxContext *ctx, gfxPattern::GraphicsFilter f)
return NS_ERROR_FAILURE;
gl->ReadPixelsIntoImageSurface(0, 0, mWidth, mHeight, surf);
gfxUtils::PremultiplyImageSurface(surf);
bool srcPremultAlpha = mOptions.premultipliedAlpha;
bool dstPremultAlpha = aFlags & RenderFlagPremultAlpha;
if (!srcPremultAlpha && dstPremultAlpha) {
gfxUtils::PremultiplyImageSurface(surf);
} else if (srcPremultAlpha && !dstPremultAlpha) {
gfxUtils::UnpremultiplyImageSurface(surf);
}
nsRefPtr<gfxPattern> pat = new gfxPattern(surf);
pat->SetFilter(f);
@ -608,7 +616,8 @@ WebGLContext::GetInputStream(const char* aMimeType,
nsRefPtr<gfxContext> tmpcx = new gfxContext(surf);
// Use Render() to make sure that appropriate y-flip gets applied
nsresult rv = Render(tmpcx, gfxPattern::FILTER_NEAREST);
PRUint32 flags = mOptions.premultipliedAlpha ? RenderFlagPremultAlpha : 0;
nsresult rv = Render(tmpcx, gfxPattern::FILTER_NEAREST, flags);
if (NS_FAILED(rv))
return rv;
@ -625,11 +634,22 @@ WebGLContext::GetInputStream(const char* aMimeType,
if (!encoder)
return NS_ERROR_FAILURE;
int format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
if (!mOptions.premultipliedAlpha) {
// We need to convert to INPUT_FORMAT_RGBA, otherwise
// we are automatically considered premult, and unpremult'd.
// Yes, it is THAT silly.
// Except for different lossy conversions by color,
// we could probably just change the label, and not change the data.
gfxUtils::ConvertBGRAtoRGBA(surf);
format = imgIEncoder::INPUT_FORMAT_RGBA;
}
rv = encoder->InitFromData(surf->Data(),
mWidth * mHeight * 4,
mWidth, mHeight,
surf->Stride(),
imgIEncoder::INPUT_FORMAT_HOSTARGB,
format,
nsDependentString(aEncoderOptions));
NS_ENSURE_SUCCESS(rv, rv);

Просмотреть файл

@ -542,7 +542,9 @@ public:
{ return NS_ERROR_NOT_IMPLEMENTED; }
NS_IMETHOD Reset()
{ /* (InitializeWithSurface) */ return NS_ERROR_NOT_IMPLEMENTED; }
NS_IMETHOD Render(gfxContext *ctx, gfxPattern::GraphicsFilter f);
NS_IMETHOD Render(gfxContext *ctx,
gfxPattern::GraphicsFilter f,
PRUint32 aFlags = RenderFlagPremultAlpha);
NS_IMETHOD GetInputStream(const char* aMimeType,
const PRUnichar* aEncoderOptions,
nsIInputStream **aStream);

Просмотреть файл

@ -351,7 +351,9 @@ public:
void Initialize(nsIDocShell *shell, PRInt32 width, PRInt32 height);
NS_IMETHOD InitializeWithSurface(nsIDocShell *shell, gfxASurface *surface, PRInt32 width, PRInt32 height);
bool EnsureSurface();
NS_IMETHOD Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter);
NS_IMETHOD Render(gfxContext *ctx,
gfxPattern::GraphicsFilter aFilter,
PRUint32 aFlags = RenderFlagPremultAlpha);
NS_IMETHOD GetInputStream(const char* aMimeType,
const PRUnichar* aEncoderOptions,
nsIInputStream **aStream);
@ -1278,7 +1280,7 @@ nsCanvasRenderingContext2D::SetIsIPC(bool isIPC)
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter)
nsCanvasRenderingContext2D::Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter, PRUint32 aFlags)
{
nsresult rv = NS_OK;
@ -1303,6 +1305,14 @@ nsCanvasRenderingContext2D::Render(gfxContext *ctx, gfxPattern::GraphicsFilter a
if (mOpaque)
ctx->SetOperator(op);
if (!(aFlags & RenderFlagPremultAlpha)) {
nsRefPtr<gfxASurface> curSurface = ctx->CurrentSurface();
nsRefPtr<gfxImageSurface> gis = curSurface->GetAsImageSurface();
NS_ABORT_IF_FALSE(gis, "If non-premult alpha, must be able to get image surface!");
gfxUtils::UnpremultiplyImageSurface(gis);
}
return rv;
}

Просмотреть файл

@ -406,7 +406,9 @@ public:
NS_IMETHOD InitializeWithSurface(nsIDocShell *shell, gfxASurface *surface, PRInt32 width, PRInt32 height)
{ return NS_ERROR_NOT_IMPLEMENTED; }
NS_IMETHOD Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter);
NS_IMETHOD Render(gfxContext *ctx,
gfxPattern::GraphicsFilter aFilter,
PRUint32 aFlags = RenderFlagPremultAlpha);
NS_IMETHOD GetInputStream(const char* aMimeType,
const PRUnichar* aEncoderOptions,
nsIInputStream **aStream);
@ -1382,7 +1384,7 @@ nsCanvasRenderingContext2DAzure::SetIsIPC(bool isIPC)
}
NS_IMETHODIMP
nsCanvasRenderingContext2DAzure::Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter)
nsCanvasRenderingContext2DAzure::Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter, PRUint32 aFlags)
{
nsresult rv = NS_OK;
@ -1414,6 +1416,14 @@ nsCanvasRenderingContext2DAzure::Render(gfxContext *ctx, gfxPattern::GraphicsFil
if (mOpaque)
ctx->SetOperator(op);
if (!(aFlags & RenderFlagPremultAlpha)) {
nsRefPtr<gfxASurface> curSurface = ctx->CurrentSurface();
nsRefPtr<gfxImageSurface> gis = curSurface->GetAsImageSurface();
NS_ABORT_IF_FALSE(gis, "If non-premult alpha, must be able to get image surface!");
gfxUtils::UnpremultiplyImageSurface(gis);
}
return rv;
}

Просмотреть файл

@ -1,4 +1,3 @@
conformance/context/premultiplyalpha-test.html
conformance/misc/uninitialized-test.html
conformance/programs/gl-get-active-attribute.html
conformance/textures/texture-mips.html

Просмотреть файл

@ -1,4 +1,3 @@
conformance/context/premultiplyalpha-test.html
conformance/glsl/misc/glsl-function-nodes.html
conformance/more/conformance/quickCheckAPI-S_V.html
conformance/programs/program-test.html

Просмотреть файл

@ -1,4 +1,3 @@
conformance/context/premultiplyalpha-test.html
conformance/glsl/functions/glsl-function-atan.html
conformance/glsl/functions/glsl-function-atan-xy.html
conformance/more/conformance/quickCheckAPI-S_V.html

Просмотреть файл

@ -142,7 +142,9 @@ public:
* nsICanvasElementExternal -- for use outside of content/layout
*/
NS_IMETHOD_(nsIntSize) GetSizeExternal();
NS_IMETHOD RenderContextsExternal(gfxContext *aContext, gfxPattern::GraphicsFilter aFilter);
NS_IMETHOD RenderContextsExternal(gfxContext *aContext,
gfxPattern::GraphicsFilter aFilter,
PRUint32 aFlags = RenderFlagPremultAlpha);
virtual bool ParseAttribute(PRInt32 aNamespaceID,
nsIAtom* aAttribute,

Просмотреть файл

@ -794,12 +794,12 @@ nsHTMLCanvasElement::GetSizeExternal()
}
NS_IMETHODIMP
nsHTMLCanvasElement::RenderContextsExternal(gfxContext *aContext, gfxPattern::GraphicsFilter aFilter)
nsHTMLCanvasElement::RenderContextsExternal(gfxContext *aContext, gfxPattern::GraphicsFilter aFilter, PRUint32 aFlags)
{
if (!mCurrentContext)
return NS_OK;
return mCurrentContext->Render(aContext, aFilter);
return mCurrentContext->Render(aContext, aFilter, aFlags);
}
nsresult NS_NewCanvasRenderingContext2DThebes(nsIDOMCanvasRenderingContext2D** aResult);

Просмотреть файл

@ -184,6 +184,8 @@ struct THEBES_API gfxRGBA {
* The color value is interpreted based on colorType;
* all values use the native platform endianness.
*
* Resulting gfxRGBA stores non-premultiplied data.
*
* @see gfxRGBA::Packed
*/
gfxRGBA(PRUint32 c, PackedColorType colorType = PACKED_ABGR) {

Просмотреть файл

@ -210,6 +210,51 @@ gfxUtils::UnpremultiplyImageSurface(gfxImageSurface *aSourceSurface,
}
}
void
gfxUtils::ConvertBGRAtoRGBA(gfxImageSurface *aSourceSurface,
gfxImageSurface *aDestSurface) {
if (!aDestSurface)
aDestSurface = aSourceSurface;
NS_ABORT_IF_FALSE(aSourceSurface->Format() == aDestSurface->Format() &&
aSourceSurface->Width() == aDestSurface->Width() &&
aSourceSurface->Height() == aDestSurface->Height() &&
aSourceSurface->Stride() == aDestSurface->Stride(),
"Source and destination surfaces don't have identical characteristics");
NS_ABORT_IF_FALSE(aSourceSurface->Stride() == aSourceSurface->Width() * 4,
"Source surface stride isn't tightly packed");
NS_ABORT_IF_FALSE(aSourceSurface->Format() == gfxASurface::ImageFormatARGB32,
"Surfaces must be ARGB32");
PRUint8 *src = aSourceSurface->Data();
PRUint8 *dst = aDestSurface->Data();
PRUint32 dim = aSourceSurface->Width() * aSourceSurface->Height();
PRUint8 *srcEnd = src + 4*dim;
if (src == dst) {
PRUint8 buffer[4];
for (; src != srcEnd; src += 4) {
buffer[0] = src[2];
buffer[1] = src[1];
buffer[2] = src[0];
src[0] = buffer[0];
src[1] = buffer[1];
src[2] = buffer[2];
}
} else {
for (; src != srcEnd; src += 4, dst += 4) {
dst[0] = src[2];
dst[1] = src[1];
dst[2] = src[0];
dst[3] = src[3];
}
}
}
static bool
IsSafeImageTransformComponent(gfxFloat aValue)
{

Просмотреть файл

@ -65,6 +65,9 @@ public:
static void UnpremultiplyImageSurface(gfxImageSurface *aSurface,
gfxImageSurface *aDestSurface = nsnull);
static void ConvertBGRAtoRGBA(gfxImageSurface *aSourceSurface,
gfxImageSurface *aDestSurface = nsnull);
/**
* Draw something drawable while working around limitations like bad support
* for EXTEND_PAD, lack of source-clipping, or cairo / pixman bugs with

Просмотреть файл

@ -209,6 +209,9 @@ void nsPNGDecoder::EndImageFrame()
if (mFrameHasNoAlpha)
mImage.SetFrameHasNoAlpha(numFrames - 1);
// PNG is always non-premult
mImage.SetFrameAsNonPremult(numFrames - 1, true);
PostInvalidation(mFrameRect);
}
#endif

Просмотреть файл

@ -300,6 +300,13 @@ Decoder::PostDecodeDone()
NS_ABORT_IF_FALSE(!mDecodeDone, "Decode already done!");
mDecodeDone = true;
// Set premult before DecodingComplete(), since DecodingComplete() calls Optimize()
int frames = GetFrameCount();
bool isNonPremult = GetDecodeFlags() & DECODER_NO_PREMULTIPLY_ALPHA;
for (int i = 0; i < frames; i++) {
mImage.SetFrameAsNonPremult(i, isNonPremult);
}
// Notify
mImage.DecodingComplete();
if (mObserver) {

Просмотреть файл

@ -150,8 +150,8 @@ public:
// SetDecodeFlags must be called before Init(), otherwise
// default flags are assumed.
enum {
DECODER_NO_PREMULTIPLY_ALPHA = 0x2,
DECODER_NO_COLORSPACE_CONVERSION = 0x4
DECODER_NO_PREMULTIPLY_ALPHA = 0x2, // imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA
DECODER_NO_COLORSPACE_CONVERSION = 0x4 // imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION
};
void SetDecodeFlags(PRUint32 aFlags) { mDecodeFlags = aFlags; }
PRUint32 GetDecodeFlags() { return mDecodeFlags; }

Просмотреть файл

@ -1324,6 +1324,25 @@ RasterImage::SetFrameHasNoAlpha(PRUint32 aFrameNum)
return NS_OK;
}
nsresult
RasterImage::SetFrameAsNonPremult(PRUint32 aFrameNum, bool aIsNonPremult)
{
if (mError)
return NS_ERROR_FAILURE;
NS_ABORT_IF_FALSE(aFrameNum < mFrames.Length(), "Invalid frame index!");
if (aFrameNum >= mFrames.Length())
return NS_ERROR_INVALID_ARG;
imgFrame* frame = GetImgFrame(aFrameNum);
NS_ABORT_IF_FALSE(frame, "Calling SetFrameAsNonPremult on frame that doesn't exist!");
NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
frame->SetAsNonPremult(aIsNonPremult);
return NS_OK;
}
nsresult
RasterImage::DecodingComplete()
{

Просмотреть файл

@ -244,6 +244,7 @@ public:
nsresult SetFrameTimeout(PRUint32 aFrameNum, PRInt32 aTimeout);
nsresult SetFrameBlendMethod(PRUint32 aFrameNum, PRInt32 aBlendMethod);
nsresult SetFrameHasNoAlpha(PRUint32 aFrameNum);
nsresult SetFrameAsNonPremult(PRUint32 aFrameNum, bool aIsNonPremult);
/**
* Sets the size of the container. This should only be called by the

Просмотреть файл

@ -145,12 +145,13 @@ imgFrame::imgFrame() :
mSinglePixel(false),
mNeverUseDeviceSurface(false),
mFormatChanged(false),
mCompositingFailed(false)
mCompositingFailed(false),
mNonPremult(false),
#ifdef USE_WIN_SURFACE
, mIsDDBSurface(false)
mIsDDBSurface(false),
#endif
, mLocked(false)
, mInformedDiscardTracker(false)
mLocked(false),
mInformedDiscardTracker(false)
{
static bool hasCheckedOptimize = false;
if (!hasCheckedOptimize) {
@ -254,6 +255,11 @@ nsresult imgFrame::Optimize()
if (mPalettedImageData || mOptSurface || mSinglePixel)
return NS_OK;
// Don't do single-color opts on non-premult data.
// Cairo doesn't support non-premult single-colors.
if (mNonPremult)
return NS_OK;
/* Figure out if the entire image is a constant color */
// this should always be true
@ -270,11 +276,12 @@ nsresult imgFrame::Optimize()
if (mFormat == gfxASurface::ImageFormatARGB32 ||
mFormat == gfxASurface::ImageFormatRGB24)
{
mSinglePixelColor = gfxRGBA
(firstPixel,
(mFormat == gfxImageSurface::ImageFormatRGB24 ?
gfxRGBA::PACKED_XRGB :
gfxRGBA::PACKED_ARGB_PREMULTIPLIED));
// Should already be premult if desired.
gfxRGBA::PackedColorType inputType = gfxRGBA::PACKED_XRGB;
if (mFormat == gfxASurface::ImageFormatARGB32)
inputType = gfxRGBA::PACKED_ARGB_PREMULTIPLIED;
mSinglePixelColor = gfxRGBA(firstPixel, inputType);
mSinglePixel = true;
@ -434,6 +441,7 @@ imgFrame::SurfaceForDrawing(bool aDoPadding,
}
tmpCtx.Rectangle(available);
tmpCtx.Fill();
return SurfaceWithFormat(new gfxSurfaceDrawable(surface, size), format);
}
@ -517,6 +525,8 @@ nsresult imgFrame::Extract(const nsIntRect& aRegion, imgFrame** aResult)
mFormat, mPaletteDepth);
NS_ENSURE_SUCCESS(rv, rv);
subImage->SetAsNonPremult(mNonPremult);
// scope to destroy ctx
{
gfxContext ctx(subImage->ThebesSurface());
@ -772,6 +782,11 @@ void imgFrame::SetHasNoAlpha()
}
}
void imgFrame::SetAsNonPremult(bool aIsNonPremult)
{
mNonPremult = aIsNonPremult;
}
bool imgFrame::GetCompositingFailed() const
{
return mCompositingFailed;

Просмотреть файл

@ -92,6 +92,7 @@ public:
bool ImageComplete() const;
void SetHasNoAlpha();
void SetAsNonPremult(bool aIsNonPremult);
bool GetCompositingFailed() const;
void SetCompositingFailed(bool val);
@ -180,6 +181,7 @@ private: // data
// Total length is PaletteDataLength() + GetImageDataLength().
PRUint8* mPalettedImageData;
// Note that the data stored in gfxRGBA is *non-alpha-premultiplied*.
gfxRGBA mSinglePixelColor;
PRInt32 mTimeout; // -1 means display forever
@ -192,6 +194,7 @@ private: // data
bool mNeverUseDeviceSurface;
bool mFormatChanged;
bool mCompositingFailed;
bool mNonPremult;
/** Indicates if the image data is currently locked */
bool mLocked;

Просмотреть файл

@ -4050,8 +4050,9 @@ nsLayoutUtils::SurfaceFromElement(dom::Element* aElement,
bool forceCopy = (aSurfaceFlags & SFE_WANT_NEW_SURFACE) != 0;
bool wantImageSurface = (aSurfaceFlags & SFE_WANT_IMAGE_SURFACE) != 0;
bool premultAlpha = (aSurfaceFlags & SFE_NO_PREMULTIPLY_ALPHA) == 0;
if (aSurfaceFlags & SFE_NO_PREMULTIPLY_ALPHA) {
if (!premultAlpha) {
forceCopy = true;
wantImageSurface = true;
}
@ -4082,7 +4083,8 @@ nsLayoutUtils::SurfaceFromElement(dom::Element* aElement,
nsRefPtr<gfxContext> ctx = new gfxContext(surf);
// XXX shouldn't use the external interface, but maybe we can layerify this
rv = canvas->RenderContextsExternal(ctx, gfxPattern::FILTER_NEAREST);
PRUint32 flags = premultAlpha ? nsHTMLCanvasElement::RenderFlagPremultAlpha : 0;
rv = canvas->RenderContextsExternal(ctx, gfxPattern::FILTER_NEAREST, flags);
if (NS_FAILED(rv))
return result;
}
@ -4091,12 +4093,6 @@ nsLayoutUtils::SurfaceFromElement(dom::Element* aElement,
// in case this is being used by -moz-element()
canvas->MarkContextClean();
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()));
}
result.mSurface = surf;
result.mSize = size;
result.mPrincipal = aElement->NodePrincipal();