i have just a problem with my MKPolyLineView. I simply try to make a color gradient to the Polyline, but with CAGradient it doenst work. I subclasses MKPolylineView and redrawing in
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context
UIColor *darker = [UIColor blackColor];
CGFloat baseWidth = self.lineWidth / zoomScale;
// draw the dark colour thicker
CGContextAddPath(context, self.path);
CGContextSetStrokeColorWithColor(context, darker.CGColor);
CGContextSetLineWidth(context, baseWidth * 1.5);
CGContextSetLineCap(context, self.lineCap);
CGContextStrokePath(context);
// now draw the stroke color with the regular width
CGContextAddPath(context, self.path);
CGContextSetStrokeColorWithColor(context, self.strokeColor.CGColor);
CGContextSetLineWidth(context, baseWidth);
CGContextSetLineCap(context, self.lineCap);
CGContextStrokePath(context);
[super drawMapRect:mapRect zoomScale:zoomScale inContext:context];
}
but even that is not working (StrokeColor = red). Any ideas how to get a gradient into
the Polyline? (Highcolor, centercolor, lowcolor)
Thanks everyone.
To paint a MKPolyline with a gradient, you can use a custom subclass of MKPolylineView. As CoreGraphics does not support stroking a path with a gradient, we have to
convert the path to a shape that traces the paths edge using CGPathCreateCopyByStrokingPath
clip the context to that shape
fill using CGContextDrawLinearGradient
Here is a subclass to get you started:
#interface TWOGradientPolylineView : MKPolylineView
#end
#implementation TWOGradientPolylineView
- (void)strokePath:(CGPathRef)path inContext:(CGContextRef)context
{
CGFloat lineWidth = CGContextConvertSizeToUserSpace(context, (CGSize){self.lineWidth, self.lineWidth}).width;
CGPathRef pathToFill = CGPathCreateCopyByStrokingPath(path, NULL, lineWidth, self.lineCap, self.lineJoin, self.miterLimit);
CGRect rect = CGPathGetBoundingBox(pathToFill);
CGContextAddPath(context, pathToFill);
CGPathRelease(pathToFill);
CGContextClip(context);
CGFloat gradientLocations[2] = {0.0f, 1.0f};
CGFloat gradientColors[8] = {1.0f, 0.0f, 0.0f, 0.75f, 1.0f, 1.0f, 0.0f, 0.75f};
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradientColors, gradientLocations, 2);
CGColorSpaceRelease(colorSpace);
CGPoint gradientStart = rect.origin;
CGPoint gradientEnd = {CGRectGetMaxX(rect), CGRectGetMaxY(rect)};
CGContextDrawLinearGradient(context, gradient, gradientStart, gradientEnd, kCGGradientDrawsAfterEndLocation);
CGGradientRelease(gradient);
}
#end
Here is a screenshot of a path drawn with the class above:
Related
I have implemented custom implementation of MKCircleRenderer.I need to have a circle with attached color. I can fill it with specific color. But how can add blur effect?
Here is my code:
- (void)fillPath:(CGPathRef)path inContext:(CGContextRef)context_{
if(!distance)
return;
#synchronized (self) {
if(!image){
rect = CGPathGetBoundingBox(path);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context, path);
CGContextSetBlendMode (context, kCGBlendModePlusDarker);
CGContextClip(context);
CGFloat gradientLocations[2] = {0, 1.0f};
UIColor* color = [UIColor colorWithHTMLString:#"#27B255"];
const CGFloat* components = CGColorGetComponents(color.CGColor);
CGFloat gradientColors[8] = {components[0], components[1], components[2], 0.3, components[0], components[1], components[2], 0.3};
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradientColors, gradientLocations, 2);
CGColorSpaceRelease(colorSpace);
CGPoint gradientCenter = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
CGFloat gradientRadius = MIN(rect.size.width, rect.size.height) / 2;
CGContextDrawRadialGradient(context, gradient, gradientCenter, 0, gradientCenter, gradientRadius, kCGGradientDrawsAfterEndLocation);
CGGradientRelease(gradient);
if(distance.accuracy > 0){
CGFloat k = (CGFloat)(rect.size.width/(2*(distance.distance + distance.accuracy)));
CGFloat radius = (distance.distance - distance.accuracy) * k;
if(radius > 0){
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextAddArc(context, CGRectGetMidX(rect), CGRectGetMidY(rect), radius, 0, 2 * M_PI, 0);
CGContextDrawPath(context, kCGPathFill);
CGContextSetBlendMode(context, kCGBlendModeNormal);
}
}
image = UIGraphicsGetImageFromCurrentImageContext();
image = [UIImageEffects imageByApplyingBlurToImage:image withRadius:1 tintColor:nil saturationDeltaFactor:1.0 maskImage:nil];
UIGraphicsEndImageContext();
}
}
CGContextDrawImage(context_, rect, image.CGImage);
}
I use UIImageEffects classes from Apple, but no result that I expect. Could you provide any other varian how to do this?
I'm using a MKCircleRenderer which is going to be repainted when the size of the circle is changed, but when the circle resizes, this happens...
Screenshot
Instead of a circle, it ends drawing a square when it doesn't have space
this is the method I'm using
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)ctx{
MKMapPoint mpoint = MKMapPointForCoordinate([[self overlay] coordinate]);
CGFloat radiusAtLatitude = (_mapRadius)*MKMapPointsPerMeterAtLatitude([[self overlay] coordinate].latitude);
MKMapRect circleRect = MKMapRectMake(mpoint.x, mpoint.y, radiusAtLatitude,radiusAtLatitude);
CGRect overlayRect = [self rectForMapRect:circleRect];
UIGraphicsPushContext(ctx);
CGContextSetStrokeColorWithColor(ctx, self.fillColor.CGColor);
CGContextSetFillColorWithColor(ctx, [self.fillColor colorWithAlphaComponent:_circleAlpha].CGColor);
CGContextSetLineWidth(ctx, _border);
CGContextSetShouldAntialias(ctx, YES);
CGContextAddArc(ctx, overlayRect.origin.x, overlayRect.origin.y, radiusAtLatitude, 0, 2 * M_PI, true);
CGContextDrawPath(ctx, kCGPathFillStroke);
UIGraphicsPopContext();
}
Any clue would help me a lot, thanks in advance!
I wrote a simple drawRect with a intention of drawing a circle, drop a shadow and fill it with blue. I have successfully achieved drawing a circle and filling it in blue but my shadow is not in effect. Instead of fill if I stroke my circle, i see the shadow is inside the circle and during fill it is drawn over by fill colour
rect to my drawRect has dimension [[CustomButton alloc]initWithFrame:CGRectMake(0, 0, 50, 50)];
#implementation CustomButton
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self setBackgroundColor:[UIColor clearColor]];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorRef colorRef = [[UIColor blueColor] CGColor];
CGContextSetStrokeColorWithColor(context, colorRef);
CGContextSetFillColorWithColor(context, [[UIColor blueColor] CGColor]);
CGRect buttonRect = CGRectInset(rect, 3, 3);
CGPoint centerPoint = CGPointMake(CGRectGetMidX(buttonRect ), CGRectGetMidY(buttonRect));
CGFloat radius = CGRectGetWidth(buttonRect) / 2;
CGContextSaveGState(context);
CGContextSetShadow(context, CGSizeMake(15.0, 20.0), 1.0);
CGContextAddArc(context, centerPoint.x, centerPoint.y, radius, 0, 2*M_PI, 1);
CGContextClip(context);
CGContextFillRect(context, buttonRect);
CGContextRestoreGState(context);
CGColorRelease(colorRef);
}
#end
Thank you
Try this:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorRef colorRef = [[UIColor blueColor] CGColor];
CGContextSetStrokeColorWithColor(context, colorRef);
CGContextSetFillColorWithColor(context, [[UIColor blueColor] CGColor]);
CGRect buttonRect = CGRectInset(rect, 3, 3);
CGPoint centerPoint = CGPointMake(CGRectGetMidX(buttonRect ), CGRectGetMidY(buttonRect));
CGFloat radius = CGRectGetWidth(buttonRect) / 2;
CGContextSaveGState(context);
CGContextSetShadow(context, CGSizeMake(2.0, 2.0), 2.0);
CGContextAddArc(context, centerPoint.x, centerPoint.y, radius, 0, 2*M_PI, 1);
//CGContextClip(context);
CGContextFillPath(context);
CGContextRestoreGState(context);
CGColorRelease(colorRef);
}
Your shadow was rectangular and that is why it was not seen under the circle. I changed the call CGContextFillRect to CGContextFillPath, the path which you already create using CGContextAddArc.
Is this what you were going for?
EDIT
You can find a project here: https://bitbucket.org/reydan/so_circleshadow
I'm having trouble with drawing a line on the map with a stroke. (inside color and outside color) I beleive i'm on the right path and have subclassed mkOverlayView to override the drawing (needs to fill with the road size) so inside drawMapRect...
CGFloat lineWidth = MKRoadWidthAtZoomScale(zoomScale);
MKMapRect clipRect = MKMapRectInset(mapRect, -lineWidth, -lineWidth);
...
CGContextAddPath(context, path);
CGContextSetStrokeColorWithColor(context, line.color.CGColor);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, lineWidth);
CGContextSetAlpha(context, 0.4f);
CGContextStrokePath(context);
CGPathRelease(path);
I'm not sure how to add the stroke. Any help would be greatly appreciated. xcode 4.6 / ios 6.0+
stroke first a path ( road with) with color 1, then change stroke width and color to a thinner line with road with * 0.6 in color 2, and stroke again.
Ok, I managed to figure it out based on Mathew's suggestion but with a few tweaks...
Essentially I created a stroked path using CGPathCreateCopyByStrokingPath and made the stroke slightly darker than the base color. Then created a transparentLayer with the path and stroke, used the blend mode kCGBlendModeDestinationAtop and drew another path with only a fill this time on top. I'm not sure if this is the best approach but appears to work well.
CGPathRef newpath = CGPathCreateCopyByStrokingPath(path, NULL, lineWidth, kCGLineCapRound, kCGLineJoinMiter, 0.0);
CGContextAddPath(context, newpath);
CGContextSetStrokeColorWithColor(context, line.color.CGColor);
CGContextSetFillColorWithColor(context, line.color.CGColor);
CGContextSetLineWidth(context, lineWidth * 0.3);
CGContextSetAlpha(context, 0.8f);
CGContextBeginTransparencyLayer (context, NULL);
CGContextStrokePath(context);
CGContextBeginPath(context);
CGContextAddPath(context, newpath);
CGContextFillPath(context);
CGContextEndTransparencyLayer(context);
CGContextSetBlendMode(context, kCGBlendModeDestinationAtop);
CGContextSetAlpha(context, 0.3f);
CGContextBeginPath(context);
CGContextAddPath(context, newpath);
CGContextFillPath(context);
CGPathRelease(path);
CGPathRelease(newpath);
EDIT Here is a simpler solution based on AlexWien's approach
if (path != nil)
{
CGPathRef path2 = CGPathCreateCopy(path);
CGFloat hue;
CGFloat saturation;
CGFloat brightness;
CGFloat alpha;
[line.color getHue:&hue saturation:&saturation brightness:&brightness alpha:&alpha];
UIColor *c2 =[UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:0.4];
CGContextAddPath(context, path);
CGContextSetStrokeColorWithColor(context, c2.CGColor);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, lineWidth);
CGContextStrokePath(context);
CGPathRelease(path);
CGContextSetBlendMode(context, kCGBlendModeSourceAtop);
CGContextAddPath(context, path2);
CGContextSetRGBStrokeColor(context, 1.0f, 1.0f, 1.0f, 0.3f);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, lineWidth/2.0f);
CGContextStrokePath(context);
CGPathRelease(path2);
}
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