Bug 664884, parts 1-4: Implement mozCurrentTransform/mozCurrentTransformInverse attributes for manipulating the canvas CTM. r=joe,roc sr=vlad

This commit is contained in:
Chris Jones 2011-06-29 14:34:58 -07:00
parent 844412f551
commit 82ed6117fd
5 changed files with 327 additions and 116 deletions

View File

@ -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<nsIConsoleService> 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<size_t N>
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<size_t N>
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

View File

@ -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_ */

View File

@ -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
//

View File

@ -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++;

View File

@ -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 */