Text rotation issue in CGContext - ios

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 ...

Related

Get real frame after affine transformation

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

iOS - Draw image with CGContext and transform

I am trying to draw an image on top of another image. I have the image's size, transform and origin. My code below shows correct size and transform angle but not at the correct point.
Code:
UIGraphicsBeginImageContextWithOptions(backgroundImage.size, NO, [[UIScreen mainScreen] scale]);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect baseRect = CGRectMake(0, 0, backgroundImage.size.width, backgroundImage.size.height);
[backgroundImage drawInRect:baseRect];
CGRect newRect = CGRectMake(x, y, width, height);
CGContextTranslateCTM(context, x, y);
CGContextConcatCTM(context, watermarkImageView.transform);
CGContextTranslateCTM(context, -x, -y);
[watermarkImageView.image drawInRect:newRect];
UIImage* result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
The watermark image should be placed like this:
But currently its looking like this:
What did I miss?
Thanks in advance
EDIT
The x,y is the edge of the bounding box
Your code doesn't show what watermarkImageView.transform is and that is important because when you concat transformations, the effects of previous transformations will also effect all the following transformations.
E.g. a translation that moves the object 10 pixels along the x-axis will move the object 10 pixels to the right. However, if you first have a rotation that rotates the object by 45 degrees and then add a translation that moves 10 pixels along the x-axis, the object will not move 10 pixels to the right, it will move 10 pixels along a line that is 45 degrees rotated, which means it will move about 7 pixels up and 7 pixels to the right. That's because a rotation does not really rotate the object itself, it actually rotates the whole coordinate system which causes the object to be drawn rotated.
See this image:
Initially the translation coordinate system (red lines) matches the "real coordinate" system. But after the rotation by 45 degrees, the translation coordinate system has been rotated and now translating across the red lines moves the object diagonally.
Think about a sheet of paper and a stamp. The stamp always has the same position and the same orientation, you cannot move or rotate the stamp. But you can move and rotate the sheet of paper below the stamp! And that's what your transformations do. They transform the sheet before the stamp is pressed upon it.
For most people it is very hard to imagine the effects of transforming the whole space, it's much easier for them to think about transforming the object. The trick is: You must read your transformations in the opposite order than you wrote them. I guess what you want to do is actually:
CGContextTranslateCTM(context, x, y);
CGContextConcatCTM(context, watermarkImageView.transform);
CGContextTranslateCTM(context,
-watermarkImageView.size.with * 0.5,
-watermarkImageView.size.height * 0.5
);
Now read them in the opposite order (from bottom to top). First you center the watermark around (0,0) by moving it up half the height and left half the width. Now the center of your watermark is exactly at (0,0). Then you rotate it as desired. Finally you move it to the desired position. Of course you wrote all transformations the other way round but that's only because you are transforming the coordinate space, not the object.
Centering your watermark prior to rotation is important because rotation always rotates around (0,0) coordinates. If you'd just rotate, the rotation looks like this:
That's not what you want as it will not just rotate the object but also changes its position. If you center the image around (0,0) first, the rotation looks like this instead:
The answer to my question was
I had to translate to the centre of where I want to draw the context.
CGContextTranslateCTM(context, imageView.center.x, imageView.center.y);
Then rotate context.
CGFloat angle = [(NSNumber *)[imageView valueForKeyPath:#"layer.transform.rotation.z"] floatValue];
CGContextRotateCTM(context, angle);
Then draw
[imageView.image drawInRect:CGRectMake(-width * 0.5f, -height * 0.5f, width, height)];

How do I rotate text in Quartz running on iOS so the origin does not move?

I am trying to create a PDF that will run on iOS devices (iPad only). I have created my PDF and I am able to write text and graphics into it. But I am stumped on writing rotated text such that the text will rotate but the origin will remain the same. Here is an image of the output I am trying to achieve:
I create my PDF like this:
-(void)drawPDF:(NSString*)fileName
{
self.pageSize = CGSizeMake(792, 612);
// Create the PDF context using the default page size of 612 x 792.
UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
// Mark the beginning of a new page.
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, pageSize.width, pageSize.height), nil);
// Get the graphics context.
context = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, 654);
CGContextScaleCTM(context, 1.0, -1.0);
UIFont *font = [UIFont fontWithName:#"Helvetica-Light" size:8];
[self drawText:#"here is a really long line of text so we can see" withFrame:CGRectMake(400, 100, 50, 50) withFont:font rotation:-50];
// Close the PDF context and write the contents out.
UIGraphicsEndPDFContext();
}
Then I have this function for drawing the text, that I've tried many variations of:
-(void)drawText:(NSString *)text withFrame:(CGRect)rect withFont:(UIFont *)font rotation:(float)degrees
{
float radians = [PDFMaker DegreesToRadians:degrees];
NSDictionary *attribs = #{NSFontAttributeName:[UIFont fontWithName:#"Helvetica-Light" size:10.0]};
CGContextSaveGState(context);
CGPoint pos = CGPointMake(rect.origin.x, -50 - rect.origin.y);
// Translate so we're drawing in the right coordinate space
CGContextScaleCTM(context, 1.0, -1.0);
CGSize stringSize = [text sizeWithAttributes:attribs];
CGContextTranslateCTM(context, 0-stringSize.width/2, 0-stringSize.height/2);
CGContextRotateCTM(context, radians);
CGContextTranslateCTM(context, stringSize.width/2, stringSize.height/2);
[text drawAtPoint:CGPointMake(pos.x, pos.y) withAttributes:attribs];
CGContextRestoreGState(context);
}
But no matter how much I play with the translates, the text jumps around as if being rotated around an axis at the origin. I've read in other answers to rotate the text on its center at the origin and have tried that but that doesn't seem to work (or, probably more accurately, I can't get it to work). I suspect the problem may be something related to using a PDF context but really don't know. Any help would be greatly appreciated.
After a lot of research and experimentation I figured it out. I'm not sure if it's OK to answer my own question so, if not, will an op please let me know and I'll delete it. I do think the answer and the reason behind the answer might be valuable for other community members.
Here is the working code:
-(void)drawText:(NSString *)text withFrame:(CGRect)rect withFont:(UIFont *)font
rotation:(float)degrees alignment:(int)alignment center:(BOOL)center
{
// Pivot rect
rect = CGRectMake(rect.origin.x, rect.origin.y*-1, rect.size.width, rect.size.height);
float radians = [PDFMaker DegreesToRadians:degrees];
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
paragraphStyle.alignment = alignment;
NSDictionary *attributes = #{ NSFontAttributeName: font,
NSParagraphStyleAttributeName: paragraphStyle,
NSForegroundColorAttributeName: [Util ColorFromHexString:#"FFFFFF"] };
CGContextSaveGState(context);
// Translate so we're drawing in the right coordinate space
CGContextScaleCTM(context, 1.0, -1.0);
// Save this is we want/need to offset by size
CGRect textSize = [text boundingRectWithSize:rect.size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attributes
context:nil];
// Translate to origin, rotate, translate back
CGContextTranslateCTM(context, rect.origin.x, rect.origin.y);
CGContextRotateCTM(context, radians);
CGContextTranslateCTM(context, -rect.origin.x, -rect.origin.y);
// Center if necessary
if(center==YES)
{
CGContextTranslateCTM(context, 0, (rect.size.height/2)-(textSize.size.height/2));
}
[text drawInRect:rect withAttributes:attributes];
CGContextRestoreGState
}
What's as interesting or more interesting is the why behind it. All this has been written in other questions and answers, though usually in pieces and many of the code the code samples in other Q&A's have since been deprecated.
So the why: iOS starts with a matrix with the origin at the top-left, like Windows. But the text rendering functions of iOS are carried over from OSX (which is carried over from NEXT), and the origin is at the bottom-left. PDF is normally written from the bottom left but it's standard practice to write a transform so it runs from the top-left. But since these are text rendering functions that use Core Text we flip again. So you need to take into account and get the transforms correct or your text will print upside down and backwards or off-screen.
After that we need to rotate at the origin which, at least the way I've set things up, is now at the bottom left. If we don't rotate at the origin our text will seem to fly around: the further away from the origin the more it will fly. So we translate to the origin, rotate, then translate back. This is not unique to PDF and applies to any rotation in Quartz.
Finally one editorial comment on making PDF on an iOS device that should not be overlooked: it can be slow and consume a lot of memory. I think it's fine to do for a small number of pages when you want high-quality vector output for printing or embedding into things like Office docs; charts, graphs, maybe game instructions, printable tickets .. small things. But if you're printing something like a large report or a book it's probably best to produce PDF on a server.

Line isn't drawing at the top corner

I'm attempting to draw two lines -- I understand that 0 is suppose to start at the corner, but for me it seems off. I know it isn't the code that's wrong and I've tried doing negative numbers for the x-coordinate, but the line disappears.
Those who wanted the size of my frames:
2014-03-29 21:12:28.294 touchScreenClockTest[5178:70b] height = 568.000000
2014-03-29 21:12:28.295 touchScreenClockTest[5178:70b] width = 320.000000
What I want it to look like:
What it looks like:
// Draw the top line
CGContextRef drawTopLine = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(drawTopLine, [UIColor whiteColor].CGColor);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(drawTopLine, 1.5);
CGContextMoveToPoint(drawTopLine, 0,5); //start at this point
CGContextAddLineToPoint(drawTopLine, 300, 5); //draw to this point
// and now draw the Path!
CGContextStrokePath(drawTopLine);
// Draw the bottom line
CGContextRef drawBotLine = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(drawBotLine, [UIColor whiteColor].CGColor);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(drawBotLine, 1.5);
CGContextMoveToPoint(drawBotLine, 0, 30); //start at this point
CGContextAddLineToPoint(drawBotLine, 300, 30); //draw to this point
// and now draw the Path!
CGContextStrokePath(drawBotLine);
Also one more question. I wanted to know how to put the navigation button on my own. I have the image of the navigation, but I didn't know what I would need to call to place the image UIView? (since I don't have a storyboard)

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