I wanted to do something with Quartz 2D which I though should be simple, but turns out not to be :-(
What I try to do is the following. I want to rotate the drawing area by 90 degrees, so that basically anything I draw is rotated 90 degrees as well. Turns out the rotation works ok, but the rectangle I draw starts off screen, and it does not cover the whole height, but only is as heigh as the width (320 pixels) see screenshot.
Here's my code (inside drawRect):
CGContextRef ctx = UIGraphicsGetCurrentContext(); //get the graphics context
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGContextSetRGBStrokeColor(ctx, 0.0, 0.0, 0.0, 1);
CGContextSetLineWidth(ctx, 1.0) ;
float width = rect.size.width ;
float height = rect.size.height ;
CGContextTranslateCTM(ctx, rect.origin.x + width / 2, rect.origin.y + height / 2 ) ; // make rotation point the middle
CGContextRotateCTM(ctx, 1.57079633) ; // 90 degrees
CGContextTranslateCTM(ctx, - height / 2, - width / 2) ; // move x / y back to where they belong
CGRect myRect = CGRectMake(0, 0, height, width) ;
CGContextFillRect(ctx, myRect) ;
The result is as follows:
What am I missing here?
CGRect myRect = CGRectMake(0, 0, height, width) ;
Params are {x,y,w,h}
try
CGRect myRect = CGRectMake(0, 0, width, height) ;
also, you should implement a DEG_TO_RAD function to help with passing angles into these drawing functions to make your coding easier
#define DEG_TO_RAD(angle) ((angle) / 180.0 * M_PI)
This
CGContextRotateCTM(ctx, 1.57079633) ; // 90 degrees
becomes
CGContextRotateCTM(ctx, DEG_TO_RAD(90)) ;
Added benefit: You lose the comment and you can increment this value programatically in degrees
I found the problem. When creating the view I mistakenly used width for heigh and vice versa. After I've fixed that all I had to do within the view was the following:
CGContextTranslateCTM(ctx, rect.origin.x + width / 2, rect.origin.y + height / 2 ) ; // make rotation point the middle
CGContextRotateCTM(ctx, DEG_TO_RAD(90)) ;
CGContextTranslateCTM(ctx, - height / 2, - width / 2) ; // move x / y back to where they belong
CGRect myRect = CGRectMake(0, 0, height-5, width) ; // height determines width and width is height
CGContextClipToRect(ctx, myRect) ; // prevent lines being draws outside
From that onwards I use only myRect as a reference for all drawing operations.
And here's the result.
Related
I'm new to core graphics and I'm struggling with a simple task of putting a sweeping circle inside a square. The outcome I got looks like this:
The circle won't appear at the center of the square, and the size of the circle appears much smaller than I specified.
Below is my drawRect method for drawing the circle. I have put the printed-out variable values while debugging in the comments for your convenience. I also printed out the value passed to initWithFrame: frame=(0 0; 256 256). The frame is the orange square you see in the picture.
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat midX = CGRectGetMidX(self.bounds); // bounds = (0 0; 256 256); midX = 128
CGFloat midY = CGRectGetMidY(self.bounds); // midY = 128
CGFloat radius = midY - 4; // radius = 124
// Outer grey pie
[endColor setFill];
CGContextMoveToPoint(context, midX, midY); // move to center
CGContextAddEllipseInRect(context, CGRectMake(midX - radius, midY - radius, radius * 2, radius * 2)); // adds a circle of radius = square_side_length - 4
CGContextFillPath(context); // fill the circle above with grey
// Show the clock
NSTimeInterval seconds = [[NSDate date] timeIntervalSince1970];
CGFloat mod = fmod(seconds, self.period);
CGFloat percent = mod / self.period;
[fillColor setFill];
CGFloat start = -M_PI_2;
CGFloat end = 2 * M_PI;
CGFloat sweep = end * percent + start;
CGContextMoveToPoint(context, midX, midY);
CGContextAddArc(context, midX, midY, radius, start, sweep, 0); // radius = square_side_length - 24
CGContextFillPath(context);
// Innermost white pie
radius -= 50; // radius = square_side_length - 54
[bgColor setFill]; // white
CGContextMoveToPoint(context, midX, midY);
CGContextAddEllipseInRect(context, CGRectMake(midX - radius, midY - radius, radius * 2, radius * 2));
CGContextFillPath(context);
}
And below is the code that adds the clock to its superview:
clock = [[ProgressClock alloc] initWithFrame:self.clockHolder.bounds // bounds=[0 0; 256 256]
period:[TOTPGenerator defaultPeriod]
bgColor:[UIColor whiteColor]
strokeColor:[UIColor colorWithWhite:0 alpha:0.2]
fillColor:[UIColor blueColor]
endColor:[UIColor grayColor]
shade:NO];
[self.clockHolder addSubview:clock];
Can anyone spot the mistake I made? Thanks in advance.
Thanks a lot to #originaluser2's comment, I have fixed this issue simply by moving the clock presenting logic from viewDidLoad to viewDidAppear and the clock showed up perfectly. There was nothing wrong with the drawing code I posted; however the auto-layout initialization and the animation of my clock happened in a sequence that gave my drawing canvas a wrong frame. By putting the drawing logic in viewDidAppear, we are guaranteed that all the auto-layout setup has been completed, thus frames are fixed, before continue onto drawing the circle.
I want to create a perfect semi circle at the bottom of my view and now I'm getting just an arc in the top of a rectangle (see attached picture). This is the code I'm using is the following where circularRect is origin = (x = 128, y = 514), size = (width = 64, height = 54)
CGFloat arcHeight = 60.0;
CGRect arcRect = CGRectMake(circularRect.origin.x, circularRect.origin.y + circularRect.size.height - arcHeight, circularRect.size.width, arcHeight);
CGFloat arcRadius = 60;
CGPoint arcCenter = CGPointMake(arcRect.origin.x + arcRect.size.width/2, arcRect.origin.y + arcRadius);
CGFloat angle = acos(arcRect.size.width / (2*arcRadius));
CGFloat startAngle = 270 * M_PI/180 + angle;
CGFloat endAngle = 90 * M_PI/180 - angle;
CGContextAddArc(context, arcCenter.x, arcCenter.y, arcHeight, startAngle, endAngle, 1);
CGContextClip(context);
CGContextClearRect(context, arcRect);
CGContextSetFillColorWithColor(context, [UIColor greenColor].CGColor);
CGContextFillRect( context, arcRect);
What I'm doing wrong? Thanks!
The radius you're supplying to the arc function is too large. Your rectangle width being 64, you can only fit a circle that has a radius of 32 or less in it.
I have a question about scale operation of a frame to a specific size.
I have a CGRect and would to resize it to a specific CGSize.
I would to move the center of this CGRect in proportion to my rescale value.
If by chance you're modifying a UIView, you could take this approach:
CGPoint previousCenter = view.center;
// Set width and height here:
view.frame = CGRectMake(view.frame.origin.x,view.frame.origin.y, width, height);
view.center = previousCenter;
That'll maintain the center point while changing the size of the view.
I am a little confused by your question, so I may not be answering it correctly. If you are using a CGAffineTransform to scale, as your tags suggest, that's another matter entirely.
You can use CGRectInset(rect, x, y). This will inset the CGRect by x and y, and push the origin in by x and y. (https://developer.apple.com/library/ios/documentation/graphicsimaging/reference/CGGeometry/Reference/reference.html#//apple_ref/c/func/CGRectInset)
CGSize targetSize = CGSizeMake(100.0f, 100.0f);
CGRect rect = CGRectMake(50.0f, 50.0f, 200.0f, 200.0f);
rect = CGRectInset(rect, roundf((rect.size.width - targetSize.width) / 2.0f), roundf((rect.size.height - targetSize.height) / 2.0f);
Edit: Note that I'm using the difference between the two sizes, halved. My justification here is that CGRectInset will affect the entire rect. What I mean by that is...
CGRect rect = CGRectMake(0, 0, 10, 10);
rect = CGRectInset(rect, 2, 2);
rect is now a CGRect with (2, 2, 6, 6)
I have tried everything I can think of, but none of its working. In an iOS UIView drawRect, I want a function that takes an image, a point on the image, scale, and an angle, and draws the image rotated around the given point at the center of the view. So if I want to rotate around the origin (0,0) the edge of my image will appear in the center of the view.
Here's my first version of the code:
-(void)drawImage:(UIImage*)image atAngle:(float)theta atScale:(float)scale whereCenter:(JBPoint*)center inContext:(CGContextRef)context
{
UIGraphicsPushContext(context);
//Rotate
CGContextTranslateCTM (context, -center.x, -center.y);
CGContextRotateCTM (context, theta);
CGContextTranslateCTM (context, center.x, center.y);
//Scale
CGContextScaleCTM(context, scale, scale);
//Draw
GContextDrawImage(context, self.frame, image.CGImage);
UIGraphicsPopContext();
}
However, this skewed the image such that it was not an affine transformation even though I only performed affine transformations on it.
After fiddling with it for a time, I just decided to do it by hand, and calculate the locations of the four corner points and draw it then:
-(void)drawImage:(UIImage*)image atAngle:(float)theta atScale:(float)scale whereCenter:(JBPoint*)center inContext:(CGContextRef)context
{
UIGraphicsPushContext(context);
CGContextRotateCTM (context, theta);
float ux = cos(theta);
float uy = sin(theta);
float vx = sin(theta);
float vy = -cos(theta);
CGPoint p1 = CGPointMake(self.frame.size.width/2.0f-ux*scale*center.x-vx*scale*(image.size.height - center.y), self.frame.size.height/2.0f-uy*scale*center.x-vy*scale*(image.size.height - center.y));
CGPoint p2 = CGPointMake(p1.x+ux*image.size.width*scale, p1.y+uy*image.size.width*scale);
CGPoint p3 = CGPointMake(p2.x+vx*image.size.height*scale, p2.y+vy*image.size.height*scale);
CGPoint p4 = CGPointMake(p1.x+vx*image.size.height*scale, p1.y+vy*image.size.height*scale);
//Sanity check. These all should be the same since I want to center at the center of the frame
NSLog(#"Center: %f, %f", (p1.x+p3.x)/2.0f, (p1.y+p3.y)/2.0f);
NSLog(#"Center: %f, %f", (p2.x+p4.x)/2.0f, (p2.y+p4.y)/2.0f);
NSLog(#"Center: %f, %f", self.frame.size.width/2.0f, self.frame.size.height/2.0f);
float minX = MIN(MIN(p1.x, p2.x), MIN(p3.x, p4.x));
float minY = MIN(MIN(p1.y, p2.y), MIN(p3.y, p4.y));
float maxX = MAX(MAX(p1.x, p2.x), MAX(p3.x, p4.x));
float maxY = MAX(MAX(p1.y, p2.y), MAX(p3.y, p4.y));
CGContextSetAlpha(context, alpha);
CGContextScaleCTM(context, 1, -1);
CGContextDrawImage(context, CGRectMake(minX, -minY, (maxX-minX), -(maxY-minY)), image.CGImage);
UIGraphicsPopContext();
}
This works for angles of 0, but as soon as the angle becomes 90 degrees (pi/2) the image is extremely skewed, which doesn't make any sense.
This seems to be such a simple problem. Why is it so hard? Does anyone see what I'm doing wrong?
With this formula I got angle
double rotateAngle = atan2(y,x)
with this code I can draw a rectangle
CGRect rect = CGRectMake(x,y , width ,height);
CGContextAddRect(context, rect);
CGContextStrokePath(context);
How can I rotate the rectangle around the angle ?
Here's how you'd do that:
CGContextSaveGState(context);
CGFloat halfWidth = width / 2.0;
CGFloat halfHeight = height / 2.0;
CGPoint center = CGPointMake(x + halfWidth, y + halfHeight);
// Move to the center of the rectangle:
CGContextTranslateCTM(context, center.x, center.y);
// Rotate:
CGContextRotateCTM(context, rotateAngle);
// Draw the rectangle centered about the center:
CGRect rect = CGRectMake(-halfWidth, -halfHeight, width, height);
CGContextAddRect(context, rect);
CGContextStrokePath(context);
CGContextRestoreGState(context);