gecko/content/canvas/src/nsCanvasRenderingContext2D.cpp

3218 lines
94 KiB
C++
Raw Normal View History

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Vladimir Vukicevic <vladimir@pobox.com>
* Portions created by the Initial Developer are Copyright (C) 2005
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifdef _MSC_VER
#define _USE_MATH_DEFINES
#endif
#include <math.h>
#include "prmem.h"
#include "nsIServiceManager.h"
#include "nsContentUtils.h"
#include "nsIDOMDocument.h"
#include "nsIDocument.h"
#include "nsIDOMCanvasRenderingContext2D.h"
#include "nsICanvasRenderingContextInternal.h"
#include "nsPresContext.h"
#include "nsIPresShell.h"
#include "nsIVariant.h"
#ifdef MOZILLA_1_8_BRANCH
#include "nsIScriptGlobalObject.h"
#include "nsIViewManager.h"
#include "nsIScrollableView.h"
#endif
#include "imgIRequest.h"
#include "imgIContainer.h"
#include "gfxIImageFrame.h"
#include "nsIDOMHTMLCanvasElement.h"
#include "nsICanvasElement.h"
#include "nsIDOMHTMLImageElement.h"
#include "nsIImageLoadingContent.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIImage.h"
#include "nsIFrame.h"
#include "nsDOMError.h"
#include "nsIScriptError.h"
#include "nsICSSParser.h"
#include "nsPrintfCString.h"
#include "nsReadableUtils.h"
#include "nsColor.h"
#include "nsTransform2D.h"
#include "nsIRenderingContext.h"
#include "nsIDeviceContext.h"
#include "nsIBlender.h"
#include "nsGfxCIID.h"
#include "nsIDrawingSurface.h"
#include "nsIScriptSecurityManager.h"
#include "nsIDocShell.h"
#include "nsPresContext.h"
#include "nsIPresShell.h"
#include "nsIDOMWindow.h"
#include "nsPIDOMWindow.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "nsIDocShellTreeNode.h"
#include "nsIXPConnect.h"
#include "jsapi.h"
#include "jsnum.h"
#include "nsTArray.h"
#include "cairo.h"
#include "imgIEncoder.h"
#ifdef MOZILLA_1_8_BRANCH
#define imgIEncoder imgIEncoder_MOZILLA_1_8_BRANCH
#endif
#ifdef MOZ_CAIRO_GFX
#include "gfxContext.h"
#include "gfxASurface.h"
#include "gfxPlatform.h"
#include "nsDisplayList.h"
#include "nsIViewManager.h"
#include "nsIScrollableView.h"
#include "nsFrameManager.h"
#include "nsRegion.h"
#endif
#ifdef XP_WIN
#include "cairo-win32.h"
#ifdef MOZILLA_1_8_BRANCH
extern "C" {
cairo_surface_t *
_cairo_win32_surface_create_dib (cairo_format_t format,
int width,
int height);
}
#endif
#ifndef M_PI
#define M_PI 3.14159265358979323846
#define M_PI_2 1.57079632679489661923
#endif
#endif
#ifdef XP_OS2
#define INCL_WINWINDOWMGR
#define INCL_GPIBITMAPS
#include <os2.h>
#include "nsDrawingSurfaceOS2.h"
#include "cairo-os2.h"
#endif
#ifdef MOZ_WIDGET_GTK2
#include "cairo-xlib.h"
#include "cairo-xlib-xrender.h"
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#endif
#ifdef XP_MACOSX
#include <Quickdraw.h>
#include <CGContext.h>
/* This has to be 0 on machines less than 10.4 (which are always PPC);
* otherwise the older CG gets confused if we pass in
* kCGBitmapByteOrder32Big since it doesn't understand it.
*/
#ifdef __BIG_ENDIAN__
#define CG_BITMAP_BYTE_ORDER_FLAG 0
#else /* Little endian. */
/* x86, and will be a 10.4u SDK; ByteOrder32Host will be defined */
#define CG_BITMAP_BYTE_ORDER_FLAG kCGBitmapByteOrder32Host
#endif
#ifndef MOZ_CAIRO_GFX
#include "nsDrawingSurfaceMac.h"
#endif
#endif
static NS_DEFINE_IID(kBlenderCID, NS_BLENDER_CID);
/* Maximum depth of save() which has style information saved */
#define STYLE_STACK_DEPTH 50
#define STYLE_CURRENT_STACK ((mSaveCount<STYLE_STACK_DEPTH)?mSaveCount:STYLE_STACK_DEPTH-1)
static PRBool CheckSaneImageSize (PRInt32 width, PRInt32 height);
static PRBool CheckSaneSubrectSize (PRInt32 x, PRInt32 y, PRInt32 w, PRInt32 h, PRInt32 realWidth, PRInt32 realHeight);
/* Float validation stuff */
#define VALIDATE(_f) if (!JSDOUBLE_IS_FINITE(_f)) return PR_FALSE
/* These must take doubles as args, because JSDOUBLE_IS_FINITE expects
* to take the address of its argument; we can't cast/convert in the
* macro.
*/
static PRBool FloatValidate (double f1) {
VALIDATE(f1);
return PR_TRUE;
}
static PRBool FloatValidate (double f1, double f2) {
VALIDATE(f1); VALIDATE(f2);
return PR_TRUE;
}
static PRBool FloatValidate (double f1, double f2, double f3) {
VALIDATE(f1); VALIDATE(f2); VALIDATE(f3);
return PR_TRUE;
}
static PRBool FloatValidate (double f1, double f2, double f3, double f4) {
VALIDATE(f1); VALIDATE(f2); VALIDATE(f3); VALIDATE(f4);
return PR_TRUE;
}
static PRBool FloatValidate (double f1, double f2, double f3, double f4, double f5) {
VALIDATE(f1); VALIDATE(f2); VALIDATE(f3); VALIDATE(f4); VALIDATE(f5);
return PR_TRUE;
}
static PRBool FloatValidate (double f1, double f2, double f3, double f4, double f5, double f6) {
VALIDATE(f1); VALIDATE(f2); VALIDATE(f3); VALIDATE(f4); VALIDATE(f5); VALIDATE(f6);
return PR_TRUE;
}
#undef VALIDATE
/**
** nsCanvasGradient
**/
#define NS_CANVASGRADIENT_PRIVATE_IID \
{ 0x491d39d8, 0x4058, 0x42bd, { 0xac, 0x76, 0x70, 0xd5, 0x62, 0x7f, 0x02, 0x10 } }
class nsCanvasGradient : public nsIDOMCanvasGradient
{
public:
#ifdef MOZILLA_1_8_BRANCH
NS_DEFINE_STATIC_IID_ACCESSOR(NS_CANVASGRADIENT_PRIVATE_IID)
#else
NS_DECLARE_STATIC_IID_ACCESSOR(NS_CANVASGRADIENT_PRIVATE_IID)
#endif
nsCanvasGradient(cairo_pattern_t *cpat, nsICSSParser *cssparser)
: mPattern(cpat), mCSSParser(cssparser)
{
}
~nsCanvasGradient() {
if (mPattern)
cairo_pattern_destroy(mPattern);
}
void Apply(cairo_t *cairo) {
cairo_set_source(cairo, mPattern);
}
/* nsIDOMCanvasGradient */
NS_IMETHOD AddColorStop (float offset,
const nsAString& colorstr)
{
nscolor color;
if (!FloatValidate(offset))
return NS_ERROR_DOM_SYNTAX_ERR;
if (offset < 0.0 || offset > 1.0)
return NS_ERROR_DOM_INDEX_SIZE_ERR;
nsresult rv = mCSSParser->ParseColorString(nsString(colorstr), nsnull, 0, PR_TRUE, &color);
if (NS_FAILED(rv))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_pattern_add_color_stop_rgba (mPattern, (double) offset,
NS_GET_R(color) / 255.0,
NS_GET_G(color) / 255.0,
NS_GET_B(color) / 255.0,
NS_GET_A(color) / 255.0);
return NS_OK;
}
NS_DECL_ISUPPORTS
protected:
cairo_pattern_t *mPattern;
nsCOMPtr<nsICSSParser> mCSSParser;
};
#ifndef MOZILLA_1_8_BRANCH
NS_DEFINE_STATIC_IID_ACCESSOR(nsCanvasGradient, NS_CANVASGRADIENT_PRIVATE_IID)
#endif
NS_IMPL_ADDREF(nsCanvasGradient)
NS_IMPL_RELEASE(nsCanvasGradient)
NS_INTERFACE_MAP_BEGIN(nsCanvasGradient)
NS_INTERFACE_MAP_ENTRY(nsCanvasGradient)
NS_INTERFACE_MAP_ENTRY(nsIDOMCanvasGradient)
NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(CanvasGradient)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
/**
** nsCanvasPattern
**/
#define NS_CANVASPATTERN_PRIVATE_IID \
{ 0xb85c6c8a, 0x0624, 0x4530, { 0xb8, 0xee, 0xff, 0xdf, 0x42, 0xe8, 0x21, 0x6d } }
class nsCanvasPattern : public nsIDOMCanvasPattern
{
public:
#ifdef MOZILLA_1_8_BRANCH
NS_DEFINE_STATIC_IID_ACCESSOR(NS_CANVASPATTERN_PRIVATE_IID)
#else
NS_DECLARE_STATIC_IID_ACCESSOR(NS_CANVASPATTERN_PRIVATE_IID)
#endif
nsCanvasPattern(cairo_pattern_t *cpat, PRUint8 *dataToFree,
nsIURI* URIForSecurityCheck, PRBool forceWriteOnly)
: mPattern(cpat), mData(dataToFree), mURI(URIForSecurityCheck), mForceWriteOnly(forceWriteOnly)
{ }
~nsCanvasPattern() {
if (mPattern)
cairo_pattern_destroy(mPattern);
if (mData)
nsMemory::Free(mData);
}
void Apply(cairo_t *cairo) {
cairo_set_source(cairo, mPattern);
}
nsIURI* GetURI() { return mURI; }
PRBool GetForceWriteOnly() { return mForceWriteOnly; }
NS_DECL_ISUPPORTS
protected:
cairo_pattern_t *mPattern;
PRUint8 *mData;
nsCOMPtr<nsIURI> mURI;
PRPackedBool mForceWriteOnly;
};
#ifndef MOZILLA_1_8_BRANCH
NS_DEFINE_STATIC_IID_ACCESSOR(nsCanvasPattern, NS_CANVASPATTERN_PRIVATE_IID)
#endif
NS_IMPL_ADDREF(nsCanvasPattern)
NS_IMPL_RELEASE(nsCanvasPattern)
NS_INTERFACE_MAP_BEGIN(nsCanvasPattern)
NS_INTERFACE_MAP_ENTRY(nsCanvasPattern)
NS_INTERFACE_MAP_ENTRY(nsIDOMCanvasPattern)
NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(CanvasPattern)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
/**
** nsCanvasRenderingContext2D
**/
class nsCanvasRenderingContext2D :
public nsIDOMCanvasRenderingContext2D,
public nsICanvasRenderingContextInternal
{
public:
nsCanvasRenderingContext2D();
virtual ~nsCanvasRenderingContext2D();
nsresult Redraw();
void SetCairoColor(nscolor c);
// nsICanvasRenderingContextInternal
NS_IMETHOD SetCanvasElement(nsICanvasElement* aParentCanvas);
NS_IMETHOD SetDimensions(PRInt32 width, PRInt32 height);
NS_IMETHOD Render(nsIRenderingContext *rc);
NS_IMETHOD RenderToSurface(cairo_surface_t *surf);
NS_IMETHOD GetInputStream(const nsACString& aMimeType,
const nsAString& aEncoderOptions,
nsIInputStream **aStream);
// nsISupports interface
NS_DECL_ISUPPORTS
// nsIDOMCanvasRenderingContext2D interface
NS_DECL_NSIDOMCANVASRENDERINGCONTEXT2D
protected:
// destroy cairo/image stuff, in preparation for possibly recreating
void Destroy();
nsIFrame *GetCanvasLayoutFrame();
// Some helpers. Doesn't modify acolor on failure.
enum {
STYLE_STROKE = 0,
STYLE_FILL,
STYLE_SHADOW
//STYLE_MAX
};
// VC6 sucks
#define STYLE_MAX 3
nsresult SetStyleFromVariant(nsIVariant* aStyle, PRInt32 aWhichStyle);
void StyleColorToString(const nscolor& aColor, nsAString& aStr);
void DirtyAllStyles();
void ApplyStyle(PRInt32 aWhichStyle);
// If aURI has a different origin than the current script, then
// we make the canvas write-only so bad guys can't extract the pixel
// data. If forceWriteOnly is set, we force write only to be set
// and ignore aURI. (This is used for when the original data came
// from a <canvas> that had write-only set.)
void DoDrawImageSecurityCheck(nsIURI* aURI, PRBool forceWriteOnly);
// Member vars
PRInt32 mWidth, mHeight;
// the canvas element informs us when it's going away,
// so these are not nsCOMPtrs
nsICanvasElement* mCanvasElement;
// our CSS parser, for colors and whatnot
nsCOMPtr<nsICSSParser> mCSSParser;
// yay cairo
#ifdef MOZ_CAIRO_GFX
nsRefPtr<gfxContext> mThebesContext;
nsRefPtr<gfxASurface> mThebesSurface;
#endif
PRUint32 mSaveCount;
cairo_t *mCairo;
cairo_surface_t *mSurface;
// only non-null if mSurface is an image surface
PRUint8 *mImageSurfaceData;
// style handling
PRInt32 mLastStyle;
PRPackedBool mDirtyStyle[STYLE_MAX];
// state stack handling
class ContextState {
public:
ContextState() : globalAlpha(1.0) { }
ContextState(const ContextState& other)
: globalAlpha(other.globalAlpha)
{
for (int i = 0; i < STYLE_MAX; i++) {
colorStyles[i] = other.colorStyles[i];
gradientStyles[i] = other.gradientStyles[i];
patternStyles[i] = other.patternStyles[i];
}
}
inline void SetColorStyle(int whichStyle, nscolor color) {
colorStyles[whichStyle] = color;
gradientStyles[whichStyle] = nsnull;
patternStyles[whichStyle] = nsnull;
}
inline void SetPatternStyle(int whichStyle, nsCanvasPattern* pat) {
gradientStyles[whichStyle] = nsnull;
patternStyles[whichStyle] = pat;
}
inline void SetGradientStyle(int whichStyle, nsCanvasGradient* grad) {
gradientStyles[whichStyle] = grad;
patternStyles[whichStyle] = nsnull;
}
float globalAlpha;
nscolor colorStyles[STYLE_MAX];
nsCOMPtr<nsCanvasGradient> gradientStyles[STYLE_MAX];
nsCOMPtr<nsCanvasPattern> patternStyles[STYLE_MAX];
};
nsTArray<ContextState> mStyleStack;
inline ContextState& CurrentState() {
return mStyleStack[mSaveCount];
}
#ifdef MOZ_WIDGET_GTK2
Pixmap mSurfacePixmap;
#endif
// stolen from nsJSUtils
static PRBool ConvertJSValToUint32(PRUint32* aProp, JSContext* aContext,
jsval aValue);
static PRBool ConvertJSValToXPCObject(nsISupports** aSupports, REFNSIID aIID,
JSContext* aContext, jsval aValue);
static PRBool ConvertJSValToDouble(double* aProp, JSContext* aContext,
jsval aValue);
// cairo helpers
nsresult CairoSurfaceFromElement(nsIDOMElement *imgElt,
cairo_surface_t **aCairoSurface,
PRUint8 **imgDataOut,
PRInt32 *widthOut, PRInt32 *heightOut,
nsIURI **uriOut, PRBool *forceWriteOnlyOut);
nsresult DrawNativeSurfaces(nsIDrawingSurface* aBlackSurface,
nsIDrawingSurface* aWhiteSurface,
const nsIntSize& aSurfaceSize,
nsIRenderingContext* aBlackContext);
};
NS_IMPL_ADDREF(nsCanvasRenderingContext2D)
NS_IMPL_RELEASE(nsCanvasRenderingContext2D)
NS_INTERFACE_MAP_BEGIN(nsCanvasRenderingContext2D)
NS_INTERFACE_MAP_ENTRY(nsIDOMCanvasRenderingContext2D)
NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMCanvasRenderingContext2D)
NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(CanvasRenderingContext2D)
NS_INTERFACE_MAP_END
/**
** CanvasRenderingContext2D impl
**/
nsresult
NS_NewCanvasRenderingContext2D(nsIDOMCanvasRenderingContext2D** aResult)
{
nsIDOMCanvasRenderingContext2D* ctx = new nsCanvasRenderingContext2D();
if (!ctx)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*aResult = ctx);
return NS_OK;
}
nsCanvasRenderingContext2D::nsCanvasRenderingContext2D()
: mCanvasElement(nsnull),
mSaveCount(0), mCairo(nsnull), mSurface(nsnull), mImageSurfaceData(nsnull), mStyleStack(20)
{
#ifdef MOZ_WIDGET_GTK2
mSurfacePixmap = None;
#endif
}
nsCanvasRenderingContext2D::~nsCanvasRenderingContext2D()
{
Destroy();
}
nsIFrame*
nsCanvasRenderingContext2D::GetCanvasLayoutFrame()
{
if (!mCanvasElement)
return nsnull;
nsIFrame *fr = nsnull;
mCanvasElement->GetPrimaryCanvasFrame(&fr);
return fr;
}
void
nsCanvasRenderingContext2D::Destroy()
{
if (mCairo) {
cairo_destroy(mCairo);
mCairo = nsnull;
}
if (mSurface) {
cairo_surface_destroy(mSurface);
mSurface = nsnull;
}
#ifdef MOZ_WIDGET_GTK2
if (mSurfacePixmap != None) {
XFreePixmap(GDK_DISPLAY(), mSurfacePixmap);
mSurfacePixmap = None;
}
#endif
if (mImageSurfaceData) {
PR_Free (mImageSurfaceData);
mImageSurfaceData = nsnull;
}
}
nsresult
nsCanvasRenderingContext2D::SetStyleFromVariant(nsIVariant* aStyle, PRInt32 aWhichStyle)
{
nsresult rv;
nscolor color;
PRUint16 paramType;
rv = aStyle->GetDataType(&paramType);
NS_ENSURE_SUCCESS(rv, rv);
if (paramType == nsIDataType::VTYPE_DOMSTRING ||
paramType == nsIDataType::VTYPE_WSTRING_SIZE_IS) {
nsAutoString str;
if (paramType == nsIDataType::VTYPE_DOMSTRING) {
rv = aStyle->GetAsDOMString(str);
} else {
rv = aStyle->GetAsAString(str);
}
NS_ENSURE_SUCCESS(rv, rv);
rv = mCSSParser->ParseColorString(str, nsnull, 0, PR_TRUE, &color);
if (NS_FAILED(rv)) {
// Error reporting happens inside the CSS parser
return NS_OK;
}
CurrentState().SetColorStyle(aWhichStyle, color);
mDirtyStyle[aWhichStyle] = PR_TRUE;
return NS_OK;
} else if (paramType == nsIDataType::VTYPE_INTERFACE ||
paramType == nsIDataType::VTYPE_INTERFACE_IS)
{
nsID *iid;
nsCOMPtr<nsISupports> iface;
rv = aStyle->GetAsInterface(&iid, getter_AddRefs(iface));
nsCOMPtr<nsCanvasGradient> grad(do_QueryInterface(iface));
if (grad) {
CurrentState().SetGradientStyle(aWhichStyle, grad);
mDirtyStyle[aWhichStyle] = PR_TRUE;
return NS_OK;
}
nsCOMPtr<nsCanvasPattern> pattern(do_QueryInterface(iface));
if (pattern) {
CurrentState().SetPatternStyle(aWhichStyle, pattern);
mDirtyStyle[aWhichStyle] = PR_TRUE;
return NS_OK;
}
}
nsContentUtils::ReportToConsole(
nsContentUtils::eDOM_PROPERTIES,
"UnexpectedCanvasVariantStyle",
nsnull, 0,
nsnull,
EmptyString(), 0, 0,
nsIScriptError::warningFlag,
"Canvas");
return NS_OK;
}
void
nsCanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, nsAString& aStr)
{
if (NS_GET_A(aColor) == 255) {
CopyUTF8toUTF16(nsPrintfCString(100, "#%02x%02x%02x",
NS_GET_R(aColor),
NS_GET_G(aColor),
NS_GET_B(aColor)),
aStr);
} else {
// "%0.5f" in nsPrintfCString would use the locale-specific
// decimal separator. That's why we have to do this:
PRUint32 alpha = NS_GET_A(aColor) * 100000 / 255;
CopyUTF8toUTF16(nsPrintfCString(100, "rgba(%d, %d, %d, 0.%d)",
NS_GET_R(aColor),
NS_GET_G(aColor),
NS_GET_B(aColor),
alpha),
aStr);
}
}
void
nsCanvasRenderingContext2D::DirtyAllStyles()
{
for (int i = 0; i < STYLE_MAX; i++) {
mDirtyStyle[i] = PR_TRUE;
}
}
void
nsCanvasRenderingContext2D::DoDrawImageSecurityCheck(nsIURI* aURI, PRBool forceWriteOnly)
{
if (mCanvasElement->IsWriteOnly())
return;
// If we explicitly set WriteOnly just do it and get out
if (forceWriteOnly) {
mCanvasElement->SetWriteOnly();
return;
}
// Further security checks require a URI. If we don't have one the image
// must be another canvas and we inherit the forceWriteOnly state
if (!aURI)
return;
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
#ifdef MOZILLA_1_8_BRANCH
nsCOMPtr<nsIDOMNode> elem = do_QueryInterface(mCanvasElement);
if (elem && ssm) {
nsCOMPtr<nsIPrincipal> elemPrincipal;
nsCOMPtr<nsIPrincipal> uriPrincipal;
nsCOMPtr<nsIDocument> elemDocument;
nsContentUtils::GetDocumentAndPrincipal(elem, getter_AddRefs(elemDocument), getter_AddRefs(elemPrincipal));
ssm->GetCodebasePrincipal(aURI, getter_AddRefs(uriPrincipal));
if (uriPrincipal && elemPrincipal) {
nsresult rv =
ssm->CheckSameOriginPrincipal(elemPrincipal, uriPrincipal);
if (NS_SUCCEEDED(rv)) {
// Same origin
return;
}
}
}
#else
nsCOMPtr<nsINode> elem = do_QueryInterface(mCanvasElement);
if (elem && ssm) {
nsCOMPtr<nsIPrincipal> uriPrincipal;
ssm->GetCodebasePrincipal(aURI, getter_AddRefs(uriPrincipal));
if (uriPrincipal) {
nsresult rv = ssm->CheckSameOriginPrincipal(elem->NodePrincipal(),
uriPrincipal);
if (NS_SUCCEEDED(rv)) {
// Same origin
return;
}
}
}
#endif
mCanvasElement->SetWriteOnly();
}
void
nsCanvasRenderingContext2D::ApplyStyle(PRInt32 aWhichStyle)
{
if (mLastStyle == aWhichStyle &&
!mDirtyStyle[aWhichStyle])
{
// nothing to do, this is already the set style
return;
}
mDirtyStyle[aWhichStyle] = PR_FALSE;
mLastStyle = aWhichStyle;
nsCanvasPattern* pattern = CurrentState().patternStyles[aWhichStyle];
if (pattern) {
DoDrawImageSecurityCheck(pattern->GetURI(), pattern->GetForceWriteOnly());
pattern->Apply(mCairo);
return;
}
if (CurrentState().gradientStyles[aWhichStyle]) {
CurrentState().gradientStyles[aWhichStyle]->Apply(mCairo);
return;
}
SetCairoColor(CurrentState().colorStyles[aWhichStyle]);
}
nsresult
nsCanvasRenderingContext2D::Redraw()
{
nsIFrame *frame = GetCanvasLayoutFrame();
if (frame) {
nsRect r = frame->GetRect();
r.x = r.y = 0;
frame->Invalidate(r, PR_FALSE);
}
return NS_OK;
}
void
nsCanvasRenderingContext2D::SetCairoColor(nscolor c)
{
double r = double(NS_GET_R(c) / 255.0);
double g = double(NS_GET_G(c) / 255.0);
double b = double(NS_GET_B(c) / 255.0);
double a = double(NS_GET_A(c) / 255.0) * CurrentState().globalAlpha;
cairo_set_source_rgba (mCairo, r, g, b, a);
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::SetDimensions(PRInt32 width, PRInt32 height)
{
Destroy();
// Check that the dimensions are sane
if (!CheckSaneImageSize(width, height))
return NS_ERROR_FAILURE;
mWidth = width;
mHeight = height;
#ifdef MOZ_CAIRO_GFX
mThebesSurface = gfxPlatform::GetPlatform()->CreateOffscreenSurface(gfxIntSize(width, height), gfxASurface::ImageFormatARGB32);
mThebesContext = new gfxContext(mThebesSurface);
mSurface = mThebesSurface->CairoSurface();
cairo_surface_reference(mSurface);
mCairo = mThebesContext->GetCairo();
cairo_reference(mCairo);
#else
// non-cairo gfx
#ifdef XP_WIN
#ifndef MOZILLA_1_8_BRANCH
mSurface = cairo_win32_surface_create_with_dib (CAIRO_FORMAT_ARGB32,
mWidth, mHeight);
#else
mSurface = _cairo_win32_surface_create_dib (CAIRO_FORMAT_ARGB32,
mWidth, mHeight);
#endif
#elif MOZ_WIDGET_GTK2
// On most current X servers, using the software-only surface
// actually provides a much smoother and faster display.
// However, we provide MOZ_CANVAS_USE_RENDER for whomever wants to
// go that route.
if (getenv("MOZ_CANVAS_USE_RENDER")) {
XRenderPictFormat *fmt = XRenderFindStandardFormat (GDK_DISPLAY(),
PictStandardARGB32);
if (fmt) {
int npfmts = 0;
XPixmapFormatValues *pfmts = XListPixmapFormats(GDK_DISPLAY(), &npfmts);
for (int i = 0; i < npfmts; i++) {
if (pfmts[i].depth == 32) {
npfmts = -1;
break;
}
}
XFree(pfmts);
if (npfmts == -1) {
mSurfacePixmap = XCreatePixmap (GDK_DISPLAY(),
DefaultRootWindow(GDK_DISPLAY()),
width, height, 32);
mSurface = cairo_xlib_surface_create_with_xrender_format
(GDK_DISPLAY(), mSurfacePixmap, DefaultScreenOfDisplay(GDK_DISPLAY()),
fmt, mWidth, mHeight);
}
}
}
#endif
// fall back to image surface
if (!mSurface) {
mImageSurfaceData = (PRUint8*) PR_Malloc (mWidth * mHeight * 4);
if (!mImageSurfaceData)
return NS_ERROR_OUT_OF_MEMORY;
mSurface = cairo_image_surface_create_for_data (mImageSurfaceData,
CAIRO_FORMAT_ARGB32,
mWidth, mHeight,
mWidth * 4);
}
mCairo = cairo_create(mSurface);
#endif
// set up the initial canvas defaults
mStyleStack.Clear();
mSaveCount = 0;
ContextState *state = mStyleStack.AppendElement();
state->globalAlpha = 1.0;
for (int i = 0; i < STYLE_MAX; i++)
state->colorStyles[i] = NS_RGB(0,0,0);
mLastStyle = -1;
DirtyAllStyles();
cairo_set_operator(mCairo, CAIRO_OPERATOR_CLEAR);
cairo_new_path(mCairo);
cairo_rectangle(mCairo, 0, 0, mWidth, mHeight);
cairo_fill(mCairo);
cairo_set_line_width(mCairo, 1.0);
cairo_set_operator(mCairo, CAIRO_OPERATOR_OVER);
cairo_set_miter_limit(mCairo, 10.0);
cairo_set_line_cap(mCairo, CAIRO_LINE_CAP_BUTT);
cairo_set_line_join(mCairo, CAIRO_LINE_JOIN_MITER);
cairo_new_path(mCairo);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::Render(nsIRenderingContext *rc)
{
nsresult rv = NS_OK;
#ifdef MOZ_CAIRO_GFX
gfxContext* ctx = (gfxContext*) rc->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT);
nsRefPtr<gfxPattern> pat = new gfxPattern(mThebesSurface);
// XXX I don't want to use PixelSnapped here, but layout doesn't guarantee
// pixel alignment for this stuff!
ctx->NewPath();
ctx->PixelSnappedRectangleAndSetPattern(gfxRect(0, 0, mWidth, mHeight), pat);
ctx->Fill();
#else
// non-Thebes; this becomes exciting
cairo_surface_t *dest = nsnull;
cairo_t *dest_cr = nsnull;
#ifdef XP_WIN
void *ptr = nsnull;
#ifdef MOZILLA_1_8_BRANCH
rv = rc->RetrieveCurrentNativeGraphicData(&ptr);
if (NS_FAILED(rv) || !ptr)
return NS_ERROR_FAILURE;
#else
ptr = rc->GetNativeGraphicData(nsIRenderingContext::NATIVE_WINDOWS_DC);
#endif
HDC dc = (HDC) ptr;
dest = cairo_win32_surface_create (dc);
dest_cr = cairo_create (dest);
#endif
#ifdef XP_OS2
void *ptr = nsnull;
#ifdef MOZILLA_1_8_BRANCH
rv = rc->RetrieveCurrentNativeGraphicData(&ptr);
if (NS_FAILED(rv) || !ptr)
return NS_ERROR_FAILURE;
#else
/* OS/2 also uses NATIVE_WINDOWS_DC to get a native OS/2 PS */
ptr = rc->GetNativeGraphicData(nsIRenderingContext::NATIVE_WINDOWS_DC);
#endif
HPS hps = (HPS)ptr;
nsDrawingSurfaceOS2 *surface; /* to get the dimensions from this */
PRUint32 width, height;
rc->GetDrawingSurface((nsIDrawingSurface**)&surface);
surface->GetDimensions(&width, &height);
dest = cairo_os2_surface_create(hps, width, height);
cairo_surface_mark_dirty(dest); // needed on OS/2 for initialization
dest_cr = cairo_create(dest);
#endif
#ifdef MOZ_WIDGET_GTK2
GdkDrawable *gdkdraw = nsnull;
#ifdef MOZILLA_1_8_BRANCH
rv = rc->RetrieveCurrentNativeGraphicData((void**) &gdkdraw);
if (NS_FAILED(rv) || !gdkdraw)
return NS_ERROR_FAILURE;
#else
gdkdraw = (GdkDrawable*) rc->GetNativeGraphicData(nsIRenderingContext::NATIVE_GDK_DRAWABLE);
if (!gdkdraw)
return NS_ERROR_FAILURE;
#endif
gint w, h;
gdk_drawable_get_size (gdkdraw, &w, &h);
dest = cairo_xlib_surface_create (GDK_DRAWABLE_XDISPLAY(gdkdraw),
GDK_DRAWABLE_XID(gdkdraw),
GDK_VISUAL_XVISUAL(gdk_drawable_get_visual(gdkdraw)),
w, h);
dest_cr = cairo_create (dest);
#endif
nsTransform2D *tx = nsnull;
rc->GetCurrentTransform(tx);
nsCOMPtr<nsIDeviceContext> dctx;
rc->GetDeviceContext(*getter_AddRefs(dctx));
// Until we can use the quartz2 surface, mac will be different,
// since we'll use CG to render.
#ifndef XP_MACOSX
float x0 = 0.0, y0 = 0.0;
float sx = 1.0, sy = 1.0;
if (tx->GetType() & MG_2DTRANSLATION) {
tx->Transform(&x0, &y0);
}
if (tx->GetType() & MG_2DSCALE) {
sx = sy = dctx->DevUnitsToTwips();
tx->TransformNoXLate(&sx, &sy);
}
cairo_translate (dest_cr, NSToIntRound(x0), NSToIntRound(y0));
if (sx != 1.0 || sy != 1.0)
cairo_scale (dest_cr, sx, sy);
cairo_rectangle (dest_cr, 0, 0, mWidth, mHeight);
cairo_clip (dest_cr);
cairo_set_source_surface (dest_cr, mSurface, 0, 0);
cairo_paint (dest_cr);
if (dest_cr)
cairo_destroy (dest_cr);
if (dest)
cairo_surface_destroy (dest);
#else
// OSX path
nsIDrawingSurface *ds = nsnull;
rc->GetDrawingSurface(&ds);
if (!ds)
return NS_ERROR_FAILURE;
nsDrawingSurfaceMac *macds = NS_STATIC_CAST(nsDrawingSurfaceMac*, ds);
CGContextRef cgc = macds->StartQuartzDrawing();
CGDataProviderRef dataProvider;
CGImageRef img;
dataProvider = CGDataProviderCreateWithData (NULL, mImageSurfaceData,
mWidth * mHeight * 4,
NULL);
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
img = CGImageCreate (mWidth, mHeight, 8, 32, mWidth * 4, rgb,
(CGImageAlphaInfo)(kCGImageAlphaPremultipliedFirst | CG_BITMAP_BYTE_ORDER_FLAG),
dataProvider, NULL, false, kCGRenderingIntentDefault);
CGColorSpaceRelease (rgb);
CGDataProviderRelease (dataProvider);
float x0 = 0.0, y0 = 0.0;
float sx = 1.0, sy = 1.0;
if (tx->GetType() & MG_2DTRANSLATION) {
tx->Transform(&x0, &y0);
}
if (tx->GetType() & MG_2DSCALE) {
float p2t = dctx->DevUnitsToTwips();
sx = p2t, sy = p2t;
tx->TransformNoXLate(&sx, &sy);
}
CGContextTranslateCTM (cgc, NSToIntRound(x0), NSToIntRound(y0));
if (sx != 1.0 || sy != 1.0)
CGContextScaleCTM (cgc, sx, sy);
// flip, so that the image gets drawn correct-side up
CGContextScaleCTM (cgc, 1.0, -1.0);
CGContextDrawImage (cgc, CGRectMake(0, -mHeight, mWidth, mHeight), img);
CGImageRelease (img);
macds->EndQuartzDrawing(cgc);
rv = NS_OK;
#endif
#endif
return rv;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::RenderToSurface(cairo_surface_t *surf)
{
cairo_t *cr = cairo_create (surf);
cairo_set_source_surface (cr, mSurface, 0, 0);
cairo_paint (cr);
cairo_destroy (cr);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::GetInputStream(const nsACString& aMimeType,
const nsAString& aEncoderOptions,
nsIInputStream **aStream)
{
nsCString conid(NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type="));
conid += aMimeType;
nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(conid.get());
if (!encoder)
return NS_ERROR_FAILURE;
if (mImageSurfaceData) {
encoder->InitFromData(mImageSurfaceData,
mWidth * mHeight * 4, mWidth, mHeight, mWidth * 4,
imgIEncoder::INPUT_FORMAT_HOSTARGB,
aEncoderOptions);
} else {
nsAutoArrayPtr<PRUint8> imageBuffer(new PRUint8[mWidth * mHeight * 4]);
if (!imageBuffer)
return NS_ERROR_FAILURE;
cairo_surface_t *imgsurf = cairo_image_surface_create_for_data (imageBuffer.get(),
CAIRO_FORMAT_ARGB32,
mWidth, mHeight, mWidth * 4);
if (!imgsurf || cairo_surface_status(imgsurf))
return NS_ERROR_FAILURE;
cairo_t *cr = cairo_create(imgsurf);
cairo_surface_destroy (imgsurf);
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_surface (cr, mSurface, 0, 0);
cairo_paint (cr);
cairo_destroy (cr);
encoder->InitFromData(imageBuffer.get(),
mWidth * mHeight * 4, mWidth, mHeight, mWidth * 4,
imgIEncoder::INPUT_FORMAT_HOSTARGB,
aEncoderOptions);
}
return CallQueryInterface(encoder, aStream);
}
//
// nsCanvasRenderingContext2D impl
//
NS_IMETHODIMP
nsCanvasRenderingContext2D::SetCanvasElement(nsICanvasElement* aCanvasElement)
{
// don't hold a ref to this!
mCanvasElement = aCanvasElement;
// set up our css parser, if necessary
if (!mCSSParser) {
mCSSParser = do_CreateInstance("@mozilla.org/content/css-parser;1");
}
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::GetCanvas(nsIDOMHTMLCanvasElement **canvas)
{
if (mCanvasElement == nsnull) {
*canvas = nsnull;
return NS_OK;
}
return CallQueryInterface(mCanvasElement, canvas);
}
//
// state
//
NS_IMETHODIMP
nsCanvasRenderingContext2D::Save()
{
ContextState state = CurrentState();
mStyleStack.AppendElement(state);
cairo_save (mCairo);
mSaveCount++;
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::Restore()
{
if (mSaveCount <= 0)
return NS_ERROR_DOM_INVALID_STATE_ERR;
mStyleStack.RemoveElementAt(mSaveCount);
cairo_restore (mCairo);
mLastStyle = -1;
DirtyAllStyles();
mSaveCount--;
return NS_OK;
}
//
// transformations
//
NS_IMETHODIMP
nsCanvasRenderingContext2D::Scale(float x, float y)
{
if (!FloatValidate(x,y))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_scale (mCairo, x, y);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::Rotate(float angle)
{
if (!FloatValidate(angle))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_rotate (mCairo, angle);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::Translate(float x, float y)
{
if (!FloatValidate(x,y))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_translate (mCairo, x, y);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::Transform(float m11, float m12, float m21, float m22, float dx, float dy)
{
if (!FloatValidate(m11,m12,m21,m22,dx,dy))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_matrix_t mat;
cairo_matrix_init (&mat, m11, m12, m21, m22, dx, dy);
cairo_transform (mCairo, &mat);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::SetTransform(float m11, float m12, float m21, float m22, float dx, float dy)
{
if (!FloatValidate(m11,m12,m21,m22,dx,dy))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_matrix_t mat;
cairo_matrix_init (&mat, m11, m12, m21, m22, dx, dy);
cairo_set_matrix (mCairo, &mat);
return NS_OK;
}
//
// colors
//
NS_IMETHODIMP
nsCanvasRenderingContext2D::SetGlobalAlpha(float aGlobalAlpha)
{
if (!FloatValidate(aGlobalAlpha))
return NS_ERROR_DOM_SYNTAX_ERR;
// ignore invalid values, as per spec
if (aGlobalAlpha < 0.0 || aGlobalAlpha > 1.0)
return NS_OK;
CurrentState().globalAlpha = aGlobalAlpha;
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::GetGlobalAlpha(float *aGlobalAlpha)
{
*aGlobalAlpha = CurrentState().globalAlpha;
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::SetStrokeStyle(nsIVariant* aStyle)
{
return SetStyleFromVariant(aStyle, STYLE_STROKE);
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::GetStrokeStyle(nsIVariant** aStyle)
{
nsresult rv;
nsCOMPtr<nsIWritableVariant> var = do_CreateInstance("@mozilla.org/variant;1");
if (!var)
return NS_ERROR_FAILURE;
rv = var->SetWritable(PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
if (CurrentState().patternStyles[STYLE_STROKE]) {
rv = var->SetAsISupports(CurrentState().patternStyles[STYLE_STROKE]);
NS_ENSURE_SUCCESS(rv, rv);
} else if (CurrentState().gradientStyles[STYLE_STROKE]) {
rv = var->SetAsISupports(CurrentState().gradientStyles[STYLE_STROKE]);
NS_ENSURE_SUCCESS(rv, rv);
} else {
nsString styleStr;
StyleColorToString(CurrentState().colorStyles[STYLE_STROKE], styleStr);
rv = var->SetAsDOMString(styleStr);
NS_ENSURE_SUCCESS(rv, rv);
}
NS_ADDREF(*aStyle = var);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::SetFillStyle(nsIVariant* aStyle)
{
return SetStyleFromVariant(aStyle, STYLE_FILL);
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::GetFillStyle(nsIVariant** aStyle)
{
nsresult rv;
nsCOMPtr<nsIWritableVariant> var = do_CreateInstance("@mozilla.org/variant;1");
if (!var)
return NS_ERROR_FAILURE;
rv = var->SetWritable(PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
if (CurrentState().patternStyles[STYLE_FILL]) {
rv = var->SetAsISupports(CurrentState().patternStyles[STYLE_FILL]);
NS_ENSURE_SUCCESS(rv, rv);
} else if (CurrentState().gradientStyles[STYLE_FILL]) {
rv = var->SetAsISupports(CurrentState().gradientStyles[STYLE_FILL]);
NS_ENSURE_SUCCESS(rv, rv);
} else {
nsString styleStr;
StyleColorToString(CurrentState().colorStyles[STYLE_FILL], styleStr);
rv = var->SetAsDOMString(styleStr);
NS_ENSURE_SUCCESS(rv, rv);
}
NS_ADDREF(*aStyle = var);
return NS_OK;
}
//
// gradients and patterns
//
NS_IMETHODIMP
nsCanvasRenderingContext2D::CreateLinearGradient(float x0, float y0, float x1, float y1,
nsIDOMCanvasGradient **_retval)
{
if (!FloatValidate(x0,y0,x1,y1))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_pattern_t *gradpat = nsnull;
gradpat = cairo_pattern_create_linear ((double) x0, (double) y0, (double) x1, (double) y1);
nsCanvasGradient *grad = new nsCanvasGradient(gradpat, mCSSParser);
if (!grad) {
cairo_pattern_destroy(gradpat);
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ADDREF(*_retval = grad);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::CreateRadialGradient(float x0, float y0, float r0, float x1, float y1, float r1,
nsIDOMCanvasGradient **_retval)
{
if (!FloatValidate(x0,y0,r0,x1,y1,r1))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_pattern_t *gradpat = nsnull;
gradpat = cairo_pattern_create_radial ((double) x0, (double) y0, (double) r0,
(double) x1, (double) y1, (double) r1);
nsCanvasGradient *grad = new nsCanvasGradient(gradpat, mCSSParser);
if (!grad) {
cairo_pattern_destroy(gradpat);
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ADDREF(*_retval = grad);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::CreatePattern(nsIDOMHTMLElement *image,
const nsAString& repeat,
nsIDOMCanvasPattern **_retval)
{
nsresult rv;
cairo_extend_t extend;
if (repeat.IsEmpty() || repeat.EqualsLiteral("repeat")) {
extend = CAIRO_EXTEND_REPEAT;
} else if (repeat.EqualsLiteral("repeat-x")) {
// XX
extend = CAIRO_EXTEND_REPEAT;
} else if (repeat.EqualsLiteral("repeat-y")) {
// XX
extend = CAIRO_EXTEND_REPEAT;
} else if (repeat.EqualsLiteral("no-repeat")) {
extend = CAIRO_EXTEND_NONE;
} else {
// XXX ERRMSG we need to report an error to developers here! (bug 329026)
return NS_ERROR_DOM_SYNTAX_ERR;
}
cairo_surface_t *imgSurf = nsnull;
PRUint8 *imgData = nsnull;
PRInt32 imgWidth, imgHeight;
nsCOMPtr<nsIURI> uri;
PRBool forceWriteOnly = PR_FALSE;
rv = CairoSurfaceFromElement(image, &imgSurf, &imgData,
&imgWidth, &imgHeight, getter_AddRefs(uri), &forceWriteOnly);
if (NS_FAILED(rv))
return rv;
cairo_pattern_t *cairopat = cairo_pattern_create_for_surface(imgSurf);
cairo_surface_destroy(imgSurf);
cairo_pattern_set_extend (cairopat, extend);
nsCanvasPattern *pat = new nsCanvasPattern(cairopat, imgData, uri, forceWriteOnly);
if (!pat) {
cairo_pattern_destroy(cairopat);
nsMemory::Free(imgData);
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ADDREF(*_retval = pat);
return NS_OK;
}
//
// shadows
//
NS_IMETHODIMP
nsCanvasRenderingContext2D::SetShadowOffsetX(float x)
{
if (!FloatValidate(x))
return NS_ERROR_DOM_SYNTAX_ERR;
// XXX ERRMSG we need to report an error to developers here! (bug 329026)
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::GetShadowOffsetX(float *x)
{
*x = 0.0f;
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::SetShadowOffsetY(float y)
{
if (!FloatValidate(y))
return NS_ERROR_DOM_SYNTAX_ERR;
// XXX ERRMSG we need to report an error to developers here! (bug 329026)
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::GetShadowOffsetY(float *y)
{
*y = 0.0f;
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::SetShadowBlur(float blur)
{
if (!FloatValidate(blur))
return NS_ERROR_DOM_SYNTAX_ERR;
// XXX ERRMSG we need to report an error to developers here! (bug 329026)
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::GetShadowBlur(float *blur)
{
*blur = 0.0f;
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::SetShadowColor(const nsAString& color)
{
// XXX ERRMSG we need to report an error to developers here! (bug 329026)
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::GetShadowColor(nsAString& color)
{
color.SetIsVoid(PR_TRUE);
return NS_OK;
}
//
// rects
//
NS_IMETHODIMP
nsCanvasRenderingContext2D::ClearRect(float x, float y, float w, float h)
{
if (!FloatValidate(x,y,w,h))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_save (mCairo);
cairo_set_operator (mCairo, CAIRO_OPERATOR_CLEAR);
cairo_new_path (mCairo);
cairo_rectangle (mCairo, x, y, w, h);
cairo_fill (mCairo);
cairo_restore (mCairo);
return Redraw();
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::FillRect(float x, float y, float w, float h)
{
if (!FloatValidate(x,y,w,h))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_new_path (mCairo);
cairo_rectangle (mCairo, x, y, w, h);
ApplyStyle(STYLE_FILL);
cairo_fill (mCairo);
return Redraw();
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::StrokeRect(float x, float y, float w, float h)
{
if (!FloatValidate(x,y,w,h))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_new_path (mCairo);
cairo_rectangle (mCairo, x, y, w, h);
ApplyStyle(STYLE_STROKE);
cairo_stroke (mCairo);
return Redraw();
}
//
// path bits
//
NS_IMETHODIMP
nsCanvasRenderingContext2D::BeginPath()
{
cairo_new_path(mCairo);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::ClosePath()
{
cairo_close_path(mCairo);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::Fill()
{
ApplyStyle(STYLE_FILL);
cairo_fill_preserve(mCairo);
return Redraw();
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::Stroke()
{
ApplyStyle(STYLE_STROKE);
cairo_stroke_preserve(mCairo);
return Redraw();
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::Clip()
{
cairo_clip_preserve(mCairo);
return Redraw();
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::MoveTo(float x, float y)
{
if (!FloatValidate(x,y))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_move_to(mCairo, x, y);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::LineTo(float x, float y)
{
if (!FloatValidate(x,y))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_line_to(mCairo, x, y);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::QuadraticCurveTo(float cpx, float cpy, float x, float y)
{
if (!FloatValidate(cpx,cpy,x,y))
return NS_ERROR_DOM_SYNTAX_ERR;
double cx, cy;
// we will always have a current point, since beginPath forces
// a moveto(0,0)
cairo_get_current_point(mCairo, &cx, &cy);
cairo_curve_to(mCairo,
(cx + cpx * 2.0) / 3.0,
(cy + cpy * 2.0) / 3.0,
(cpx * 2.0 + x) / 3.0,
(cpy * 2.0 + y) / 3.0,
x,
y);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::BezierCurveTo(float cp1x, float cp1y,
float cp2x, float cp2y,
float x, float y)
{
if (!FloatValidate(cp1x,cp1y,cp2x,cp2y,x,y))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_curve_to(mCairo, cp1x, cp1y, cp2x, cp2y, x, y);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::ArcTo(float x1, float y1, float x2, float y2, float radius)
{
if (!FloatValidate(x1,y1,x2,y2,radius))
return NS_ERROR_DOM_SYNTAX_ERR;
if (radius <= 0)
return NS_ERROR_DOM_INDEX_SIZE_ERR;
/* This is an adaptation of the cairo_arc_to patch from Behdad
* Esfahbod; once that patch is accepted, we should remove this
* and just call cairo_arc_to() directly.
*/
double x0, y0;
double angle0, angle1, angle2, angled;
double d0, d2;
double sin_, cos_;
double xc, yc, dc;
int forward;
cairo_get_current_point(mCairo, &x0, &y0);
angle0 = atan2 (y0 - y1, x0 - x1); /* angle from (x1,y1) to (x0,y0) */
angle2 = atan2 (y2 - y1, x2 - x1); /* angle from (x1,y1) to (x2,y2) */
angle1 = (angle0 + angle2) / 2; /* angle from (x1,y1) to (xc,yc) */
angled = angle2 - angle0; /* the angle (x0,y0)--(x1,y1)--(x2,y2) */
/* Shall we go forward or backward? */
if (angled > M_PI || (angled < 0 && angled > -M_PI)) {
angle1 += M_PI;
angled = 2 * M_PI - angled;
forward = 1;
} else {
double tmp;
tmp = angle0;
angle0 = angle2;
angle2 = tmp;
forward = 0;
}
angle0 += M_PI_2; /* angle from (xc,yc) to (x0,y0) */
angle2 -= M_PI_2; /* angle from (xc,yc) to (x2,y2) */
angled /= 2; /* the angle (x0,y0)--(x1,y1)--(xc,yc) */
/* distance from (x1,y1) to (x0,y0) */
d0 = sqrt ((x0-x1)*(x0-x1)+(y0-y1)*(y0-y1));
/* distance from (x2,y2) to (x0,y0) */
d2 = sqrt ((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
dc = -1;
sin_ = sin(angled);
cos_ = cos(angled);
if (fabs(cos_) >= 1e-5) { /* the arc may not fit */
/* min distance of end-points from corner */
double min_d = d0 < d2 ? d0 : d2;
/* max radius of an arc that fits */
double max_r = min_d * sin_ / cos_;
if (radius > max_r) {
/* arc with requested radius doesn't fit */
radius = (float) max_r;
dc = min_d / cos_; /* distance of (xc,yc) from (x1,y1) */
}
}
if (dc < 0)
dc = radius / sin_; /* distance of (xc,yc) from (x1,y1) */
/* find (cx,cy), the center of the arc */
xc = x1 + sin(angle1) * dc;
yc = y1 + cos(angle1) * dc;
/* the arc operation draws the line from current point (x0,y0)
* to arc center too. */
if (forward)
cairo_arc (mCairo, xc, yc, radius, angle0, angle2);
else
cairo_arc_negative (mCairo, xc, yc, radius, angle2, angle0);
cairo_line_to (mCairo, x2, y2);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::Arc(float x, float y, float r, float startAngle, float endAngle, int ccw)
{
if (!FloatValidate(x,y,r,startAngle,endAngle))
return NS_ERROR_DOM_SYNTAX_ERR;
if (ccw)
cairo_arc_negative (mCairo, x, y, r, startAngle, endAngle);
else
cairo_arc (mCairo, x, y, r, startAngle, endAngle);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::Rect(float x, float y, float w, float h)
{
if (!FloatValidate(x,y,w,h))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_rectangle (mCairo, x, y, w, h);
return NS_OK;
}
//
// line caps/joins
//
NS_IMETHODIMP
nsCanvasRenderingContext2D::SetLineWidth(float width)
{
if (!FloatValidate(width))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_set_line_width(mCairo, width);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::GetLineWidth(float *width)
{
double d = cairo_get_line_width(mCairo);
*width = (float) d;
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::SetLineCap(const nsAString& capstyle)
{
cairo_line_cap_t cap;
if (capstyle.EqualsLiteral("butt"))
cap = CAIRO_LINE_CAP_BUTT;
else if (capstyle.EqualsLiteral("round"))
cap = CAIRO_LINE_CAP_ROUND;
else if (capstyle.EqualsLiteral("square"))
cap = CAIRO_LINE_CAP_SQUARE;
else
// XXX ERRMSG we need to report an error to developers here! (bug 329026)
return NS_ERROR_NOT_IMPLEMENTED;
cairo_set_line_cap (mCairo, cap);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::GetLineCap(nsAString& capstyle)
{
cairo_line_cap_t cap = cairo_get_line_cap(mCairo);
if (cap == CAIRO_LINE_CAP_BUTT)
capstyle.AssignLiteral("butt");
else if (cap == CAIRO_LINE_CAP_ROUND)
capstyle.AssignLiteral("round");
else if (cap == CAIRO_LINE_CAP_SQUARE)
capstyle.AssignLiteral("square");
else
return NS_ERROR_FAILURE;
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::SetLineJoin(const nsAString& joinstyle)
{
cairo_line_join_t j;
if (joinstyle.EqualsLiteral("round"))
j = CAIRO_LINE_JOIN_ROUND;
else if (joinstyle.EqualsLiteral("bevel"))
j = CAIRO_LINE_JOIN_BEVEL;
else if (joinstyle.EqualsLiteral("miter"))
j = CAIRO_LINE_JOIN_MITER;
else
// XXX ERRMSG we need to report an error to developers here! (bug 329026)
return NS_ERROR_NOT_IMPLEMENTED;
cairo_set_line_join (mCairo, j);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::GetLineJoin(nsAString& joinstyle)
{
cairo_line_join_t j = cairo_get_line_join(mCairo);
if (j == CAIRO_LINE_JOIN_ROUND)
joinstyle.AssignLiteral("round");
else if (j == CAIRO_LINE_JOIN_BEVEL)
joinstyle.AssignLiteral("bevel");
else if (j == CAIRO_LINE_JOIN_MITER)
joinstyle.AssignLiteral("miter");
else
return NS_ERROR_FAILURE;
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::SetMiterLimit(float miter)
{
if (!FloatValidate(miter))
return NS_ERROR_DOM_SYNTAX_ERR;
cairo_set_miter_limit(mCairo, miter);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::GetMiterLimit(float *miter)
{
double d = cairo_get_miter_limit(mCairo);
*miter = (float) d;
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::IsPointInPath(float x, float y, PRBool *retVal)
{
if (!FloatValidate(x,y))
return NS_ERROR_DOM_SYNTAX_ERR;
*retVal = (PRBool) cairo_in_fill(mCairo, x, y);
return NS_OK;
}
//
// image
//
// drawImage(in HTMLImageElement image, in float dx, in float dy);
// -- render image from 0,0 at dx,dy top-left coords
// drawImage(in HTMLImageElement image, in float dx, in float dy, in float sw, in float sh);
// -- render image from 0,0 at dx,dy top-left coords clipping it to sw,sh
// drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh);
// -- render the region defined by (sx,sy,sw,wh) in image-local space into the region (dx,dy,dw,dh) on the canvas
NS_IMETHODIMP
nsCanvasRenderingContext2D::DrawImage()
{
nsresult rv;
nsCOMPtr<nsIXPCNativeCallContext> ncc;
rv = nsContentUtils::XPConnect()->
GetCurrentNativeCallContext(getter_AddRefs(ncc));
NS_ENSURE_SUCCESS(rv, rv);
if (!ncc)
return NS_ERROR_FAILURE;
JSContext *ctx = nsnull;
rv = ncc->GetJSContext(&ctx);
NS_ENSURE_SUCCESS(rv, rv);
PRUint32 argc;
jsval *argv = nsnull;
ncc->GetArgc(&argc);
ncc->GetArgvPtr(&argv);
// we always need at least an image and a dx,dy
if (argc < 3)
return NS_ERROR_INVALID_ARG;
double sx,sy,sw,sh;
double dx,dy,dw,dh;
nsCOMPtr<nsIDOMElement> imgElt;
if (!ConvertJSValToXPCObject(getter_AddRefs(imgElt),
NS_GET_IID(nsIDOMElement),
ctx, argv[0]))
return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
cairo_surface_t *imgSurf = nsnull;
cairo_matrix_t surfMat;
cairo_pattern_t* pat;
PRUint8 *imgData = nsnull;
PRInt32 imgWidth, imgHeight;
nsCOMPtr<nsIURI> uri;
PRBool forceWriteOnly = PR_FALSE;
rv = CairoSurfaceFromElement(imgElt, &imgSurf, &imgData,
&imgWidth, &imgHeight, getter_AddRefs(uri), &forceWriteOnly);
if (NS_FAILED(rv))
return rv;
DoDrawImageSecurityCheck(uri, forceWriteOnly);
#define GET_ARG(dest,whicharg) \
do { if (!ConvertJSValToDouble(dest, ctx, whicharg)) { rv = NS_ERROR_INVALID_ARG; goto FAIL; } } while (0)
rv = NS_OK;
if (argc == 3) {
GET_ARG(&dx, argv[1]);
GET_ARG(&dy, argv[2]);
sx = sy = 0.0;
dw = sw = (double) imgWidth;
dh = sh = (double) imgHeight;
} else if (argc == 5) {
GET_ARG(&dx, argv[1]);
GET_ARG(&dy, argv[2]);
GET_ARG(&dw, argv[3]);
GET_ARG(&dh, argv[4]);
sx = sy = 0.0;
sw = (double) imgWidth;
sh = (double) imgHeight;
} else if (argc == 9) {
GET_ARG(&sx, argv[1]);
GET_ARG(&sy, argv[2]);
GET_ARG(&sw, argv[3]);
GET_ARG(&sh, argv[4]);
GET_ARG(&dx, argv[5]);
GET_ARG(&dy, argv[6]);
GET_ARG(&dw, argv[7]);
GET_ARG(&dh, argv[8]);
} else {
// XXX ERRMSG we need to report an error to developers here! (bug 329026)
rv = NS_ERROR_INVALID_ARG;
goto FAIL;
}
#undef GET_ARG
if (!FloatValidate(sx,sy,sw,sh))
return NS_ERROR_DOM_SYNTAX_ERR;
if (!FloatValidate(dx,dy,dw,dh))
return NS_ERROR_DOM_SYNTAX_ERR;
// check args
if (sx < 0.0 || sy < 0.0 ||
sw < 0.0 || sw > (double) imgWidth ||
sh < 0.0 || sh > (double) imgHeight ||
dw < 0.0 || dh < 0.0)
{
// XXX ERRMSG we need to report an error to developers here! (bug 329026)
rv = NS_ERROR_DOM_INDEX_SIZE_ERR;
goto FAIL;
}
cairo_matrix_init_translate(&surfMat, sx, sy);
cairo_matrix_scale(&surfMat, sw/dw, sh/dh);
pat = cairo_pattern_create_for_surface(imgSurf);
cairo_pattern_set_matrix(pat, &surfMat);
cairo_save(mCairo);
cairo_translate(mCairo, dx, dy);
cairo_new_path(mCairo);
cairo_rectangle(mCairo, 0, 0, dw, dh);
cairo_set_source(mCairo, pat);
cairo_clip(mCairo);
cairo_paint_with_alpha(mCairo, CurrentState().globalAlpha);
cairo_restore(mCairo);
#if 1
// XXX cairo bug workaround; force a clip update on mCairo.
// Otherwise, a pixman clip gets left around somewhere, and pixman
// (Render) does source clipping as well -- so we end up
// compositing with an incorrect clip. This only seems to affect
// fallback cases, which happen when we have CSS scaling going on.
// This will blow away the current path, but we already blew it
// away in this function earlier.
cairo_new_path(mCairo);
cairo_rectangle(mCairo, 0, 0, 0, 0);
cairo_fill(mCairo);
#endif
cairo_pattern_destroy(pat);
FAIL:
if (imgData)
nsMemory::Free(imgData);
if (imgSurf)
cairo_surface_destroy(imgSurf);
if (NS_SUCCEEDED(rv))
rv = Redraw();
return rv;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::SetGlobalCompositeOperation(const nsAString& op)
{
cairo_operator_t cairo_op;
#define CANVAS_OP_TO_CAIRO_OP(cvsop,cairoop) \
if (op.EqualsLiteral(cvsop)) \
cairo_op = CAIRO_OPERATOR_##cairoop;
// XXX "darker" isn't really correct
CANVAS_OP_TO_CAIRO_OP("clear", CLEAR)
else CANVAS_OP_TO_CAIRO_OP("copy", SOURCE)
else CANVAS_OP_TO_CAIRO_OP("darker", SATURATE) // XXX
else CANVAS_OP_TO_CAIRO_OP("destination-atop", DEST_ATOP)
else CANVAS_OP_TO_CAIRO_OP("destination-in", DEST_IN)
else CANVAS_OP_TO_CAIRO_OP("destination-out", DEST_OUT)
else CANVAS_OP_TO_CAIRO_OP("destination-over", DEST_OVER)
else CANVAS_OP_TO_CAIRO_OP("lighter", ADD)
else CANVAS_OP_TO_CAIRO_OP("source-atop", ATOP)
else CANVAS_OP_TO_CAIRO_OP("source-in", IN)
else CANVAS_OP_TO_CAIRO_OP("source-out", OUT)
else CANVAS_OP_TO_CAIRO_OP("source-over", OVER)
else CANVAS_OP_TO_CAIRO_OP("xor", XOR)
// not part of spec, kept here for compat
else CANVAS_OP_TO_CAIRO_OP("over", OVER)
else return NS_ERROR_NOT_IMPLEMENTED;
#undef CANVAS_OP_TO_CAIRO_OP
cairo_set_operator(mCairo, cairo_op);
return NS_OK;
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::GetGlobalCompositeOperation(nsAString& op)
{
cairo_operator_t cairo_op = cairo_get_operator(mCairo);
#define CANVAS_OP_TO_CAIRO_OP(cvsop,cairoop) \
if (cairo_op == CAIRO_OPERATOR_##cairoop) \
op.AssignLiteral(cvsop);
// XXX "darker" isn't really correct
CANVAS_OP_TO_CAIRO_OP("clear", CLEAR)
else CANVAS_OP_TO_CAIRO_OP("copy", SOURCE)
else CANVAS_OP_TO_CAIRO_OP("darker", SATURATE) // XXX
else CANVAS_OP_TO_CAIRO_OP("destination-atop", DEST_ATOP)
else CANVAS_OP_TO_CAIRO_OP("destination-in", DEST_IN)
else CANVAS_OP_TO_CAIRO_OP("destination-out", DEST_OUT)
else CANVAS_OP_TO_CAIRO_OP("destination-over", DEST_OVER)
else CANVAS_OP_TO_CAIRO_OP("lighter", ADD)
else CANVAS_OP_TO_CAIRO_OP("source-atop", ATOP)
else CANVAS_OP_TO_CAIRO_OP("source-in", IN)
else CANVAS_OP_TO_CAIRO_OP("source-out", OUT)
else CANVAS_OP_TO_CAIRO_OP("source-over", OVER)
else CANVAS_OP_TO_CAIRO_OP("xor", XOR)
else return NS_ERROR_FAILURE;
#undef CANVAS_OP_TO_CAIRO_OP
return NS_OK;
}
//
// Utils
//
PRBool
nsCanvasRenderingContext2D::ConvertJSValToUint32(PRUint32* aProp, JSContext* aContext,
jsval aValue)
{
uint32 temp;
if (::JS_ValueToECMAUint32(aContext, aValue, &temp)) {
*aProp = (PRUint32)temp;
}
else {
::JS_ReportError(aContext, "Parameter must be an integer");
return JS_FALSE;
}
return JS_TRUE;
}
PRBool
nsCanvasRenderingContext2D::ConvertJSValToDouble(double* aProp, JSContext* aContext,
jsval aValue)
{
jsdouble temp;
if (::JS_ValueToNumber(aContext, aValue, &temp)) {
*aProp = (jsdouble)temp;
}
else {
::JS_ReportError(aContext, "Parameter must be a number");
return JS_FALSE;
}
return JS_TRUE;
}
PRBool
nsCanvasRenderingContext2D::ConvertJSValToXPCObject(nsISupports** aSupports, REFNSIID aIID,
JSContext* aContext, jsval aValue)
{
*aSupports = nsnull;
if (JSVAL_IS_NULL(aValue)) {
return JS_TRUE;
}
if (JSVAL_IS_OBJECT(aValue)) {
// WrapJS does all the work to recycle an existing wrapper and/or do a QI
nsresult rv = nsContentUtils::XPConnect()->
WrapJS(aContext, JSVAL_TO_OBJECT(aValue), aIID, (void**)aSupports);
return NS_SUCCEEDED(rv);
}
return JS_FALSE;
}
/* cairo ARGB32 surfaces are ARGB stored as a packed 32-bit integer; on little-endian
* platforms, they appear as BGRA bytes in the surface data. The color values are also
* stored with premultiplied alpha.
*/
nsresult
nsCanvasRenderingContext2D::CairoSurfaceFromElement(nsIDOMElement *imgElt,
cairo_surface_t **aCairoSurface,
PRUint8 **imgData,
PRInt32 *widthOut, PRInt32 *heightOut,
nsIURI **uriOut, PRBool *forceWriteOnlyOut)
{
nsresult rv;
nsCOMPtr<imgIContainer> imgContainer;
nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(imgElt);
if (imageLoader) {
nsCOMPtr<imgIRequest> imgRequest;
rv = imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
getter_AddRefs(imgRequest));
NS_ENSURE_SUCCESS(rv, rv);
if (!imgRequest)
// XXX ERRMSG we need to report an error to developers here! (bug 329026)
return NS_ERROR_NOT_AVAILABLE;
nsCOMPtr<nsIURI> uri;
rv = imageLoader->GetCurrentURI(uriOut);
NS_ENSURE_SUCCESS(rv, rv);
*forceWriteOnlyOut = PR_FALSE;
rv = imgRequest->GetImage(getter_AddRefs(imgContainer));
NS_ENSURE_SUCCESS(rv, rv);
} else {
// maybe a canvas
nsCOMPtr<nsICanvasElement> canvas = do_QueryInterface(imgElt);
if (canvas) {
PRUint32 w, h;
rv = canvas->GetSize(&w, &h);
NS_ENSURE_SUCCESS(rv, rv);
cairo_surface_t *surf =
cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
w, h);
cairo_t *cr = cairo_create (surf);
cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
cairo_paint (cr);
cairo_destroy (cr);
rv = canvas->RenderContextsToSurface(surf);
if (NS_FAILED(rv)) {
cairo_surface_destroy (surf);
return rv;
}
*aCairoSurface = surf;
*imgData = nsnull;
*widthOut = w;
*heightOut = h;
*uriOut = nsnull;
*forceWriteOnlyOut = canvas->IsWriteOnly();
return NS_OK;
} else {
NS_WARNING("No way to get surface from non-canvas, non-imageloader");
return NS_ERROR_NOT_AVAILABLE;
}
}
if (!imgContainer)
return NS_ERROR_NOT_AVAILABLE;
nsCOMPtr<gfxIImageFrame> frame;
rv = imgContainer->GetCurrentFrame(getter_AddRefs(frame));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIImage> img(do_GetInterface(frame));
PRInt32 imgWidth, imgHeight;
rv = frame->GetWidth(&imgWidth);
rv |= frame->GetHeight(&imgHeight);
if (NS_FAILED(rv))
return NS_ERROR_FAILURE;
if (widthOut)
*widthOut = imgWidth;
if (heightOut)
*heightOut = imgHeight;
#ifdef MOZ_CAIRO_GFX
gfxASurface* gfxsurf = nsnull;
rv = img->GetSurface(&gfxsurf);
NS_ENSURE_SUCCESS(rv, rv);
*aCairoSurface = gfxsurf->CairoSurface();
cairo_surface_reference (*aCairoSurface);
*imgData = nsnull;
#else
//
// We now need to create a cairo_surface with the same data as
// this image element.
//
PRUint8 *cairoImgData = (PRUint8 *)nsMemory::Alloc(imgHeight * imgWidth * 4);
PRUint8 *outData = cairoImgData;
gfx_format format;
rv = frame->GetFormat(&format);
NS_ENSURE_SUCCESS(rv, rv);
rv = frame->LockImageData();
if (img->GetHasAlphaMask())
rv |= frame->LockAlphaData();
if (NS_FAILED(rv)) {
nsMemory::Free(cairoImgData);
return NS_ERROR_FAILURE;
}
PRUint8 *inPixBits, *inAlphaBits = nsnull;
PRUint32 inPixStride, inAlphaStride = 0;
inPixBits = img->GetBits();
inPixStride = img->GetLineStride();
if (img->GetHasAlphaMask()) {
inAlphaBits = img->GetAlphaBits();
inAlphaStride = img->GetAlphaLineStride();
}
PRBool topToBottom = img->GetIsRowOrderTopToBottom();
PRBool useBGR;
// The gtk backend optimizes away the alpha mask of images
// with a fully opaque alpha, but doesn't update its format (bug?);
// you end up with a RGB_A8 image with GetHasAlphaMask() == false.
// We need to treat that case as RGB.
if ((format == gfxIFormats::RGB || format == gfxIFormats::BGR) ||
(!(img->GetHasAlphaMask()) && (format == gfxIFormats::RGB_A8 || format == gfxIFormats::BGR_A8)))
{
useBGR = (format & 1);
#ifdef IS_BIG_ENDIAN
useBGR = !useBGR;
#endif
for (PRUint32 j = 0; j < (PRUint32) imgHeight; j++) {
PRUint32 rowIndex;
if (topToBottom)
rowIndex = j;
else
rowIndex = imgHeight - j - 1;
PRUint8 *inrowrgb = inPixBits + (inPixStride * rowIndex);
for (PRUint32 i = 0; i < (PRUint32) imgWidth; i++) {
// handle rgb data; no alpha to premultiply
#ifdef XP_MACOSX
// skip extra OSX byte
inrowrgb++;
#endif
PRUint8 b = *inrowrgb++;
PRUint8 g = *inrowrgb++;
PRUint8 r = *inrowrgb++;
#ifdef IS_BIG_ENDIAN
// alpha
*outData++ = 0xff;
#endif
if (useBGR) {
*outData++ = b;
*outData++ = g;
*outData++ = r;
} else {
*outData++ = r;
*outData++ = g;
*outData++ = b;
}
#ifdef IS_LITTLE_ENDIAN
// alpha
*outData++ = 0xff;
#endif
}
}
rv = NS_OK;
} else if (format == gfxIFormats::RGB_A1 || format == gfxIFormats::BGR_A1) {
useBGR = (format & 1);
#ifdef IS_BIG_ENDIAN
useBGR = !useBGR;
#endif
for (PRUint32 j = 0; j < (PRUint32) imgHeight; j++) {
PRUint32 rowIndex;
if (topToBottom)
rowIndex = j;
else
rowIndex = imgHeight - j - 1;
PRUint8 *inrowrgb = inPixBits + (inPixStride * rowIndex);
PRUint8 *inrowalpha = inAlphaBits + (inAlphaStride * rowIndex);
for (PRUint32 i = 0; i < (PRUint32) imgWidth; i++) {
// pull out the bit value into alpha
PRInt32 bit = i % 8;
PRInt32 byte = i / 8;
#ifdef IS_LITTLE_ENDIAN
PRUint8 a = (inrowalpha[byte] >> (7-bit)) & 1;
#else
PRUint8 a = (inrowalpha[byte] >> bit) & 1;
#endif
#ifdef XP_MACOSX
// skip extra X8 byte on OSX
inrowrgb++;
#endif
// handle rgb data; need to multiply the alpha out,
// but we short-circuit that here since we know that a
// can only be 0 or 1
if (a) {
PRUint8 b = *inrowrgb++;
PRUint8 g = *inrowrgb++;
PRUint8 r = *inrowrgb++;
#ifdef IS_BIG_ENDIAN
// alpha
*outData++ = 0xff;
#endif
if (useBGR) {
*outData++ = b;
*outData++ = g;
*outData++ = r;
} else {
*outData++ = r;
*outData++ = g;
*outData++ = b;
}
#ifdef IS_LITTLE_ENDIAN
// alpha
*outData++ = 0xff;
#endif
} else {
// alpha is 0, so we need to write all 0's,
// ignoring input color
inrowrgb += 3;
*outData++ = 0;
*outData++ = 0;
*outData++ = 0;
*outData++ = 0;
}
}
}
rv = NS_OK;
} else if (format == gfxIFormats::RGB_A8 || format == gfxIFormats::BGR_A8) {
useBGR = (format & 1);
#ifdef IS_BIG_ENDIAN
useBGR = !useBGR;
#endif
for (PRUint32 j = 0; j < (PRUint32) imgHeight; j++) {
PRUint32 rowIndex;
if (topToBottom)
rowIndex = j;
else
rowIndex = imgHeight - j - 1;
PRUint8 *inrowrgb = inPixBits + (inPixStride * rowIndex);
PRUint8 *inrowalpha = inAlphaBits + (inAlphaStride * rowIndex);
for (PRUint32 i = 0; i < (PRUint32) imgWidth; i++) {
// pull out alpha; we'll need it to premultiply
PRUint8 a = *inrowalpha++;
// handle rgb data; we need to fully premultiply
// with the alpha
#ifdef XP_MACOSX
// skip extra X8 byte on OSX
inrowrgb++;
#endif
// XXX gcc bug: gcc seems to push "r" into a register
// early, and pretends that it's in that register
// throughout the 3 macros below. At the end
// of the 3rd macro, the correct r value is
// calculated but never stored anywhere -- the r variable
// has the value of the low byte of register that it
// was stuffed into, which has the result of some
// intermediate calculation.
// I've seen this on gcc 3.4.2 x86 (Fedora Core 3)
// and gcc 3.3 PPC (OS X 10.3)
//PRUint8 b, g, r;
//FAST_DIVIDE_BY_255(b, *inrowrgb++ * a - a / 2);
//FAST_DIVIDE_BY_255(g, *inrowrgb++ * a - a / 2);
//FAST_DIVIDE_BY_255(r, *inrowrgb++ * a - a / 2);
PRUint8 b = (*inrowrgb++ * a - a / 2) / 255;
PRUint8 g = (*inrowrgb++ * a - a / 2) / 255;
PRUint8 r = (*inrowrgb++ * a - a / 2) / 255;
#ifdef IS_BIG_ENDIAN
*outData++ = a;
#endif
if (useBGR) {
*outData++ = b;
*outData++ = g;
*outData++ = r;
} else {
*outData++ = r;
*outData++ = g;
*outData++ = b;
}
#ifdef IS_LITTLE_ENDIAN
*outData++ = a;
#endif
}
}
rv = NS_OK;
} else {
rv = NS_ERROR_FAILURE;
}
if (img->GetHasAlphaMask())
frame->UnlockAlphaData();
frame->UnlockImageData();
if (NS_FAILED(rv)) {
nsMemory::Free(cairoImgData);
return rv;
}
cairo_surface_t *imgSurf =
cairo_image_surface_create_for_data(cairoImgData, CAIRO_FORMAT_ARGB32,
imgWidth, imgHeight, imgWidth*4);
*aCairoSurface = imgSurf;
*imgData = cairoImgData;
#endif
return NS_OK;
}
PRBool
CheckSaneImageSize (PRInt32 width, PRInt32 height)
{
if (width <= 0 || height <= 0)
return PR_FALSE;
/* check to make sure we don't overflow a 32-bit */
PRInt32 tmp = width * height;
if (tmp / height != width)
return PR_FALSE;
tmp = tmp * 4;
if (tmp / 4 != width * height)
return PR_FALSE;
/* reject over-wide or over-tall images */
const PRInt32 k64KLimit = 0x0000FFFF;
if (width > k64KLimit || height > k64KLimit)
return PR_FALSE;
return PR_TRUE;
}
/* Check that the rect [x,y,w,h] is a valid subrect of [0,0,realWidth,realHeight]
* without overflowing any integers and the like.
*/
PRBool
CheckSaneSubrectSize (PRInt32 x, PRInt32 y, PRInt32 w, PRInt32 h, PRInt32 realWidth, PRInt32 realHeight)
{
if (w <= 0 || h <= 0 || x < 0 || y < 0)
return PR_FALSE;
if (x >= realWidth || w > (realWidth - x) ||
y >= realHeight || h > (realHeight - y))
return PR_FALSE;
return PR_TRUE;
}
static void
FlushLayoutForTree(nsIDOMWindow* aWindow)
{
// Note that because FlushPendingNotifications flushes parents, this
// is O(N^2) in docshell tree depth. However, the docshell tree is
// usually pretty shallow.
nsCOMPtr<nsIDOMDocument> domDoc;
aWindow->GetDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
if (doc) {
doc->FlushPendingNotifications(Flush_Layout);
}
nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow);
nsCOMPtr<nsIDocShellTreeNode> node =
do_QueryInterface(piWin->GetDocShell());
if (node) {
PRInt32 i = 0, i_end;
node->GetChildCount(&i_end);
for (; i < i_end; ++i) {
nsCOMPtr<nsIDocShellTreeItem> item;
node->GetChildAt(i, getter_AddRefs(item));
nsCOMPtr<nsIDOMWindow> win = do_GetInterface(item);
if (win) {
FlushLayoutForTree(win);
}
}
}
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::DrawWindow(nsIDOMWindow* aWindow, PRInt32 aX, PRInt32 aY,
PRInt32 aW, PRInt32 aH,
const nsAString& aBGColor)
{
NS_ENSURE_ARG(aWindow != nsnull);
// protect against too-large surfaces that will cause allocation
// or overflow issues
if (!CheckSaneImageSize (aW, aH))
return NS_ERROR_FAILURE;
// We can't allow web apps to call this until we fix at least the
// following potential security issues:
// -- rendering cross-domain IFRAMEs and then extracting the results
// -- rendering the user's theme and then extracting the results
// -- rendering native anonymous content (e.g., file input paths;
// scrollbars should be allowed)
if (!nsContentUtils::IsCallerTrustedForRead()) {
// not permitted to use DrawWindow
// XXX ERRMSG we need to report an error to developers here! (bug 329026)
return NS_ERROR_DOM_SECURITY_ERR;
}
// Flush layout updates
FlushLayoutForTree(aWindow);
nsCOMPtr<nsPresContext> presContext;
#ifdef MOZILLA_1_8_BRANCH
nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aWindow);
if (sgo) {
nsIDocShell* docshell = sgo->GetDocShell();
if (docshell) {
docshell->GetPresContext(getter_AddRefs(presContext));
}
}
#else
nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aWindow);
if (win) {
nsIDocShell* docshell = win->GetDocShell();
if (docshell) {
docshell->GetPresContext(getter_AddRefs(presContext));
}
}
#endif
if (!presContext)
return NS_ERROR_FAILURE;
#ifdef MOZILLA_1_8_BRANCH
// Dig down past the viewport scroll stuff
nsIViewManager* vm = presContext->GetViewManager();
nsIView* view;
vm->GetRootView(view);
NS_ASSERTION(view, "Must have root view!");
#endif
nscolor bgColor;
nsresult rv = mCSSParser->ParseColorString(PromiseFlatString(aBGColor),
nsnull, 0, PR_TRUE, &bgColor);
NS_ENSURE_SUCCESS(rv, rv);
#ifndef MOZILLA_1_8_BRANCH
nsIPresShell* presShell = presContext->PresShell();
#endif
#ifdef MOZ_CAIRO_GFX
mThebesContext->Save();
//mThebesContext->NewPath();
//mThebesContext->Rectangle(gfxRect(0, 0, aW, aH));
//mThebesContext->Clip();
// XXX vlad says this shouldn't both be COLOR_ALPHA but that it is a workaround for some bug
mThebesContext->PushGroup(NS_GET_A(bgColor) == 0xff ? gfxASurface::CONTENT_COLOR_ALPHA : gfxASurface::CONTENT_COLOR_ALPHA);
// draw background color
if (NS_GET_A(bgColor) > 0) {
mThebesContext->SetColor(gfxRGBA(bgColor));
mThebesContext->SetOperator(gfxContext::OPERATOR_SOURCE);
mThebesContext->Paint();
}
// we want the window to be composited as a single image using
// whatever operator was set, so set this to the default OVER;
// the original operator will be present when we PopGroup
mThebesContext->SetOperator(gfxContext::OPERATOR_OVER);
nsIFrame* rootFrame = presShell->FrameManager()->GetRootFrame();
if (rootFrame) {
// XXX This shadows the other |r|, above.
nsRect r(nsPresContext::CSSPixelsToAppUnits(aX),
nsPresContext::CSSPixelsToAppUnits(aY),
nsPresContext::CSSPixelsToAppUnits(aW),
nsPresContext::CSSPixelsToAppUnits(aH));
nsDisplayListBuilder builder(rootFrame, PR_FALSE, PR_FALSE);
nsDisplayList list;
nsIScrollableView* scrollingView = nsnull;
presContext->GetViewManager()->GetRootScrollableView(&scrollingView);
if (scrollingView) {
nscoord x, y;
scrollingView->GetScrollPosition(x, y);
r.MoveBy(-x, -y);
builder.SetIgnoreScrollFrame(presShell->GetRootScrollFrame());
}
rv = rootFrame->BuildDisplayListForStackingContext(&builder, r, &list);
if (NS_SUCCEEDED(rv)) {
nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
// Ensure that r.x,r.y gets drawn at (0,0)
mThebesContext->Save();
mThebesContext->Translate(gfxPoint(-NSAppUnitsToFloatPixels(r.x,appUnitsPerDevPixel),
-NSAppUnitsToFloatPixels(r.y,appUnitsPerDevPixel)));
nsIDeviceContext* devCtx = presContext->DeviceContext();
nsCOMPtr<nsIRenderingContext> rc;
devCtx->CreateRenderingContextInstance(*getter_AddRefs(rc));
rc->Init(devCtx, mThebesContext);
nsRegion region(r);
list.OptimizeVisibility(&builder, &region);
list.Paint(&builder, rc, r);
// Flush the list so we don't trigger the IsEmpty-on-destruction assertion
list.DeleteAll();
mThebesContext->Restore();
}
}
mThebesContext->PopGroupToSource();
mThebesContext->Paint();
mThebesContext->Restore();
// get rid of the pattern surface ref, just in case
cairo_set_source_rgba (mCairo, 1, 1, 1, 1);
DirtyAllStyles();
Redraw();
#else
nsCOMPtr<nsIRenderingContext> blackCtx;
#ifdef MOZILLA_1_8_BRANCH
rv = vm->RenderOffscreen(view, r, PR_FALSE, PR_TRUE,
NS_ComposeColors(NS_RGB(0, 0, 0), bgColor),
getter_AddRefs(blackCtx));
#else
rv = presShell->RenderOffscreen(r, PR_FALSE, PR_TRUE,
NS_ComposeColors(NS_RGB(0, 0, 0), bgColor),
getter_AddRefs(blackCtx));
#endif
NS_ENSURE_SUCCESS(rv, rv);
nsIDrawingSurface* blackSurface;
blackCtx->GetDrawingSurface(&blackSurface);
if (!blackSurface)
return NS_ERROR_FAILURE;
// Render it!
if (NS_GET_A(bgColor) == 0xFF) {
// opaque background. Do it the easy way.
rv = DrawNativeSurfaces(blackSurface, nsnull, nsSize(aW, aH), blackCtx);
blackCtx->DestroyDrawingSurface(blackSurface);
return rv;
}
// transparent background. Do it the hard way. We've drawn onto black,
// now draw onto white so we can recover the translucency information.
// But we need to compose our given background color onto black/white
// to get the real background to use.
nsCOMPtr<nsIRenderingContext> whiteCtx;
#ifdef MOZILLA_1_8_BRANCH
rv = vm->RenderOffscreen(view, r, PR_FALSE, PR_TRUE,
NS_ComposeColors(NS_RGB(255, 255, 255), bgColor),
getter_AddRefs(whiteCtx));
#else
rv = presShell->RenderOffscreen(r, PR_FALSE, PR_TRUE,
NS_ComposeColors(NS_RGB(255, 255, 255), bgColor),
getter_AddRefs(whiteCtx));
#endif
if (NS_SUCCEEDED(rv)) {
nsIDrawingSurface* whiteSurface;
whiteCtx->GetDrawingSurface(&whiteSurface);
if (!whiteSurface) {
rv = NS_ERROR_FAILURE;
} else {
rv = DrawNativeSurfaces(blackSurface, whiteSurface, nsSize(aW, aH), blackCtx);
whiteCtx->DestroyDrawingSurface(whiteSurface);
}
}
blackCtx->DestroyDrawingSurface(blackSurface);
#endif
return rv;
}
/**
* Given aBits, the number of bits in a color channel, compute a number N
* such that for values v with aBits bits, floor((N*v)/256) is close to
* v*255.0/(2^aBits - 1) and in particular we need
* floor((N*(2^aBits - 1))/256) = 255.
* We'll just use a table that gives good results :-).
*/
static PRUint32 ComputeScaleFactor(PRUint32 aBits)
{
static PRUint32 table[9] = {
0, 255*256, 85*256, 9330, 17*256, 2110, 1038, 515, 256
};
NS_ASSERTION(aBits <= 8, "more than 8 bits in a color channel not supported");
NS_ASSERTION(((table[aBits]*((1 << aBits) - 1)) >> 8) == 255,
"Invalid table entry");
return table[aBits];
}
nsresult
nsCanvasRenderingContext2D::DrawNativeSurfaces(nsIDrawingSurface* aBlackSurface,
nsIDrawingSurface* aWhiteSurface,
const nsIntSize& aSurfaceSize,
nsIRenderingContext* aBlackContext)
{
// check if the dimensions are too large;
// if they are, we may easily overflow malloc later on
if (!CheckSaneImageSize (aSurfaceSize.width, aSurfaceSize.height))
return NS_ERROR_FAILURE;
// Acquire alpha values
nsAutoArrayPtr<PRUint8> alphas;
nsresult rv;
if (aWhiteSurface) {
// There is transparency. Use the blender to recover alphas.
nsCOMPtr<nsIBlender> blender = do_CreateInstance(kBlenderCID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDeviceContext> dc;
aBlackContext->GetDeviceContext(*getter_AddRefs(dc));
rv = blender->Init(dc);
NS_ENSURE_SUCCESS(rv, rv);
rv = blender->GetAlphas(nsRect(0, 0, aSurfaceSize.width, aSurfaceSize.height),
aBlackSurface, aWhiteSurface, getter_Transfers(alphas));
NS_ENSURE_SUCCESS(rv, rv);
}
// We use aBlackSurface to get the image color data
PRUint8* data;
PRInt32 rowLen, rowSpan;
rv = aBlackSurface->Lock(0, 0, aSurfaceSize.width, aSurfaceSize.height,
(void**)&data, &rowSpan, &rowLen,
NS_LOCK_SURFACE_READ_ONLY);
if (NS_FAILED(rv))
return rv;
// Get info about native surface layout
PRUint32 bytesPerPix = rowLen/aSurfaceSize.width;
nsPixelFormat format;
#ifndef XP_MACOSX
rv = aBlackSurface->GetPixelFormat(&format);
if (NS_FAILED(rv)) {
aBlackSurface->Unlock();
return rv;
}
#else
// On the mac, GetPixelFormat returns NS_ERROR_NOT_IMPLEMENTED;
// we fake the pixel format here. The data that we care about
// will be in ABGR format, either 8-8-8 or 5-5-5.
if (bytesPerPix == 4) {
format.mRedZeroMask = 0xff;
format.mGreenZeroMask = 0xff;
format.mBlueZeroMask = 0xff;
format.mAlphaZeroMask = 0;
format.mRedMask = 0x00ff0000;
format.mGreenMask = 0x0000ff00;
format.mBlueMask = 0x000000ff;
format.mAlphaMask = 0;
format.mRedCount = 8;
format.mGreenCount = 8;
format.mBlueCount = 8;
format.mAlphaCount = 0;
format.mRedShift = 16;
format.mGreenShift = 8;
format.mBlueShift = 0;
format.mAlphaShift = 0;
} else if (bytesPerPix == 2) {
format.mRedZeroMask = 0x1f;
format.mGreenZeroMask = 0x1f;
format.mBlueZeroMask = 0x1f;
format.mAlphaZeroMask = 0;
format.mRedMask = 0x7C00;
format.mGreenMask = 0x03E0;
format.mBlueMask = 0x001F;
format.mAlphaMask = 0;
format.mRedCount = 5;
format.mGreenCount = 5;
format.mBlueCount = 5;
format.mAlphaCount = 0;
format.mRedShift = 10;
format.mGreenShift = 5;
format.mBlueShift = 0;
format.mAlphaShift = 0;
} else {
// no clue!
aBlackSurface->Unlock();
return NS_ERROR_FAILURE;
}
#endif
// Create a temporary surface to hold the full-size image in cairo
// image format.
nsAutoArrayPtr<PRUint8> tmpBuf(new PRUint8[aSurfaceSize.width*aSurfaceSize.height*4]);
if (!tmpBuf) {
aBlackSurface->Unlock();
return NS_ERROR_OUT_OF_MEMORY;
}
cairo_surface_t *tmpSurf =
cairo_image_surface_create_for_data(tmpBuf.get(),
CAIRO_FORMAT_ARGB32, aSurfaceSize.width, aSurfaceSize.height,
aSurfaceSize.width*4);
if (!tmpSurf) {
aBlackSurface->Unlock();
return NS_ERROR_OUT_OF_MEMORY;
}
#ifdef IS_BIG_ENDIAN
#define BLUE_BYTE 3
#define GREEN_BYTE 2
#define RED_BYTE 1
#define ALPHA_BYTE 0
#else
#define BLUE_BYTE 0
#define GREEN_BYTE 1
#define RED_BYTE 2
#define ALPHA_BYTE 3
#endif
// Mac surfaces are big endian.
#if defined(IS_BIG_ENDIAN) || defined(XP_MACOSX)
#define NATIVE_SURFACE_IS_BIG_ENDIAN
#endif
// OS/2 needs this painted the other way around
#ifdef XP_OS2
#define NATIVE_SURFACE_IS_VERTICALLY_FLIPPED
#endif
// Convert the data
PRUint8* dest = tmpBuf;
PRInt32 index = 0;
PRUint32 RScale = ComputeScaleFactor(format.mRedCount);
PRUint32 GScale = ComputeScaleFactor(format.mGreenCount);
PRUint32 BScale = ComputeScaleFactor(format.mBlueCount);
for (PRInt32 i = 0; i < aSurfaceSize.height; ++i) {
#ifdef NATIVE_SURFACE_IS_VERTICALLY_FLIPPED
PRUint8* src = data + (aSurfaceSize.height-1 - i)*rowSpan;
#else
PRUint8* src = data + i*rowSpan;
#endif
for (PRInt32 j = 0; j < aSurfaceSize.width; ++j) {
/* v is the pixel value */
#ifdef NATIVE_SURFACE_IS_BIG_ENDIAN
PRUint32 v = (src[0] << 24) | (src[1] << 16) | (src[2] << 8) | src[3];
v >>= (32 - 8*bytesPerPix);
#else
PRUint32 v = src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
#endif
// Note that because aBlackSurface is the image rendered
// onto black, the channel values we get here have
// effectively been premultipled by the alpha value.
dest[BLUE_BYTE] =
(PRUint8)((((v & format.mBlueMask) >> format.mBlueShift)*BScale) >> 8);
dest[GREEN_BYTE] =
(PRUint8)((((v & format.mGreenMask) >> format.mGreenShift)*GScale) >> 8);
dest[RED_BYTE] =
(PRUint8)((((v & format.mRedMask) >> format.mRedShift)*RScale) >> 8);
dest[ALPHA_BYTE] = alphas ? alphas[index++] : 0xFF;
src += bytesPerPix;
dest += 4;
}
}
#undef RED_BYTE
#undef GREEN_BYTE
#undef BLUE_BYTE
#undef ALPHA_BYTE
cairo_set_source_surface(mCairo, tmpSurf, 0, 0);
cairo_paint_with_alpha(mCairo, CurrentState().globalAlpha);
cairo_surface_destroy(tmpSurf);
aBlackSurface->Unlock();
return Redraw();
}
//
// device pixel getting/setting
//
// ImageData getImageData (in float x, in float y, in float width, in float height);
NS_IMETHODIMP
nsCanvasRenderingContext2D::GetImageData()
{
if (mCanvasElement->IsWriteOnly() && !nsContentUtils::IsCallerTrustedForRead()) {
// not permitted to use DrawWindow
// XXX ERRMSG we need to report an error to developers here! (bug 329026)
return NS_ERROR_DOM_SECURITY_ERR;
}
nsCOMPtr<nsIXPCNativeCallContext> ncc;
nsresult rv = nsContentUtils::XPConnect()->
GetCurrentNativeCallContext(getter_AddRefs(ncc));
NS_ENSURE_SUCCESS(rv, rv);
if (!ncc)
return NS_ERROR_FAILURE;
JSContext *ctx = nsnull;
rv = ncc->GetJSContext(&ctx);
NS_ENSURE_SUCCESS(rv, rv);
PRUint32 argc;
jsval *argv = nsnull;
ncc->GetArgc(&argc);
ncc->GetArgvPtr(&argv);
int32 x, y, w, h;
if (!JS_ConvertArguments (ctx, argc, argv, "jjjj", &x, &y, &w, &h))
return NS_ERROR_DOM_SYNTAX_ERR;
if (!CheckSaneSubrectSize (x, y, w, h, mWidth, mHeight))
return NS_ERROR_DOM_SYNTAX_ERR;
PRUint8 *surfaceData = mImageSurfaceData;
nsAutoArrayPtr<PRUint8> allocatedSurfaceData;
int surfaceDataStride = mWidth * 4;
int surfaceDataOffset = (surfaceDataStride * y) + (x * 4);
if (!surfaceData) {
allocatedSurfaceData = new PRUint8[w * h * 4];
if (!allocatedSurfaceData)
return NS_ERROR_OUT_OF_MEMORY;
surfaceData = allocatedSurfaceData.get();
cairo_surface_t *tmpsurf = cairo_image_surface_create_for_data (surfaceData,
CAIRO_FORMAT_ARGB32,
w, h, w*4);
cairo_t *tmpcr = cairo_create (tmpsurf);
cairo_set_operator (tmpcr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_surface (tmpcr, mSurface, -(int)x, -(int)y);
cairo_paint (tmpcr);
cairo_destroy (tmpcr);
cairo_surface_destroy (tmpsurf);
surfaceDataStride = w * 4;
surfaceDataOffset = 0;
}
PRUint32 len = w * h * 4;
if (len > (((PRUint32)0xfff00000)/sizeof(jsval)))
return NS_ERROR_INVALID_ARG;
nsAutoArrayPtr<jsval> jsvector(new jsval[w * h * 4]);
if (!jsvector)
return NS_ERROR_OUT_OF_MEMORY;
jsval *dest = jsvector.get();
PRUint8 *row;
for (int j = 0; j < h; j++) {
row = surfaceData + surfaceDataOffset + (surfaceDataStride * j);
for (int i = 0; i < w; i++) {
// XXX Is there some useful swizzle MMX we can use here?
// I guess we have to INT_TO_JSVAL still
#ifdef IS_LITTLE_ENDIAN
PRUint8 b = *row++;
PRUint8 g = *row++;
PRUint8 r = *row++;
PRUint8 a = *row++;
#else
PRUint8 a = *row++;
PRUint8 r = *row++;
PRUint8 g = *row++;
PRUint8 b = *row++;
#endif
*dest++ = INT_TO_JSVAL(r);
*dest++ = INT_TO_JSVAL(g);
*dest++ = INT_TO_JSVAL(b);
*dest++ = INT_TO_JSVAL(a);
}
}
JSObject *dataArray = JS_NewArrayObject(ctx, w*h*4, jsvector.get());
if (!dataArray)
return NS_ERROR_OUT_OF_MEMORY;
nsAutoGCRoot arrayGCRoot(&dataArray, &rv);
NS_ENSURE_SUCCESS(rv, rv);
JSObject *result = JS_NewObject(ctx, NULL, NULL, NULL);
if (!result)
return NS_ERROR_OUT_OF_MEMORY;
nsAutoGCRoot resultGCRoot(&result, &rv);
NS_ENSURE_SUCCESS(rv, rv);
if (!JS_DefineProperty(ctx, result, "width", INT_TO_JSVAL(w), NULL, NULL, 0) ||
!JS_DefineProperty(ctx, result, "height", INT_TO_JSVAL(h), NULL, NULL, 0) ||
!JS_DefineProperty(ctx, result, "data", OBJECT_TO_JSVAL(dataArray), NULL, NULL, 0))
return NS_ERROR_FAILURE;
jsval *retvalPtr;
ncc->GetRetValPtr(&retvalPtr);
*retvalPtr = OBJECT_TO_JSVAL(result);
ncc->SetReturnValueWasSet(PR_TRUE);
return NS_OK;
}
// void putImageData (in ImageData d, in float x, in float y);
NS_IMETHODIMP
nsCanvasRenderingContext2D::PutImageData()
{
nsresult rv;
nsCOMPtr<nsIXPCNativeCallContext> ncc;
rv = nsContentUtils::XPConnect()->
GetCurrentNativeCallContext(getter_AddRefs(ncc));
NS_ENSURE_SUCCESS(rv, rv);
if (!ncc)
return NS_ERROR_FAILURE;
JSContext *ctx = nsnull;
rv = ncc->GetJSContext(&ctx);
NS_ENSURE_SUCCESS(rv, rv);
PRUint32 argc;
jsval *argv = nsnull;
ncc->GetArgc(&argc);
ncc->GetArgvPtr(&argv);
JSObject *dataObject;
int32 x, y;
if (!JS_ConvertArguments (ctx, argc, argv, "ojj", &dataObject, &x, &y))
return NS_ERROR_DOM_SYNTAX_ERR;
int32 w, h;
JSObject *dataArray;
jsval v;
if (!JS_GetProperty(ctx, dataObject, "width", &v) ||
!JS_ValueToInt32(ctx, v, &w))
return NS_ERROR_DOM_SYNTAX_ERR;
if (!JS_GetProperty(ctx, dataObject, "height", &v) ||
!JS_ValueToInt32(ctx, v, &h))
return NS_ERROR_DOM_SYNTAX_ERR;
if (!JS_GetProperty(ctx, dataObject, "data", &v) ||
!JSVAL_IS_OBJECT(v))
return NS_ERROR_DOM_SYNTAX_ERR;
dataArray = JSVAL_TO_OBJECT(v);
if (!CheckSaneSubrectSize (x, y, w, h, mWidth, mHeight))
return NS_ERROR_DOM_SYNTAX_ERR;
jsuint arrayLen;
if (!JS_IsArrayObject(ctx, dataArray) ||
!JS_GetArrayLength(ctx, dataArray, &arrayLen) ||
arrayLen < (jsuint)(w * h * 4))
return NS_ERROR_DOM_SYNTAX_ERR;
nsAutoArrayPtr<PRUint8> imageBuffer(new PRUint8[w * h * 4]);
cairo_surface_t *imgsurf;
PRUint8 *imgPtr = imageBuffer.get();
jsval vr, vg, vb, va;
PRUint8 ir, ig, ib, ia;
for (int32 j = 0; j < h; j++) {
for (int32 i = 0; i < w; i++) {
if (!JS_GetElement(ctx, dataArray, (j*w*4) + i*4 + 0, &vr) ||
!JS_GetElement(ctx, dataArray, (j*w*4) + i*4 + 1, &vg) ||
!JS_GetElement(ctx, dataArray, (j*w*4) + i*4 + 2, &vb) ||
!JS_GetElement(ctx, dataArray, (j*w*4) + i*4 + 3, &va))
return NS_ERROR_DOM_SYNTAX_ERR;
if (JSVAL_IS_INT(vr)) ir = (PRUint8) JSVAL_TO_INT(vr);
else if (JSVAL_IS_DOUBLE(vr)) ir = (PRUint8) (*JSVAL_TO_DOUBLE(vr));
else return NS_ERROR_DOM_SYNTAX_ERR;
if (JSVAL_IS_INT(vg)) ig = (PRUint8) JSVAL_TO_INT(vg);
else if (JSVAL_IS_DOUBLE(vg)) ig = (PRUint8) (*JSVAL_TO_DOUBLE(vg));
else return NS_ERROR_DOM_SYNTAX_ERR;
if (JSVAL_IS_INT(vb)) ib = (PRUint8) JSVAL_TO_INT(vb);
else if (JSVAL_IS_DOUBLE(vb)) ib = (PRUint8) (*JSVAL_TO_DOUBLE(vb));
else return NS_ERROR_DOM_SYNTAX_ERR;
if (JSVAL_IS_INT(va)) ia = (PRUint8) JSVAL_TO_INT(va);
else if (JSVAL_IS_DOUBLE(va)) ia = (PRUint8) (*JSVAL_TO_DOUBLE(va));
else return NS_ERROR_DOM_SYNTAX_ERR;
#ifdef IS_LITTLE_ENDIAN
*imgPtr++ = ib;
*imgPtr++ = ig;
*imgPtr++ = ir;
*imgPtr++ = ia;
#else
*imgPtr++ = ia;
*imgPtr++ = ir;
*imgPtr++ = ig;
*imgPtr++ = ib;
#endif
}
}
if (mImageSurfaceData) {
int stride = mWidth*4;
PRUint8 *dest = mImageSurfaceData + stride*y + x*4;
for (int32 i = 0; i < y; i++) {
memcpy(dest, imgPtr + (w*4)*i, w*4);
dest += stride;
}
} else {
imgsurf = cairo_image_surface_create_for_data (imageBuffer.get(),
CAIRO_FORMAT_ARGB32,
w, h, w*4);
cairo_save (mCairo);
cairo_identity_matrix (mCairo);
cairo_translate (mCairo, x, y);
cairo_new_path (mCairo);
cairo_rectangle (mCairo, 0, 0, w, h);
cairo_set_source_surface (mCairo, imgsurf, 0, 0);
cairo_set_operator (mCairo, CAIRO_OPERATOR_SOURCE);
cairo_fill (mCairo);
cairo_restore (mCairo);
cairo_surface_destroy (imgsurf);
}
return Redraw();
}