IOS transform coordinates change

Time:2020-12-1

When using cgcontext, because the coordinates of quartz 2D and UIKit are inconsistent, it is necessary to change the context again to achieve the desired effect.

1. Introduction to origin of different coordinates

In quartz 2D, the coordinate origin is in the lower left corner of the canvas, while in UIKit, it is consistent with the screen coordinates, and the upper left corner is taken as the coordinate origin.

If f is drawn from the (0, 0) point, the following results will be obtained in different coordinate systems.
image.png

2. Transformation of quartz 2D and UIKit coordinate system

2.1 uiimage rendering

In the UI development of IOS, take uiimage as an example, draw a picture, set the frame of image to (0, 0, 320, 320), and you will get the picture on the right of the above figure.

If you use the following code to read the transform of context, you can see that the transform is not an identity matrix.

CGRect frame = CGRectMake(0.0, 0.0, 720, 1280);
UIGraphicsBeginImageContext(frame.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform contextTransform = CGContextGetCTM(context);;

The transform setting here will make the coordinate origin of quartz 2D and UIKit coincide, which is also convenient for the drawing of control in UIKit. The change of coordinate system can refer to the following figure.
image.png
This is why if cgcontextdrawimage is called directly on the acquired context, an inverted image will be obtained.

However, if you use the drawinrect method of uiimage, the document reads as follows:
Instance Method
draw(in:)
Draws the entire image in the specified rectangle, scaling it as necessary to fit.
Declaration
func draw(in rect: CGRect)
Parameters
rect
The rectangle (in the coordinate system of the graphics context) in which to draw the image.
Discussion
This method draws the entire image in the current graphics context, respecting the image’s orientation setting. In the default coordinate system, images are situated down and to the right of the origin of the specified rectangle. This method respects any transforms applied to the current graphics context, however.
This method draws the image at full opacity using the CGBlendMode.normal blend mode.

In other words, the coordinates used in uiimage rendering are still the internal coordinates of UIKit, so it is not necessary to make any changes to the coordinate system to draw an image with the same position as rect. Of course, this method will also draw according to the orientation of the image.

2.2 cgcontextdrawimage rendering

When changing the transform of context, what is actually changing is the coordinate system itself.

When we call the drawing method, we still use the internal coordinates of the coordinate system, so when we want to draw an image like UIKit display based on the obtained context, we need to adjust the coordinate system before drawing.

//Y-axis flip
CGContextScaleCTM(context, 1, -1);

//The origin of the picture should be aligned with the upper left corner, and the height of the picture should be shifted downward along the Y axis
CGContextTranslateCTM(context, 0, -imageSize.height);

//Drawing pictures
CGContextDrawImage(context, frame, image.CGImage);
image.png

3 transform anchor change

For example, in the picture editing page, we often encounter the use of gestures to scale, rotate and shift the image, and then generate a new image.

According to different gesture callback, we can modify it view.transform To make the view on the interface change corresponding to the gesture.

Here, in order to facilitate us to modify the UI interface, the view transform takes the view center as the anchor point.

The description in uiview’s transform is to use center to modify position.

Use this property to scale or rotate the view’s frame rectangle within its superview’s coordinate system. (To change the position of the view, modify the center property instead.) The default value of this property is CGAffineTransformIdentity.

When many other methods are called, transform needs to take the upper left corner as the anchor point, so a conversion is needed here. The anchor effect is shown in the figure below.
image.png
In the modification of UI interface, we can use the callback values of zoom and rotation gestures to directly modify the transform of view, and the callback of displacement to modify the center, which can achieve the desired effect. However, this transform cannot be used in context rendering because the change of coordinate system is done with the origin as anchor point.

Therefore, for the position of the existing coordinate system of context, the anchor point is in the upper left corner, so a transform modification is needed.

As can be seen from the above figure, the anchor only affects the position information and does not change the scale and rotation.

Uiimage * image; / / initialize image

Uiview * view; / / apply the changed view, the size of the view should be consistent with the image, and ensure that the zoom ratio is correct.

CGAffineTransform transform = view.transform;
CGSize imageSize = image.size;
transform.tx = view.center.x;
transform.ty = view.center.y;
transform = CGAffineTransformTranslate(transform, -imageSize.width * 0.5, -imageSize.height * 0.5);

Where TX, ty are the position of the anchor in the coordinate system

The current anchor is in the center of the view. We need to change it to the upper left corner of the view so that it can coincide with the origin of the coordinate system. among(imageSize.width * 0.5, imageSize.height * 0.5)Is the position of the anchor in the image, and transform is the change matrix of the anchor in the upper left corner of the view.

4. Combine transform

In order to get the result in the middle of the above figure on the cgcontext, it is necessary not only to apply the change of 1 / 2 reduction and 45 degree rotation, but also to adjust it.

As mentioned before, the application of rotation in cgcontext is applied to the coordinate system, which is consistent with the way that the view applies rotation to its own coordinate system. Therefore, when the cgcontext is directly obtained and the optimized coordinate system is also the origin of the upper left corner, we can directly apply the transform we calculated on the cgcontext.

After that, because the coordinate system drawing is calculated according to its own coordinate system, and then a round of coordinate system flip and displacement is done to get the final result, similar to the operation in the figure below.

image.png

It should be noted that every new change is based on the previous changes. Therefore, both the view and the change of the context are sequential. This is consistent with matrix multiplication, and the order will affect the results.