Trying to better understand CGAffineTransform.... and need a bit of guidance.

I have a CoreImage pipeline and one of my steps is to rotate my image about the origin (bottom left corner) and then translate it. I'm not seeing the behaviour I'm expecting, and I think my problem is in how I'm combining these two steps. As an example, I start with an identity transform

(lldb) po transform333
▿ CGAffineTransform
  - a : 1.0
  - b : 0.0
  - c : 0.0
  - d : 1.0
  - tx : 0.0
  - ty : 0.0

I then rotate 1.57 radians (approx. 90 degrees, CCW)

        transform333 = transform333.rotated(by: 1.57)
  - a : 0.0007963267107332633
  - b : 0.9999996829318346
  - c : -0.9999996829318346
  - d : 0.0007963267107332633
  - tx : 0.0
  - ty : 0.0

I understand the current contents of the transform. But then I translate by 10, 10:

(lldb) po transform333.translatedBy(x: 10, y: 10)
  - a : 0.0007963267107332633
  - b : 0.9999996829318346
  - c : -0.9999996829318346
  - d : 0.0007963267107332633
  - tx : -9.992033562211013
  - ty : 10.007960096425679

I was expecting tx and ty to be 10 and 10. I have noticed that when I reverse the order of these operations, the transform contents look correct. So I'll most likely just perform the steps in what feels to me like the incorrect order. Is anyone willing/able to point me to an explanation of why the steps I'm performing are giving me these results?

thanks,

mike

So I'll most likely just perform the steps in what feels to me like the incorrect order.

Here's an anecdote from about 1990:

A friend was on a transatlantic flight, and the guy sat next to him had a laptop computer - which was a bit of a novelty at the time, and since this was before at-seat power, he also had a large bag of batteries to swap. (Yes, removable batteries!).

My friend was of course watching what the guy was doing. He seemed to be writing 3D graphics code. He would hack around for a bit, then run it, quietly swear, and repeat.

Eventually, my friend had to say something. "Excuse me", "Huh, yeah?", "You're multiplying the matricies in the wrong order. Swap A and B on line 481."

Anyway.

I don't recall the semantics of the Core Graphics transformation functions. There are definitely two ways of thinking about it. My preference with any API of this sort is to form the complete matrix myself and call a single API function to set it. Then I'm in control of what happens and I don't need to worry about whether this particular library does AxB or BxA.

Beware that that's not the only way it can go wrong. There is one affine transformation API - I forget which one, maybe it's SVG? - where rotations are about the center of the image, not the origin!

Good luck.

If understand, you call rotate then translate ?

Could you show how you create the transforms ? In your case, I think it should be:

        var t = CGAffineTransform.identity
        t = t.translatedBy(x: deltaX, y: -deltaY)
        t = t.rotated(by: rotation)

so when you apply t = Id ° trans ° rot, you apply rot first.

If you call sequentially, this may help: https://stackoverflow.com/questions/24926062/sequence-of-cgaffinetransform-on-a-single-uiview

Note: I also had to test and try before getting the expected result.

Here are some tidbits about affine transforms on Apple platforms that I have collected over the years. I hope they are helpful for you. This might be overkill for your particular question (as it appears to have received some pretty good answers already), but I'm putting it on the forums in case it's useful to anyone else.

Documentation

Core Graphics affine transform is defined here:

CGAffineTransform

There is also a detailed discussion here:

Quartz 2D Programming Guide: Transforms

Core Animation includes a unit based coordinate system discussed here:

Core Animation Programming Guide: Core Animation Basics

Foundation also has an affine transform:

Foundation: AffineTransform

Accelerate also includes simd based affine transforms used with 3D coordinaes:

Accelerate: Working with Matrices

A technote about debugging coordinate space issues:

TN3124: Debugging coordinate space issues

Understanding affine transforms

There are many interesting discussions of affine transforms and how to use them available elsewhere. I'm not going to talk about them here, but if you're interested please consider taking a look around in books about computer graphics and on the Internet.

The key thing you need to know about using affine transforms

All of the transformations are relative to the origin. If you rotate, the rotation operation will be centered at the origin. If you are scaling, the scaling operation is centered at the origin. if you need to rotate a square at its center, for example, you will need to perform three operations - translate the square so it is centered at the origin, rotate the square at the origin, and then translate the square back to wherever it was.

Implementation details

Mathematically speaking, there are two approaches to implementing affine transformations with matrices. When reviewing documentation about them, you'll probably find different sources that use one or the other approach. Both approaches are equivalent and are the transpose of one another (you can translate from one format to the other by applying the matrix rule (AB)' = (B')(A') - where I'm using ' to indicate the transpose operation). For all practical purposes, either method will produce the same results. However, when using affine transformation matrices together with our APIs may sometimes need to be aware of the convention being employed by the API that you are using. For example, Foundation and Core Graphics use the row-major organization for matrices, but the Accelerate article mentioned above uses the column-major format.

Column-major format

Affine transformation by multiplying a column vector on the right:

|a c tx| |x| |x'|
|b d ty|x|y|=|y'|
|0 0 1 | |1| |1 |

Row-major format

Affine transformation by multiplying a row vector on the left:

        |a  b  0|
[x y 1]x|c  d  0|=[x' y' 1]
        |tx ty 1|

note, I'm calling these column-major and row-major, but this is only to do with how the values are organized in the matrices and not to the underlying organization of the arrays in computer memory.

Choice of matrix format determines the order of operations

The matrix format used will determine the order in which you multiply your transformation matrices together to construct a combined transformation matrix:

  • With the column-major format, operations are added by multiplying in new matrices on the left hand side: ( last_operation * ... * second_operation * first_operation * input_column_vector) → output_column_vector
  • With the row-major format, operations are added by multiplying in matrices on the right hand side: ( input_row_vector * first_operation * second_operation * ... * last_operation) → output_row_vector

This is important because it will completely determine how you write your matrix handling code. However, though it's useful to be aware of these details in some situations, they are mostly hidden by the APIs.

On Apple platforms

The Core Graphics CGAffineTransform API uses row-major format, and adding new transformation operations by multiplying transformation matrices in on the right is accomplished using the concatenating(_:) method or one of the modifying operations such as rotated(by:), translatedBy(x:, y:), etc.

The Foundation AffineTransform uses row-major format also API provides prepend(:) and append(:) for multiplying in new transform matrices on either the left or the right respectively. For adding new operations, use the append(_:) method.

Trying to better understand CGAffineTransform.... and need a bit of guidance.
 
 
Q