UIBezierPath's shadow: how to hide the stroke's shadow? - ios

I'm trying to draw a UIBezierPathShape in iOS7 then apply a shadow. This works great except that when I stroke the path, the stroke shows up behind the shape. How can I rectify this?
Code:
- (void)drawDiamondWithCount:(NSUInteger)count inRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIGraphicsPushContext(ctx);
UIEdgeInsets insets = UIEdgeInsetsMake(cardEdgeInsetTop, cardEdgeInsetRight, cardEdgeInsetBottom, cardEdgeInsetLeft);
CGRect insetsRect = UIEdgeInsetsInsetRect(rect, insets);
CGFloat shapeHeight = insetsRect.size.height / (double) count;
CGRect shapeRect;
for (NSUInteger i = 0; i < count; ++i) {
// Get the rect for the single shape
int numRemainingShapes = count - i - 1;
CGFloat remainingBottomSpace = numRemainingShapes * shapeHeight;
insets = UIEdgeInsetsMake(i * shapeHeight + shapeEdgeInsets, 0, remainingBottomSpace + shapeEdgeInsets, 0);
shapeRect = UIEdgeInsetsInsetRect(insetsRect, insets);
UIBezierPath *path = [self getDiamondPath:shapeRect];
[[UIColor redColor] setFill];
[[UIColor blackColor] setStroke];
UIGraphicsPushContext(ctx);
CGContextSetShadow(ctx, CGSizeMake(5, 2), 5);
[path fill];
UIGraphicsPopContext();
//[path stroke];
}
UIGraphicsPopContext();
}
This gives me what I want, minus the stroke
Uncommenting [path stroke] gives me this. I want the stroke, but don't want to see it behind the shape.

I suspect that instead of UIGraphicsPushContext and UIGraphicsPopContext, I think you want CGContextSaveGState and CGContextRestoreGState:
// create context and configure
CGContextRef ctx = UIGraphicsGetCurrentContext();
[[UIColor redColor] setFill];
[[UIColor blackColor] setStroke];
// create path
UIBezierPath *path = ...;
path.lineJoinStyle = kCGLineJoinMiter;
path.lineWidth = 2.0;
// fill the center with shadow
CGContextSaveGState(ctx);
CGContextSetShadow(ctx, CGSizeMake(5, 2), 5);
[path fill];
CGContextRestoreGState(ctx);
// stroke border without shadow
CGContextSetLineWidth(ctx, 2.0);
[path stroke];
With UIGraphicsPushContext and UIGraphicsPopContext you get:
With CGContextSaveGState and CGContextRestoreGState you get:

Related

IOS Create Mask on an image using drawrect

I have created a function to draw a line graph, how can I use the line graph as a mask and put it on top of an image? the image have different color on different height, so what i wanna do is after masking with the line graph, the line graph will display different color on different level. Many thanks for helping me out :)
- (void)drawLineGraphWithContext:(CGContextRef)ctx
{
CGContextSetLineWidth(ctx, 2.0);
CGContextSetStrokeColorWithColor(ctx, [[UIColor colorWithRed:1.0 green:0.5 blue:0 alpha:1.0] CGColor]);
int maxGraphHeight = kGraphHeight - kOffsetY;
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, kOffsetX, kGraphHeight - maxGraphHeight * data[0]);
for (int i = 1; i < sizeof(data); i++)
{
CGContextAddLineToPoint(ctx, kOffsetX + i * kStepX, kGraphHeight - maxGraphHeight * data[i]);
}
CGContextDrawPath(ctx, kCGPathStroke);
CGContextSetFillColorWithColor(ctx, [[UIColor colorWithRed:1.0 green:0.5 blue:0 alpha:1.0] CGColor]);
for (int i = 0; i < sizeof(data) - 1; i++)
{
float x = kOffsetX + i * kStepX;
float y = kGraphHeight - maxGraphHeight * data[i];
CGRect rect = CGRectMake(x - kCircleRadius, y - kCircleRadius, 2 * kCircleRadius, 2 * kCircleRadius);
CGContextAddEllipseInRect(ctx, rect);
}
CGContextDrawPath(ctx, kCGPathFillStroke);
CGPathRef myPath = CGContextCopyPath(ctx);
CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
CGPathRef myPath = CGContextCopyPath(ctx);
shapeLayer.path = myPath;
_imageView.layer.mask = shapeLayer;
}
Try to do it this way.
1. Put the Image into an ImageView.
2. Since you already have the Path. And rite now, you will get the last
point as `path.currentPoint`. Add a Line to the `bottomRight` then
to `bottomLeft` and then to the `startingPoint` of the Path. You
will need to store the FirstPoint of the Path Somewhere for this.
Also, all the Path drawing should be in reference to the `bounds` of
the ImageView.
3. Create a CAShapeLayer with the path above. `shapeLayer.path = path`.
4. Set it as mask of the imageView -- imageView.mask = shapeLayer`.
Try it out this way. Cheers, have fun.

Objective C - Draw multiple arc within circle

I am trying to draw arc within a circle and fill the arc's. Here is the code i am using:
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(ctx, rect);
CGContextSetFillColor(ctx, CGColorGetComponents([[clrArray objectAtIndex:0] CGColor]));
CGContextFillPath(ctx);
CGFloat radius = 12.5;
CGFloat startAngle1 = DegreesToRadians(0);
CGFloat endAngle1 = DegreesToRadians(135);
CGFloat startAngle2 = DegreesToRadians(135);
CGFloat endAngle2 = DegreesToRadians(270);
//draw arc
CGPoint center = CGPointMake(radius,radius);
//Arc 1
CGContextAddArc(ctx,
center.x,
center.y,
radius,
startAngle1,
endAngle1,
YES);
[(UIColor*)[clrArray objectAtIndex:1] set];
CGContextFillPath(ctx);
//Arc 2
CGContextAddArc(ctx,
center.x,
center.y,
radius,
startAngle2,
endAngle2,
YES);
[(UIColor*)[clrArray objectAtIndex:2] set];
CGContextFillPath(ctx);
It is not drawing right. If i only provide one arc then if its angle is less than 180 then it is also not daring right. What i am doing wrong?
Edit: Here is the image of what is happening
I used start angle 0 and end angle 45 in this image
Got another solution using bezier Path
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat radius = 50.0/2.0;
CGPoint center = CGPointMake(radius,radius);
CGFloat startAngle1 = DegreesToRadians(0);
CGFloat endAngle1 = DegreesToRadians(120);
CGFloat startAngle2 = DegreesToRadians(120);
CGFloat endAngle2 = DegreesToRadians(240);
CGContextSaveGState(ctx);
UIBezierPath* clip1 = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:startAngle1
endAngle:endAngle1
clockwise:YES];
[clip1 addLineToPoint:center];
[clip1 closePath];
[clip1 addClip];
UIBezierPath *arc1 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 50, 50)];
[[UIColor blackColor] set];
[arc1 fill];
CGContextRestoreGState(ctx);
CGContextSaveGState(ctx);
UIBezierPath* clip2 = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:startAngle2
endAngle:endAngle2
clockwise:YES];
[clip2 addLineToPoint:center];
[clip2 closePath];
[clip2 addClip];
UIBezierPath *arc2 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 50, 50)];
[[UIColor orangeColor] set];
[arc2 fill];
CGContextRestoreGState(ctx);

Draw square and bullet arrow with core graphics

Is it possible to draw the following in objective-c? I tried using the image, but It pixelates. So I figured it is best to draw it programatically. Can someone provide me some sample code to achieve this.
Thank you.
Use a CAShapeLayerand assign it a path describing your shape:
- (UIBezierPath *)createBubblePathInFrame:(CGRect) frame{
CGFloat arrowInset = 10;
CGFloat arrowHeight = 10;
CGFloat arrowWidth = 20;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, arrowHeight)];
[path addLineToPoint:CGPointMake(arrowInset, arrowHeight)];
[path addLineToPoint:CGPointMake(arrowInset+arrowWidth/2, 0)];
[path addLineToPoint:CGPointMake(arrowInset+arrowWidth, arrowHeight)];
[path addLineToPoint:CGPointMake(frame.size.width, arrowHeight)];
[path addLineToPoint:CGPointMake(frame.size.width, frame.size.height)];
[path addLineToPoint:CGPointMake(0, frame.size.height)];
[path addLineToPoint:CGPointMake(0, arrowHeight)];
return path;
}
Use it like this:
CAShapeLayer *shapelayer = [CAShapeLayer layer];
shapelayer.frame = CGRectMake(50, 50, 200, 100);
shapelayer.path = [self createBubblePathInFrame:shapelayer.bounds].CGPath;
shapelayer.fillColor = [UIColor grayColor].CGColor;
shapelayer.strokeColor = [UIColor redColor].CGColor;
shapelayer.lineWidth = 1;
[self.view.layer addSublayer:shapelayer];
Or you can draw it yourself in drawRect:
[[UIColor grayColor] setFill];
[bezierPath fill];
[[UIColor redColor] setStroke];
[bezierPath stroke];
You can use this as a triangle view:
#implementation Triangle
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = [UIColor clearColor];
self.alpha = 0.75;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, CGRectGetMinX(rect), CGRectGetMinY(rect)); // top left
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMinY(rect)); // top right
CGContextAddLineToPoint(ctx, CGRectGetMidX(rect), CGRectGetMaxY(rect)); // bottom mid
CGContextClosePath(ctx);
CGContextSetRGBFillColor(ctx, 25.0/255.0, 25.0/255.0, 25.0/255.0, 1.0);
CGContextFillPath(ctx);
}
#end
Then just subclass a UIView and add an instance of Triangle where appropriate (in your case on the top left corner).
For your purpose you also should rotate this triangle 180 degrees as the code above drwas a triangle that points downwards.

Trying to fill a graph color drawn with a Bezier Path

I'm trying a UIBezierPath for the first time and am trying to fill a graph I've drawn with a color but without success. This is what I've done within a custom UIView:
- (void)calculateGraphSize
{
self.topY = CGRectGetMinY(self.bounds);
self.bottomY = CGRectGetMaxY(self.bounds);
self.minX = CGRectGetMinX(self.bounds);
self.maxX = CGRectGetMaxX(self.bounds);
}
- (void) drawRect: (CGRect) rect
{
CGSize cornerRadius = CGSizeMake(10.0f, 10.0f);
[self calculateGraphSize];
UIBezierPath *graphBorder = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:cornerRadius];
[[UIColor redColor] setFill];
[graphBorder fill];
UIBezierPath *barGraph = [UIBezierPath bezierPath];
barGraph.lineWidth = 2.0f;
CGPoint point = CGPointMake(self.minX, self.bottomY);
[barGraph moveToPoint:point];
point = CGPointMake(self.minX + 50, self.bottomY - 50);
[barGraph addLineToPoint:point];
point = CGPointMake(self.minX + 100, self.bottomY - 75);
[barGraph addLineToPoint:point];
point = CGPointMake(self.minX + 200, self.bottomY);
[barGraph addLineToPoint:point];
[[UIColor blackColor] setStroke];
[[UIColor greenColor] setFill];
[barGraph closePath];
[barGraph stroke];
}
Just add [barGraph fill]:
[[UIColor blackColor] setStroke];
[[UIColor greenColor] setFill];
[barGraph closePath];
[barGraph fill];
[barGraph stroke];
You probably want to fill and then stroke as I can't remember if the fill will cover up the stroke (I always have to check ha!)

iOS Brush Hardness like Photoshop

How to get the following brush smoothness(hardness) effect like photoshop?
My attempt:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, 30);
CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:0.5f].CGColor);
CGContextSetShadowWithColor(context, CGSizeMake(0, 0), 20.0f, [UIColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:1.0f].CGColor);
CGContextAddPath(context, path);
CGContextStrokePath(context);
CGContextRestoreGState(context);
I tried adjusting alpha values, and shadow blur factor, but no successful result.
Does anybody have a solution to this? Any help would be appreciated.
On this image you can see following code result. I believe it is almost same to what you want.
Just outer shadow is not just enough to give that smooth effect that is why I add some inner shadow to shape with white color.
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
// Shadows
UIColor* shadow = UIColor.redColor;
CGSize shadowOffset = CGSizeMake(0.1, -0.1);
CGFloat shadowBlurRadius = 11;
UIColor* shadow2 = UIColor.whiteColor; // Here you can adjust softness of inner shadow.
CGSize shadow2Offset = CGSizeMake(0.1, -0.1);
CGFloat shadow2BlurRadius = 9;
// Rectangle Drawing
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(59, 58, 439, 52) cornerRadius: 21];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, [shadow CGColor]);
[UIColor.redColor setFill];
[rectanglePath fill];
// Rectangle Inner Shadow
CGContextSaveGState(context);
UIRectClip(rectanglePath.bounds);
CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL);
CGContextSetAlpha(context, CGColorGetAlpha([shadow2 CGColor]));
CGContextBeginTransparencyLayer(context, NULL);
{
UIColor* opaqueShadow = [shadow2 colorWithAlphaComponent: 1];
CGContextSetShadowWithColor(context, shadow2Offset, shadow2BlurRadius, [opaqueShadow CGColor]);
CGContextSetBlendMode(context, kCGBlendModeSourceOut);
CGContextBeginTransparencyLayer(context, NULL);
[opaqueShadow setFill];
[rectanglePath fill];
CGContextEndTransparencyLayer(context);
}
CGContextEndTransparencyLayer(context);
CGContextRestoreGState(context);
CGContextRestoreGState(context);
}
Regarding size of the shape you have to adjust both inner and outer shadows blur radius.
You can get an effect similar to what you're trying to achieve by blending your shadow with your stroke
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context, path);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSetShadowWithColor(context, CGSizeMake(0.f, 0.f), self.lineWidth/4, [self.lineColor CGColor]);
CGContextSetBlendMode(context, kCGBlendModeMultiply);
CGContextSetAlpha(context, self.lineAlpha);
CGContextStrokePath(context);
With Multiply blending mode, using white color as stroke color and setting the color of the brush you want to the shadow, you get the following result:
I've connected the drawing function to touchesMoved event, so that way the longer I take to paint a part of the image, the harder the "Brush" draws (see the black line).
This probably isn't the perfect answer, but it's the best I can do for my needs.
Grab the FXBlurView: https://github.com/nicklockwood/FXBlurView
You can either draw your strokes on an FXBlurView or convert your UIView to UIImage after you've finished drawing (using the code I took from this answer https://stackoverflow.com/a/22494886/505259):
+ (UIImage *) imageWithView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0f);
[view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO];
UIImage * snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshotImage;
}
and use FXBlurView's category on UIImage:
- (UIImage *)blurredImageWithRadius:(CGFloat)radius
iterations:(NSUInteger)iterations
tintColor:(UIColor *)tintColor;
to blur the resulting image, giving it a Photoshop soft brush like appearance.
I'm still looking for a real answer though. I have an OpenCV project that requires an exact replica of Photoshop's soft brush tool.
I've been working on drawing the path with inner glow, and somehow succeeded (at least for my taste).
I've implemented the drawing code on top of the levinunnick's Smooth-Line-View. The code is MIT licensed, so you'll need to add it to your project.
Currently you can assign the line color, width and the smoothness for the line you want to draw. Be careful with smoothness, use a float between 0 - 1. I've changed the touch methods cause I needed to access the drawing methods from another view. Check the original code, if you want to revert to the touch methods.
I did not optimize the code, if you've got a better idea, just edit this answer.
Here is the H file:
#interface LineView : UIView
- (instancetype)initWithFrame:(CGRect)frame andColor:(UIColor *)lineColor andWidth:(CGFloat)lineWidth andSmoothness:(CGFloat)lineSmooth;
- (void)touchStartedWith:(CGPoint)location;
- (void)touchMovedWith:(CGPoint)location;
#end
This is the M file:
#import "LineView.h"
static const CGFloat kPointMinDistance = 0.05f;
static const CGFloat kPointMinDistanceSquared = kPointMinDistance * kPointMinDistance;
#interface LineView ()
#property (strong) UIColor *lineColor;
#property (assign) CGFloat lineWidth;
#property (assign) CGFloat lineSmooth;
#property (assign) CGPoint currentPoint;
#property (assign) CGPoint previousPoint;
#property (assign) CGPoint previousPreviousPoint;
#end
#implementation LineView
{
#private
CGMutablePathRef _path;
}
- (instancetype)initWithFrame:(CGRect)frame andColor:(UIColor *)lineColor andWidth:(CGFloat)lineWidth andSmoothness:(CGFloat)lineSmooth
{
self = [super initWithFrame:frame];
if ( self ) {
_path = CGPathCreateMutable();
if ( lineSmooth < 0 ) lineSmooth = 0;
if ( lineSmooth > 1 ) lineSmooth = 1;
self.backgroundColor = [UIColor clearColor];
self.lineColor = lineColor;
self.lineWidth = lineWidth;
self.lineSmooth = lineWidth * ( lineSmooth / 4 );
self.opaque = NO;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
[self.backgroundColor set];
UIRectFill(rect);
#autoreleasepool {
CGColorRef theColor = self.lineColor.CGColor;
UIColor *theClearOpaque = [[UIColor whiteColor] colorWithAlphaComponent:1];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context, _path);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, theColor);
// Outer shadow
CGSize shadowOffset = CGSizeMake(0.1f, -0.1f);
CGFloat shadowBlurRadius = self.lineSmooth;
CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, theColor);
CGContextStrokePath(context);
if ( self.lineSmooth > 0 ) {
// Inner shadow
CGRect bounds = CGPathGetBoundingBox(_path);
CGRect drawBox = CGRectInset(bounds, -2.0f * self.lineWidth, -2.0f * self.lineWidth);
CGContextSaveGState(context);
UIRectClip(drawBox);
CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL);
CGContextSetAlpha(context, CGColorGetAlpha(theClearOpaque.CGColor));
CGContextBeginTransparencyLayer(context, NULL);
{
// Outer shadow
UIColor *oShadow = [theClearOpaque colorWithAlphaComponent:1];
CGContextSetShadowWithColor(context, CGSizeMake(0.1f, -0.1f), self.lineWidth / 64 * self.lineSmooth, oShadow.CGColor);
CGContextSetBlendMode(context, kCGBlendModeSourceOut);
CGContextBeginTransparencyLayer(context, NULL);
[oShadow setFill];
// Draw the line again
CGContextAddPath(context, _path);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, oShadow.CGColor);
CGContextStrokePath(context);
CGContextEndTransparencyLayer(context);
}
CGContextEndTransparencyLayer(context);
CGContextRestoreGState(context);
}
}
}
- (void)touchStartedWith:(CGPoint)location
{
self.previousPoint = location;
self.previousPreviousPoint = location;
self.currentPoint = location;
[self touchMovedWith:location];
}
- (void)touchMovedWith:(CGPoint)location
{
CGRect drawBox;
#autoreleasepool {
CGFloat dx = location.x - self.currentPoint.x;
CGFloat dy = location.y - self.currentPoint.y;
if ( ( dx * dx + dy * dy ) < kPointMinDistanceSquared ) {
return;
}
self.previousPreviousPoint = self.previousPoint;
self.previousPoint = self.currentPoint;
self.currentPoint = location;
CGPoint mid1 = midPoint(self.previousPoint, self.previousPreviousPoint);
CGPoint mid2 = midPoint(self.currentPoint, self.previousPoint);
CGMutablePathRef subpath = CGPathCreateMutable();
CGPathMoveToPoint(subpath, NULL, mid1.x, mid1.y);
CGPathAddQuadCurveToPoint(subpath, NULL, self.previousPoint.x, self.previousPoint.y, mid2.x, mid2.y);
CGRect bounds = CGPathGetBoundingBox(subpath);
drawBox = CGRectInset(bounds, -2.0f * self.lineWidth, -2.0f * self.lineWidth);
CGPathAddPath(_path, NULL, subpath);
CGPathRelease(subpath);
}
[self setNeedsDisplayInRect:drawBox];
}
- (void)dealloc
{
CGPathRelease(_path);
_path = NULL;
}
#end

Resources