Showing posts with label CGAffineTransform. Show all posts
Showing posts with label CGAffineTransform. Show all posts

Tuesday, October 18, 2011

CGAffineTransform Additions

As you probably know, Apple provides a bunch of functionality for manipulating objects in 2D space using CGAffineTransform. Oddly, Apple doesn't provide you with a way to extract the scale, transform, and rotation information from a CGAffineTransform and they don't provide any shearing functionality at all.

Here are some additional inline functions that I use. This adds the ability to extract component values of the CGAffineTransform and also adds the ability to create and extract shear information.

As always, this code is free to use without restriction or limitation, but has no warranty whatsoever. If you fix a bug, feel free to let me know about the fix so I can incorporate the fix.

#ifndef __MCP_AFFINE_TRANSFORM_ADDITIONS__
#define __MCP_AFFINE_TRANSFORM_ADDITIONS__

#import <CoreGraphics/CoreGraphics.h>

#ifdef __cplusplus
extern "C" {
#endif

#define degreesToRadian(x) (M_PI * x / 180.0)
#define radiansToDegrees(x) (180.0 * x / M_PI)

static inline CGAffineTransform CGAffineTransformMakeShear(CGFloat shearX, CGFloat shearY)
{
return CGAffineTransformMake(1.f, shearY, shearX, 1.f, 0.f, 0.f);
}

static inline CGAffineTransform CGAffineTransformShear(CGAffineTransform transform, CGFloat shearX, CGFloat shearY)
{
CGAffineTransform sheared = CGAffineTransformMakeShear(shearX, shearY);
return CGAffineTransformConcat(transform, sheared);
}

static inline CGFloat CGAffineTransformGetDeltaX(CGAffineTransform transform) {return transform.tx;};
static inline CGFloat CGAffineTransformGetDeltaY(CGAffineTransform transform) {return transform.ty;};
static inline CGFloat CGAffineTransformGetScaleX(CGAffineTransform transform) {return sqrtf( (transform.a * transform.a) + (transform.c * transform.c) );};
static inline CGFloat CGAffineTransformGetScaleY(CGAffineTransform transform) {return sqrtf( (transform.b * transform.b) + (transform.d * transform.d) );};
static inline CGFloat CGAffineTransformGetShearX(CGAffineTransform transform) {return transform.b;};
static inline CGFloat CGAffineTransformGetShearY(CGAffineTransform transform) {return transform.c;};
static inline CGFloat CGPointAngleBetweenPoints(CGPoint first, CGPoint second)
{
CGFloat dy = second.y - first.y;
CGFloat dx = second.x - first.x;
return atan2f(dy, dx);
}

static inline CGFloat CGAffineTransformGetRotation(CGAffineTransform transform)
{
// No exact way to get rotation out without knowing order of all previous operations
// So, we'll cheat. We'll apply the transformation to two points and then determine the
// angle betwen those two points

CGPoint testPoint1 = CGPointMake(-100.f, 0.f);
CGPoint testPoint2 = CGPointMake(100.f, 0.f);
CGPoint transformed1 = CGPointApplyAffineTransform(testPoint1, transform);
CGPoint transformed2 = CGPointApplyAffineTransform(testPoint2, transform);
return CGPointAngleBetweenPoints(transformed1, transformed2);
}


#ifdef __cplusplus
}
#endif

#endif

Tuesday, February 15, 2011

A Couple CGAffineTransform Goodies

Thanks to Core Animation, we iOS programmers tend to use affine transformations (by way of CGAffineTransform) a lot. By being able to combine multiple 2D transformations into a single matrix, we have the ability to do a lot of cool animation effects with only a few lines of code.

Take the following example, which is fairly typical:

    CGAffineTransform transform = CGAffineTransformMakeTranslation(0, -translation);
transform = CGAffineTransformScale(transform, scaleFactor, scaleFactor);
view.transform = transform;


Not bad, right? In just three lines of code, we're able to both scale and translate a view or layer. But in reality, there's actually quite a few operations going on behind these three lines of code. The CGAffineTransformScale() function calls CGAffineTransformConcat() to perform a matrix multiplication operation between two affine matrices. But, as you probably know, you can't multiply a 2x3 matrix by another 2x3 matrix. To multiply affine transformations, they have to be converted back to 3x3 vector matrices first.

On today's devices (even mobile devices), this all takes a trivial amount of processing power. But sometimes, when you're doing a lot of these transformations — say thousand or tens of thousands a second — it can be valuable to be able to avoid that conversion and matrix multiplication.

It just so happens that with certain commonly used CGAffineTransforms, you can cheat. Certain matrices can be joined together without performing matrix multiplication. For example, here are the matrices created by CGAffineTransformMakeScale() and CGAffineTransformMakeTranslation(), respectively:

Equation08           Equation06


Go ahead and multiply those two together. Plug in any number for tx, ty, sx, and sy and run the numbers. I'll wait. Okay, you don't have to. This is what you'll get:
Equation0x

So, if that's the result we're going to get, why bother going through the matrix multiplication in the first place? Why not just populate the matrix with both the scale and translate values right from the get-go? Well, we can. We can also do the same thing with translate and rotate.

This is all there is to it:

static inline CGAffineTransform CGAffineTransformMakeRotateTranslate(CGFloat angle, CGFloat dx, CGFloat dy)
{
return CGAffineTransformMake(cosf(angle), sinf(angle), -sinf(angle), cosf(angle), dx, dy);
}

static inline CGAffineTransform CGAffineTransformMakeScaleTranslate(CGFloat sx, CGFloat sy, CGFloat dx, CGFloat dy)
{
return CGAffineTransformMake(sx, 0.f, 0.f, sy, dx, dy);
}


That's it. It only saves you two lines of code:

    view.transform = CGAffineTransformMakeScaleTranslate(scaleFactor, scaleFactor, 0, -translation);


But, your stack allocation is considerably smaller (one CGAffineTransform instead of two CGAffineTransforms and an intermediate 3x3 array. It also saves you eighteen floating point multiplications and nine floating point additions. 99.9% of the time, that number of operations is going to have no noticeable affect on your application - it's a trivial amount of both memory and FLOPS under most normal situations.

But… if you're doing a lot per second, they can add up and it's nice to know there's a way that you can save yourself a little overhead in some situations.