I am trying to use CGContextClip() to do some drawing, however I am running into some weird antialiasing problems. The problem appears to be that that the filling is being blended with the colour that it is replacing, instead of the surrounding pixels. See images for a slightly better description of the problem!
Example of issue:
(Note that the blue edge is being antialiased correctly)
Weird antialiased edge:
(Edge should be blended with white, not blue)
Code:
-(void) drawRect:(CGRect)rect {
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSaveGState(c);
{
CGContextBeginPath(c);
CGContextAddEllipseInRect(c, CGRectMake(25, 25, 200, 200));
CGContextClosePath(c);
CGContextClip(c);
CGContextSetFillColorWithColor(c, [UIColor blueColor].CGColor);
CGContextFillRect(c, rect);
CGContextSetFillColorWithColor(c, [UIColor redColor].CGColor);
CGContextFillRect(c, CGRectMake(0, 0, 250, 125));
}
CGContextRestoreGState(c);
}
Any ideas on how to prevent this weird antialiasing? I can't disable it, as the circle edges still need antialiasing!
Just to be clear, this is simply an example. I am trying to solve this issue on a much more complicated shape, where the solution of only filling half of the clipping to begin with simply isn't possible!
Well, this doesn't 100% fix the problem, but it does reduce the weird antialiasing by a considerable amount. I got the idea from this question. The idea is that you clear any previous rendering in the area where you are going to draw before drawing, by changing the blend mode to clear, performing the drawing and then switching it back to normal and then re-drawing. The modified code looks like this:
-(void) drawRect:(CGRect)rect {
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSaveGState(c);
{
CGContextBeginPath(c);
CGContextAddEllipseInRect(c, CGRectMake(25, 25, 200, 200));
CGContextClosePath(c);
CGContextClip(c);
CGContextSetFillColorWithColor(c, [UIColor blueColor].CGColor);
CGContextFillRect(c, CGRectMake(25, 25, 200, 200));
CGContextSetFillColorWithColor(c, [UIColor redColor].CGColor);
CGRect r = CGRectMake(0, 0, 250, 125);
CGContextSetBlendMode(c, kCGBlendModeClear);
CGContextFillRect(c, r);
CGContextSetBlendMode(c, kCGBlendModeNormal);
CGContextFillRect(c, r);
}
CGContextRestoreGState(c);
}
If anyone comes up with a better solution that 100% fixes this issue, then post it as an answer and I'll accept it!
Related
I have the following piece of code to round only specific corners of a view:
- (void)roundOnlySpecifiedCornersInView:(UIView *)view corners:(UIRectCorner)corners
{
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:(corners) cornerRadii:CGSizeMake(4.0, 4.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.path = maskPath.CGPath;
view.layer.mask = maskLayer;
}
This works perfectly in isolation. Now I also want shadow in my view, but I specifically want to apply shadow in different cases:
on all sides
all sides except bottom
all sides except top
left/right sides only
All techniques I encountered work by creating an inset of the view. The problem with this, is that, say you want to only keep shadow on left/right sides, you offset bottom and top. Since the Rect is now less high, the shadow at the left and right does not cover the full height of the view. Also, the mask layer used for rounding corners causes the shadow to no longer appear.
Example code for this:
innerView.layer.shadowColor = [[UIColor colorWithWhite:0.0f alpha:0.1f] CGColor];
innerView.layer.shadowOpacity = 1.0f;
innerView.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
innerView.layer.shadowRadius = 6.0f;
CGRect shadowFrame = UIEdgeInsetsInsetRect(innerView.bounds, UIEdgeInsetsMake(9, 0, 9, 0));
CGPathRef shadowPath = [UIBezierPath bezierPathWithRect:shadowFrame].CGPath;
innerView.layer.shadowPath = shadowPath;
How can I round specific corners in a view and at the same time show shadow only at specified sides?
Answers in Swift are appreciated too!
Screenshot of what I want (this one is easy since all corners need to be rounded so I can use .layer.cornerRadius and it has shadow at all sides):
Now I just want to round only 2 of the corners (top left and top right, bottom left and bottom right) and add shadow to only some sides.
I'm not sure if it meet your demand. The code create an image with top and bottom shadow, and all rounding corner, you can modify the code to achieve what you need. You can use the image as the background of your cell(It's seems that it is UITableViewCell)
Let me know if it don't work for you.
The image:
// create a shadow image
CGSize size = CGSizeMake(ScreenWidth, ScreenWidth);
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor *backgroundColor = [UIColor colorWithRed:246.0/255.0 green:246.0/255.0 blue:246.0/255.0 alpha:1.0];
UIColor *fillColor = [UIColor whiteColor];
CGRect rect = CGRectMake(10, 10, 100, 44);
// re-draw the background
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));
// set top and bottom shadow
CGRect rectTop = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, 5);
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0, -5), 5, [UIColor colorWithRed:0 green:0 blue:0 alpha:0.1].CGColor);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rectTop);
CGContextRestoreGState(context);
CGRect rectBottom = CGRectMake(rect.origin.x, rect.origin.y+rect.size.height-5, rect.size.width, 5);
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0, 5), 5, [UIColor colorWithRed:0 green:0 blue:0 alpha:0.1].CGColor);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rectBottom);
CGContextRestoreGState(context);
// re-draw the background
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillRect(context, rect);
CGContextSaveGState(context);
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(4.0, 4.0)];
[maskPath addClip];
CGContextSetFillColorWithColor(context, fillColor.CGColor);
CGContextFillRect(context, rect);
CGContextRestoreGState(context);
You can modify the code to get a top left shadow:
// create a shadow image
CGSize size = CGSizeMake(ScreenWidth, ScreenWidth);
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor *backgroundColor = [UIColor colorWithRed:246.0/255.0 green:246.0/255.0 blue:246.0/255.0 alpha:1.0];
UIColor *fillColor = [UIColor whiteColor];
CGRect rect = CGRectMake(10, 10, 100, 44);
// re-draw the background
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));
// set top and left shadow
CGRect rectTop = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, 5);
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0, -5), 5, [UIColor colorWithRed:0 green:0 blue:0 alpha:0.1].CGColor);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rectTop);
CGContextRestoreGState(context);
CGRect rectLeft = CGRectMake(rect.origin.x, rect.origin.y, 5, rect.size.height);
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(-5, 0), 5, [UIColor colorWithRed:0 green:0 blue:0 alpha:0.1].CGColor);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rectLeft);
CGContextRestoreGState(context);
// re-draw the background
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillRect(context, rect);
CGContextSaveGState(context);
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(4.0, 4.0)];
[maskPath addClip];
CGContextSetFillColorWithColor(context, fillColor.CGColor);
CGContextFillRect(context, rect);
CGContextRestoreGState(context);
HTH
In many, many cases you would want to achieve this with drawing the shadow, of course. However, you may consider the following approach: using a stretchable image with the shadow just on the sides that you need, e.g. something similar to (this one has shadow on all sides though):
Just before you say: "Ewwwww! He's using an image for a simple shadow!" let me emphasise that this will work much better in terms of performance. For instance, if you have a lot of cells in a UICollectionView and each one is re-drawing its shadow it could significantly slow down your app during scrolling, whereas with an image-based shadow it's going to be essentially the same.
I would go even further and suggest that you could actually use a stretchable image for masking the view with rounded corners, too! Now, this may look like going a bit too far, but if you notice a drastic decrease in performance I would give it a shot. What you essentially need to do is prepare another stretchable image with a transparent "middle" part and rounded corners of the same colour as your background. Something like:
And again, before you downvote this bizarre way of doing something that can be easily achieved with two lines of code...
view.layer.cornerRadius = 20
view.layer.masksToBounds = true
...Let me point out that this will work ridiculously faster in cases when you have to display a bunch of those masked views simultaneously: for instance, masking UICollectionViewCells. Basically, if you set a mask on a UIView's layer, this mask is going to be re-calculated and applied in real time, which would cost you a lot in terms of performance during frequent redrawing of the UIView contents — when scrolling a UITableView or UICollectionView, for example.
Obviously this wouldn't work if your background is not of a solid colour, and has a gradient for instance. However, in many other cases this may help you achieve much smoother UI performance. Another advantage in your particular case is that you can easily control which corners to mask and where the shadow should drop.
Again, I'm not implying that this is the way to go in each and every case. What I'm saying is that there are a lot of cases when this could help increase the performance significantly: for example, if you are masking far too many views that are rendered on the screen at the same time, and the layout is expected to be redrawn frequently.
for specific corner set corner radius
refer from : how to set cornerRadius for only top-left and top-right corner of a UIView?
UIBezierPath *maskPath;
maskPath = [UIBezierPathbezierPathWithRoundedRect:_backgroundImageView.bounds
byRoundingCorners:(UIRectCornerBottomLeft|UIRectCornerBottomRight)
cornerRadii:CGSizeMake(3.0, 3.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
_imageView.layer.mask = maskLayer;
[maskLayer release];
I'm using CoreGraphics to draw a line with circular endpoints and a drop shadow using the code below:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetShadowWithColor(UIGraphicsGetCurrentContext(), CGSizeMake(0, 0), 4.0, [UIColor blackColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillEllipseInRect(context, CGRectMake(_startPoint.x - 5, _startPoint.y - 5, 10, 10));
CGContextFillEllipseInRect(context, CGRectMake(_endPoint.x - 5, _endPoint.y - 5, 10, 10));
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSetLineWidth(context, 3.0);
CGContextMoveToPoint(context, _startPoint.x, _startPoint.y);
CGContextAddLineToPoint(context, _endPoint.x, _endPoint.y);
CGContextDrawPath(context, kCGPathStroke);
However, this results in something like this:
The shadow is being applied to the line and circles individually. Instead, what I want is for the shadow to be applied to the 'compound' shape created by all three (i.e. only use the compound alpha channel as the basis for the shadow), like so:
Any idea how I might achieve this effect instead? (note that I cannot use the layer.shadow property of my UIView as this line is rendered in response to touch drag events and that method is not performant enough).
This is exactly what Transparency Layers are for.
I think you'll find that the examples look familiar.
everybody,
I want to make the circle mask on the camera.
i try this way:
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddArc(context, self.frame.size.width/2, self.frame.size.height/2, 200, 0, M_PI *2, 0);
//Set the stroke color to black
[[UIColor blackColor]setStroke];
//Define line width and cap
CGContextSetLineWidth(context, 200);
CGContextSetLineCap(context, kCGLineCapButt);
//draw it!
CGContextDrawPath(context, kCGPathStroke);
}
just set the line with enough big,but i think it can set alpha.
thanks.
[[UIColor colorWithRed:0 green:0 blue:0 alpha:alpha/255.0] setStroke];
set the alpha percent you want
As you don't mentioned adjustable circle mask, I assume you need a static circle mask over on the camera view. If you need dynamically created circle, this is not the right answer.
Anyway it might help to know a simple way to do this:
prepare a png which is a circle actually, and the inside of that circle is transparent (make sure you handle the proper width and height or even create multiple circle pngs for different devices eg. iPhone5 and iPhone4)
use the imagePickerController's cameraOverlayView property to add the overlay image
UIView *tmp_camera_overlay = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
UIImageView *tmp_camera_bkgr =[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"circle_screen_overlay.png"]];
tmp_camera_bkgr.frame = CGRectMake(0, 0, 320, 480);
[tmp_camera_overlay addSubview:tmp_camera_bkgr];
imagePickerController.cameraOverlayView = tmp_camera_overlay;
I am drawing an rectangle using tutorial at here
FlagClass.m
-(void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextMoveToPoint(context, 100, 100);
CGContextAddLineToPoint(context, 150, 150);
CGContextAddLineToPoint(context, 100, 200);
CGContextAddLineToPoint(context, 50, 150);
CGContextAddLineToPoint(context, 100, 100);
CGContextStrokePath(context);
}
#end
Then I am adding this view to another view like below
-(IBAction)drawRectangle {
FlagClass *flag = [[FlagClass alloc] initWithFrame:CGRectMake(20.0, 100.0, 80, 40)];
[self.view addSubview:flag];
}
After clicking on the button, what I got is
My question :
These coordinate of my rectangle is (20,100,80,40).What are the numbers in drawRect method
Why I am just getting a black rectangle instead of the blue one with defined coordinates in drawRect
Please help if you have any ideas about it.
Because of the dimensions of your view (the FlagClass instance), all of your drawing is going on outside of the visible bounds (the view is "clipping" the blue rectangle). The black rectangle you're seeing is the default background fill of UIView.
To get what you want, you could adjust the frame of your subview so it's large enough to contain the stroked path. Or change the coordinates you're using to draw; those are the numbers in the calls to CGContextAddLineToPoint. Here's one way to at least see what you're doing (while removing the black background):
FlagClass *flag = [[FlagClass alloc] initWithFrame:CGRectMake(20.0, 100.0, 250, 250)];
flag.backgroundColor = [UIColor clearColor];
[self.view addSubview:flag];
By changing the width and height of the subview (the 3rd and 4th parameters to CGRectMake), the subview becomes large enough to contain the square being drawn.
I'm trying to modify some simple "create a paint app" code to have a white background colour, rather than the black that it is set to. The example code is located at:
http://blog.effectiveui.com/?p=8105
I've tried setting self.backgroundcolor = [UIColor whiteColor], also [[UIColor whiteColor] setFill] with no effect. I'm missing something very basic due to my inexperience.
Does anyone have any ideas? Many thanks in advance!
I've added a couple of lines to the drawRect that should do it for you. You were on the right track, but when you set the color to white, you actually need to then fill the paintView rectangle with it:
- (void)drawRect:(CGRect)rect {
if(touch != nil){
CGContextRef context = UIGraphicsGetCurrentContext();
//clear background color to white
[[UIColor whiteColor] setFill];
CGContextFillRect(context, rect);
hue += 0.005;
if(hue > 1.0) hue = 0.0;
UIColor *color = [UIColor colorWithHue:hue saturation:0.7 brightness:1.0 alpha:1.0];
CGContextSetStrokeColorWithColor(context, [color CGColor]);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, 15);
CGPoint lastPoint = [touch previousLocationInView:self];
CGPoint newPoint = [touch locationInView:self];
CGContextMoveToPoint(context, lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(context, newPoint.x, newPoint.y);
CGContextStrokePath(context);
}
}
The reason view.backgroundColor = [UIColor whiteColor]; didn't work is that any view that implements drawRect ignores the backgroundColor property, and the programmer is responsible for drawing the whole view contents, including the background.
I think the part you've missed is this section of PaintView:
- (BOOL) initContext:(CGSize)size {
int bitmapByteCount;
int bitmapBytesPerRow;
// Declare the number of bytes per row. Each pixel in the bitmap in this
// example is represented by 4 bytes; 8 bits each of red, green, blue, and
// alpha.
bitmapBytesPerRow = (size.width * 4);
bitmapByteCount = (bitmapBytesPerRow * size.height);
// Allocate memory for image data. This is the destination in memory
// where any drawing to the bitmap context will be rendered.
cacheBitmap = malloc( bitmapByteCount );
if (cacheBitmap == NULL){
return NO;
}
cacheContext = CGBitmapContextCreate (cacheBitmap, size.width, size.height, 8, bitmapBytesPerRow, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaNoneSkipFirst);
return YES;
}
That creates a single context, which it calls a cache, that all subsequent touches are drawn to. In the view's drawRect: it simply copies the cache to the output.
One of the flags it provides — kCGImageAlphaNoneSkipFirst — specifies that the cached context has no alpha channel. So when it's drawn there's no chance for the background to show through regardless of any other factor; the black comes from the cacheContext just as if you'd painted black with your finger.
So what you really want to do is to fill the cacheContext with white before you begin. You can either do that by memsetting the cacheBitmap array, since you've explicitly told the context where to store its data, or you can use a suitable CGContextFillRect to the cacheContext.
If you want to use the source code but have a clear background to the image you are creating and 'inking' on - when you setup the cachedBitmap, do it like this.
cacheContext = CGBitmapContextCreate ( cacheBitmap
, size.width
, size.height
, 8
, bitmapBytesPerRow
, CGColorSpaceCreateDeviceRGB()
, kCGImageAlphaPremultipliedFirst
);
That way, when you set your stroke color for the 'ink' only the 'ink' will be painted. Ensure that the drawing View also has a background color of clearColor, and opaque set to NO.
This means that the view that the drawing view has been added to will now be visible underneath or through this drawing view. Therefore set the background color of that view to whatever you want or alternatively, put a UIImageView behind the drawing view and voila! You can insert an image for lined paper or graph paper or whatever you want!