Pregunta

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.

¿Fue útil?

Solución

On this image you can see following code result. I believe it is almost same to what you want.

enter image description here

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.

Otros consejos

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:

enter image description here

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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top