From 79e107f091eec7e83588406ca0a247d4908e9734 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 29 Jun 2011 14:34:58 -0700 Subject: [PATCH] Bug 664884, parts 1-4: Implement mozCurrentTransform/mozCurrentTransformInverse attributes for manipulating the canvas CTM. r=joe,roc sr=vlad --- content/canvas/src/CanvasUtils.cpp | 126 +++++++++++++++++- content/canvas/src/CanvasUtils.h | 102 ++++++++++---- .../canvas/src/nsCanvasRenderingContext2D.cpp | 99 ++++++++------ .../src/nsCanvasRenderingContext2DAzure.cpp | 112 +++++++++------- .../canvas/nsIDOMCanvasRenderingContext2D.idl | 4 + 5 files changed, 327 insertions(+), 116 deletions(-) diff --git a/content/canvas/src/CanvasUtils.cpp b/content/canvas/src/CanvasUtils.cpp index 4cfd2c00e9e..983ce906964 100644 --- a/content/canvas/src/CanvasUtils.cpp +++ b/content/canvas/src/CanvasUtils.cpp @@ -57,13 +57,15 @@ #include "nsTArray.h" #include "CanvasUtils.h" +#include "mozilla/gfx/Matrix.h" -using namespace mozilla; +namespace mozilla { +namespace CanvasUtils { void -CanvasUtils::DoDrawImageSecurityCheck(nsHTMLCanvasElement *aCanvasElement, - nsIPrincipal *aPrincipal, - PRBool forceWriteOnly) +DoDrawImageSecurityCheck(nsHTMLCanvasElement *aCanvasElement, + nsIPrincipal *aPrincipal, + PRBool forceWriteOnly) { // Callers should ensure that mCanvasElement is non-null before calling this if (!aCanvasElement) { @@ -96,7 +98,7 @@ CanvasUtils::DoDrawImageSecurityCheck(nsHTMLCanvasElement *aCanvasElement, } void -CanvasUtils::LogMessage (const nsCString& errorString) +LogMessage (const nsCString& errorString) { nsCOMPtr console(do_GetService(NS_CONSOLESERVICE_CONTRACTID)); if (!console) @@ -107,7 +109,7 @@ CanvasUtils::LogMessage (const nsCString& errorString) } void -CanvasUtils::LogMessagef (const char *fmt, ...) +LogMessagef (const char *fmt, ...) { va_list ap; va_start(ap, fmt); @@ -122,3 +124,115 @@ CanvasUtils::LogMessagef (const char *fmt, ...) va_end(ap); } + +bool +CoerceDouble(jsval v, double* d) +{ + if (JSVAL_IS_DOUBLE(v)) { + *d = JSVAL_TO_DOUBLE(v); + } else if (JSVAL_IS_INT(v)) { + *d = double(JSVAL_TO_INT(v)); + } else if (JSVAL_IS_VOID(v)) { + *d = 0.0; + } else { + return false; + } + return true; +} + +template +static bool +JSValToMatrixElts(JSContext* cx, const jsval& val, + double* (&elts)[N], nsresult* rv) +{ + JSObject* obj; + jsuint length; + + if (JSVAL_IS_PRIMITIVE(val) || + !(obj = JSVAL_TO_OBJECT(val)) || + !JS_GetArrayLength(cx, obj, &length) || + N != length) { + // Not an array-like thing or wrong size + *rv = NS_ERROR_INVALID_ARG; + return false; + } + + for (PRUint32 i = 0; i < N; ++i) { + jsval elt; + double d; + if (!JS_GetElement(cx, obj, i, &elt)) { + *rv = NS_ERROR_FAILURE; + return false; + } + if (!CoerceDouble(elt, &d)) { + *rv = NS_ERROR_INVALID_ARG; + return false; + } + if (!FloatValidate(d)) { + // This is weird, but it's the behavior of SetTransform() + *rv = NS_OK; + return false; + } + *elts[i] = d; + } + + *rv = NS_OK; + return true; +} + +bool +JSValToMatrix(JSContext* cx, const jsval& val, gfxMatrix* matrix, nsresult* rv) +{ + double* elts[] = { &matrix->xx, &matrix->yx, &matrix->xy, &matrix->yy, + &matrix->x0, &matrix->y0 }; + return JSValToMatrixElts(cx, val, elts, rv); +} + +bool +JSValToMatrix(JSContext* cx, const jsval& val, Matrix* matrix, nsresult* rv) +{ + gfxMatrix m; + if (!JSValToMatrix(cx, val, &m, rv)) + return false; + *matrix = Matrix(m.xx, m.yx, m.xy, m.yy, m.x0, m.y0); + return true; +} + +template +static nsresult +MatrixEltsToJSVal(/*const*/ jsval (&elts)[N], JSContext* cx, jsval* val) +{ + JSObject* obj = JS_NewArrayObject(cx, N, elts); + if (!obj) { + return NS_ERROR_OUT_OF_MEMORY; + } + + *val = OBJECT_TO_JSVAL(obj); + + return NS_OK; +} + +nsresult +MatrixToJSVal(const gfxMatrix& matrix, JSContext* cx, jsval* val) +{ + jsval elts[] = { + DOUBLE_TO_JSVAL(matrix.xx), DOUBLE_TO_JSVAL(matrix.yx), + DOUBLE_TO_JSVAL(matrix.xy), DOUBLE_TO_JSVAL(matrix.yy), + DOUBLE_TO_JSVAL(matrix.x0), DOUBLE_TO_JSVAL(matrix.y0) + }; + return MatrixEltsToJSVal(elts, cx, val); +} + +nsresult +MatrixToJSVal(const Matrix& matrix, JSContext* cx, jsval* val) +{ + jsval elts[] = { + DOUBLE_TO_JSVAL(matrix._11), DOUBLE_TO_JSVAL(matrix._12), + DOUBLE_TO_JSVAL(matrix._21), DOUBLE_TO_JSVAL(matrix._22), + DOUBLE_TO_JSVAL(matrix._31), DOUBLE_TO_JSVAL(matrix._32) + }; + return MatrixEltsToJSVal(elts, cx, val); +} + +} // namespace CanvasUtils +} // namespace mozilla diff --git a/content/canvas/src/CanvasUtils.h b/content/canvas/src/CanvasUtils.h index e9087f64379..d1ec418dbcf 100644 --- a/content/canvas/src/CanvasUtils.h +++ b/content/canvas/src/CanvasUtils.h @@ -47,37 +47,91 @@ class nsIPrincipal; namespace mozilla { -class CanvasUtils { -public: - // Check that the rectangle [x,y,w,h] is a subrectangle of [0,0,realWidth,realHeight] +namespace gfx { +class Matrix; +} - static PRBool CheckSaneSubrectSize(PRInt32 x, PRInt32 y, PRInt32 w, PRInt32 h, - PRInt32 realWidth, PRInt32 realHeight) { - CheckedInt32 checked_x_plus_w = CheckedInt32(x) + w; - CheckedInt32 checked_y_plus_h = CheckedInt32(y) + h; +namespace CanvasUtils { - return w >= 0 && h >= 0 && x >= 0 && y >= 0 && - checked_x_plus_w.valid() && - checked_x_plus_w.value() <= realWidth && - checked_y_plus_h.valid() && - checked_y_plus_h.value() <= realHeight; - } +using namespace gfx; - // Flag aCanvasElement as write-only if drawing an image with aPrincipal - // onto it would make it such. +// Check that the rectangle [x,y,w,h] is a subrectangle of [0,0,realWidth,realHeight] - static void DoDrawImageSecurityCheck(nsHTMLCanvasElement *aCanvasElement, - nsIPrincipal *aPrincipal, - PRBool forceWriteOnly); +inline PRBool CheckSaneSubrectSize(PRInt32 x, PRInt32 y, PRInt32 w, PRInt32 h, + PRInt32 realWidth, PRInt32 realHeight) { + CheckedInt32 checked_xmost = CheckedInt32(x) + w; + CheckedInt32 checked_ymost = CheckedInt32(y) + h; - static void LogMessage (const nsCString& errorString); - static void LogMessagef (const char *fmt, ...); + return w >= 0 && h >= 0 && x >= 0 && y >= 0 && + checked_xmost.valid() && + checked_xmost.value() <= realWidth && + checked_ymost.valid() && + checked_ymost.value() <= realHeight; +} -private: - // this can't be instantiated - CanvasUtils() { } -}; +// Flag aCanvasElement as write-only if drawing an image with aPrincipal +// onto it would make it such. +void DoDrawImageSecurityCheck(nsHTMLCanvasElement *aCanvasElement, + nsIPrincipal *aPrincipal, + PRBool forceWriteOnly); + +void LogMessage (const nsCString& errorString); +void LogMessagef (const char *fmt, ...); + +// Make a double out of |v|, treating undefined values as 0.0 (for +// the sake of sparse arrays). Return true iff coercion +// succeeded. +bool CoerceDouble(jsval v, double* d); + +// Return true iff the conversion succeeded, false otherwise. *rv is +// the value to return to script if this returns false. +bool JSValToMatrix(JSContext* cx, const jsval& val, + gfxMatrix* matrix, nsresult* rv); +bool JSValToMatrix(JSContext* cx, const jsval& val, + Matrix* matrix, nsresult* rv); + +nsresult MatrixToJSVal(const gfxMatrix& matrix, + JSContext* cx, jsval* val); +nsresult MatrixToJSVal(const Matrix& matrix, + JSContext* cx, jsval* val); + + /* Float validation stuff */ +#define VALIDATE(_f) if (!NS_finite(_f)) return PR_FALSE + +inline PRBool FloatValidate (double f1) { + VALIDATE(f1); + return PR_TRUE; +} + +inline PRBool FloatValidate (double f1, double f2) { + VALIDATE(f1); VALIDATE(f2); + return PR_TRUE; +} + +inline PRBool FloatValidate (double f1, double f2, double f3) { + VALIDATE(f1); VALIDATE(f2); VALIDATE(f3); + return PR_TRUE; +} + +inline PRBool FloatValidate (double f1, double f2, double f3, double f4) { + VALIDATE(f1); VALIDATE(f2); VALIDATE(f3); VALIDATE(f4); + return PR_TRUE; +} + +inline 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; +} + +inline 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 + +} } #endif /* _CANVASUTILS_H_ */ diff --git a/content/canvas/src/nsCanvasRenderingContext2D.cpp b/content/canvas/src/nsCanvasRenderingContext2D.cpp index ab35bb47536..c1552757498 100644 --- a/content/canvas/src/nsCanvasRenderingContext2D.cpp +++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp @@ -119,51 +119,16 @@ // windows.h (included by chromium code) defines this, in its infinite wisdom #undef DrawText -using namespace mozilla::ipc; - using namespace mozilla; -using namespace mozilla::layers; +using namespace mozilla::CanvasUtils; using namespace mozilla::dom; +using namespace mozilla::ipc; +using namespace mozilla::layers; static float kDefaultFontSize = 10.0; static NS_NAMED_LITERAL_STRING(kDefaultFontName, "sans-serif"); static NS_NAMED_LITERAL_STRING(kDefaultFontStyle, "10px sans-serif"); -/* Float validation stuff */ -#define VALIDATE(_f) if (!NS_finite(_f)) return PR_FALSE - -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 - /* Memory reporter stuff */ static nsIMemoryReporter *gCanvasMemoryReporter = nsnull; static PRInt64 gCanvasMemoryUsed = 0; @@ -1436,6 +1401,64 @@ nsCanvasRenderingContext2D::SetTransform(float m11, float m12, float m21, float return NS_OK; } +NS_IMETHODIMP +nsCanvasRenderingContext2D::SetMozCurrentTransform(JSContext* cx, + const jsval& matrix) +{ + nsresult rv; + gfxMatrix newCTM; + + if (!JSValToMatrix(cx, matrix, &newCTM, &rv)) { + return rv; + } + + mThebes->SetMatrix(newCTM); + + return NS_OK; +} + +NS_IMETHODIMP +nsCanvasRenderingContext2D::GetMozCurrentTransform(JSContext* cx, + jsval* matrix) +{ + return MatrixToJSVal(mThebes->CurrentMatrix(), cx, matrix); +} + +NS_IMETHODIMP +nsCanvasRenderingContext2D::SetMozCurrentTransformInverse(JSContext* cx, + const jsval& matrix) +{ + nsresult rv; + gfxMatrix newCTMInverse; + + if (!JSValToMatrix(cx, matrix, &newCTMInverse, &rv)) { + return rv; + } + + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + if (!newCTMInverse.IsSingular()) { + mThebes->SetMatrix(newCTMInverse.Invert()); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCanvasRenderingContext2D::GetMozCurrentTransformInverse(JSContext* cx, + jsval* matrix) +{ + gfxMatrix ctm = mThebes->CurrentMatrix(); + + if (!mThebes->CurrentMatrix().IsSingular()) { + ctm.Invert(); + } else { + double NaN = JSVAL_TO_DOUBLE(JS_GetNaNValue(cx)); + ctm = gfxMatrix(NaN, NaN, NaN, NaN, NaN, NaN); + } + + return MatrixToJSVal(ctm, cx, matrix); +} + // // colors // diff --git a/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp b/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp index f4793d499d6..68333f25981 100644 --- a/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp +++ b/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp @@ -127,11 +127,12 @@ #undef DrawText using namespace mozilla; -using namespace mozilla::layers; +using namespace mozilla::CanvasUtils; +using namespace mozilla::css; using namespace mozilla::dom; using namespace mozilla::gfx; using namespace mozilla::ipc; -using namespace mozilla::css; +using namespace mozilla::layers; namespace mgfx = mozilla::gfx; @@ -139,41 +140,6 @@ static float kDefaultFontSize = 10.0; static NS_NAMED_LITERAL_STRING(kDefaultFontName, "sans-serif"); static NS_NAMED_LITERAL_STRING(kDefaultFontStyle, "10px sans-serif"); -/* Float validation stuff */ -#define VALIDATE(_f) if (!NS_finite(_f)) return PR_FALSE - -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 - /* Memory reporter stuff */ static nsIMemoryReporter *gCanvasAzureMemoryReporter = nsnull; static PRInt64 gCanvasAzureMemoryUsed = 0; @@ -1625,6 +1591,62 @@ nsCanvasRenderingContext2DAzure::SetTransform(float m11, float m12, float m21, f return NS_OK; } +NS_IMETHODIMP +nsCanvasRenderingContext2DAzure::SetMozCurrentTransform(JSContext* cx, + const jsval& matrix) +{ + nsresult rv; + Matrix newCTM; + + if (!JSValToMatrix(cx, matrix, &newCTM, &rv)) { + return rv; + } + + mTarget->SetTransform(newCTM); + + return NS_OK; +} + +NS_IMETHODIMP +nsCanvasRenderingContext2DAzure::GetMozCurrentTransform(JSContext* cx, + jsval* matrix) +{ + return MatrixToJSVal(mTarget->GetTransform(), cx, matrix); +} + +NS_IMETHODIMP +nsCanvasRenderingContext2DAzure::SetMozCurrentTransformInverse(JSContext* cx, + const jsval& matrix) +{ + nsresult rv; + Matrix newCTMInverse; + + if (!JSValToMatrix(cx, matrix, &newCTMInverse, &rv)) { + return rv; + } + + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + if (newCTMInverse.Invert()) { + mTarget->SetTransform(newCTMInverse); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCanvasRenderingContext2DAzure::GetMozCurrentTransformInverse(JSContext* cx, + jsval* matrix) +{ + Matrix ctm = mTarget->GetTransform(); + + if (!ctm.Invert()) { + double NaN = JSVAL_TO_DOUBLE(JS_GetNaNValue(cx)); + ctm = Matrix(NaN, NaN, NaN, NaN, NaN, NaN); + } + + return MatrixToJSVal(ctm, cx, matrix); +} + // // colors // @@ -2050,8 +2072,6 @@ nsCanvasRenderingContext2DAzure::FillRect(float x, float y, float w, float h) return NS_OK; } - bool doDrawShadow = NeedToDrawShadow(); - const ContextState &state = CurrentState(); if (state.patternStyles[STYLE_FILL]) { @@ -2434,8 +2454,6 @@ nsCanvasRenderingContext2DAzure::Arc(float x, float y, // Calculate the total arc we're going to sweep. Float arcSweepLeft = abs(endAngle - startAngle); - // Calculate the amount of curves needed, 1 per quarter circle. - Float curves = ceil(arcSweepLeft / (M_PI / 2.0f)); Float sweepDirection = ccw ? -1.0f : 1.0f; @@ -3017,7 +3035,7 @@ struct NS_STACK_CLASS nsCanvasBidiProcessorAzure : public nsBidiPresUtils::BidiP Point baselineOrigin = Point(point.x * devUnitsPerAppUnit, point.y * devUnitsPerAppUnit); - for (int c = 0; c < numRuns; c++) { + for (PRUint32 c = 0; c < numRuns; c++) { gfxFont *font = runs[c].mFont; PRUint32 endRun = 0; if (c + 1 < numRuns) { @@ -3037,7 +3055,7 @@ struct NS_STACK_CLASS nsCanvasBidiProcessorAzure : public nsBidiPresUtils::BidiP float advanceSum = 0; - for (int i = runs[c].mCharacterOffset; i < endRun; i++) { + for (PRUint32 i = runs[c].mCharacterOffset; i < endRun; i++) { Glyph newGlyph; if (glyphs[i].IsSimpleGlyph()) { newGlyph.mIndex = glyphs[i].GetSimpleGlyph(); @@ -3060,7 +3078,7 @@ struct NS_STACK_CLASS nsCanvasBidiProcessorAzure : public nsBidiPresUtils::BidiP gfxTextRun::DetailedGlyph *detailedGlyphs = mTextRun->GetDetailedGlyphs(i); - for (int c = 0; c < glyphs[i].GetGlyphCount(); c++) { + for (PRUint32 c = 0; c < glyphs[i].GetGlyphCount(); c++) { newGlyph.mIndex = detailedGlyphs[c].mGlyphID; if (mTextRun->IsRightToLeft()) { newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit - @@ -4030,8 +4048,6 @@ nsCanvasRenderingContext2DAzure::GetImageData_explicit(PRInt32 x, PRInt32 y, PRU memset(aData, 0, aDataLen); } - bool finishedPainting = false; - IntRect srcReadRect = srcRect.Intersect(destRect); IntRect dstWriteRect = srcReadRect; dstWriteRect.MoveBy(-x, -y); @@ -4055,8 +4071,8 @@ nsCanvasRenderingContext2DAzure::GetImageData_explicit(PRInt32 x, PRInt32 y, PRU // from src and advancing that ptr before writing to dst. PRUint8 *dst = aData + dstWriteRect.y * (w * 4) + dstWriteRect.x * 4; - for (PRUint32 j = 0; j < dstWriteRect.height; j++) { - for (PRUint32 i = 0; i < dstWriteRect.width; i++) { + for (int j = 0; j < dstWriteRect.height; j++) { + for (int i = 0; i < dstWriteRect.width; i++) { // XXX Is there some useful swizzle MMX we can use here? #ifdef IS_LITTLE_ENDIAN PRUint8 b = *src++; diff --git a/dom/interfaces/canvas/nsIDOMCanvasRenderingContext2D.idl b/dom/interfaces/canvas/nsIDOMCanvasRenderingContext2D.idl index 2cc18ccd668..a2ad7a2cc54 100644 --- a/dom/interfaces/canvas/nsIDOMCanvasRenderingContext2D.idl +++ b/dom/interfaces/canvas/nsIDOMCanvasRenderingContext2D.idl @@ -79,6 +79,10 @@ interface nsIDOMCanvasRenderingContext2D : nsISupports void translate(in float x, in float y); void transform(in float m11, in float m12, in float m21, in float m22, in float dx, in float dy); void setTransform(in float m11, in float m12, in float m21, in float m22, in float dx, in float dy); + [implicit_jscontext] + attribute jsval mozCurrentTransform; // [ m11, m12, m21, m22, dx, dy ], i.e. row major + [implicit_jscontext] + attribute jsval mozCurrentTransformInverse; // compositing attribute float globalAlpha; /* default 1.0 -- opaque */