Make a line drawn with CoreGraphics dynamic - ios

I am drawing a line using the following method:
- (void)drawLineWithColor:(UIColor *)color andRect: (CGRect)rect{
if (uploadButtonHidden == 2) {
uploadPhotoButton.hidden = NO;
}
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 15, 110);
// set the stroke color and width
CGContextSetStrokeColorWithColor(context, [color CGColor]);
CGContextSetLineWidth(context, 6.0);
// move to your first point
CGContextMoveToPoint(context, 455, coords.y - 140);
// add a line to your second point
CGContextAddLineToPoint(context, coordsFinal.x, coordsFinal.y);
// tell the context to draw the stroked line
CGContextStrokePath(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Draw image on view
[image drawAtPoint:CGPointZero];
}
As you can see coords.y sets the starting point of my line. Is there any way when changing the coords.y point to update my line? If for example I have a method which adds 50 to coords.y every 0.5 seconds, how can I update the line without redrawing it (to prevent a memory crash)??
EDIT:
this method is called like this:
UIGraphicsBeginImageContext(self.view.frame.size);
[self drawLineWithColor:[UIColor blackColor] andRect:CGRectMake(20, 30, 1476, 1965)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
imageViewForArrows = [[UIImageView alloc] initWithImage:image];
[imageArrowsArray addObject:imageViewForArrows];

Don't draw into an image, draw straight into the context instead. I'm assuming this method is called from within your drawRect method, in which case there will already be a value CGContextRef. You just have to get it and draw into it. Be sure to use CGContextSaveGState and CGContextRestoreGState when you apply a transform or clipping.
- (void)drawLineWithColor:(UIColor *)color andRect: (CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, 15, 110);
// set the stroke color and width
CGContextSetStrokeColorWithColor(context, [color CGColor]);
CGContextSetLineWidth(context, 6.0);
// move to your first point
CGContextMoveToPoint(context, 455, coords.y - 140);
// add a line to your second point
CGContextAddLineToPoint(context, coordsFinal.x, coordsFinal.y);
// tell the context to draw the stroked line
CGContextStrokePath(context);
CGContextRestoreGState(context);
}

Related

Remove outside of bezier path

I have a function where I fill the image with a color and use a UIBezierPath to erase a point for corners.
CGRect rect = CGRectMake(0.0f, 0.0f, width, height);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(context, kCGBlendModeCopy);
// Fill image
CGContextSetFillColorWithColor(context, [[UIColor redColor] CGColor]);
CGContextFillRect(context, rect);
// Round corners
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:15.0];
CGContextSetStrokeColorWithColor(context, [[UIColor clearColor] CGColor]);
[bezierPath stroke];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
With the above, I get an image that does have the Bézier path cut out, and the background filled.
However, how can I remove the corners outside of the path, or get at least some way to reference where they are so I can clear them?
A couple of options:
Use CoreGraphics, like you have, but clip it to a path:
CGRect rect = CGRectMake(0.0f, 0.0f, width, height);
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(context, kCGBlendModeCopy);
// Round corners
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:15.0];
CGContextAddPath(context, bezierPath.CGPath);
CGContextClip(context);
// Fill image
CGContextSetFillColorWithColor(context, [[UIColor redColor] CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Alternatively, eliminate CoreGraphics and just fill the UIBezierPath:
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0);
[[UIColor redColor] setFill];
[[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:15.0] fill];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Note, in both of those examples, I used UIGraphicsBeginImageContextWithOptions, supplying a scale of 0 (a scale optimized for display on the device in question). If you really want, you can supply a scale of 1, which obviously will be a bit pixelated when rendered on a retina device, but that's up to you.

Drawing a Hollow Circle with Core Graphics for iOS

I am looking for the way how to draw a hollow circle using Core Graphics (CGContext) in iOS. I tried to do it using the code:
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, 8.0);
CGContextSetStrokeColor(ctx, CGColorGetComponents([[UIColor redColor] CGColor]));
CGContextSetFillColor(ctx, CGColorGetComponents([[UIColor blueColor] CGColor]));
CGContextFillEllipseInRect(ctx, rect);
CGContextFillPath(ctx);
}
but it draws the filled circle.
Since this question is over discussed here but other examples like this give me only filled circles.
If you want to draw a circle within the rect, using Stroke methods and the rect in parameter you will draw a part of the cercle outside the rect.
You then have two choices, assuming you know the width of the circle you want to draw.
First, you can stroke the line, but you have to draw it in a smaller rect :
-(void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat innerWidth = 8;
[[UIColor redColor] setStroke];
CGContextSetLineWidth(ctx, innerWidth);
// We add an ellipsis shifted by half the inner Width
CGContextAddEllipseInRect(ctx, CGRectInset(rect, innerWidth, innerWidth));
// Stroke the path
CGContextStrokePath(ctx);
}
You can also fill the circles using the even-odd rule (Apple docs)
-(void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat innerWidth = 8;
[[UIColor redColor] setStroke];
// Add the outer circle
CGContextAddEllipseInRect(ctx, rect);
// Add the inner circle
CGContextAddEllipseInRect(ctx, CGRectInset(rect, innerWidth, innerWidth));
// Fill the path using the EO rule
CGContextEOFillPath(ctx);
}
If you only want the circle outline, don't fill it.
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, 8.0);
[[UIColor redColor] set];
CGContextStrokeEllipseInRect(ctx, rect);
}
Use CGContextStrokeEllipseInRect instead of CGContextFillEllipseInRect. And since you haven't built a path in the current context, get rid of CGContextFillPath.
One way you could do it is draw a filled circle and then draw a slightly smaller circle with CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeClear)
After your code add something like
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, 5.0);
CGContextSetBlendMode(ctx,kCGBlendModeClear)
CGContextSetFillColor(ctx, CGColorGetComponents([[UIColor blueColor] CGColor]));
CGContextFillEllipseInRect(ctx, rect);
CGContextFillPath(ctx);
UIGraphicsEndImageContext();
I use CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeClear) in my drawing application, which is probably a better use. Stroking the path will probably work for you.

Core graphics images low quality

I'm the new one in Core Graphics and I try to draw circles. They can be transparent (only stroke) and filled with the stroke color. So the method looks like this:
+ (UIImage *)iconImageWithColor: (UIColor *)markColor strokeOnly:(BOOL)strokeOnly
{
CGRect rect = CGRectMake(0.0f, 0.0f, 20.0f, 20.0f);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGPathRef clippingPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:7.5f].CGPath;
CGContextAddPath(context, clippingPath);
CGContextClip(context);
CGContextSetFillColorWithColor(context, strokeOnly == YES ? [[UIColor clearColor] CGColor] : [markColor CGColor]);
CGContextFillRect(context, rect);
CGPathRef path = CGPathCreateWithRoundedRect(rect, 10.f, 10.f, NULL);
[markColor setStroke];
CGContextAddPath(context, path);
CGContextDrawPath(context, kCGPathFillStroke);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
The result is strange - it looks like the resolution of the image is very low. Is it possible to make the result image looks good on retina display?
UIGraphicsBeginImageContext creates a context with a scale of 1, so non retina.
What you want to use is
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0f)
scale 0 means it will use the device screen scale
Use UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0.0);
Last parameter means the resulting pixel density (scale). When you pass 0 it picks it up automatically from device native screen scale.

how to clip a circle created using CGContextFillEllipseInRect with another circle

I am drawing a simple filled circle using following code in draw rect.
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(con, CGRectMake(0,0,self.bounds.size.width,self.bounds.size.height));
CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
CGContextFillPath(con);
it draws a circle properly. Now I want to clip the circle with another small circle in the middle so that it becomes a hollow circle and you can see whatever is behind the main circle. How can i do that?
Use the even-odd rule with CGContextEOClip()
CGSize size = rect.size;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, 0);
CGContextSaveGState(context);
CGContextAddPath(context, ([UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, size.width, size.height)].CGPath));
CGContextAddPath(context, ([UIBezierPath bezierPathWithOvalInRect:CGRectMake(size.width/4, size.height/4, size.width/2, size.height/2)].CGPath));
CGContextEOClip(context); //clip
CGContextAddPath(context, [UIBezierPath bezierPathWithRect:rect].CGPath);
CGContextSetFillColorWithColor(context, [UIColor greenColor].CGColor);
CGContextFillPath(context);
CGContextRestoreGState(context);
I was able to do that using the EO rule.
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(con, rect);
CGContextAddEllipseInRect(con, CGRectInset(rect, 40, 40));
CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
CGContextEOFillPath(con);

CGContextClipToMask does not clip

This code results in the image below. As far as I understand CGContextClipToMask, the red rectangle should not be visible, since it is outside of the clipped area. What am I missing here? Thanks for any help!
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextFillRect(context, rect);
CGContextSetLineWidth(context, 20);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
// draw partial circle
UIBezierPath *arc = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:NO];
CGContextAddPath(context, [arc CGPath]);
CGContextStrokePath(context);
// create mask
CGImageRef mask = CGBitmapContextCreateImage(context);
self.maskCreated(mask);
// save state
CGContextSaveGState(context);
// clip with mask
CGContextClipToMask(context, rect, mask);
// draw test rect
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, CGRectMake(0, 0, 100, 100));
// restore state
CGContextRestoreGState(context);
The documentation for CGContextClipToMask says:
If mask is an image, then it must be in the DeviceGray color space,
may not have an alpha component, and may not be masked by an image
mask or masking color.
I'm assuming your code is in a the -drawRect: method of a subclass of UIView, so you are using the CGContext which was provided to you, which is in an RGB color space and probably has an alpha component. Your mask image is created from that context, so it gets the same attributes.
To fix this, use a separate bitmap context to generate the mask, using a gray colorspace with no alpha. Here's a self-contained example that does something similar to your code.
- (void)drawRect:(CGRect)rect
{
// Create a context for the mask
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
CGContextRef maskContext = CGBitmapContextCreate(NULL, rect.size.width, rect.size.height, 8, 0, colorSpace, kCGImageAlphaNone | kCGBitmapByteOrderDefault);
CGColorSpaceRelease(colorSpace);
// Fill with black
CGContextSetFillColorWithColor(maskContext, [UIColor blackColor].CGColor);
CGContextFillRect(maskContext, rect);
// Draw an arc in white
CGContextSetLineWidth(maskContext, 20);
CGContextSetStrokeColorWithColor(maskContext, [UIColor whiteColor].CGColor);
CGContextAddArc(maskContext, CGRectGetMidX(rect), CGRectGetMidY(rect), 50, M_PI, 0, false);
CGContextStrokePath(maskContext);
// Create the mask image from the context, and discard the context
CGImageRef mask = CGBitmapContextCreateImage(maskContext);
CGContextRelease(maskContext);
// Now draw into the view itself
CGContextRef context = UIGraphicsGetCurrentContext();
// Apply the mask
CGContextClipToMask(context, rect, mask);
// Then draw something that overlaps the mask
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rect);
// Make sure to clean up when we're done
CGImageRelease(mask);
}
Actually don't understand your concern, but you can hide rectangle in your method like this:
// draw a test rect
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGRect rect = CGRectZero;
CGContextFillRect(context, rect);

Resources