yackle / CLImageEditor

MIT License
2.22k stars 574 forks source link

question about CLDrawTool #174

Closed LhiKien closed 7 years ago

LhiKien commented 7 years ago

If I set the alpha of the strokeColor when drawing a line, the result will be very stiff and grainy.

strokeColor = [strokeColor colorWithAlphaComponent:0.5];

Excuse me, is there a better solution to get a smoother and translucent line? Thanks for your attentions. simulator screen shot

yackle commented 7 years ago

Please reference to the following articles:

http://stackoverflow.com/questions/9407546/how-do-i-draw-lines-with-alpha-1-in-quartz-2d https://www.raywenderlich.com/87899/make-simple-drawing-app-uikit-swift

LhiKien commented 7 years ago
//
//  CLDrawTool.m
//
//  Created by sho yakushiji on 2014/06/20.
//  Copyright (c) 2014年 CALACULU. All rights reserved.
//

#import "CLDrawTool.h"

static NSString* const kCLDrawToolEraserIconName = @"eraserIconAssetsName";

@implementation CLDrawTool
{
    UIImageView *_drawingView;
    UIImageView *_tempView;

    CGSize _originalImageSize;

    CGPoint _prevDraggingPosition;
    UIView *_menuView;
    UISlider *_colorSlider;
    UISlider *_widthSlider;
    UISlider *_alphaSlider;
    UIView *_strokePreview;
    UIView *_strokePreviewBackground;
    UIImageView *_eraserIcon;

    CLToolbarMenuItem *_colorBtn;
}

+ (NSArray*)subtools
{
    return nil;
}

+ (NSString*)defaultTitle
{
    return [CLImageEditorTheme localizedString:@"CLDrawTool_DefaultTitle" withDefault:@"Draw"];
}

+ (BOOL)isAvailable
{
    return YES;
}

+ (CGFloat)defaultDockedNumber
{
    return 4.5;
}

#pragma mark- optional info

+ (NSDictionary*)optionalInfo
{
    return @{
             kCLDrawToolEraserIconName : @"",
             };
}

#pragma mark- implementation

- (void)setup
{
    _originalImageSize = self.editor.imageView.image.size;

    UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(drawingViewDidPan:)];
    panGesture.maximumNumberOfTouches = 1;

    _drawingView = [[UIImageView alloc] initWithFrame:self.editor.imageView.bounds];
    _drawingView.userInteractionEnabled = YES;
    [_drawingView addGestureRecognizer:panGesture];
    [self.editor.imageView addSubview:_drawingView];

    _tempView = [[UIImageView alloc] initWithFrame:self.editor.imageView.bounds];
    [self.editor.imageView addSubview:_tempView];

    self.editor.imageView.userInteractionEnabled = YES;
    self.editor.scrollView.panGestureRecognizer.minimumNumberOfTouches = 2;
    self.editor.scrollView.panGestureRecognizer.delaysTouchesBegan = NO;
    self.editor.scrollView.pinchGestureRecognizer.delaysTouchesBegan = NO;

    _menuView = [[UIView alloc] initWithFrame:self.editor.menuView.frame];
    _menuView.backgroundColor = self.editor.menuView.backgroundColor;
    [self.editor.view addSubview:_menuView];

    [self setMenu];

    _menuView.transform = CGAffineTransformMakeTranslation(0, self.editor.view.height-_menuView.top);
    [UIView animateWithDuration:kCLImageToolAnimationDuration
                     animations:^{
                         _menuView.transform = CGAffineTransformIdentity;
                     }];
}

- (void)cleanup
{
    [_drawingView removeFromSuperview];
    self.editor.imageView.userInteractionEnabled = NO;
    self.editor.scrollView.panGestureRecognizer.minimumNumberOfTouches = 1;

    [UIView animateWithDuration:kCLImageToolAnimationDuration
                     animations:^{
                         _menuView.transform = CGAffineTransformMakeTranslation(0, self.editor.view.height-_menuView.top);
                     }
                     completion:^(BOOL finished) {
                         [_menuView removeFromSuperview];
                     }];
}

- (void)executeWithCompletionBlock:(void (^)(UIImage *, NSError *, NSDictionary *))completionBlock
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        UIImage *image = [self buildImage];

        dispatch_async(dispatch_get_main_queue(), ^{
            completionBlock(image, nil, nil);
        });
    });
}

#pragma mark-

- (UISlider*)defaultSliderWithWidth:(CGFloat)width
{
    UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(0, 0, width, 34)];

    [slider setMaximumTrackImage:[UIImage new] forState:UIControlStateNormal];
    [slider setMinimumTrackImage:[UIImage new] forState:UIControlStateNormal];
    [slider setThumbImage:[UIImage new] forState:UIControlStateNormal];
    slider.thumbTintColor = [UIColor whiteColor];

    return slider;
}

- (UIImage*)colorSliderBackground
{
    CGSize size = _colorSlider.frame.size;

    UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);

    CGContextRef context = UIGraphicsGetCurrentContext();

    CGRect frame = CGRectMake(5, (size.height-10)/2, size.width-10, 5);
    CGPathRef path = [UIBezierPath bezierPathWithRoundedRect:frame cornerRadius:5].CGPath;
    CGContextAddPath(context, path);
    CGContextClip(context);

    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
    CGFloat components[] = {
        0.0f, 0.0f, 0.0f, 1.0f,
        1.0f, 1.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 0.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
        0.0f, 1.0f, 0.0f, 1.0f,
        0.0f, 1.0f, 1.0f, 1.0f,
        0.0f, 0.0f, 1.0f, 1.0f
    };

    size_t count = sizeof(components)/ (sizeof(CGFloat)* 4);
    CGFloat locations[] = {0.0f, 0.9/3.0, 1/3.0, 1.5/3.0, 2/3.0, 2.5/3.0, 1.0};

    CGPoint startPoint = CGPointMake(5, 0);
    CGPoint endPoint = CGPointMake(size.width-5, 0);

    CGGradientRef gradientRef = CGGradientCreateWithColorComponents(colorSpaceRef, components, locations, count);

    CGContextDrawLinearGradient(context, gradientRef, startPoint, endPoint, kCGGradientDrawsAfterEndLocation);

    UIImage *tmp = UIGraphicsGetImageFromCurrentImageContext();

    CGGradientRelease(gradientRef);
    CGColorSpaceRelease(colorSpaceRef);

    UIGraphicsEndImageContext();

    return tmp;
}

- (UIImage*)widthSliderBackground
{
    CGSize size = _widthSlider.frame.size;

    UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);

    CGContextRef context = UIGraphicsGetCurrentContext();

    UIColor *color = [[[CLImageEditorTheme theme] toolbarTextColor] colorWithAlphaComponent:0.5];

    CGFloat strRadius = 1;
    CGFloat endRadius = size.height/2 * 0.6;

    CGPoint strPoint = CGPointMake(strRadius + 5, size.height/2 - 2);
    CGPoint endPoint = CGPointMake(size.width-endRadius - 1, strPoint.y);

    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddArc(path, NULL, strPoint.x, strPoint.y, strRadius, -M_PI/2, M_PI-M_PI/2, YES);
    CGPathAddLineToPoint(path, NULL, endPoint.x, endPoint.y + endRadius);
    CGPathAddArc(path, NULL, endPoint.x, endPoint.y, endRadius, M_PI/2, M_PI+M_PI/2, YES);
    CGPathAddLineToPoint(path, NULL, strPoint.x, strPoint.y - strRadius);

    CGPathCloseSubpath(path);

    CGContextAddPath(context, path);
    CGContextSetFillColorWithColor(context, color.CGColor);
    CGContextFillPath(context);

    UIImage *tmp = UIGraphicsGetImageFromCurrentImageContext();

    CGPathRelease(path);

    UIGraphicsEndImageContext();

    return tmp;
}

- (UIColor*)colorForValue:(CGFloat)value
{
    if(value<1/3.0){
        return [UIColor colorWithWhite:value/0.3 alpha:1];
    }
    return [UIColor colorWithHue:((value-1/3.0)/0.7)*2/3.0 saturation:1 brightness:1 alpha:1];
}

- (void)setMenu
{
    CGFloat W = 70;

    _colorSlider = [self defaultSliderWithWidth:_menuView.width - W - 20];
    _colorSlider.left = 10;
    _colorSlider.top  = 5;
    [_colorSlider addTarget:self action:@selector(colorSliderDidChange:) forControlEvents:UIControlEventValueChanged];
    _colorSlider.backgroundColor = [UIColor colorWithPatternImage:[self colorSliderBackground]];
    _colorSlider.value = 0;
    [_menuView addSubview:_colorSlider];

    _widthSlider = [self defaultSliderWithWidth:_colorSlider.width/2 - 5];
    _widthSlider.left = 10;
    _widthSlider.top = _colorSlider.bottom + 5;
    [_widthSlider addTarget:self action:@selector(widthSliderDidChange:) forControlEvents:UIControlEventValueChanged];
    _widthSlider.value = 0.1;
    _widthSlider.backgroundColor = [UIColor colorWithPatternImage:[self widthSliderBackground]];
    [_menuView addSubview:_widthSlider];

    _alphaSlider = [self defaultSliderWithWidth:_colorSlider.width/2 - 5];
    _alphaSlider.left = _widthSlider.left + _widthSlider.width + 10;
    _alphaSlider.top = _colorSlider.bottom + 5;
    [_alphaSlider addTarget:self action:@selector(alphaSliderDidChange:) forControlEvents:UIControlEventValueChanged];
    _alphaSlider.value = 0.1;
    _alphaSlider.backgroundColor = [UIColor colorWithPatternImage:[self widthSliderBackground]];
    [_menuView addSubview:_alphaSlider];

    _strokePreview = [[UIView alloc] initWithFrame:CGRectMake(0, 0, W - 5, W - 5)];
    _strokePreview.layer.cornerRadius = _strokePreview.height/2;
    _strokePreview.layer.borderWidth = 1;
    _strokePreview.layer.borderColor = [[[CLImageEditorTheme theme] toolbarTextColor] CGColor];
    _strokePreview.center = CGPointMake(_menuView.width-W/2, _menuView.height/2);
    [_menuView addSubview:_strokePreview];

    _strokePreviewBackground = [[UIView alloc] initWithFrame:_strokePreview.frame];
    _strokePreviewBackground.layer.cornerRadius = _strokePreviewBackground.height/2;
    _strokePreviewBackground.alpha = 0.3;
    [_strokePreviewBackground addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(strokePreviewDidTap:)]];
    [_menuView insertSubview:_strokePreviewBackground aboveSubview:_strokePreview];

    _eraserIcon = [[UIImageView alloc] initWithFrame:_strokePreview.frame];
    _eraserIcon.image  =  [self imageForKey:kCLDrawToolEraserIconName defaultImageName:@"btn_eraser.png"];
    _eraserIcon.hidden = YES;
    [_menuView addSubview:_eraserIcon];

    [self colorSliderDidChange:_colorSlider];
    [self widthSliderDidChange:_widthSlider];
    [self alphaSliderDidChange:_alphaSlider];

    _menuView.clipsToBounds = NO;
}

- (void)colorSliderDidChange:(UISlider*)sender
{
    if(_eraserIcon.hidden){
        _strokePreview.backgroundColor = [self colorForValue:_colorSlider.value];
        _strokePreviewBackground.backgroundColor = _strokePreview.backgroundColor;
        _colorSlider.thumbTintColor = _strokePreview.backgroundColor;
    }
}

- (void)widthSliderDidChange:(UISlider*)sender
{
    CGFloat scale = MAX(0.05, _widthSlider.value);
    _strokePreview.transform = CGAffineTransformMakeScale(scale, scale);
    _strokePreview.layer.borderWidth = 2/scale;
}

- (void)alphaSliderDidChange:(UISlider*)sender
{
    CGFloat scale = MAX(0.01, _alphaSlider.value);
    _strokePreview.alpha = scale;
}

- (void)strokePreviewDidTap:(UITapGestureRecognizer*)sender
{
    _eraserIcon.hidden = !_eraserIcon.hidden;

    if(_eraserIcon.hidden){
        [self colorSliderDidChange:_colorSlider];
    }
    else{
        _strokePreview.backgroundColor = [[CLImageEditorTheme theme] toolbarTextColor];
        _strokePreviewBackground.backgroundColor = _strokePreview.backgroundColor;
    }
}

- (void)drawingViewDidPan:(UIPanGestureRecognizer*)sender
{
    CGPoint currentDraggingPosition = [sender locationInView:_drawingView];

    if(sender.state == UIGestureRecognizerStateBegan){
        _prevDraggingPosition = currentDraggingPosition;
    }

    if(sender.state != UIGestureRecognizerStateEnded){
        [self drawLine:_prevDraggingPosition to:currentDraggingPosition];
    }

    if(sender.state == UIGestureRecognizerStateEnded){
        UIGraphicsBeginImageContext(_drawingView.frame.size);
        [_drawingView.image drawAtPoint:CGPointZero];

        if(!_eraserIcon.hidden) {
            [_tempView.image drawInRect: CGRectMake(0, 0, _drawingView.width, _drawingView.height)];
        } else {
            [_tempView.image drawInRect: CGRectMake(0, 0, _drawingView.width, _drawingView.height)
                              blendMode: kCGBlendModeNormal alpha: _alphaSlider.value];
        }

        _drawingView.image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        _tempView.image = nil;
    }
    _prevDraggingPosition = currentDraggingPosition;
}

-(void)drawLine:(CGPoint)from to:(CGPoint)to
{

    CGSize size = _tempView.frame.size;
    if(!_eraserIcon.hidden) {
        size = _drawingView.frame.size;
    }
    UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);

    CGContextRef context = UIGraphicsGetCurrentContext();

    if(!_eraserIcon.hidden) {
        [_drawingView.image drawAtPoint:CGPointZero];
    } else {
        [_tempView.image drawAtPoint:CGPointZero];
    }

    CGFloat strokeWidth = MAX(1, _widthSlider.value * 65);
    CGContextSetLineWidth(context, strokeWidth);

    UIColor *strokeColor = _strokePreview.backgroundColor;
    CGContextSetStrokeColorWithColor(context, strokeColor.CGColor);

    CGContextSetLineCap(context, kCGLineCapRound);

    if(!_eraserIcon.hidden) {
        CGContextSetBlendMode(context, kCGBlendModeClear);
    } else {
        CGContextSetBlendMode(context, kCGBlendModeNormal);
    }

    CGContextMoveToPoint(context, from.x, from.y);
    CGContextAddLineToPoint(context, to.x, to.y);
    CGContextStrokePath(context);

    if(!_eraserIcon.hidden) {
        _drawingView.alpha = MAX(0.01, _alphaSlider.value);
        _drawingView.image = UIGraphicsGetImageFromCurrentImageContext();
    } else {
        _tempView.alpha = MAX(0.01, _alphaSlider.value);
        _tempView.image = UIGraphicsGetImageFromCurrentImageContext();
    }

    UIGraphicsEndImageContext();

}

- (UIImage*)buildImage
{
    UIGraphicsBeginImageContextWithOptions(_originalImageSize, NO, self.editor.imageView.image.scale);

    [self.editor.imageView.image drawAtPoint:CGPointZero];
    [_drawingView.image drawInRect:CGRectMake(0, 0, _originalImageSize.width, _originalImageSize.height)];

    UIImage *tmp = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return tmp;
}

@end
LhiKien commented 7 years ago

111111

LhiKien commented 7 years ago

@yackle Thanks for your suggestions. I updated the code of CLDrawTool. A UIImageView for drawing the current line is added. A Slider for changing the alpha of the current line is added too.

Could you help me to update the CLDrawTool's code online using above code? This maybe benefit other people.

yackle commented 7 years ago

nice work! If you open a pull request I'll be able to merge.