I have an image (its actually circular) and wish to only draw the part of that image that corresponds to the area between two concentric circles, the outer one is fixed, the inner one can adjust in size.
The outer concentric circle will always correspond to the outside edge of the image - but a "hole" needs to be left in the drawn image - corresponding to the size of the smaller concentric circle.
I'm trying to do this in objective-c for iOS - to draw a variable-sized "viewfinder" control.
Any help much appreciated.
Sounds like the standard case to use CGContextEOClip. Haven't checked this code, but something like:
CGContextRef ctxt = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddEllipseInRect(path, NULL, outerCircle);
CGPathAddEllipseInRect(path, NULL, innerCircle);
CGContextEOClip(ctxt);
//Draw your stuff
The easiest and most performant solution would probably be to use a UIImageView and apply a CAShapeLayer as the mask of its layer.
Example:
UIImage *image = ...;
CGFloat ringWidth = 20.0;
CGRect outerCircleRect = CGRectMake(0, 0, image.size.width, image.size.height);
CGRect innerCircleRect = CGRectInset(outerCircleRect, ringWidth, ringWidth);
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:outerCircleRect];
[path appendPath:[UIBezierPath bezierPathWithOvalInRect:innerCircleRect]];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.fillRule = kCAFillRuleEvenOdd;
shapeLayer.path = [path CGPath];
UIImageView *imageView = [[[UIImageView alloc] initWithImage:image] autorelease];
imageView.layer.mask = shapeLayer;
[self.view addSubview:imageView];
You'll have to include the QuartzCore framework for this to work.
When you change the radius of the inner circle, you can simply assign a new path to the shape layer. This can even be animated.
Related
The Problem I facing right now is that after I draw the dots and lines by CAShapeLayer, I need to modify both of their colors by changing with different for rect regions. The regions I set right now have four portions, and following are the sample codes:
- (void)drawChartWithRecord:(Chart *)Chart
lineWidth:(CGFloat)lineWidth
radius:(CGFloat)radius
interval:(CGFloat)interval
rect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
float start_angle = 0;
float end_angle = (float) (2 * M_PI);
CAShapeLayer *avg = [CAShapeLayer layer];
avg.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(X,Y) radius:radius startAngle:start_angle endAngle:end_angle clockwise:YES].CGPath;
CGContextMoveToPoint(context, X, Y);
CGContextAddLineToPoint(context, pastX, pastY);
CGContextSetStrokeColorWithColor(context, LINE_COLOR);
CGContextSetLineWidth(context, lineWidth);
CGContextStrokePath(context);
[self.layer addSublayer:avg];
}
Upon with the line and dots codes; however, I didn't find out any method to import the rect to the same CAShapeLayer. If I make a new layer as
CAShapeLayer *mask = [CAShapeLayer layer];
There would be a problem that If I use
[self.layer addSublayer:mask];
Two of the sublayers would mix, and I don't want to change my background color.
Thanks for everyone's help : )
Unfortunately, I was wrong about my question.
The thing I want could be easy to solve by the CAGradientLayer & CAShapeLayer. Once you finish your own CAShapeLayer, by using
[self.gradientLayer setMask:avgCircle];
you can easily add the the your own gradient color to the line shape CAShapeLayer
Be sure add addSublayer: for both CAGradientLayer & CAShapeLayer
I am making a pdf in an iPad app. Now i can make the pdf however want to add a picture with a rounded corner border. For example to achieve the effect i want on the border on a simple view item i use the following code.
self.SaveButtonProp.layer.cornerRadius=8.0f;
self.SaveButtonProp.layer.masksToBounds=YES;
self.SaveButtonProp.layer.borderColor=[[UIColor blackColor]CGColor];
self.SaveButtonProp.layer.borderWidth= 1.0f;
With the pdf i am using the following method to add the picture with the border to the pdf.
CGContextRef currentContext = UIGraphicsGetCurrentContext();
UIImage * demoImage = [UIImage imageWithData : Image];
UIColor *borderColor = [UIColor blackColor];
CGRect rectFrame = CGRectMake(20, 125, 200, 200);
[demoImage drawInRect:rectFrame];
CGContextSetStrokeColorWithColor(currentContext, borderColor.CGColor);
CGContextSetLineWidth(currentContext, 2);
CGContextStrokeRect(currentContext, rectFrame);
How do i round the corners?
Thanks
While drawing you can set clipping masks. For example, it's relatively easy to create a Bezier path with the shape of a rounded rectangle and apply that as clipping mask to your graphics context. Everything subsequently drawn will be clipped.
If you want remove the clipping mask later (for example because you have an image with rounded corners but follow that by other elements) you'll have to save the graphic state first, then apply your clipping mask and restore the graphics state when you're done with your rounded corners.
You can see actual code that comes pretty close to what I think you need here:
UIImage with rounded corners
You can use a method to get any UIView/UIImageView to PDF NSData:
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
NSData *data = [self makePDFfromView:imageView];
Method:
- (NSData *)makePDFfromView:(UIView *)view
{
NSMutableData *pdfData = [NSMutableData data];
UIGraphicsBeginPDFContextToData(pdfData, view.bounds, nil);
UIGraphicsBeginPDFPage();
CGContextRef pdfContext = UIGraphicsGetCurrentContext();
[view.layer renderInContext:pdfContext];
UIGraphicsEndPDFContext();
return pdfData;
}
Maybe you can change or use this code to help you with your problem.
I have a sequence of off screen NSViews in a Cocoa application, which are used to compose a PDF for printing. The views are not in an NSWindow, or visible in any way.
I'd like to be able to generate thumbnail images of that view, exactly as the PDF would look, but scaled down to fit a certain pixel size (constrained to a width or height). This needs to be as fast as possible, so I'd like to avoid rendering to PDF, then converting to raster and scaling - I'd like to go direct to the raster.
At the moment I'm doing:
NSBitmapImageRep *bitmapImageRep = [pageView bitmapImageRepForCachingDisplayInRect:pageView.bounds];
[pageView cacheDisplayInRect:pageView.bounds toBitmapImageRep:bitmapImageRep];
NSImage *image = [[NSImage alloc] initWithSize:bitmapImageRep.size];
[image addRepresentation:bitmapImageRep];
This approach is working well, but I can't work out how to apply a scaling to the NSView before rendering the bitmapImageRep. I want to avoid using scaleUnitSquareToSize, because as I understand it, that only changes the bounds, not the frame of the NSView.
Any suggestions on the best way of doing this?
This is what I ended up doing, which works perfectly. We draw directly into an NSBitmapImageRep, but scale the context explicitly using CGContextScaleCTM beforehand. graphicsContext.graphicsPort gives you the handle on the CGContextRef for the NSGraphicsContext.
NSView *pageView = [self viewForPageIndex:pageIndex];
float scale = width / pageView.bounds.size.width;
float height = scale * pageView.bounds.size.height;
NSRect targetRect = NSMakeRect(0.0, 0.0, width, height);
NSBitmapImageRep *bitmapRep;
bitmapRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil
pixelsWide:targetRect.size.width
pixelsHigh:targetRect.size.height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bitmapFormat:0
bytesPerRow:(4 * targetRect.size.width)
bitsPerPixel:32];
[NSGraphicsContext saveGraphicsState];
NSGraphicsContext *graphicsContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:bitmapRep];
[NSGraphicsContext setCurrentContext:graphicsContext];
CGContextScaleCTM(graphicsContext.graphicsPort, scale, scale);
[pageView displayRectIgnoringOpacity:pageView.bounds inContext:graphicsContext];
[NSGraphicsContext restoreGraphicsState];
NSImage *image = [[NSImage alloc] initWithSize:bitmapRep.size];
[image addRepresentation:bitmapRep];
return image;
How about using scaleUnitSquareToSize: and then passing in a smaller rect in to bitmapImageRepForCachingDisplayInRect: and cacheDisplayInRect:toBitmapImageRep:?
So, if you downscale it by a factor of 2, you'd pass a rect to with half with bounds and height.
In one of my methods i have this code:
-(void)myMethod {
UIBezierPath *circle = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(75, 100, 200, 200)];
}
How do i get it to show on the view?
I tried addSubview but it gave me an incompatible type error because its expecting a UIView.
I'm sure this must be simple.
Thanks
Just thought I'd add that you don't have to necessarily draw this in a UIView's "drawRect:" method. You can draw it anywhere you'd like to provided you do it inside of a UIGraphics image context. I do this all of the time when I don't want to create a subclass of UIView. Here's a working example:
UIBezierPath *circle = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(75, 100, 200, 200)];
//you have to account for the x and y values of your UIBezierPath rect
//add the x to the width (75 + 200)
//add the y to the height (100 + 200)
UIGraphicsBeginImageContext(CGSizeMake(275, 300));
//this gets the graphic context
CGContextRef context = UIGraphicsGetCurrentContext();
//you can stroke and/or fill
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor lightGrayColor].CGColor);
[circle fill];
[circle stroke];
//now get the image from the context
UIImage *bezierImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *bezierImageView = [[UIImageView alloc]initWithImage:bezierImage];
Now just add the UIImageView as a subview.
Also, you can use this for other drawing too. Again, after a little bit of setup, it works just like the drawRect: method.
//this is an arbitrary size for example
CGSize aSize = CGSizeMake(50.f, 50.f);
//this can take any CGSize
//it works like the frame.size would in the drawRect: method
//in the way that it represents the context's size
UIGraphicsBeginImageContext(aSize);
//this gets the graphic context
CGContextRef context = UIGraphicsGetCurrentContext();
//you can do drawing just like you would in the drawRect: method
//I am drawing a square just for an example to show you that you can do any sort of drawing in here
CGContextMoveToPoint(context, 0.f, 0.f);
CGContextAddLineToPoint(context, aSize.width, 0.f);
CGContextAddLineToPoint(context, aSize.width, aSize.height);
CGContextAddLineToPoint(context, 0.f, aSize.height);
CGContextClosePath(context);
//you can stroke and/or fill
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor lightGrayColor].CGColor);
CGContextDrawPath(context, kCGPathFillStroke);
//now get the image from the context
UIImage *squareImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *squareImageView = [[UIImageView alloc]initWithImage:squareImage];
Edit: One thing I should add is that for any modern day drawing of this kind, you should be swapping out
UIGraphicsBeginImageContext(size);
for
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
This will draw your graphics correctly for retina and non retina displays.
FYI, UIGraphicsBeginImageContext(size) is equivalent to UIGraphicsBeginImageContextWithOptions(size, FALSE, 1.f) which is fine for none retina displays that may have some transparency.
However, if you don't need transparency, it is more optimized to pass in TRUE for the opaque argument.
The safest and recommended way of drawing is to pass in [[UIScreen mainScreen]scale] as the scale argument.
So for the example(s) above, you would use this instead:
UIGraphicsBeginImageContextWithOptions(aSize, FALSE, [[UIScreen mainScreen] scale]);
For more info, check out Apple's docs.
You can draw it using either fill or stroke methods for example in custom view's drawInRect: implementation:
- (void)drawRect:(CGRect)rect {
// Drawing code
UIBezierPath *circle = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(75, 100, 200, 200)];
[circle fill];
}
You can also add UIBezierPath to UIView without subclassing by using a CAShapeLayer.
For example, to add your path as a 3pt white line centered in a UIView:
UIBezierPath *mybezierpath = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(0, 0, 100, 100)];
CAShapeLayer *lines = [CAShapeLayer layer];
lines.path = mybezierpath.CGPath;
lines.bounds = CGPathGetBoundingBox(lines.path);
lines.strokeColor = [UIColor whiteColor].CGColor;
lines.fillColor = [UIColor clearColor].CGColor; /*if you just want lines*/
lines.lineWidth = 3;
lines.position = CGPointMake(self.myview.frame.size.width/2.0, self.myview.frame.size.height/2.0);
lines.anchorPoint = CGPointMake(.5, .5);
[self.myview.layer addSublayer:lines];
Drawing is the exclusive provision of views. Make a custom view, give it your path, and implement the view's drawRect: method to fill and/or stroke the path.
In Swift 2.0:
let path = UIBezierPath()
let p1 = CGPointMake(0,self.view.frame.height/2)
let p3 = CGPointMake(self.view.frame.width,self.view.frame.height/2)
path.moveToPoint(p1)
path.addQuadCurveToPoint(p3, controlPoint: CGPoint(x: self.view.frame.width/2, y: 0))
let line = CAShapeLayer()
line.path = path.CGPath;
line.strokeColor = UIColor.blackColor().CGColor
line.fillColor = UIColor.redColor().CGColor
view.layer.addSublayer(line)
I have a UITableViewCell, and I want to use the image property to fill it with an image. When it is first in a grouped UITableView, I want it to have the standard rounded corners.
Unfortnately, the image fills the rounded corners as well.. Is there any way to retain them without using a transparent image?
You can round the corners of any view programmatically by using its layer property. If you play about with the cornerRadius property of the layer you should be able to achieve the results you want.
#include <QuartzCore/QuartzCore.h>
UIImage *myImage = [UIImage imageNamed:#"image.png"];
UIImageView *imgView = [[UIImageView alloc] initWithImage:myImage];
imgView.layer.cornerRadius = 10.0;
If you just want to round some of the corners, you should look at the UIBezierPath API and use the path to mask your image. This isn't tested but it should point you in the right direction:
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:cell.bounds
byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight
cornerRadii:CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = imageView.frame;
maskLayer.path = path;
imageView.layer.mask = maskLayer;
I just made my non-transparent images have the rounded corner. Take a screen shot to get the dimensions of the rounded corner then use that as the basis for your image.