michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef GFX_MATRIX_H michael@0: #define GFX_MATRIX_H michael@0: michael@0: #include "gfxPoint.h" michael@0: #include "gfxTypes.h" michael@0: #include "gfxRect.h" michael@0: michael@0: // XX - I don't think this class should use gfxFloat at all, michael@0: // but should use 'double' and be called gfxDoubleMatrix; michael@0: // we can then typedef that to gfxMatrix where we typedef michael@0: // double to be gfxFloat. michael@0: michael@0: /** michael@0: * A matrix that represents an affine transformation. Projective michael@0: * transformations are not supported. This matrix looks like: michael@0: * michael@0: * / a b 0 \ michael@0: * | c d 0 | michael@0: * \ tx ty 1 / michael@0: * michael@0: * So, transforming a point (x, y) results in: michael@0: * michael@0: * / a b 0 \ / a * x + c * y + tx \ T michael@0: * (x y 1) * | c d 0 | = | b * x + d * y + ty | michael@0: * \ tx ty 1 / \ 1 / michael@0: * michael@0: */ michael@0: struct gfxMatrix { michael@0: double xx; double yx; michael@0: double xy; double yy; michael@0: double x0; double y0; michael@0: michael@0: public: michael@0: /** michael@0: * Initializes this matrix as the identity matrix. michael@0: */ michael@0: gfxMatrix() { Reset(); } michael@0: michael@0: /** michael@0: * Initializes the matrix from individual components. See the class michael@0: * description for the layout of the matrix. michael@0: */ michael@0: gfxMatrix(gfxFloat a, gfxFloat b, gfxFloat c, gfxFloat d, gfxFloat tx, gfxFloat ty) : michael@0: xx(a), yx(b), michael@0: xy(c), yy(d), michael@0: x0(tx), y0(ty) { } michael@0: michael@0: /** michael@0: * Post-multiplies m onto the matrix. michael@0: */ michael@0: const gfxMatrix& operator *= (const gfxMatrix& m) { michael@0: return Multiply(m); michael@0: } michael@0: michael@0: /** michael@0: * Multiplies *this with m and returns the result. michael@0: */ michael@0: gfxMatrix operator * (const gfxMatrix& m) const { michael@0: return gfxMatrix(*this).Multiply(m); michael@0: } michael@0: michael@0: /* Returns true if the other matrix is fuzzy-equal to this matrix. michael@0: * Note that this isn't a cheap comparison! michael@0: */ michael@0: bool operator==(const gfxMatrix& other) const michael@0: { michael@0: return FuzzyEqual(xx, other.xx) && FuzzyEqual(yx, other.yx) && michael@0: FuzzyEqual(xy, other.xy) && FuzzyEqual(yy, other.yy) && michael@0: FuzzyEqual(x0, other.x0) && FuzzyEqual(y0, other.y0); michael@0: } michael@0: michael@0: bool operator!=(const gfxMatrix& other) const michael@0: { michael@0: return !(*this == other); michael@0: } michael@0: michael@0: // matrix operations michael@0: /** michael@0: * Resets this matrix to the identity matrix. michael@0: */ michael@0: const gfxMatrix& Reset(); michael@0: michael@0: bool IsIdentity() const { michael@0: return xx == 1.0 && yx == 0.0 && michael@0: xy == 0.0 && yy == 1.0 && michael@0: x0 == 0.0 && y0 == 0.0; michael@0: } michael@0: michael@0: /** michael@0: * Inverts this matrix, if possible. Otherwise, the matrix is left michael@0: * unchanged. michael@0: * michael@0: * XXX should this do something with the return value of michael@0: * cairo_matrix_invert? michael@0: */ michael@0: const gfxMatrix& Invert(); michael@0: michael@0: /** michael@0: * Check if matrix is singular (no inverse exists). michael@0: */ michael@0: bool IsSingular() const { michael@0: // if the determinant (ad - bc) is zero it's singular michael@0: return (xx * yy) == (yx * xy); michael@0: } michael@0: michael@0: /** michael@0: * Scales this matrix. The scale is pre-multiplied onto this matrix, michael@0: * i.e. the scaling takes place before the other transformations. michael@0: */ michael@0: const gfxMatrix& Scale(gfxFloat x, gfxFloat y); michael@0: michael@0: /** michael@0: * Translates this matrix. The translation is pre-multiplied onto this matrix, michael@0: * i.e. the translation takes place before the other transformations. michael@0: */ michael@0: const gfxMatrix& Translate(const gfxPoint& pt); michael@0: michael@0: /** michael@0: * Rotates this matrix. The rotation is pre-multiplied onto this matrix, michael@0: * i.e. the translation takes place after the other transformations. michael@0: * michael@0: * @param radians Angle in radians. michael@0: */ michael@0: const gfxMatrix& Rotate(gfxFloat radians); michael@0: michael@0: /** michael@0: * Multiplies the current matrix with m. michael@0: * This is a post-multiplication, i.e. the transformations of m are michael@0: * applied _after_ the existing transformations. michael@0: * michael@0: * XXX is that difference (compared to Rotate etc) a good thing? michael@0: */ michael@0: const gfxMatrix& Multiply(const gfxMatrix& m); michael@0: michael@0: /** michael@0: * Multiplies the current matrix with m. michael@0: * This is a pre-multiplication, i.e. the transformations of m are michael@0: * applied _before_ the existing transformations. michael@0: */ michael@0: const gfxMatrix& PreMultiply(const gfxMatrix& m); michael@0: michael@0: /** michael@0: * Transforms a point according to this matrix. michael@0: */ michael@0: gfxPoint Transform(const gfxPoint& point) const; michael@0: michael@0: michael@0: /** michael@0: * Transform a distance according to this matrix. This does not apply michael@0: * any translation components. michael@0: */ michael@0: gfxSize Transform(const gfxSize& size) const; michael@0: michael@0: /** michael@0: * Transforms both the point and distance according to this matrix. michael@0: */ michael@0: gfxRect Transform(const gfxRect& rect) const; michael@0: michael@0: gfxRect TransformBounds(const gfxRect& rect) const; michael@0: michael@0: /** michael@0: * Returns the translation component of this matrix. michael@0: */ michael@0: gfxPoint GetTranslation() const { michael@0: return gfxPoint(x0, y0); michael@0: } michael@0: michael@0: /** michael@0: * Returns true if the matrix is anything other than a straight michael@0: * translation by integers. michael@0: */ michael@0: bool HasNonIntegerTranslation() const { michael@0: return HasNonTranslation() || michael@0: !FuzzyEqual(x0, floor(x0 + 0.5)) || michael@0: !FuzzyEqual(y0, floor(y0 + 0.5)); michael@0: } michael@0: michael@0: /** michael@0: * Returns true if the matrix has any transform other michael@0: * than a straight translation michael@0: */ michael@0: bool HasNonTranslation() const { michael@0: return !FuzzyEqual(xx, 1.0) || !FuzzyEqual(yy, 1.0) || michael@0: !FuzzyEqual(xy, 0.0) || !FuzzyEqual(yx, 0.0); michael@0: } michael@0: michael@0: /** michael@0: * Returns true if the matrix only has an integer translation. michael@0: */ michael@0: bool HasOnlyIntegerTranslation() const { michael@0: return !HasNonIntegerTranslation(); michael@0: } michael@0: michael@0: /** michael@0: * Returns true if the matrix has any transform other michael@0: * than a translation or a -1 y scale (y axis flip) michael@0: */ michael@0: bool HasNonTranslationOrFlip() const { michael@0: return !FuzzyEqual(xx, 1.0) || michael@0: (!FuzzyEqual(yy, 1.0) && !FuzzyEqual(yy, -1.0)) || michael@0: !FuzzyEqual(xy, 0.0) || !FuzzyEqual(yx, 0.0); michael@0: } michael@0: michael@0: /** michael@0: * Returns true if the matrix has any transform other michael@0: * than a translation or scale; this is, if there is michael@0: * no rotation. michael@0: */ michael@0: bool HasNonAxisAlignedTransform() const { michael@0: return !FuzzyEqual(xy, 0.0) || !FuzzyEqual(yx, 0.0); michael@0: } michael@0: michael@0: /** michael@0: * Computes the determinant of this matrix. michael@0: */ michael@0: double Determinant() const { michael@0: return xx*yy - yx*xy; michael@0: } michael@0: michael@0: /* Computes the scale factors of this matrix; that is, michael@0: * the amounts each basis vector is scaled by. michael@0: * The xMajor parameter indicates if the larger scale is michael@0: * to be assumed to be in the X direction or not. michael@0: */ michael@0: gfxSize ScaleFactors(bool xMajor) const { michael@0: double det = Determinant(); michael@0: michael@0: if (det == 0.0) michael@0: return gfxSize(0.0, 0.0); michael@0: michael@0: gfxSize sz = xMajor ? gfxSize(1.0, 0.0) : gfxSize(0.0, 1.0); michael@0: sz = Transform(sz); michael@0: michael@0: double major = sqrt(sz.width * sz.width + sz.height * sz.height); michael@0: double minor = 0.0; michael@0: michael@0: // ignore mirroring michael@0: if (det < 0.0) michael@0: det = - det; michael@0: michael@0: if (major) michael@0: minor = det / major; michael@0: michael@0: if (xMajor) michael@0: return gfxSize(major, minor); michael@0: michael@0: return gfxSize(minor, major); michael@0: } michael@0: michael@0: /** michael@0: * Snap matrix components that are close to integers michael@0: * to integers. In particular, components that are integral when michael@0: * converted to single precision are set to those integers. michael@0: */ michael@0: void NudgeToIntegers(void); michael@0: michael@0: /** michael@0: * Returns true if matrix is multiple of 90 degrees rotation with flipping, michael@0: * scaling and translation. michael@0: */ michael@0: bool PreservesAxisAlignedRectangles() const { michael@0: return ((FuzzyEqual(xx, 0.0) && FuzzyEqual(yy, 0.0)) michael@0: || (FuzzyEqual(xy, 0.0) && FuzzyEqual(yx, 0.0))); michael@0: } michael@0: michael@0: /** michael@0: * Returns true if the matrix has non-integer scale michael@0: */ michael@0: bool HasNonIntegerScale() const { michael@0: return !FuzzyEqual(xx, floor(xx + 0.5)) || michael@0: !FuzzyEqual(yy, floor(yy + 0.5)); michael@0: } michael@0: michael@0: private: michael@0: static bool FuzzyEqual(gfxFloat aV1, gfxFloat aV2) { michael@0: return fabs(aV2 - aV1) < 1e-6; michael@0: } michael@0: }; michael@0: michael@0: #endif /* GFX_MATRIX_H */