|
|
/**************************************************************************\
* * Copyright (c) 1998 Microsoft Corporation * * Module Name: * * Matrix.cpp * * Abstract: * * Implementation of matrix class * * Revision History: * * 12/02/1998 davidx * Created it. * \**************************************************************************/
#include "precomp.hpp"
/**************************************************************************\
* * Function Description: * * Infer an affine transformation matrix * from a rectangle-to-rectangle mapping * * Arguments: * * [IN] destRect - Specifies the destination rectangle * [IN] srcRect - Specifies the source rectangle * * Return Value: * * GpStatus - Ok or failure status * * Created: * * 3/10/1999 DCurtis * \**************************************************************************/
GpStatus GpMatrix::InferAffineMatrix( const GpRectF & destRect, const GpRectF & srcRect ) { REAL srcLeft = srcRect.X; REAL srcRight = srcRect.GetRight(); REAL srcTop = srcRect.Y; REAL srcBottom = srcRect.GetBottom(); REAL destLeft = destRect.X; REAL destRight = destRect.GetRight(); REAL destTop = destRect.Y; REAL destBottom = destRect.GetBottom();
if ((srcLeft == srcRight) || (srcTop == srcBottom)) { return InvalidParameter; }
M12 = 0; M21 = 0; M11 = (destRight - destLeft) / (srcRight - srcLeft); M22 = (destBottom - destTop) / (srcBottom - srcTop); Dx = destRight - (M11 * srcRight); Dy = destBottom - (M22 * srcBottom);
Complexity = ComputeComplexity(); return Ok; }
/**************************************************************************\
* * Function Description: * * Infer an affine transformation matrix * from a rectangle-to-parallelogram mapping * * Arguments: * * [IN] rect - Specifies the source rectangle * [IN] destPoints - Specifies the destination parallelogram * The array must contain at least 3 points. * destPoints[0] <=> top-left corner of the source rectangle * destPoints[1] <=> top-right corner * destPoints[2] <=> bottom-left corner * * Return Value: * * Status code (error when 3 points for the destination * parallelogram is colinear). * * Reference: * * Digital Image Warping * by George Wolberg * pp. 50-51 * \**************************************************************************/
GpStatus GpMatrix::InferAffineMatrix( const GpPointF* destPoints, const GpRectF& srcRect ) { REAL x0, y0, x1, y1, x2, y2; REAL u0, v0, u1, v1, u2, v2; REAL d;
x0 = destPoints[0].X; y0 = destPoints[0].Y; x1 = destPoints[1].X; y1 = destPoints[1].Y; x2 = destPoints[2].X; y2 = destPoints[2].Y;
u0 = srcRect.X; v0 = srcRect.Y; u1 = u0 + srcRect.Width; v1 = v0; u2 = u0; v2 = v0 + srcRect.Height;
d = u0*(v1-v2) - v0*(u1-u2) + (u1*v2-u2*v1);
if (REALABS(d) < REAL_EPSILON) { WARNING(("Colinear points in inferAffineMatrix")); return InvalidParameter; } d = TOREAL(1.0) / d;
REAL t0, t1, t2;
t0 = v1-v2; t1 = v2-v0; t2 = v0-v1; M11 = d * (x0*t0 + x1*t1 + x2*t2); M12 = d * (y0*t0 + y1*t1 + y2*t2);
t0 = u2-u1; t1 = u0-u2; t2 = u1-u0; M21 = d * (x0*t0 + x1*t1 + x2*t2); M22 = d * (y0*t0 + y1*t1 + y2*t2);
t0 = u1*v2-u2*v1; t1 = u2*v0-u0*v2; t2 = u2*v1-u1*v0; Dx = d * (x0*t0 + x1*t1 + x2*t2); Dy = d * (y0*t0 + y1*t1 + y2*t2);
Complexity = ComputeComplexity(); return Ok; }
GpMatrix::GpMatrix( const GpPointF* destPoints, const GpRectF& srcRect ) { // !!!
// Should we throw an exception if inferAffineMatrix fails?
SetValid(InferAffineMatrix(destPoints, srcRect) == Ok); }
/**************************************************************************\
* * Function Description: * * Invert the matrix (in place) * * Arguments: * * NONE * * Return Value: * * Status code (error if the matrix is not invertible) * * Reference: * * Digital Image Warping * by George Wolberg * pp. 52-53 * \**************************************************************************/
GpStatus GpMatrix::Invert() { if(IsIdentity()) { // Invert the identity matrix - this is easy.
return Ok; } if (!IsInvertible()) { WARNING(("Matrix is non-invertible")); return InvalidParameter; }
REAL t11, t12, t21, t22, tx, ty; REAL d = (M11*M22 - M12*M21); d = TOREAL(1.0) / d;
t11 = M22; t12 = -M12; t21 = -M21; t22 = M11; tx = M21*Dy - M22*Dx; ty = M12*Dx - M11*Dy;
M11 = d*t11; M12 = d*t12; M21 = d*t21; M22 = d*t22; Dx = d*tx; Dy = d*ty;
Complexity = ComputeComplexity(); return Ok; }
/**************************************************************************\
* * Function Description: * * Prepend or append a scale matrix to the current matrix, i.e. * * | scaleX 0 0 | * m = | 0 scaleY 0 | * | 0 0 1 | * * matrix = m * matrix // for prepend
* matrix = matrix * m // for append
* * Arguments: * * scaleX - scale factor along x-axis * scaleY - scale factor along y-axis * order - prepend or append. * * Return Value: * * NONE * \**************************************************************************/
VOID GpMatrix::Scale( REAL scaleX, REAL scaleY, GpMatrixOrder order ) { if (order == MatrixOrderPrepend) { M11 *= scaleX; M12 *= scaleX; M21 *= scaleY; M22 *= scaleY; } else // Append
{ M11 *= scaleX; M21 *= scaleX; M12 *= scaleY; M22 *= scaleY; Dx *= scaleX; Dy *= scaleY; }
// Scaling can magnify the error of other components.
// So it is safest to always recompute the complexity always.
Complexity = ComputeComplexity(); }
/**************************************************************************\
* * Function Description: * * Prepend or append a rotation matrix to the current matrix, i.e. * * | cos(angle) sin(angle) 0 | * m = | -sin(angle) cos(angle) 0 | * | 0 0 1 | * * matrix = m * matrix // for prepend
* matrix = matrix * m // for append
* * Arguments: * * angle - Specify the rotation angle * order - prepend or append. * * Return Value: * * NONE * \**************************************************************************/
#define PI 3.1415926535897932384626433832795
#define DEGREES_TO_RADIANS (PI / 180.0)
VOID GpMatrix::Rotate( REAL angle, GpMatrixOrder order ) { REAL s, c; REAL t11, t12, t21, t22;
angle *= (REAL)DEGREES_TO_RADIANS;
s = REALSIN(angle); c = REALCOS(angle);
if (order == MatrixOrderPrepend) { t11 = c*M11 + s*M21; t12 = c*M12 + s*M22; t21 = c*M21 - s*M11; t22 = c*M22 - s*M12; } else // Append
{ t11 = c*M11 - s*M12; t12 = s*M11 + c*M12; t21 = c*M21 - s*M22; t22 = s*M21 + c*M22;
REAL tx, ty; tx = c*Dx - s*Dy; ty = s*Dx + c*Dy; Dx = tx; Dy = ty; }
M11 = t11; M12 = t12; M21 = t21; M22 = t22;
// Rotation is very complex; we choose to simply recalculate the
// complexity:
Complexity = ComputeComplexity(); }
/**************************************************************************\
* * Function Description: * * Prepend or append a translation matrix to the current matrix, i.e. * * | 1 0 0 | * m = | 0 1 0 | * | offsetX offsetY 1 | * * matrix = m * matrix // for prepend
* matrix = matrix * m // for append
* * Arguments: * * offsetX - offset along x-axis * offsetY - offset along y-axis * order - prepend or append. * * Return Value: * * NONE * \**************************************************************************/
VOID GpMatrix::Translate( REAL offsetX, REAL offsetY, GpMatrixOrder order ) { if (order == MatrixOrderPrepend) { Dx += (offsetX * M11) + (offsetY * M21); Dy += (offsetX * M12) + (offsetY * M22); } else // Append
{ Dx += offsetX; Dy += offsetY; }
Complexity |= TranslationMask; AssertComplexity(); }
/**************************************************************************\
* * Function Description: * * Prepend or append a shear matrix to the current matrix, i.e. * * | 1 shearY 0 | * m = | shearX 1 0 | * | 0 0 1 | * * matrix = m * matrix // for prepend
* matrix = matrix * m // for append
* * Arguments: * * shearX - Amount to shear along x-axis * shearY - Amount to shear along y-axis * order - prepend or append. * * Return Value: * * NONE * \**************************************************************************/
VOID GpMatrix::Shear( REAL shearX, REAL shearY, GpMatrixOrder order ) { REAL t; if (order == MatrixOrderPrepend) { t = M11; M11 += shearY*M21; M21 += shearX*t;
t = M12; M12 += shearY*M22; M22 += shearX*t; } else // Append
{ t = M11; M11 += shearX*M12; M12 += shearY*t;
t = M21; M21 += shearX*M22; M22 += shearY*t;
t= Dx; Dx += shearX*Dy; Dy += shearY*t; }
// Shear is very complex; we choose to simply recalculate the
// complexity:
Complexity = ComputeComplexity(); }
/**************************************************************************\
* * Function Description: * * Multiply two matrices and place the result in the 3rd one: * m = m1 * m2 * * Arguments: * * m - Destination matrix * m1, m2 - Source matrices * * Return Value: * * NONE * * Notes: * * m can be the same matrix as m1 and/or m2. * \**************************************************************************/
VOID GpMatrix::MultiplyMatrix( GpMatrix& m, const GpMatrix& m1, const GpMatrix& m2 ) { REAL t11, t12, t21, t22, tx, ty;
t11 = m1.M11 * m2.M11 + m1.M12 * m2.M21; t12 = m1.M11 * m2.M12 + m1.M12 * m2.M22; t21 = m1.M21 * m2.M11 + m1.M22 * m2.M21; t22 = m1.M21 * m2.M12 + m1.M22 * m2.M22; tx = m1.Dx * m2.M11 + m1.Dy * m2.M21 + m2.Dx; ty = m1.Dx * m2.M12 + m1.Dy * m2.M22 + m2.Dy;
m.M11 = t11; m.M12 = t12; m.M21 = t21; m.M22 = t22; m.Dx = tx; m.Dy = ty;
// Multiply can be very complex; we choose to simply recalculate the
// complexity:
m.Complexity = m.ComputeComplexity(); }
/**************************************************************************\
* * Function Description: * * Scale the entire matrix by the scale value. * * +-- --+ +-- --+ * | M11 M12 0 | | Sx 0 0 | * | M21 M22 0 | x | 0 Sy 0 | => dest matrix * | Dx Dy 1 | | 0 0 1 | * +-- --+ +-- --+ * * Arguments: * * [OUT] m - destination matrix * [IN] m1 - source matrix * [IN] scaleX - dest = source * scaleValue * [IN] scaleY - dest = source * scaleValue * * Return Value: * * NONE * * Created: * * 3/1/1999 DCurtis * \**************************************************************************/ VOID GpMatrix::ScaleMatrix( GpMatrix& m, const GpMatrix& m1, REAL scaleX, REAL scaleY ) { // !!! some kind of epsilon checking maybe?
if ((scaleX != 1) || (scaleY != 1)) { m.M11 = scaleX * m1.M11; m.M12 = scaleY * m1.M12; m.M21 = scaleX * m1.M21; m.M22 = scaleY * m1.M22; m.Dx = scaleX * m1.Dx; m.Dy = scaleY * m1.Dy;
//!!! Since the scaling can magnify the other component,
// it is safer to recompute the complexity.
m.Complexity = m.ComputeComplexity(); /*
if(m1.IsTranslateScale()) { m.Complexity = m1.Complexity | ScaleMask; } else { // Scaling a rotation by different scale factors in x and y
// results in a shear. Instead of working out the correct
// optimized complexity, we just recompute - this is a rotation
// or shear already anyway.
m.Complexity = m.ComputeComplexity(); } m.AssertComplexity(); */ } else { m = m1; } }
/**************************************************************************\
* * Function Description: * * Query for special types of transformation matrices * * Arguments: * * Return Value: * * MatrixRotate enum indicating type of rotation * \**************************************************************************/
MatrixRotate GpMatrix::GetRotation() const { // Check for no rotate.
if(IsTranslateScale()) { return MatrixRotateBy0; } // Check for Rotate by 90 degrees
if (REALABS(M12) < REAL_EPSILON && REALABS(M21) < REAL_EPSILON && (M11 < 0.0f) && (M22 < 0.0f) ) { return MatrixRotateBy180; } else if (REALABS(M11) < REAL_EPSILON && REALABS(M22) < REAL_EPSILON) { if (M12 > 0.0f) { return MatrixRotateBy90; } else { return MatrixRotateBy270; } }
return MatrixRotateByOther; }
/**************************************************************************\
* * Function Description: * * Query for special types of transformation matrices. * This will return a RotateFlipType for the rotation. If the rotation * is Identity or an arbitrary non supported format, return value is * RotateNoneFlipNone * \**************************************************************************/
RotateFlipType GpMatrix::AnalyzeRotateFlip() const { // Early out the identity case because we have a flag for it in the matrix.
if(IsIntegerTranslate()) { return RotateNoneFlipNone; } // Main Diagonal is zero.
if( (REALABS(M11) < REAL_EPSILON) && // M11 == 0.0
(REALABS(M22) < REAL_EPSILON) ) // M22 == 0.0
{ // Rotate 270 or Rotate 90 + Flip X
if( REALABS(M21-1) < REAL_EPSILON ) // M21 == 1.0
{ if( REALABS(M12-1) < REAL_EPSILON ) // M12 == 1.0
{ return Rotate90FlipX; } if( REALABS(M12+1) < REAL_EPSILON ) // M21 == -1.0
{ return Rotate270FlipNone; } } // Rotate 90 or Rotate 270 + Flip X
if( REALABS(M21+1) < REAL_EPSILON ) // M21 == -1.0
{ if( REALABS(M12-1) < REAL_EPSILON ) // M12 == 1.0
{ return Rotate90FlipNone; } if( REALABS(M12+1) < REAL_EPSILON ) // M12 == -1.0
{ return Rotate270FlipX; } } } // Main Diagonal matrix (non zero).
if( (REALABS(M12) < REAL_EPSILON) && // M12 == 0.0
(REALABS(M21) < REAL_EPSILON) ) // M21 == 0.0
{ // Identity or Flip Y
if( REALABS(M11-1) < REAL_EPSILON ) // M11 == 1.0
{ // Identity is handled already.
// if( REALABS(M22-1) < REAL_EPSILON ) // M22 == 1.0
if( REALABS(M22+1) < REAL_EPSILON ) // M22 == -1.0
{ return RotateNoneFlipY; } } // Flip X or Rotate 180
if( REALABS(M11+1) < REAL_EPSILON ) // M11 == -1.0
{ if( REALABS(M22-1) < REAL_EPSILON ) // M22 == 1.0
{ return RotateNoneFlipX; } if( REALABS(M22+1) < REAL_EPSILON ) // M22 == -1.0
{ return Rotate180FlipNone; } } } // We couldn't find a rotate/flip type.
return RotateNoneFlipNone; }
/**************************************************************************\
* * Function Description: * * Transform the specified array of points using the current matrix * * Arguments: * * points - Array of points to be transformed * The resulting points are stored back into the same array * * count - Number of points in the array * * Return Value: * * NONE * \**************************************************************************/
VOID GpMatrix::Transform( GpPointF* points, INT count ) const { if (count <= 0) return;
ASSERT(points != NULL);
// On checked builds, verify that the Complexity flags are correct:
AssertComplexity();
if(IsIdentity()) { return; } else if(IsTranslate()) { do { points->X += Dx; points->Y += Dy;
} while (points++, --count != 0); } else if(IsTranslateScale()) { do { points->X = points->X * M11 + Dx; points->Y = points->Y * M22 + Dy;
} while (points++, --count != 0); } else { do { REAL x = points->X; REAL y = points->Y;
points->X = (M11 * x) + (M21 * y) + Dx; points->Y = (M12 * x) + (M22 * y) + Dy;
} while (points++, --count != 0); } }
/**************************************************************************\
* * Function Description: * * Transform the specified array of points using the current matrix, * with the destination an array of integer POINTS. * * Arguments: * * srcPoints - Array of REAL points to be transformed * * destPoints - Array of REAL points to store the results * * count - Number of points in the array * * Return Value: * * NONE * \**************************************************************************/
VOID GpMatrix::Transform( const GpPointF* srcPoints, GpPointF* destPoints, INT count ) const { if (count <= 0) return;
ASSERT((srcPoints != NULL) && (destPoints != NULL));
// On checked builds, verify that the Complexity flags are correct:
AssertComplexity(); if(IsIdentity()) { GpMemcpy(destPoints, srcPoints, count*sizeof(GpPointF)); } else if (IsTranslate()) { do { destPoints->X = srcPoints->X + Dx; destPoints->Y = srcPoints->Y + Dy;
} while (destPoints++, srcPoints++, --count != 0); } else if (IsTranslateScale()) { do { destPoints->X = srcPoints->X * M11 + Dx; destPoints->Y = srcPoints->Y * M22 + Dy;
} while (destPoints++, srcPoints++, --count != 0); } else { do { REAL x = srcPoints->X; REAL y = srcPoints->Y;
destPoints->X = (M11 * x) + (M21 * y) + Dx; destPoints->Y = (M12 * x) + (M22 * y) + Dy;
} while (destPoints++, srcPoints++, --count != 0); } }
/**************************************************************************\
* * Function Description: * * Transform the specified array of points using the current matrix, * with the destination an array of integer POINTS. * * Arguments: * * srcPoints - Array of REAL points to be transformed * * destPoints - Array of INT points to store the results * * count - Number of points in the array * * Return Value: * * NONE * \**************************************************************************/
VOID GpMatrix::Transform( const GpPointF* srcPoints, POINT * destPoints, INT count ) const { if (count <= 0) return;
ASSERT((srcPoints != NULL) && (destPoints != NULL));
// On checked builds, verify that the Complexity flags are correct:
AssertComplexity(); // NOTE: This code should call RasterizeCeiling() to be consistent
// with our aliased line drawing rasterizer.
if (IsTranslate()) { do { destPoints->x = RasterizerCeiling(srcPoints->X + Dx); destPoints->y = RasterizerCeiling(srcPoints->Y + Dy);
} while (destPoints++, srcPoints++, --count != 0); } else if (IsTranslateScale()) { do { destPoints->x = RasterizerCeiling(srcPoints->X * M11 + Dx); destPoints->y = RasterizerCeiling(srcPoints->Y * M22 + Dy);
} while (destPoints++, srcPoints++, --count != 0); } else { do { REAL x = srcPoints->X; REAL y = srcPoints->Y;
destPoints->x = RasterizerCeiling((M11 * x) + (M21 * y) + Dx); destPoints->y = RasterizerCeiling((M12 * x) + (M22 * y) + Dy);
} while (destPoints++, srcPoints++, --count != 0); } }
/**************************************************************************\
* * Function Description: * * Transform a rect and return the resulting rect. * This only works if the matrix is a translate-scale matrix, but we're * assuming here that you've already checked for that. * * Arguments: * * [IN/OUT] rect - the rect to transform * * Return Value: * * NONE * * Created: * * 3/5/1999 DCurtis * \**************************************************************************/
VOID GpMatrix::TransformRect( GpRectF & rect ) const { if (IsIdentity()) return;
// NTRAID#NTBUG9-407211-2001-05-31-gillessk "Bad assert triggers when it shouldn't"
// loose the condition to allow rotation by multiple of 90 degrees
ASSERT(IsTranslateScale() || (GetRotation()==MatrixRotateBy90) || (GetRotation()==MatrixRotateBy270));
REAL xMin = rect.X; REAL yMin = rect.Y; REAL xMax = xMin + rect.Width; REAL yMax = yMin + rect.Height; REAL x; x = xMin; xMin = (M11 * x) + (M21 * yMin) + Dx; yMin = (M12 * x) + (M22 * yMin) + Dy;
x = xMax; xMax = (M11 * x) + (M21 * yMax) + Dx; yMax = (M12 * x) + (M22 * yMax) + Dy;
if (xMin > xMax) { x = xMin; xMin = xMax; xMax = x; }
if (yMin > yMax) { x = yMin; yMin = yMax; yMax = x; }
rect.X = xMin; rect.Y = yMin; rect.Width = xMax - xMin; rect.Height = yMax - yMin; }
/**************************************************************************\
* * Function Description: * * Transform the specified array of points using the current matrix, * ignoring translation. * * Arguments: * * points - Array of points to be transformed * The resulting points are stored back into the same array * * count - Number of points in the array * * Return Value: * * NONE * \**************************************************************************/
VOID GpMatrix::VectorTransform( GpPointF* points, INT count ) const { if (IsIdentity()) return;
REAL x;
for (INT i=0; i < count; i++) { x = points[i].X; points[i].X = M11*x + M21*points[i].Y; points[i].Y = M12*x + M22*points[i].Y; } }
/**************************************************************************\
* * Function Description: * * Determine matrix complexity. * * NOTE: This function is fairly expensive. It shouldn't be called * after every matrix operation. (If it was, I would argue * that's a good reason to get rid of 'Complexity' entirely, * which is intended as a short-cut and should not be expensive * to keep updated.) * * Arguments: * * NONE * * Return Value: * * Returns a bitmask representing the matrix complexity. * \**************************************************************************/
INT GpMatrix::ComputeComplexity() const { INT complexity = ComplexMask;
REAL maxM = max( max(REALABS(M11), REALABS(M22)), max(REALABS(M12), REALABS(M21))); REAL epsilon = CPLX_EPSILON*maxM;
// M12==0 && M21==0
if ((REALABS(M12) < epsilon) && (REALABS(M21) < epsilon)) { complexity &= ~(ShearMask | RotationMask);
// M11==1 && M22==1
if ((REALABS(M11 - 1.0f) < CPLX_EPSILON) && (REALABS(M22 - 1.0f) < CPLX_EPSILON)) { complexity &= ~ScaleMask; } } else { // Check if this is a pure rotation
// M11==M22 && M12==-M21
if((REALABS(M11 - M22) < epsilon) && (REALABS(M12 + M21) < epsilon)) { complexity &= ~ShearMask; // M11*M11+M12*M12==1
if (REALABS(M11*M11 + M12*M12 - 1.0f) < CPLX_EPSILON) { complexity &= ~ScaleMask; } } }
// Dx==0 && Dy==0
// We don't know the real scaling of the translational part.
// So we use the exact value.
// if ((REALABS(Dx) < CPLX_EPSILON) && (REALABS(Dy) < CPLX_EPSILON))
if(Dx == 0 && Dy == 0) { complexity &= ~TranslationMask; }
return(complexity); }
/**************************************************************************\
* * Function Description: * * Verify the matrix complexity. * \**************************************************************************/
#if DBG
VOID GpMatrix::AssertComplexity() const { INT computed = ComputeComplexity();
// The new complexity can be less complex than the old
// (a sequence or rotations may end up with an identity
// transform, for example), but that's okay - complexity
// is intended as a short cut, and as such keeping it
// updated sholud be light weight.
//
// But the calculated complexity SHOULD NOT be more complex
// than the old - if it is, we have a bug someplace -
// we're updating the matrix but not the complexity...
//
// Note: under certain circumstances - such as excessive
// repeated matrix appending or scaling up by very large
// factors - we could end up with a more complex computed
// complexity just due to rounding errors.
// Under these circumstances, the cause should be determined
// and most likely the algorithm re-evaluated because when
// rounding errors are propagated to such large proportions,
// no operations are going to have predictable results anyway.
ASSERTMSG((Complexity & computed) == computed, (("Matrix more complex than cached Complexity indicates"))); }
#endif
|