How to rotate a cgpath before drawing it? - core-animation

I can draw a path...and i am somewhat sure that i need to use affine transform.
what i don't get is how to rotate the path and keep the same center.
I apologize for the lack of code here...but all I have is a failed call to CGContextRotateCTM that seemed to halt all drawing.

To rotate around the center:
CGContextTranslateCTM (ctx, center.x, center.y);
CGContextRotateCTM (ctx, angleInRadians);
CGContextTranslateCTM (ctx, -center.x, -center.y);
CGContextAddPath (ctx, path);
I might have the translate signs backward (i.e. negative translation before rotate instead of after), although I believe the CTM methods pre-concat, so the mathematical representation of those calls is
CTMnew = -T * R * T * CTMcurrent

Related

Draw rounded linear gradient (or extended radial gradient) with CoreGraphics

I want to do some custom drawing with CoreGraphics. I need a linear gradient on my view, but the thing is that this view is a rounded rectangle so I want my gradient to be also rounded at angles. You can see what I want to achieve on the image below:
So is this possible to implement in CoreGraphics or some other programmatic and easy way?
Thank you.
I don't think there is an API for that, but you can get the same effect if you first draw a radial gradient, say, in an (N+1)x(N+1) size bitmap context, then convert the image from the context to a resizable image with left and right caps set to N.
Pseudocode:
UIGraphicsBeginImageContextWithOptions(CGSizeMake(N+1,N+1), NO, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
// <draw the gradient into 'context'>
UIImage* gradientBase = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage* gradientImage = [gradientBase resizableImageWithCapInsets:UIEdgeInsetsMake(0,N,0,N)];
In case you want the image to scale vertically as well, you just have to set the caps to UIEdgeInsetsMake(N,N,N,N).
I just want to add more sample code for this technique, as some things weren't obvious for. Maybe it will be useful for somebody:
So, let's say, we have our custom view class and in it's drawRect: method we put this:
// Defining the rect in which to draw
CGRect drawRect=self.bounds;
Float32 gradientSize=drawRect.size.height; // The size of original radial gradient
CGPoint center=CGPointMake(0.5f*gradientSize,0.5f*gradientSize); // Center of gradient
// Creating the gradient
Float32 colors[4]={0.f,1.f,1.f,0.2f}; // From opaque white to transparent black
CGGradientRef gradient=CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceGray(), colors, nil, 2);
// Starting image and drawing gradient into it
UIGraphicsBeginImageContextWithOptions(CGSizeMake(gradientSize, gradientSize), NO, 1.f);
CGContextRef context=UIGraphicsGetCurrentContext();
CGContextDrawRadialGradient(context, gradient, center, 0.f, center, center.x, 0); // Drawing gradient
UIImage* gradientImage=UIGraphicsGetImageFromCurrentImageContext(); // Retrieving image from context
UIGraphicsEndImageContext(); // Ending process
gradientImage=[gradientImage resizableImageWithCapInsets:UIEdgeInsetsMake(0.f, center.x-1.f, 0.f, center.x-1.f)]; // Leaving 2 pixels wide area in center which will be tiled to fill whole area
// Drawing image into view frame
[gradientImage drawInRect:drawRect];
That's all. Also if you're not going to ever change the gradient while app is running, you would want to put everything except last line in awakeFromNib method and then in drawRect: just draw the gradientImage into view's frame. Also don't forget to retain the gradientImage in this case.

CGContext drawing rotated around arbitrary point

I have a really annoying issue trying to draw into a bitmap CGContext. What I am trying to do is I have a couple of images to draw into the full size of the image. One can come in at any UIImageOrientation and I've written the code to correctly rotate that properly, but I'm struggling with the second bit which is trying to draw another view at an arbitrary rotation about its centre.
The other view comprises an image drawn possibly outside of its bounds. What I am having a problem with is drawing these at a rotated angle as though it was a UIView that had an affine transform applied to it. e.g. imagine a UIView at {100, 300} of size {20, 20} and an affine transform rotating it by 45 degrees. It would be rotated about {110, 310}.
What I have tried is this:
- (void)drawOtherViewInContext:(CGContextRef)context atRect:(CGRect)rect withRotation:(CGFloat)rotation contextSize:(CGSize)contextSize {
CGRect thisFrame = <SOLVED_FEATURE_FRAME_RELATIVE_TO_RECT_SIZE>;
thisFrame.origin.y = contextSize.height - thisFrame.origin.y - thisFrame.size.height;
CGRect rotatedRect = CGRectApplyAffineTransform(CGRectMake(0.0f, 0.0f, rect.size.width, rect.size.height), CGAffineTransformMakeRotation(-rotation));
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, rect.origin.x, contextSize.height - rect.origin.y - rect.size.height);
transform = CGAffineTransformTranslate(transform,
+(rotatedRect.size.width/2.0f),
+(rotatedRect.size.height/2.0f));
transform = CGAffineTransformRotate(transform, -rotation);
transform = CGAffineTransformTranslate(transform,
-(rect.size.width/2.0f),
-(rect.size.height/2.0f));
CGContextConcatCTM(context, transform);
CGContextDrawImage(context, thisFrame, theCGImageToDraw);
CGContextConcatCTM(context, CGAffineTransformInvert(transform));
}
So what I am doing there, I think, is this:
Translate to the bottom left of rect which is where this view is meant to be drawn.
Translate by half the rotated size in x and y.
Rotate by the required angle.
Translate back half the original size in x and y.
I thought that this would be what I wanted to do because the first step translates the coordinate system to be such that thisFrame is drawn correctly relative to where we're being told to draw (by the rect method parameter). Then it's a pretty normal rotate about the centre of a rectangle.
The problem is that when rotated by say 45 degrees, the image is drawn slightly out of place. It's almost correct, but just not quite. When at 0, 90, 180 or 270 degrees then the position is pretty much spot on, maybe a few pixels out but when at 45, 135, 225, 315 degrees the position is too far up and to the right.
Can anyone see what I'm doing wrong here?
Update:
Silly me, it's bigger because I was passing in the wrong rect! Edited to get rid of references to it being the wrong size. It's still not quite in the right place though.
OK I have fixed it. The first point was that I was passing in the wrong rect at first as I was grabbing the frame from a UIView which had an affine transform applied to it, and as we all know the frame in that case is undefined. More likely it's the CGRect that comes from CGRectApplyAffineTransform(bounds, transform) but anyway, I fixed that one.
Then the main problem of drawing offset was fixed by changing my transform to this:
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, rect.origin.x, contextSize.height - rect.origin.y - rect.size.height);
transform = CGAffineTransformTranslate(transform,
+(rect.size.width/2.0f),
+(rect.size.height/2.0f));
transform = CGAffineTransformRotate(transform, -rotation);
transform = CGAffineTransformTranslate(transform,
-(rect.size.width/2.0f),
-(rect.size.height/2.0f));
That's what I had originally thought I should be doing, but for some reason I changed it to use the rotated CGRect.

CGContextConcatCTM vs CALayer affineTransform

I'm trying to change the affine transform of a CALayer so what's drawn in it gets deformed.
First I was doing this without CALayers, just redrawing with a different CGAffineTransform passed to CGContextConcactCTM.
That worked, but it wasn't fast enough, so I decided to do it with CALayer so it won't get redrawn every time. It just get transformed.
The problem is that setting myLayer.affineTransform = myTransform; doesn't seem to have the same effect as redrawing with CGContextConcatCTM(myTransform);
I'm new to Objective-C and pretty lame at math, so I'm sure I'm doing something wrong. But I can't see what.
Thanks.
Ok, nevermind, I found out.
Turns out myLayer.affineTransform does the transform relative to the center of the layer where as CGContextContactCTM does it relative to the origin.
So I just concatenated 2 other transforms:
CGPoint center;
center.x = capa.bounds.origin.x + capa.bounds.size.width/2;
center.y = capa.bounds.origin.y + capa.bounds.size.height/2;
CGAffineTransform trf1 = CGAffineTransformMakeTranslation(center.x, center.y);
CGAffineTransform trf2 = CGAffineTransformMakeTranslation(-center.x, -center.y);
capa.affineTransform = CGAffineTransformConcat(trf1, CGAffineTransformConcat(mat, trf2));

Horizontal Gradient with a rounded rect path?

I have a custom UIView I'm drawing using CoreGraphics. Using CoreGraphics, how would I set a horizontal gradient and clip it to my rounded rect path while still using a shadow? I wrote as much as the path and I can fill it with a color using setFill, but the gradient (a horizontal one, too) isn't abiding by the path...
EDIT: Since the time of posting (not so long ago), I figured out how to do a complex horizontal gradient, so now my only problem is drawing it within my CGContext's path.
Well it seems I've found my own answer:
CGContextAddPath(context, path);
CGContextClosePath(context);
CGContextClip(context);
CGContextDrawLinearGradient(context, gradient, startPt, endPt, 0);
Those lines of code perfectly clip it to the path of a rounded rect, or any path desired.

CGAffineTransformMake - how to save the state of UIimageview to futher process it

I am using CGAffineTransformMake to flip an UIImageView vertically. It works fine but it does not seem to save the new flipped position of UIImageview, because when I try to flip it 2nd time (execute the line code below) it just does not work.
shape.transform = CGAffineTransformMake(1, 0, 0, 1, 0, 0);
help please.
Thanks in advance.
Kedar
Transforms are not automatically additive/accumulative as you would expect. Assigning a transform just transforms the target once.
Each transform is highly specific. If apply a rotation transform that rotates a view +45 degrees, you will see it rotate only once. Applying the same transform again does not rotate the view an additional +45 degrees. All subsequent applications of the same transforms produce no visible effect because the view is already rotated +45 degrees and that is all that transform will ever do.
To make transforms accumulative you have apply the new transform to the existing transform instead of just replacing it. So as mentioned previously for each subsequent rotation you use:
shape.transform = CGAffineTransformRotate(shape.transform, M_PI);
Which adds the new transform to the existing transform. If you add a +45 degree transform in this manner the view will rotate an additional +45 each time it is applied.
I have the same problem with you and I found the solution! I want to rotate the UIImageView, because I will have the animation. To save the image I use this method:
void CGContextConcatCTM(CGContextRef c, CGAffineTransform transform)
the transform param is the transform of your UIImageView so anything you have done to the imageView will be the same with image! And I have write a category method of UIImage.
-(UIImage *)imageRotateByTransform:(CGAffineTransform)transform{
// calculate the size of the rotated view's containing box for our drawing space
UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0,self.size.width, self.size.height)];
rotatedViewBox.transform = transform;
CGSize rotatedSize = rotatedViewBox.frame.size;
[rotatedViewBox release];
// Create the bitmap context
UIGraphicsBeginImageContext(rotatedSize);
CGContextRef bitmap = UIGraphicsGetCurrentContext();
// Move the origin to the middle of the image so we will rotate and scale around the center.
CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);
//Rotate the image context using tranform
CGContextConcatCTM(bitmap, transform);
// Now, draw the rotated/scaled image into the context
CGContextScaleCTM(bitmap, 1.0, -1.0);
CGContextDrawImage(bitmap, CGRectMake(-self.size.width / 2, -self.size.height / 2, self.size.width, self.size.height), [self CGImage]);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
Hope this will help you.
If you just want to reverse the effects of a previous transformation, you may like to look into setting the shape.transform property to the value CGAffineTransformIdentity.
When you set a view's transform property you are replacing any existing transform it has, not adding to it. So if you assign a transform which causes a rotation, it will forget about any flip you had previously configured.
If you want to add an additional rotation or scaling operation to a view which you have previously transformed you should investigate the functions which allow you to specify an existing transform.
I.e. instead of using
shape.transform = CGAffineTransformMakeRotation(M_PI);
which replaces the existing transform with the specified rotation, you could use
shape.transform = CGAffineTransformRotate(shape.transform, M_PI);
this applies the rotation to the existing transform (what ever that may be) and then assigns it to the view. Take a look at Apple's documentation for CGAffineTransformRotate, it may clarify things a little.
BTW, the documentation says: "If you don’t plan to reuse an affine transform, you may want to use CGContextScaleCTM, CGContextRotateCTM, CGContextTranslateCTM, or CGContextConcatCTM."