CGAffineTransform MakeScale resulting in black screen? - ios

Trying to flip a UIImageView with scaling it to -1.0 but it results in a black screen. Scaling with 1.0, 1.0 shows results as expected.
Here's my code:
UIImageView *vImg = [[UIImageView alloc] initWithImage:finalImg];
CGAffineTransform transform = CGAffineTransformMakeScale(-1.0, 1.0);
[vImg setTransform:transform];
What am I missing?

A quick all-in-one solution exists in UIImage:
- (id)initWithCGImage:(CGImageRef)imageRef scale:(CGFloat)scale orientation:(UIImageOrientation)orientation;
with one of the UIImageOrientation*Mirrored orientations.
EDIT: IIRC the prepended translation is only required for low-level drawRect due to the different coordinates system, so the scale transform alone should be fine. Are you sure the bug doesn't lie elsewhere?

Related

Progress View Flip and Scale

I am trying to flip a UIProgressView 180 in Xcode and I am trying to scale the progress view at the same time. The scaling works great until I add the flip and then the scaling does not work anymore. Any suggestions? Thanks!
[self.secondsPorgressBarTicker setTransform:CGAffineTransformMakeScale(1.0, 10.0)];
[self.secondsPorgressBarTicker setTransform:CGAffineTransformMakeRotation(-M_PI)];
You are setting the transform and then resetting it. If you want to combine transforms, you need to use different CG functions. Remember, order matters with transforms, so use whichever one solves your issue:
CGAffineTransform transform = CGAffineTransformMakeRotation(-M_PI);
transform = CGAffineTransformScale(transform, 1.0, 10.0);
[self.secondsPorgressBarTicker setTransform:transform];
or
CGAffineTransform transform = CGAffineTransformMakeScale(1.0, 10.0);
transform = CGAffineTransformRotate(transform, -M_PI);
[self.secondsPorgressBarTicker setTransform:transform];
You're making a new transform. Modify the existing one:
[self.secondsPorgressBarTicker setTransform:CGAffineTransformMakeScale(1.0, 10.0)];
[self.secondsPorgressBarTicker setTransform:CGAffineTransformRotate(self.secondsPorgressBarTicker, -M_PI)

CGAffineTransformMakeRotation and CGAffineTransformMakeScale

How come only one of the above works in code?
Currently I am using the following...
image.transform = CGAffineTransformMakeRotation(M_PI/2.5);
image.transform = CGAffineTransformMakeScale(1.25, 1.25);
And my image is scaled to 125% like the second line says, however it is not rotate at all.
When I flip the code around to say...
image.transform = CGAffineTransformMakeScale(1.25, 1.25);
image.transform = CGAffineTransformMakeRotation(M_PI/2.5);
My image is rotated but not scaled...
Is there a way to use both of these in the same code?
I have these in my viewDidLoad method. Can anyone help me?
Thanks!
The second one should not use the Make rendition of the function. Thus you should, for example either:
CGAffineTransform transform = CGAffineTransformMakeScale(1.25, 1.25);
image.transform = CGAffineTransformRotate(transform, M_PI/2.5);
or
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI/2.5);
image.transform = CGAffineTransformScale(transform, 1.25, 1.25);
Contrast the Creating an Affine Transformation Matrix functions with the Modifying Affine Transformations functions.
CGAffineTransformMake are applied to the identity matrix, so you are rotating, but then is like you restore the identity and apply the scale. Use CGAffineTransformMake.. only for the first, for the second use CGAffineTransform...

UIImageView image crop based on UIView mask

So i have an canvas (UIView) and a UIImageView, the canvas acts as a mask over the imageview
i am using UIGestureRecognizers to zoom and rotate the UIImageView which is under the canvas.
i want to convert the final image (show in the canvas to a UIImage, one solution is to convert the canvas to an image like below
UIGraphicsBeginImageContext(self.canvas.bounds.size);
[self.canvas.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *newCombinedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
now this works fine but the problem with this solution is the image is cropped to the dimensions of the canvas so the resolution is very low.
another option i explored was to use some custom UIImage categories to rotate and scale.
[[[self.photoImage image] imageRotatedByDegrees:rotaton_angle]
imageAtRect:CGRectMake(x,y width,height)]
i need to provide rotation angle (the rotation angle provided by UIGesture Delegate is not in Degrees or Radians, then there is x,y,width,height, i imagine these needs to be calculated based on some scale, (i do get scale value from UIGesture delegate but they do not seem to be correct for this function)
there are a number of solutions here, that guides you to crop and image given a rect. but in my case the rect is not the same scale as the image also there is rotation involved.
any help will be appreciated.
i have managed to solve this, here is my solution, it most definitely isn't the cleanest, but it works.
i needed to handle 3 things, pan, zoom and rotate.
firstly i used the the UIGestureRecognizer Delegate to get cumulative values incrementing at UIGestureRecognizerStateEnded for all 3.
then for rotation i just used the UIImage Category discussed here
self.imagetoEdit = [self.imagetoEdit imageRotatedByRadians:total_rotation];
for zooming (scale) i used GPUImage (i am using this throughout the app)
GPUImageTransformFilter *scaleFilter = [[GPUImageTransformFilter alloc] init];
[scaleFilter setAffineTransform:CGAffineTransformMakeScale(total_scale, total_scale)];
[scaleFilter prepareForImageCapture];
self.imagetoEdit = [scaleFilter imageByFilteringImage:self.imagetoEdit];
for panning, im doing this. (Not the cleanest code :S ) also using the above mention UIImage+Categories.
CGFloat x_ = (translation_point.x/canvas.frame.size.width)*self.imagetoEdit.size.width;
CGFloat y_ = (translation_point.y/canvas.frame.size.height)*self.imagetoEdit.size.height;
CGFloat xx = 0;
CGFloat yy = 0;
CGFloat ww = self.imagetoEdit.size.width-x_;
CGFloat hh = self.imagetoEdit.size.height-y_;
if (translation_point.x < 0) {
xx = x_*-1;
ww = self.imagetoEdit.size.width + xx;
}
if (translation_point.y < 0) {
yy = y_*-1;
hh = self.imagetoEdit.size.height + yy;
}
CGRect cgrect = CGRectMake(xx,yy, ww, hh);
self.imagetoEdit = [self.imagetoEdit imageAtRect:cgrect];
everything seem to work.
This might be helpful...
Resizing a UIImage the right way
It might need some updating for ARC etc... though I think there are people who have done it and have posted it on Github.

How to Rotate CAShapeLayer containing UIBezierPath? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Rotate CGPath without changing its position
I searched and tested a variety of code for a couple of hours and I can't get this to work.
I am adding an arbitrary UIBezierPath at a random location to a CAShapeLayer which gets added to a view. I need to rotate the path so that I can handle device rotations. I can rotate the layer instead of the path. I just need the result to be rotated.
I already have methods to handle transforming the bezier path by scaling and translation. It works great, but now I need to simply rotate 90 degrees left or right.
Any recommendations on how to do this?
Basic code:
UIBezierPath *path = <create arbitrary path>
CAShapeLayer *layer = [CAShapeLayer layer];
[self addPathToLayer:layer
fromPath:path];
// I could get the center of the box but where is the box center for the view it is in?
// CGRect box = CGPathGetPathBoundingBox(path.CGPath);
// layer.anchorPoint = ? How to find the center of the box for the anchor point?
// Rotating here appears to rotate around 0,0 of the view
layer.transform = CATransform3DMakeRotation(DegreesToRadians(-90), 0.0, 0.0, 1.0);
I see the following post:
BezierPath Rotation in a UIView
I suppose I could rotate as-is and then translate the path back into place. I just need to figure out what the translation values would be.
I should also state that what I am seeing after I try to rotate is that the image moves off-screen somewhere. I tried rotating 25 degrees to see movement and it pivots around the view's origin of 0,0 so that if I rotate 90 degrees the image is off-screen. I am running these test WITHOUT rotating the device - just to see how rotation works.
UPDATE #1 - 12/4/2012: For some bizarre reason if I set the position to a value I found empirically it moves the rotated bezier path into the correct position after rotation:
layer.position = CGPointMake(280, 60);
This values are a guess from starting/stopping the app and making adjustments. I have no idea why I need to adjust the position on rotation. The anchor point should be in the center of the layer. However, I did find that both the frame and position of a CAShapeLayer are all ZERO even though the path is set, and also the fact that the path is in the correct position within the view. The 280, 60 position shifts the path into what would be the center of the path bounding box when a rotation of +90 is made. If I change the rotation value I need to adjust the position. I should not have to do this manually adjustment.
I think a last resort is to somehow convert the bezier path to an image and then add it. I found that if I set the layer content to an image, then rotate, it rotates about its center point with no positional adjustment needed. Not so with setting the path.
UPDATE #2 12/4/2012 - I tried setting the frame and with fiddling I get it to center as follows:
CGRect box = CGPathGetPathBoundingBox(path.CGPath);
CGRect rect = CGRectMake(0, 0, box.origin.x + (3.5 * box.size.width), box.origin.y + (3.5 * box.size.height));
layer.frame = rect;
layer.transform = CATransform3DMakeRotation(DegreesToRadians(90), 0.0, 0.0, 1.0);
Why multiply by 3.5? I have no clue. I found that adding the box origin with about 3.5 times the size of the box shifts the rotated CAShapeLayer path to about where it should be.
There must be a better way to do this. This is a better solution than my previous post since the frame size does not depend on the rotation angle. I just don't know why the frame needs to be set to the value I am setting it to. I THOUGHT it should be
CGRectMake(0, 0, box.origin.x + (box.size.width / 2), box.origin.y + (box.size.height / 2));
However, it shifts the image to the left too much.
Another clue I found is that if I set the frame of [self view].frame (the frame of the entire parent view, which is the screen of the iPhone), then rotate, the rotation point is the center of the screen, an the path/image orbits around this center point. This is why I tried shifting the frame to what the center of the path should be so that it orbits around the box center.
UPDATE #3 12/4/2012 - I tried to render the layer as an image. However, it appears that just setting the path of a layer does not make it an "image" in the layer since it is empty
CGRect box = CGPathGetPathBoundingBox(path.CGPath);
layer.frame = box;
UIImage *image = [ImageHelper imageFromLayer:layer]; // ImageHelper library I created
CAShapeLayer *newLayer = [CAShapeLayer layer];
newLayer.frame = CGRectMake(box.origin.x, box.origin.y, image.size.width, image.size.height);
newLayer.contents = (id) image.CGImage;
It appears that rotating the layer with its path set is no different than simply rotating the bezier path itself. I will go back to rotating the bezier path and see if I can fiddle with the position elements or something. There's got to be a solution to this.
Goal: Rotate a UIBezierPath around its center point within the view it was originally created in.
UPDATE #4 12/4/2012 - I ran a series of tests measuring the values needed for translation in order to place a UIBezierPath in its previous center location.
CGAffineTransform rotate = CGAffineTransformMakeRotation(DegreesToRadians(-15));
[path applyTransform:rotate];
// CGAffineTransform translate = CGAffineTransformMakeTranslation(-110, 70); // -45
CGAffineTransform translate = CGAffineTransformMakeTranslation(-52, -58); // -15
[path applyTransform:translate];
However, the ratios of x/y translations do not correspond so I cannot extrapolate what translation is required based on the angle. It appears that 'CGAffineTransformMakeRotation' uses some arbitrary anchor put to make the rotation, which at the moment appears to be maybe (viewWidth / 2, 0). I am making this much harder than it needs to be. There's something I am missing to make a simple rotation so that the center point is maintained. I just need to "spin" the path 90 degrees left or right.
UPDATE #5 12/4/2012 - After running additional tests it appears that the anchor point for rotating a UIBezierPath is the origin from where all of the points were drawn. In this case the origin is 0,0 and all of the points are relative to that point. Therefore, it a rotation is applied, the rotation is occurring around the origin, and is why the path shifts up-right on -90 and up-left on 90. I need to somehow set the anchor point for the rotation to the center so it "spins" around the center, rather than the original origin point. 12 hours spent on this one issue.
After some detailed analysis and graphing the bounding box on paper I found my assertion that the origin of 0,0 is correct.
A solution to this problem is to translate the path (the underlying matrix) to the origin, with the center of the bounding box at origin, rotate, then translate the path back to its original location.
Here's how to rotate a UIBezierPath 90 degrees:
CGAffineTransform translate = CGAffineTransformMakeTranslation(-1 * (box.origin.x + (box.size.width / 2)), -1 * (box.origin.y + (box.size.height / 2)));
[path applyTransform:translate];
CGAffineTransform rotate = CGAffineTransformMakeRotation(DegreesToRadians(90));
[path applyTransform:rotate];
translate = CGAffineTransformMakeTranslation((box.origin.x + (box.size.width / 2)), (box.origin.y + (box.size.height / 2)));
[path applyTransform:translate];
Plug in -90 degrees to rotate in the other direction.
This formula can be used when rotating the device from portrait to landscape and vice/versa.
I still don't think this is the ideal solution but the result is what I need for now.
If anyone has a better solution for this please post.
UPDATE 12/7/2012 - I found what I think is the best solution, and very simple as I though it would be. Rather than using rotate, translate, and scale methods on the bezier path, I instead extract the array of points as CGPoint objects, and scale/translate them as needed based on the view size as well as the orientation. I then create a new bezier path and set the layer to this path.
The result is perfect scaling, translation, rotation.

CATransform3DMakeRotation and shadow

I'm creating the flip animation library.
In two words, I create the CALayer and rotate it using CoreAnimation's CATransform3DMakeRotation.
The question is – is there a way to add a shadow to that scene? Without rewriting the whole code with OpenGL :)
Have a look at CAGradientLayer. I am currently using it to add shadows in a similar situation. May be costly performance-wise (still have to check that), but looks quite convincing.
Add CAGradientLayer as sublayer(s) to your layers and animate its opacity. You may have to play around a little with the gradient stops and colors to get them right.
I am not sure about OpenGL but have you checked CATransform3D
Add
#define DEGREES_TO_RADIANS(d) (d * M_PI / 180)
in .pch file
CATransform3D myTransform = CATransform3DIdentity;
myTransform.m34 = 1.0 / -500;
myTransform = CATransform3DRotate(myTransform, DEGREES_TO_RADIANS(90), 0.0f, 0.0f, 1.0f);
myView.layer.transform = myTransform;
you can go on changing the angle here DEGREES_TO_RADIANS(90)
Here you can add shadow to myView.

Resources