/* -*- 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 * 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 ***** */ #include "nsIServiceManager.h" #include "nsContentUtils.h" #include "nsIDOMCanvasRenderingContext2D.h" #include "nsICanvasRenderingContextInternal.h" #include "nsPresContext.h" #include "nsIPresShell.h" #include "nsIVariant.h" #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 "nsICSSParser.h" #include "nsPrintfCString.h" #include "nsReadableUtils.h" #include "nsColor.h" #include "nsIRenderingContext.h" #include "nsIBlender.h" #include "nsGfxCIID.h" #include "nsIDrawingSurface.h" #include "nsIScriptSecurityManager.h" #include "nsIScriptGlobalObject.h" #include "nsIDocShell.h" #include "nsPresContext.h" #include "nsIViewManager.h" #include "nsIScrollableView.h" #include "nsIDOMWindow.h" #include "jsnum.h" #include "cairo.h" static NS_DEFINE_IID(kBlenderCID, NS_BLENDER_CID); /* 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 /* Maximum depth of save() which has style information saved */ #define STYLE_STACK_DEPTH 50 #define STYLE_CURRENT_STACK ((mSaveCountParseColorString(nsString(colorstr), nsnull, 0, PR_TRUE, &color); if (NS_FAILED(rv)) return PR_FALSE; 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 mCSSParser; }; 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: NS_DEFINE_STATIC_IID_ACCESSOR(NS_CANVASPATTERN_PRIVATE_IID) nsCanvasPattern(cairo_pattern_t *cpat, PRUint8 *dataToFree) : mPattern(cpat), mData(dataToFree) { } ~nsCanvasPattern() { if (mPattern) cairo_pattern_destroy(mPattern); if (mData) nsMemory::Free(mData); } void Apply(cairo_t *cairo) { cairo_set_source(cairo, mPattern); } NS_DECL_ISUPPORTS protected: cairo_pattern_t *mPattern; PRUint8 *mData; }; 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 SetTargetImageFrame(gfxIImageFrame* aImageFrame); NS_IMETHOD UpdateImageFrame(); // 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 }; PRBool StyleVariantToColor(nsIVariant* aStyle, PRInt32 aWhichStyle); void StyleColorToString(const nscolor& aColor, nsAString& aStr); void DirtyAllStyles(); void ApplyStyle(PRInt32 aWhichStyle); // Member vars PRInt32 mWidth, mHeight; // the canvas element informs us when its going away, // so these are not nsCOMPtrs nsICanvasElement* mCanvasElement; // image bits nsCOMPtr mImageFrame; PRBool mDirty; // our CSS parser, for colors and whatnot nsCOMPtr mCSSParser; // yay cairo PRUint32 mSaveCount; cairo_t *mCairo; cairo_surface_t *mSurface; unsigned char *mSurfaceData; float mGlobalAlpha; // style handling PRInt32 mLastStyle; PRPackedBool mDirtyStyle[STYLE_MAX]; nscolor mColorStyles[STYLE_STACK_DEPTH][STYLE_MAX]; nsCOMPtr mGradientStyles[STYLE_STACK_DEPTH][STYLE_MAX]; nsCOMPtr mPatternStyles[STYLE_STACK_DEPTH][STYLE_MAX]; // 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); 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), mDirty(PR_TRUE), mSaveCount(0), mCairo(nsnull), mSurface(nsnull), mSurfaceData(nsnull) { mColorStyles[0][STYLE_STROKE] = NS_RGB(0,0,0); mColorStyles[0][STYLE_FILL] = NS_RGB(0,0,0); mColorStyles[0][STYLE_SHADOW] = NS_RGBA(0,0,0,0); mLastStyle = -1; mGlobalAlpha = 1.0f; DirtyAllStyles(); } nsCanvasRenderingContext2D::~nsCanvasRenderingContext2D() { Destroy(); mImageFrame = nsnull; } 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; } if (mSurfaceData) { nsMemory::Free(mSurfaceData); mSurfaceData = nsnull; } } PRBool nsCanvasRenderingContext2D::StyleVariantToColor(nsIVariant* aStyle, PRInt32 aWhichStyle) { nsresult rv; nscolor color; PRUint16 paramType; rv = aStyle->GetDataType(¶mType); NS_ENSURE_SUCCESS(rv, rv); if (paramType == nsIDataType::VTYPE_DOMSTRING) { nsString str; rv = aStyle->GetAsDOMString(str); NS_ENSURE_SUCCESS(rv, rv); rv = mCSSParser->ParseColorString(str, nsnull, 0, PR_TRUE, &color); if (NS_FAILED(rv)) return PR_FALSE; mColorStyles[STYLE_CURRENT_STACK][aWhichStyle] = color; mPatternStyles[STYLE_CURRENT_STACK][aWhichStyle] = nsnull; mGradientStyles[STYLE_CURRENT_STACK][aWhichStyle] = nsnull; mDirtyStyle[aWhichStyle] = PR_TRUE; return PR_TRUE; } else if (paramType == nsIDataType::VTYPE_WSTRING_SIZE_IS) { PRUint32 sz; PRUnichar* str = nsnull; rv = aStyle->GetAsWStringWithSize(&sz, &str); NS_ENSURE_SUCCESS(rv, rv); rv = mCSSParser->ParseColorString(nsString(str, sz), nsnull, 0, PR_TRUE, &color); nsMemory::Free(str); if (NS_FAILED(rv)) return PR_FALSE; mColorStyles[STYLE_CURRENT_STACK][aWhichStyle] = color; mPatternStyles[STYLE_CURRENT_STACK][aWhichStyle] = nsnull; mGradientStyles[STYLE_CURRENT_STACK][aWhichStyle] = nsnull; mDirtyStyle[aWhichStyle] = PR_TRUE; return PR_TRUE; } else if (paramType == nsIDataType::VTYPE_INTERFACE || paramType == nsIDataType::VTYPE_INTERFACE_IS) { nsID *iid; nsCOMPtr iface; rv = aStyle->GetAsInterface(&iid, getter_AddRefs(iface)); nsCOMPtr grad (do_QueryInterface(iface)); if (grad) { mPatternStyles[STYLE_CURRENT_STACK][aWhichStyle] = nsnull; mGradientStyles[STYLE_CURRENT_STACK][aWhichStyle] = do_QueryInterface(iface); mDirtyStyle[aWhichStyle] = PR_TRUE; if (!mGradientStyles[STYLE_CURRENT_STACK][aWhichStyle]) return PR_FALSE; return PR_TRUE; } nsCOMPtr pattern (do_QueryInterface(iface)); if (pattern) { mPatternStyles[STYLE_CURRENT_STACK][aWhichStyle] = do_QueryInterface(iface); mGradientStyles[STYLE_CURRENT_STACK][aWhichStyle] = nsnull; mDirtyStyle[aWhichStyle] = PR_TRUE; if (!mPatternStyles[STYLE_CURRENT_STACK][aWhichStyle]) return PR_FALSE; return PR_TRUE; } } return PR_FALSE; } 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 { CopyUTF8toUTF16(nsPrintfCString(100, "rgb(%d,%d,%d,%0.2f)", NS_GET_R(aColor), NS_GET_G(aColor), NS_GET_B(aColor), NS_GET_A(aColor) / 255.0f), aStr); } } void nsCanvasRenderingContext2D::DirtyAllStyles() { for (int i = 0; i < STYLE_MAX; i++) { mDirtyStyle[i] = PR_TRUE; } } 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; if (mPatternStyles[STYLE_CURRENT_STACK][aWhichStyle]) { mPatternStyles[STYLE_CURRENT_STACK][aWhichStyle]->Apply(mCairo); return; } if (mGradientStyles[STYLE_CURRENT_STACK][aWhichStyle]) { mGradientStyles[STYLE_CURRENT_STACK][aWhichStyle]->Apply(mCairo); return; } SetCairoColor(mColorStyles[STYLE_CURRENT_STACK][aWhichStyle]); } nsresult nsCanvasRenderingContext2D::Redraw() { mDirty = PR_TRUE; 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) * mGlobalAlpha; cairo_set_source_rgba (mCairo, r, g, b, a); } NS_IMETHODIMP nsCanvasRenderingContext2D::SetTargetImageFrame(gfxIImageFrame* aImageFrame) { // clean up old cairo bits Destroy(); aImageFrame->GetWidth(&mWidth); aImageFrame->GetHeight(&mHeight); mSurfaceData = (unsigned char *) nsMemory::Alloc(mWidth * mHeight * 4); mSurface = cairo_image_surface_create_for_data (mSurfaceData, CAIRO_FORMAT_ARGB32, mWidth, mHeight, mWidth * 4); mCairo = cairo_create(mSurface); // set up the initial canvas defaults 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); mImageFrame = aImageFrame; return ClearRect (0, 0, mWidth, mHeight); } NS_IMETHODIMP nsCanvasRenderingContext2D::UpdateImageFrame() { nsresult rv; if (!mImageFrame) return NS_OK; if (!mSurfaceData) return NS_ERROR_FAILURE; if (mDirty) { PRUint8 *alphaBits, *rgbBits; PRUint32 alphaLen, rgbLen; PRUint32 alphaStride, rgbStride; rv = mImageFrame->LockImageData(); if (NS_FAILED(rv)) { return rv; } rv = mImageFrame->LockAlphaData(); if (NS_FAILED(rv)) { mImageFrame->UnlockImageData(); return rv; } rv = mImageFrame->GetAlphaBytesPerRow(&alphaStride); rv |= mImageFrame->GetAlphaData(&alphaBits, &alphaLen); rv |= mImageFrame->GetImageBytesPerRow(&rgbStride); rv |= mImageFrame->GetImageData(&rgbBits, &rgbLen); if (NS_FAILED(rv)) { mImageFrame->UnlockImageData(); mImageFrame->UnlockAlphaData(); return rv; } nsCOMPtr img(do_GetInterface(mImageFrame)); PRBool topToBottom = img->GetIsRowOrderTopToBottom(); for (PRUint32 j = 0; j < (PRUint32) mHeight; j++) { PRUint8 *inrow = (PRUint8*)(mSurfaceData + (mWidth * 4 * j)); PRUint32 rowIndex; if (topToBottom) rowIndex = j; else rowIndex = mHeight - j - 1; PRUint8 *outrowrgb = rgbBits + (rgbStride * rowIndex); PRUint8 *outrowalpha = alphaBits + (alphaStride * rowIndex); for (PRUint32 i = 0; i < (PRUint32) mWidth; i++) { #ifdef IS_LITTLE_ENDIAN PRUint8 b = *inrow++; PRUint8 g = *inrow++; PRUint8 r = *inrow++; PRUint8 a = *inrow++; #else PRUint8 a = *inrow++; PRUint8 r = *inrow++; PRUint8 g = *inrow++; PRUint8 b = *inrow++; #endif // now recover the real bgra from the cairo // premultiplied values if (a == 0) { // can't do much for us if we're at 0 b = g = r = 0; } else { // the (a/2) factor is a bias similar to one cairo applies // when premultiplying b = (b * 255 + a / 2) / a; g = (g * 255 + a / 2) / a; r = (r * 255 + a / 2) / a; } *outrowalpha++ = a; #ifdef XP_MACOSX // On the mac, RGB_A8 is really RGBX_A8 *outrowrgb++ = 0; #endif #ifdef XP_WIN // On windows, RGB_A8 is really BGR_A8. // in fact, BGR_A8 is also BGR_A8. *outrowrgb++ = b; *outrowrgb++ = g; *outrowrgb++ = r; #else *outrowrgb++ = r; *outrowrgb++ = g; *outrowrgb++ = b; #endif } } rv = mImageFrame->UnlockAlphaData(); rv |= mImageFrame->UnlockImageData(); if (NS_FAILED(rv)) return rv; nsRect r(0, 0, mWidth, mHeight); img->ImageUpdated(nsnull, nsImageUpdateFlags_kBitsChanged, &r); mDirty = PR_FALSE; } return NS_OK; } // // 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() { mSaveCount++; if (mSaveCount < STYLE_STACK_DEPTH) { for (PRInt32 i = 0; i < STYLE_MAX; i++) { mColorStyles[mSaveCount][i] = mColorStyles[mSaveCount-1][i]; mGradientStyles[mSaveCount][i] = mGradientStyles[mSaveCount-1][i]; mPatternStyles[mSaveCount][i] = mPatternStyles[mSaveCount-1][i]; } } cairo_save (mCairo); return NS_OK; } NS_IMETHODIMP nsCanvasRenderingContext2D::Restore() { if (mSaveCount <= 0) return NS_ERROR_DOM_INVALID_STATE_ERR; if (mSaveCount < STYLE_STACK_DEPTH) { for (PRInt32 i = 0; i < STYLE_MAX; i++) { mColorStyles[mSaveCount-1][i] = mColorStyles[mSaveCount][i]; mGradientStyles[mSaveCount-1][i] = mGradientStyles[mSaveCount][i]; mPatternStyles[mSaveCount-1][i] = mPatternStyles[mSaveCount][i]; mGradientStyles[mSaveCount][i] = nsnull; mPatternStyles[mSaveCount][i] = nsnull; } mLastStyle = -1; } mSaveCount--; cairo_restore (mCairo); 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; } // // colors // NS_IMETHODIMP nsCanvasRenderingContext2D::SetGlobalAlpha(float aGlobalAlpha) { if (!FloatValidate(aGlobalAlpha)) return NS_ERROR_DOM_SYNTAX_ERR; mGlobalAlpha = aGlobalAlpha; return NS_OK; } NS_IMETHODIMP nsCanvasRenderingContext2D::GetGlobalAlpha(float *aGlobalAlpha) { *aGlobalAlpha = mGlobalAlpha; return NS_OK; } NS_IMETHODIMP nsCanvasRenderingContext2D::SetStrokeStyle(nsIVariant* aStyle) { if (StyleVariantToColor(aStyle, STYLE_STROKE)) return NS_OK; return NS_ERROR_INVALID_ARG; } NS_IMETHODIMP nsCanvasRenderingContext2D::GetStrokeStyle(nsIVariant** aStyle) { nsresult rv; nsCOMPtr var = do_CreateInstance("@mozilla.org/variant;1"); if (!var) return NS_ERROR_FAILURE; rv = var->SetWritable(PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); if (mPatternStyles[STYLE_CURRENT_STACK][STYLE_STROKE]) { rv = var->SetAsISupports(mPatternStyles[STYLE_CURRENT_STACK][STYLE_STROKE]); NS_ENSURE_SUCCESS(rv, rv); } else if (mGradientStyles[STYLE_CURRENT_STACK][STYLE_STROKE]) { rv = var->SetAsISupports(mGradientStyles[STYLE_CURRENT_STACK][STYLE_STROKE]); NS_ENSURE_SUCCESS(rv, rv); } else { nsString styleStr; StyleColorToString(mColorStyles[STYLE_CURRENT_STACK][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) { if (StyleVariantToColor(aStyle, STYLE_FILL)) return NS_OK; return NS_ERROR_INVALID_ARG; } NS_IMETHODIMP nsCanvasRenderingContext2D::GetFillStyle(nsIVariant** aStyle) { nsresult rv; nsCOMPtr var = do_CreateInstance("@mozilla.org/variant;1"); if (!var) return NS_ERROR_FAILURE; rv = var->SetWritable(PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); if (mPatternStyles[STYLE_CURRENT_STACK][STYLE_FILL]) { rv = var->SetAsISupports(mPatternStyles[STYLE_CURRENT_STACK][STYLE_FILL]); NS_ENSURE_SUCCESS(rv, rv); } else if (mGradientStyles[STYLE_CURRENT_STACK][STYLE_FILL]) { rv = var->SetAsISupports(mGradientStyles[STYLE_CURRENT_STACK][STYLE_FILL]); NS_ENSURE_SUCCESS(rv, rv); } else { nsString styleStr; StyleColorToString(mColorStyles[STYLE_CURRENT_STACK][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(nsIDOMHTMLImageElement *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 { return NS_ERROR_DOM_SYNTAX_ERR; } cairo_surface_t *imgSurf = nsnull; PRUint8 *imgData = nsnull; PRInt32 imgWidth, imgHeight; rv = CairoSurfaceFromElement(image, &imgSurf, &imgData, &imgWidth, &imgHeight); 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); 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; 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; 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; return NS_OK; } NS_IMETHODIMP nsCanvasRenderingContext2D::GetShadowBlur(float *blur) { *blur = 0.0f; return NS_OK; } NS_IMETHODIMP nsCanvasRenderingContext2D::SetShadowColor(const nsAString& color) { return NS_OK; } NS_IMETHODIMP nsCanvasRenderingContext2D::GetShadowColor(nsAString& color) { StyleColorToString(mColorStyles[STYLE_CURRENT_STACK][STYLE_SHADOW], color); 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_set_source_rgba (mCairo, 0.0, 0.0, 0.0, 0.0); 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; cairo_get_current_point(mCairo, &cx, &cy); if (cairo_status(mCairo)) return NS_ERROR_INVALID_ARG; 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) { return NS_ERROR_NOT_IMPLEMENTED; } 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 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 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; } // // 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 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 imgElt; if (!ConvertJSValToXPCObject(getter_AddRefs(imgElt), NS_GET_IID(nsIDOMElement), ctx, argv[0])) return NS_ERROR_DOM_TYPE_MISMATCH_ERR; { nsCOMPtr image = do_QueryInterface(imgElt); nsCOMPtr canvas = do_QueryInterface(imgElt); if (!image && !canvas) return NS_ERROR_DOM_TYPE_MISMATCH_ERR; } cairo_surface_t *imgSurf = nsnull; PRUint8 *imgData = nsnull; PRInt32 imgWidth, imgHeight; rv = CairoSurfaceFromElement(imgElt, &imgSurf, &imgData, &imgWidth, &imgHeight); if (NS_FAILED(rv)) return rv; #define GET_ARG(dest,whicharg) \ if (!ConvertJSValToDouble(dest, ctx, whicharg)) return NS_ERROR_INVALID_ARG 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 { return NS_ERROR_INVALID_ARG; } #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) { return NS_ERROR_DOM_INDEX_SIZE_ERR; } cairo_matrix_t surfMat; cairo_matrix_init_translate(&surfMat, sx, sy); cairo_matrix_scale(&surfMat, sw/dw, sh/dh); cairo_pattern_t* pat = cairo_pattern_create_for_surface(imgSurf); cairo_pattern_set_matrix(pat, &surfMat); cairo_save(mCairo); cairo_translate(mCairo, dx, dy); cairo_rectangle(mCairo, 0, 0, dw, dh); cairo_set_source(mCairo, pat); cairo_clip(mCairo); cairo_paint_with_alpha(mCairo, mGlobalAlpha); cairo_restore(mCairo); cairo_pattern_destroy(pat); nsMemory::Free(imgData); cairo_surface_destroy(imgSurf); return Redraw(); } 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", SOURCE) else CANVAS_OP_TO_CAIRO_OP("xor", XOR) 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", SOURCE) else CANVAS_OP_TO_CAIRO_OP("xor", XOR) else CANVAS_OP_TO_CAIRO_OP("over", OVER) 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) { nsresult rv; nsCOMPtr imgContainer; nsCOMPtr imageLoader = do_QueryInterface(imgElt); if (imageLoader) { nsCOMPtr imgRequest; rv = imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(imgRequest)); NS_ENSURE_SUCCESS(rv, rv); if (!imgRequest) return NS_ERROR_NOT_AVAILABLE; rv = imgRequest->GetImage(getter_AddRefs(imgContainer)); NS_ENSURE_SUCCESS(rv, rv); } else { // maybe a canvas nsCOMPtr canvas = do_QueryInterface(imgElt); if (canvas) { // Ensure the canvas is up to date canvas->UpdateImageFrame(); canvas->GetCanvasImageContainer(getter_AddRefs(imgContainer)); } 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 frame; rv = imgContainer->GetCurrentFrame(getter_AddRefs(frame)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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; // // 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. We also need to understand // that with 1-bit alpha the data is actually RGB_A1, not RGB_A8, // no matter what the format says. #ifdef MOZ_WIDGET_GTK2 PRInt8 alphaDepth = img->GetAlphaDepth(); if (alphaDepth == 0) { if (format == gfxIFormats::RGB_A8) format = gfxIFormats::RGB; else if (format == gfxIFormats::BGR_A8) format = gfxIFormats::BGR; } else if (alphaDepth == 1) { if (format == gfxIFormats::RGB_A8) format = gfxIFormats::RGB_A1; else if (format == gfxIFormats::BGR_A8) format = gfxIFormats::BGR_A1; } #endif // We probably don't need the GetHasAlphaMask check here, because it // should've been handled up above, but as this patch is going into // the release branch I don't want to change this code unnecessarily. 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 alpha to premultiply PRInt32 bit = i % 8; PRInt32 byte = i / 8; PRUint8 a = (inrowalpha[byte] >> bit) & 1; #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; return NS_OK; } static 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; } 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) nsCOMPtr ssm = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); if (!ssm) return NS_ERROR_FAILURE; PRBool isTrusted = PR_FALSE; PRBool isChrome = PR_FALSE; PRBool hasCap = PR_FALSE; // The secman really should handle UniversalXPConnect case, since that // should include UniversalBrowserRead... doesn't right now, though. if ((NS_SUCCEEDED(ssm->SubjectPrincipalIsSystem(&isChrome)) && isChrome) || (NS_SUCCEEDED(ssm->IsCapabilityEnabled("UniversalBrowserRead", &hasCap)) && hasCap) || (NS_SUCCEEDED(ssm->IsCapabilityEnabled("UniversalXPConnect", &hasCap)) && hasCap)) { isTrusted = PR_TRUE; } if (!isTrusted) { // not permitted to use DrawWindow return NS_ERROR_DOM_SECURITY_ERR; } nsCOMPtr presContext; nsCOMPtr sgo = do_QueryInterface(aWindow); if (sgo) { nsIDocShell* docshell = sgo->GetDocShell(); if (docshell) { docshell->GetPresContext(getter_AddRefs(presContext)); } } if (!presContext) return NS_ERROR_FAILURE; // Dig down past the viewport scroll stuff nsIViewManager* vm = presContext->GetViewManager(); nsIView* view; vm->GetRootView(view); NS_ASSERTION(view, "Must have root view!"); nscolor bgColor; nsresult rv = mCSSParser->ParseColorString(PromiseFlatString(aBGColor), nsnull, 0, PR_TRUE, &bgColor); NS_ENSURE_SUCCESS(rv, rv); float p2t = presContext->PixelsToTwips(); nsRect r(aX, aY, aW, aH); r.ScaleRoundOut(p2t); nsCOMPtr blackCtx; rv = vm->RenderOffscreen(view, r, PR_FALSE, PR_TRUE, NS_ComposeColors(NS_RGB(0, 0, 0), bgColor), getter_AddRefs(blackCtx)); 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 whiteCtx; rv = vm->RenderOffscreen(view, r, PR_FALSE, PR_TRUE, NS_ComposeColors(NS_RGB(255, 255, 255), bgColor), getter_AddRefs(whiteCtx)); 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); 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) { if (!mImageFrame) { NS_ERROR("Must have image frame already"); return NS_ERROR_FAILURE; } // 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 alphas; nsresult rv; if (aWhiteSurface) { // There is transparency. Use the blender to recover alphas. nsCOMPtr blender = do_CreateInstance(kBlenderCID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsIDeviceContext* dc = nsnull; aBlackContext->GetDeviceContext(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 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 // 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) { PRUint8* src = data + i*rowSpan; 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, mGlobalAlpha); cairo_surface_destroy(tmpSurf); aBlackSurface->Unlock(); return Redraw(); }