Get real frame after affine transformation - ios

The goal is to get real frames in pdf page to identify the searched string, I am using PDFKitten lib to highlight the text that was searched and trying to figure out how to get frames for highlighted text. The core method is next:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
CGContextSetFillColorWithColor(ctx, [[UIColor whiteColor] CGColor]);
CGContextFillRect(ctx, layer.bounds);
// Flip the coordinate system
CGContextTranslateCTM(ctx, 0.0, layer.bounds.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
// Transform coordinate system to match PDF
NSInteger rotationAngle = CGPDFPageGetRotationAngle(pdfPage);
CGAffineTransform transform = CGPDFPageGetDrawingTransform(pdfPage, kCGPDFCropBox, layer.bounds, -rotationAngle, YES);
CGContextConcatCTM(ctx, transform);
CGContextDrawPDFPage(ctx, pdfPage);
if (self.keyword)
{
CGContextSetFillColorWithColor(ctx, [[UIColor yellowColor] CGColor]);
CGContextSetBlendMode(ctx, kCGBlendModeMultiply);
for (Selection *s in self.selections)
{
NSLog(#"layer.bounds = %f, %f, %f, %f", layer.bounds.origin.x, layer.bounds.origin.y, layer.bounds.size.width, layer.bounds.size.height);
CGContextSaveGState(ctx);
CGContextConcatCTM(ctx, s.transform);
NSLog(#"s.frame = %f, %f, %f, %f", s.frame.origin.x, s.frame.origin.y, s.frame.size.width, s.frame.size.height);
CGContextFillRect(ctx, s.frame);
CGContextRestoreGState(ctx);
}
}
}
Size of layer is (612.000000, 792.000000), but the size of s.frame is (3.110400, 1.107000). How can I get real frame from rect that is filled yellow?

As Matt says, a view/layer's frame property is not valid unless the transform is the identity transform.
If you want to transform some rectangle using a transform then the CGRect structure isn't useful, since a CGRect specifies an origin and a size, and assumes that the other 3 points of the rect are shifted right/down from the origin.
In order to create a transformed rectangle you need to build 4 points for the upper left, upper right, lower left, and lower right points of the untransformed frame rectangle, and then apply the transform to those points, before applying the transform to the view.
See the function CGPoint CGPointApplyAffineTransform(CGPoint point, CGAffineTransform t) to apply a CGAffineTransform to a point.
Once you've done that you could use the transformed points to build a bezier path containing a polygon that is your transformed rectangle. (It may or may not be a rectangle after transformation, and the only sure-fire way to represent it is as 4 points that describe a quadralateral.)

Please use bounds property. Also you have to use bounds when create custom layout. It's transform free.
frame it's rectangle which defines the size and position of the view in its superlayer’s coordinate system. bounds it's rectangle defines the size and position of the layer in its itself coordinate system. 
https://developer.apple.com/reference/quartzcore/calayer/1410915-bounds

Related

Transform a CGRect in Core Graphic coordinates

Is there a way to transform a CGRect with UIView system coordinates into Core Graphics coordinates, where the origin is in the lower-left corner?
Sure. You just have to subtract the y-origin and the height of the rect away from the view's height.
rect.origin.y = view.frame.size.height-(rect.origin.y+rect.size.height)
You can represent this with a CGAffineTransform like so:
CGAffineTransformMakeTranslation(0, view.size.height-((rect.origin.y*2.0)+rect.size.height))
You subtract the origin twice as you're now working with a relative value, instead of an absolute one.
However, if you only want to flip a context to work in UIView coordinates you'd want:
CGFloat ctxHeight = CGContextGetClipBoundingBox(c).size.height;
CGContextScaleCTM(c, 1, -1);
CGContextTranslateCTM(c, 0, -ctxHeight);

iOS graphics appearing from upper left corner on custom control

I have been making a circular control and i am doing fine, except that the graphics appears from upper left corner when i do the render first time.
The whole control is subclassed UIControl, with custom CALayer which does rendering of a circle.
This is the code that renders the circle:
- (void) drawInContext:(CGContextRef)ctx{
id modelLayer = [self modelLayer];
CGFloat radius = [[modelLayer valueForKey:DXCircularControlLayerPropertyNameRadius] floatValue];
CGFloat lineWidth = [[modelLayer valueForKey:DXCircularControlLayerPropertyNameLineWidth] floatValue];
//NSLog(#"%s, angle: %6.2f, radius: %6.2f, angle_m: %6.2f, radius_m: %6.2f", __func__, self.circleAngle, self.circleRadius, [[modelLayer valueForKey:#"circleAngle"] floatValue], [[modelLayer valueForKey:#"circleRadius"] floatValue]);
// draw circle arc up to target angle
CGRect frame = self.frame;
CGContextRef context = ctx;
CGContextSetShouldAntialias(context, YES);
CGContextSetAllowsAntialiasing(context, YES);
// draw thin circle
//CGContextSetLineWidth(context, <#CGFloat width#>)
// draw selection circle
CGContextSetLineWidth(context, lineWidth);
CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor);
CGContextAddArc(context, frame.size.width / 2.0f, frame.size.height / 2.0f, radius, 0.0f, self.circleAngle, 0);
CGContextStrokePath(context);
CGContextSetShouldAntialias(context, NO);
}
Here is the video of a problem.
If you watch carefully, you'll notice that rendering of circle somehow doesnt start centered. It skews itself from the upper left corner.
This only happens when doing the animation for the first time.
I know this can happen if one mistakes begin and end of animation commit blocks, but i dont use them here.
Just in case here is the link to the bitbucket repo of this control:
There is nothing wrong with the drawing method - you messed up a little bit setting the layer's frame ;-)
You are setting the frame for the circularControlLayer within your - (void) setAngle:(CGFloat)angle method. That means the frame is set for the first time when you animate the angle property - so the frame will be animated too. Set the frame within the - (void) commonInitDXCircularControlView method.
If you are creating custom layers, have a look at the [UIView layerClass] method. Using it will save you from trouble with bounds/frame management.

Text rotation issue in CGContext

I'm using CGContext to create a PDF file containing some text labels. Because the text would normally be upside down, I do a flip along the x axis and a translation:
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, textRect);
// *** ROTATION CODE FROM BELOW IS ADDED HERE ***
// Flip the coordinate system
CGContextTranslateCTM(context, 0, 2 * textRect.origin.y + textRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
NSAttributedString *attString = [[NSAttributedString alloc] initWithString:string attributes:attributes];
CTFramesetterRef aframesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString);
CTFrameRef frame = CTFramesetterCreateFrame(aframesetter, CFRangeMake(0, [attString length]), path, NULL);
CTFrameDraw(frame, context);
CFRelease(frame);
CFRelease(path);
CFRelease(aframesetter);
This draws text on a page like this:
Now I want all text labels to be rotated by 180°. This is what I do:
CGContextTranslateCTM(context, -1 * (textRect.origin.x + textRect.size.width/2), -1 * (textRect.origin.y + textRect.size.height/2));
CGContextRotateCTM(context, M_PI);
CGContextTranslateCTM(context, (textRect.origin.x + textRect.size.width/2), (textRect.origin.y + textRect.size.height/2));
But this moves the labels out of the page:
When I reduce the rotation angle to PI/16 or PI/8 it results in this output (labels are filled red):
What's the problem here?
In the first step you move the origin of your coordinates to a point below your view, probably not where you expect it to be due to the factor 2.
CGContextTranslateCTM(context, 0, 2 * textRect.origin.y + textRect.size.height);
I would expect this should be moving origin from upper left edge to lower left edge (it doesn't look like, but I can not really say from your code)
Then you flip it horizontally and it seems you see what you expect, but I would expect it to be too far down (again this might be because textRect is not what I expect it to be)
Next you move the origin to a point to the left of the view by half of the view's size + it's position and up again to some point which might be half of the views height above (if I got your first translation right)
This is probably not where you want it to be - I expect you want it to be in the middle of the view, which might (or might not) be done by
CGContextTranslateCTM(context, textRect.size.width/2, -textRect.size.height/2);
I expect you to be still in the same context.
Now you rotate which should be just fine and finalize with an adjustment of the origin, that has the same issue as the one before ...

CGContextAddArc counterclockwise instead of clockwise

I'm having an issue when drawing an arc inside of my drawing function inside of a CALayer subclass. The implementation for that drawing function is as follows:
-(void)drawInContext:(CGContextRef)ctx
{
CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
CGFloat radius = MIN(center.x, center.y);
CGContextBeginPath(ctx);
CGContextAddArc(ctx, center.x, center.y, radius, DEG2RAD(0), DEG2RAD(90), YES);
CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
CGContextSetLineWidth(ctx, 5);
CGContextDrawPath(ctx, kCGPathStroke);
}
Nothing too groundbreaking here, but the weird thing is that it's drawing the arc counterclockwise even though I specified that it shoud be clockwise. Conversely, when I specify NO for the clockwise parameter, it draws the arc clockwise. I did some research into flipped coordinate systems and figured that might be what the issue here is, but I didn't want to just hardcode the opposite bool parameter of what I meant. Any suggestions on how to fix this?
Thanks!
This is indeed due to the flipped coordinate system of UIKit vs. Quartz. From the docs for CGContext:
The clockwise parameter determines the direction in which the arc is created; the actual direction of the final path is dependent on the current transformation matrix of the graphics context. For example, on iOS, a UIView flips the Y-coordinate by scaling the Y values by -1. In a flipped coordinate system, specifying a clockwise arc results in a counterclockwise arc after the transformation is applied.
You can alleviate this in your code by using the transformation matrix to flip your context:
CGContextTranslateCTM(ctx, 0.0, self.bounds.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
You probably want to flip it back when you are finished with your drawing i.e.
CGContextSaveGState(ctx);
CGContextTranslateCTM(ctx, 0.0, self.bounds.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
// Draw...
CGContextRestoreGState(ctx);

cgcontext rotate rectangle

guys!
I need to draw some image to CGContext.This is the relevant code:
CGContextSaveGState(UIGraphicsGetCurrentContext());
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect rect = r;
CGContextRotateCTM(ctx, DEGREES_TO_RADIANS(350));
[image drawInRect:r];
CGContextRestoreGState(UIGraphicsGetCurrentContext());
Actually,the rectangle is rotate and display on a area what is not my purpose.I just want to
rotate the image and display on the same position.
Any ideas ?????
Rotation is about the context's origin, which is the same point that rectangles are relative to. If you imagine a sheet of graph paper in the background, you can see what's going on more clearly:
The line is the “bottom” (y=0) of your window/view/layer/context. Of course, you can draw below the bottom if you want, and if your context is transformed the right way, you might even be able to see it.
Anyway, I'm assuming that what you want to do is rotate the rectangle in place, relative to an unrotated world, not rotate the world and everything in it.
The only way to rotate anything is to rotate the world, so that's how you need to do it:
Save the graphics state.
Translate the origin to the point where you want to draw the rectangle. (You probably want to translate to its center point, not the rectangle's origin.)
Rotate the context.
Draw the rectangle centered on the origin. In other words, your rectangle's origin point should be negative half its width and negative half its height (i.e., (CGPoint){ width / -2.0, height / -2.0 })—don't use the origin it had before, because you already used that in the translate step.
Restore the gstate so that future drawing isn't rotated.
What worked for me was to first use a rotation matrix to calculate the amount of translation required to keep your image centered. Below I assume you've already calculated centerX and centerY to be the center of your drawing frame and 'theta' is your desired rotation angle in radians.
let newX = centerX*cos(theta) - centerY*sin(theta)
let newY = centerX*sin(theta) + centerY*cos(theta)
CGContextTranslateCTM(context,newX,newY)
CGContextRotateCTM(context,theta)
<redraw your image here>
Worked just fine for me. Hope it helps.
use following code to rotate your image
// convert degrees to Radians
CGFloat DegreesToRadians(CGFloat degrees)
{
return degrees * M_PI / 180;
};
write it in drawRect method
// create new context
CGContextRef context = UIGraphicsGetCurrentContext();
// define rotation angle
CGContextRotateCTM(context, DegreesToRadians(45));
// get your UIImage
UIImage *img = [UIImage imageNamed:#"yourImageName"];
// Draw your image at rect
CGContextDrawImage(context, CGRectMake(100, 0, 100, 100), [img CGImage]);
// draw context
UIGraphicsGetImageFromCurrentImageContext();

Resources