Rotation of UIImageView does not conserve center - objective-c

Just setting a rotation transform to an UIImageView does not keep the center still. The image translate around de center, I'm puzzled. Any idea?
CGAffineTransform
transform = CGAffineTransformIdentity;
transform = CGAffineTransformRotate(transform, self.filter.angle);
self.sourceImageView.transform = transform;
Note: the angle is coupled to a UISlider so that the user changes its value from -90 to 90.

I found that setting the transformation to the layer fixes the issue.
self.sourceImageView.layer.transform = CATransform3DRotate(CATransform3DMakeScale(scale, scale, 1.), angle, 0, 0, -1);

Related

Objective-c CATransform3DMakeRotation Clockwise/Counterclockwise

I'm rotating an UIView along the x axis using CATransform3DMakeRotation using the below code:
float radians = DegreesToRadians(30);
[UIView animateWithDuration:0.15 animations:^{
self.moveControlView.layer.transform = CATransform3DMakeRotation(radians,1,0,0);
}];
The rotation is applied always in the same versus (clockwise). I want to rotate the UIView in the opposite versus.
In order to achieve my goal I've tried:
Set a negative angle (-30)
Set the angle to 330
But the versus of the orientation doesn't change.
I have also try to set x = -1 leaving the angle positive.
Any suggestion?
You should apply a perspective to your transform, as it's explained in the similar question:
CATransform3D perspectiveTransform = CATransform3DIdentity;
perspectiveTransform.m34 = 1.0 / -500;
self.moveControlView.layer.transform =
CATransform3DRotate(perspectiveTransform, radians, 1.0f, 0.0f, 0.0f);;

From CATransform3D to CGAffineTransform

I'm using the following function to apply a pulse effect to a view
- (void)pulse {
CATransform3D trasform = CATransform3DScale(self.layer.transform, 1.15, 1.15, 1);
trasform = CATransform3DRotate(trasform, angle, 0, 0, 0);
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.toValue = [NSValue valueWithCATransform3D:trasform];
animation.autoreverses = YES;
animation.duration = 0.3;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.repeatCount = 2;
[self.layer addAnimation:animation forKey:#"pulseAnimation"];
}
I would like to obtain the same result using the CGAffineTransform self.transform instead of the CATransform3D self.layer.transform. Is this possible?
It's possible convert a CATransform3D to a CGAffineTransform, but you will lose some capabilities. I found it useful to convert the aggregate transform of a layer and its ancestors into a CGAffineTransform so I could render it with Core Graphics. The constraints are:
Your input will be treated as flat in the XY plane
Your output will be treated as flat in the XY plane too
Perspective / foreshortening from .m34 will be neutralized
If that sounds OK for your purposes:
// m13, m23, m33, m43 are not important since the destination is a flat XY plane.
// m31, m32 are not important since they would multiply with z = 0.
// m34 is zeroed here, so that neutralizes foreshortening. We can't avoid that.
// m44 is implicitly 1 as CGAffineTransform's m33.
CATransform3D fullTransform = <your 3D transform>
CGAffineTransform affine = CGAffineTransformMake(fullTransform.m11, fullTransform.m12, fullTransform.m21, fullTransform.m22, fullTransform.m41, fullTransform.m42);
You will want to do all your work in 3D transforms first, say by concatenating from your superlayers, and then finally convert the aggregate CATransform3D to a CGAffineTransform. Given that layers are flat to begin with and render onto a flat target, I found this very suitable since my 3D rotations became 2D shears. I also found it acceptable to sacrifice foreshortening. There's no way around that because affine transforms have to preserve parallel lines.
To render a 3D-transformed layer using Core Graphics, for instance, you might concatenate the transforms (respecting anchor points!), then convert to affine, and finally:
CGContextSaveGState(context);
CGContextConcatCTM(context, affine);
[layer renderInContext:context];
CGContextRestoreGState(context);
Of course. If you do a search on CGAffineTransform in the Xcode docs, you'll find a chapter titled "CGAffineTransform Reference". In that chapter is a section called "Functions". It includes functions that are equivalent to CATransform3DScale (CGAffineTransformScale ) and CATransform3DRotate (CGAffineTransformRotate).
Note that your call to CATransform3DRotate doesn't really make sense. You need to rotate around an axis, and you're passing 0 for all 3 axes. Typcially you want to use CATransform3DRotate(trasform, angle, 0, 0, 1.0) to rotate around the Z axis. To quote the docs:
If the vector has zero length the behavior is undefined.
You can use CATransform3DGetAffineTransform to convert CATransform3d to CGAffineTransform.
let scaleTransform = CATransform3DMakeScale( 0.8, 0.8, 1)
imageview.transform = CATransform3DGetAffineTransform(scaleTransform)

Saving 2 UIImages to one while saving rotation, resize info and its quality

I want to save 2 UIImages that are moved, resized and rotated by user. The problem is i dont want to use such function as any 'printscreen one', because it makes both images to lose a lot from their quality (resolution).
Atm i use something like this:
UIGraphicsBeginImageContext(image1.size);
[image1 drawInRect:CGRectMake(0, 0, image1.size.width, image1.size.height)];
[image2 drawInRect:CGRectMake(0, 0, image1.size.width, image1.size.height)];
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
However ofc it just adds two images, their rotation, resizing and moving isn't operated here. Can anybody help with considering these 3 aspects in coding? Any help is appreciated!
My biggest thanks in advance :)
EDIT: images can be rotated and zoomed by user (handling touch events)!
You have to set the transform of the context to match your imageView's transform before you start drawing into it.
i.e.,
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, boundingRect.size.width/2, boundingRect.size.height/2);
transform = CGAffineTransformRotate(transform, angle);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
CGContextConcatCTM(context, transform);
// Draw the image into the context
CGContextDrawImage(context, CGRectMake(-imageView.image.size.width/2, -imageView.image.size.height/2, imageView.image.size.width, imageView.image.size.height), imageView.image.CGImage);
// Get an image from the context
rotatedImage = [UIImage imageWithCGImage: CGBitmapContextCreateImage(context)];
and check out Creating a UIImage from a rotated UIImageView.
EDIT: if you don't know the angle of rotation of the image you can get the transform from the layer property of the UIImageView:
UIGraphicsBeginImageContext(rotatedImageView.image.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform transform = rotatedImageView.transform;
transform = CGAffineTransformScale(transform, 1.0, -1.0);
CGContextConcatCTM(context, transform);
// Draw the image into the context
CGContextDrawImage(context, CGRectMake(0, 0, rotatedImageView.image.size.width, rotatedImageView.image.size.height), rotatedImageView.image.CGImage);
// Get an image from the context
UIImage *newRotatedImage = [UIImage imageWithCGImage: CGBitmapContextCreateImage(context)];
UIGraphicsEndImageContext();
You will have to play about with the transform matrix to centre the image in the context and you will also have to calculate a bounding rectangle for the rotated image or it will be cropped at the corners (i.e., rotatedImageView.image.size is not big enough to encompass a rotated version of itself).
Try this:
UIImage *temp = [[UIImage alloc] initWithCGImage:image1 scale:1.0 orientation: yourOrientation];
[temp drawInRect:CGRectMake(0, 0, image1.size.width, image1.size.height)];
Similarly for image2. Rotation and resizing are handled by orientation and scale respectively. yourOrientation is a UIImageOrientation enum variable and can have a value from 0-7(check this apple documentation on different UIImageOrientation values). Hope it helps...
EDIT: To handle rotations, just write the desired orientation for the rotation you require. You can rotate 90 deg left/right or flip vertically/horizontally. For eg, in the apple documentation, UIImageOrientationUp is 0, UIImageOrientationDown is 1 and so on. Check out my github repo for an example.

Is a Right-to-Left progress bar possible on iOS?

I've tried sending [UIProgressView setProgress] negative values, and that doesn't work.
Is there some other way to get a progress bar that fills from the right-hand end?
You could try setting the transform property of your UIProgressView to a new CGAffineTransform that rotates the view by 180 degrees and flips it vertically (to preserve the "shininess") (see CGAffineTransformMake() and CGAffineTransformRotate()).
Something along the lines of:
UIProgressView *pv = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar];
pv.frame = CGRectMake(10, 100, 300, 11);
CGAffineTransform transform = CGAffineTransformMake(1, 0, 0, -1, 0, pv.frame.size.height); // Flip view vertically
transform = CGAffineTransformRotate(transform, M_PI); //Rotation angle is in radians
pv.transform = transform;
pv.progress = 0.5;
You can rotate the UIProgressView:
progressView.transform = CGAffineTransformMakeRotation(DegreesToRadians(180));
where DegreesToRadians is:
#define DegreesToRadians(d) ((d) * M_PI / 180.0)
To change the progress value, use positive numbers.
A simpler version is to flip it horizontally.
progressView.transform = CGAffineTransformMakeScale(-1.0f, 1.0f);
In iOS 9+, you can use semanticContentAttribute:
progressView.semanticContentAttribute = .forceRightToLeft
You can rotate the view by 180°:
progressView.transform = CGAffineTransformMakeRotation(-M_PI);
Swift answer:
progressView.transform = CGAffineTransform(rotationAngle: .pi)
In iOS 7 with storyboards, you can set the Progress Tint to the Track Tint and vice versa, then subtract the regular value from one and set that to the current progress. Probably better to do it the other (rotation) way, but I thought I would throw this out there.
Swift 5 Version
progressView.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)

Linear gradient aliasing with CoreGraphics

I'm trying to emulate the color tint effect from the UITabBarItem.
When I draw a linear gradient at an angle, I get visible aliasing in the middle part of the gradient where the two colors meet at the same location. Left is UITabBarItem, right is my gradient with visible aliasing (stepping):
Here is the snippet of relevant code:
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSaveGState(c);
CGContextScaleCTM(c, 1.0, -1.0);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[16] = {1,1,1,1,
109.0/255.0,175.0/255.0,246.0/255.0,1,
31.0/255.0,133.0/255.0,242.0/255.0,1,
143.0/255.0,194.0/255.0,248.0/255.0,1};
CGFloat locations[4] = {0.0, 0.62, 0.62, 1};
CGGradientRef colorGradient =
CGGradientCreateWithColorComponents(colorSpace, components,
locations, (size_t)4);
CGContextDrawLinearGradient(c, colorGradient, CGPointZero,
CGPointMake(size.width*1.0/3.9, -size.height),0);
CGGradientRelease(colorGradient);
CGColorSpaceRelease(colorSpace);
CGContextRestoreGState(c);
UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resultImage;
What do I need to change, to get a smooth angled gradient like in UITabBarItem?
What is the interpolation quality of your context set to? CGContextGetInterpolationQuality()/CGContextSetInterpolationQuality(). Try changing that if it's too low.
If that doesn't work, I'm curious what happens if you draw the gradient vertically (0,Ymin)-(0,Ymax) but apply a rotation transformation to your context...
As a current workaround, I draw the gradient at double resolution into an image and then draw the image with original dimensions. The image scaling that occurs, takes care of the aliasing. At the pixel level the result is not as smooth as in the UITabBarItem, but that probably uses an image created in Photoshop or something similar.