/** NSAffineTransform.m This class provides a way to perform affine transforms. It provides a matrix for transforming from one coordinate system to another. Copyright (C) 1996,1999 Free Software Foundation, Inc. Author: Ovidiu Predescu Date: August 1997 Author: Richard Frith-Macdonald Date: March 1999 This file is part of the GNUstep Base Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. */ #import "config.h" #include #define EXPOSE_NSAffineTransform_IVARS 1 #import "Foundation/NSArray.h" #import "Foundation/NSException.h" #import "Foundation/NSString.h" #import "Foundation/NSAffineTransform.h" #import "Foundation/NSCoder.h" #import "Foundation/NSDebug.h" /* Private definitions */ #define A _matrix.m11 #define B _matrix.m12 #define C _matrix.m21 #define D _matrix.m22 #define TX _matrix.tX #define TY _matrix.tY /* A Postscript matrix looks like this: / a b 0 \ | c d 0 | \ tx ty 1 / */ static const CGFloat pi = 3.1415926535897932384626434; #if 0 #define valid(o) NSAssert((o->_isIdentity && o->A==1.0 && o->B==0.0 && o->C==0.0 && o->D==1.0) || (o->_isFlipY && o->A==1.0 && o->B==0.0 && o->C==0.0 && o->D==-1.0) || !(o->_isIdentity||o->_isFlipY), NSInternalInconsistencyException) #define check() valid(self) #else #define valid(o) #define check() #endif /* Quick function to multiply two coordinate matrices. C = AB */ static inline NSAffineTransformStruct matrix_multiply (NSAffineTransformStruct MA, NSAffineTransformStruct MB) { NSAffineTransformStruct MC; MC.m11 = MA.m11 * MB.m11 + MA.m12 * MB.m21; MC.m12 = MA.m11 * MB.m12 + MA.m12 * MB.m22; MC.m21 = MA.m21 * MB.m11 + MA.m22 * MB.m21; MC.m22 = MA.m21 * MB.m12 + MA.m22 * MB.m22; MC.tX = MA.tX * MB.m11 + MA.tY * MB.m21 + MB.tX; MC.tY = MA.tX * MB.m12 + MA.tY * MB.m22 + MB.tY; return MC; } /* MC.m11 = MA->A * MB->A + MA->B * MB->C; MC.m12 = MA->A * MB->B + MA->B * MB->D; MC.m21 = MA->C * MB->A + MA->D * MB->C; MC.m22 = MA->C * MB->B + MA->D * MB->D; MC.tX = MA->TX * MB->A + MA->TY * MB->C + MB->TX; MC.tY = MA->TX * MB->B + MA->TY * MB->D + MB->TY; */ @implementation NSAffineTransform static NSAffineTransformStruct identityTransform = { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 }; /** * Return an autoreleased instance of this class. */ + (NSAffineTransform*) transform { NSAffineTransform *t; t = (NSAffineTransform*)NSAllocateObject(self, 0, NSDefaultMallocZone()); t->_matrix = identityTransform; t->_isIdentity = YES; return AUTORELEASE(t); } /** * Return an autoreleased instance of this class. */ + (id) new { NSAffineTransform *t; t = (NSAffineTransform*)NSAllocateObject(self, 0, NSDefaultMallocZone()); t->_matrix = identityTransform; t->_isIdentity = YES; return t; } /** * Appends the transform matrix to the receiver. This is done by performing a * matrix multiplication of the receiver with aTransform so that aTransform * is the first transform applied to the user coordinate. The new * matrix then replaces the receiver's matrix. */ - (void) appendTransform: (NSAffineTransform*)aTransform { valid(aTransform); if (aTransform->_isIdentity) { TX += aTransform->TX; TY += aTransform->TY; check(); return; } if (aTransform->_isFlipY) { B = -B; D = -D; TX = aTransform->TX + TX; TY = aTransform->TY - TY; if (_isIdentity) { _isFlipY = YES; _isIdentity = NO; } else if (_isFlipY) { _isFlipY = NO; _isIdentity = YES; } check(); return; } if (_isIdentity) { A = aTransform->A; B = aTransform->B; C = aTransform->C; D = aTransform->D; TX = TX * aTransform->A + TY * aTransform->C + aTransform->TX; TY = TX * aTransform->B + TY * aTransform->D + aTransform->TY; _isIdentity = NO; // because aTransform is not an identity transform. _isFlipY = aTransform->_isFlipY; check(); return; } if (_isFlipY) { A = aTransform->A; B = aTransform->B; C = -aTransform->C; D = -aTransform->D; TX = TX * aTransform->A + TY * aTransform->C + aTransform->TX; TY = TX * aTransform->B + TY * aTransform->D + aTransform->TY; _isIdentity = NO; _isFlipY = NO; check(); return; } _matrix = matrix_multiply(_matrix, aTransform->_matrix); _isIdentity = NO; _isFlipY = NO; check(); } - (NSString*) description { return [NSString stringWithFormat: @"NSAffineTransform ((%f, %f) (%f, %f) (%f, %f))", A, B, C, D, TX, TY]; } /** * Initialize the transformation matrix instance to the identity matrix. * The identity matrix transforms a point to itself. */ - (id) init { _matrix = identityTransform; _isIdentity = YES; return self; } /** * Initialize the receiever's instance with the instance represented * by aTransform. */ - (id) initWithTransform: (NSAffineTransform*)aTransform { _matrix = aTransform->_matrix; _isIdentity = aTransform->_isIdentity; _isFlipY = aTransform->_isFlipY; return self; } /** * Calculates the inverse of the receiver's matrix and replaces the * receiever's matrix with it. */ - (void) invert { CGFloat newA, newB, newC, newD, newTX, newTY; CGFloat det; if (_isIdentity) { TX = -TX; TY = -TY; return; } if (_isFlipY) { TX = -TX; return; } det = A * D - B * C; if (det == 0) { NSLog (@"error: determinant of matrix is 0!"); return; } newA = D / det; newB = -B / det; newC = -C / det; newD = A / det; newTX = (-D * TX + C * TY) / det; newTY = (B * TX - A * TY) / det; NSDebugLLog(@"NSAffineTransform", @"inverse of matrix ((%f, %f) (%f, %f) (%f, %f))\n" @"is ((%f, %f) (%f, %f) (%f, %f))", A, B, C, D, TX, TY, newA, newB, newC, newD, newTX, newTY); A = newA; B = newB; C = newC; D = newD; TX = newTX; TY = newTY; } /** * Prepends the transform matrix to the receiver. This is done by performing a * matrix multiplication of the receiver with aTransform so that aTransform * is the last transform applied to the user coordinate. The new * matrix then replaces the receiver's matrix. */ - (void) prependTransform: (NSAffineTransform*)aTransform { valid(aTransform); if (aTransform->_isIdentity) { TX = aTransform->TX * A + aTransform->TY * C + TX; TY = aTransform->TX * B + aTransform->TY * D + TY; check(); return; } if (aTransform->_isFlipY) { TX = aTransform->TX * A + aTransform->TY * C + TX; TY = aTransform->TX * B + aTransform->TY * D + TY; C = -C; D = -D; if (_isIdentity) { _isFlipY = YES; _isIdentity = NO; } else if (_isFlipY) { _isFlipY = NO; _isIdentity = YES; } check(); return; } if (_isIdentity) { A = aTransform->A; B = aTransform->B; C = aTransform->C; D = aTransform->D; TX += aTransform->TX; TY += aTransform->TY; _isIdentity = NO; _isFlipY = aTransform->_isFlipY; check(); return; } if (_isFlipY) { A = aTransform->A; B = -aTransform->B; C = aTransform->C; D = -aTransform->D; TX += aTransform->TX; TY -= aTransform->TY; _isIdentity = NO; _isFlipY = NO; check(); return; } _matrix = matrix_multiply(aTransform->_matrix, _matrix); _isIdentity = NO; _isFlipY = NO; check(); } /** * Applies the rotation specified by angle in degrees. Points transformed * with the transformation matrix of the receiver are rotated counter-clockwise * by the number of degrees specified by angle. */ - (void) rotateByDegrees: (CGFloat)angle { [self rotateByRadians: pi * angle / 180]; } /** * Applies the rotation specified by angle in radians. Points transformed * with the transformation matrix of the receiver are rotated counter-clockwise * by the number of radians specified by angle. */ - (void) rotateByRadians: (CGFloat)angleRad { if (angleRad != 0.0) { CGFloat sine; CGFloat cosine; NSAffineTransformStruct rotm; sine = sin (angleRad); cosine = cos (angleRad); rotm.m11 = cosine; rotm.m12 = sine; rotm.m21 = -sine; rotm.m22 = cosine; rotm.tX = rotm.tY = 0; _matrix = matrix_multiply(rotm, _matrix); _isIdentity = NO; _isFlipY = NO; check(); } } /** * Scales the transformation matrix of the reciever by the factor specified * by scale. */ - (void) scaleBy: (CGFloat)scale { NSAffineTransformStruct scam = identityTransform; scam.m11 = scale; scam.m22 = scale; _matrix = matrix_multiply(scam, _matrix); _isIdentity = NO; _isFlipY = NO; check(); } /** * Scales the X axis of the receiver's transformation matrix * by scaleX and the Y axis of the transformation matrix by scaleY. */ - (void) scaleXBy: (CGFloat)scaleX yBy: (CGFloat)scaleY { if (_isIdentity && scaleX == 1.0) { if (scaleY == 1.0) { return; // no scaling } if (scaleY == -1.0) { D = -1.0; _isFlipY = YES; _isIdentity = NO; return; } } if (_isFlipY && scaleX == 1.0) { if (scaleY == 1.0) { return; // no scaling } if (scaleY == -1.0) { D = 1.0; _isFlipY = NO; _isIdentity = YES; return; } } A *= scaleX; B *= scaleX; C *= scaleY; D *= scaleY; _isIdentity = NO; _isFlipY = NO; } /** *

* Sets the structure which represents the matrix of the reciever. * The struct is of the form:

*

{m11, m12, m21, m22, tX, tY}

*/ - (void) setTransformStruct: (NSAffineTransformStruct)val { _matrix = val; _isIdentity = NO; _isFlipY = NO; if (A == 1.0 && B == 0.0 && C == 0.0) { if (D == 1.0) { _isIdentity = YES; } else if (D == -1.0) { _isFlipY = YES; } } check(); } /** * Transforms a single point based on the transformation matrix. * Returns the resulting point. */ - (NSPoint) transformPoint: (NSPoint)aPoint { NSPoint new; if (_isIdentity) { new.x = TX + aPoint.x; new.y = TY + aPoint.y; } else if (_isFlipY) { new.x = TX + aPoint.x; new.y = TY - aPoint.y; } else { new.x = A * aPoint.x + C * aPoint.y + TX; new.y = B * aPoint.x + D * aPoint.y + TY; } return new; } /** * Transforms the NSSize represented by aSize using the reciever's * transformation matrix. Returns the resulting NSSize.
* NB. A transform can result in negative size components ... so calling * code should check for and deal with that situation. */ - (NSSize) transformSize: (NSSize)aSize { if (_isIdentity) { return aSize; } else { NSSize new; if (_isFlipY) { new.width = aSize.width; new.height = -aSize.height; } else { new.width = A * aSize.width + C * aSize.height; new.height = B * aSize.width + D * aSize.height; } return new; } } /** *

* Returns the NSAffineTransformStruct structure * which represents the matrix of the reciever. * The struct is of the form:

*

{m11, m12, m21, m22, tX, tY}

*/ - (NSAffineTransformStruct) transformStruct { return _matrix; } /** * Applies the translation specified by tranX and tranY to the receiver's * matrix. * Points transformed by the reciever's matrix after this operation will * be shifted in position based on the specified translation. */ - (void) translateXBy: (CGFloat)tranX yBy: (CGFloat)tranY { if (_isIdentity) { TX += tranX; TY += tranY; } else if (_isFlipY) { TX += tranX; TY -= tranY; } else { TX += A * tranX + C * tranY; TY += B * tranX + D * tranY; } check(); } - (id) copyWithZone: (NSZone*)zone { return NSCopyObject(self, 0, zone); } - (BOOL) isEqual: (id)anObject { if ([anObject class] == isa) { NSAffineTransform *o = anObject; if (A == o->A && B == o->B && C == o->C && D == o->D && TX == o->TX && TY == o->TY) return YES; } return NO; } - (id) initWithCoder: (NSCoder*)aCoder { NSAffineTransformStruct replace; [aCoder decodeArrayOfObjCType: @encode(CGFloat) count: 6 at: (CGFloat*)&replace]; [self setTransformStruct: replace]; return self; } - (void) encodeWithCoder: (NSCoder*)aCoder { NSAffineTransformStruct replace; replace = [self transformStruct]; [aCoder encodeArrayOfObjCType: @encode(CGFloat) count: 6 at: (CGFloat*)&replace]; } @end /* NSAffineTransform */