6

I am following this tutorial for my doodle application.

http://www.raywenderlich.com/18840/how-to-make-a-simple-drawing-app-with-uikit

his erase function is using white color drawing. But I am using image background and when I erase, I really need to erase.

I have tried

CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeClear);

But it's not working. Here is my code:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    _mouseSwiped = NO;
    UITouch *touch = [touches anyObject];
    _lastPoint = [touch locationInView:self.tempImageView];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {

    _mouseSwiped = YES;
    UITouch *touch = [touches anyObject];
    CGPoint currentPoint = [touch locationInView:self.tempImageView];

    UIGraphicsBeginImageContext(self.tempImageView.frame.size);
    [self.tempImageView.image drawInRect:CGRectMake(0, 0, self.tempImageView.frame.size.width, self.tempImageView.frame.size.height)];
    CGContextMoveToPoint(UIGraphicsGetCurrentContext(), _lastPoint.x, _lastPoint.y);
    CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
    CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
    CGContextSetLineWidth(UIGraphicsGetCurrentContext(), _width );


    CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), _red, _green, _blue, 1.0);

    if (_isErasing) {
        CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeClear);
    }
    else {
        CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeNormal);
    }

    CGContextStrokePath(UIGraphicsGetCurrentContext());
    self.tempImageView.image = UIGraphicsGetImageFromCurrentImageContext();
    [self.tempImageView setAlpha:_alpha];
    UIGraphicsEndImageContext();

    _lastPoint = currentPoint;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    CGSize size = self.tempImageView.frame.size;

    if(!_mouseSwiped) {
        UIGraphicsBeginImageContext(self.tempImageView.frame.size);
        [self.tempImageView.image drawInRect:CGRectMake(0, 0, size.width, size.height)];
        CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
        CGContextSetLineWidth(UIGraphicsGetCurrentContext(), _width);


        CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), _red, _green, _blue, _alpha);
        CGContextMoveToPoint(UIGraphicsGetCurrentContext(), _lastPoint.x, _lastPoint.y);
        CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), _lastPoint.x, _lastPoint.y);
        CGContextStrokePath(UIGraphicsGetCurrentContext());
        CGContextFlush(UIGraphicsGetCurrentContext());
        self.tempImageView.image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    }




    UIGraphicsBeginImageContext(self.drawImageView.frame.size);
    [self.drawImageView.image drawInRect:CGRectMake(0, 0, size.width, size.height) blendMode:kCGBlendModeNormal alpha:1.0];

    if (_isErasing) {
        CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeClear);
    }

    [self.tempImageView.image drawInRect:CGRectMake(0, 0, self.tempImageView.frame.size.width, self.tempImageView.frame.size.height) blendMode:kCGBlendModeNormal alpha:_alpha];


    self.drawImageView.image = UIGraphicsGetImageFromCurrentImageContext();
    self.tempImageView.image = nil;
    UIGraphicsEndImageContext();
}
OMGPOP
  • 1,995
  • 8
  • 52
  • 95

5 Answers5

8

solved this problem by swapping tempImageView and drawImageView when touch began if it's erasing

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    _mouseSwiped = NO;
    UITouch *touch = [touches anyObject];
    _lastPoint = [touch locationInView:self.tempImageView];

    if (_isErasing) {
        self.tempImageView.image = self.drawImageView.image;
        self.drawImageView.image = nil;
    }

}



- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {

    _mouseSwiped = YES;
    UITouch *touch = [touches anyObject];
    CGPoint currentPoint = [touch locationInView:self.tempImageView];

    UIGraphicsBeginImageContext(self.tempImageView.frame.size);
    [self.tempImageView.image drawInRect:CGRectMake(0, 0, self.tempImageView.frame.size.width, self.tempImageView.frame.size.height)];
    CGContextMoveToPoint(UIGraphicsGetCurrentContext(), _lastPoint.x, _lastPoint.y);
    CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
    CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
    CGContextSetLineWidth(UIGraphicsGetCurrentContext(), _width );


    if (_isErasing) {
        CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeClear);
    }
    else {
        CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), _red, _green, _blue, 1.0);
        CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeNormal);
    }

    CGContextStrokePath(UIGraphicsGetCurrentContext());
    self.tempImageView.image = UIGraphicsGetImageFromCurrentImageContext();
    [self.tempImageView setAlpha:_alpha];
    UIGraphicsEndImageContext();

    _lastPoint = currentPoint;
}





- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    CGSize size = self.tempImageView.frame.size;

    if(!_mouseSwiped) {
        UIGraphicsBeginImageContext(self.tempImageView.frame.size);
        [self.tempImageView.image drawInRect:CGRectMake(0, 0, size.width, size.height)];
        CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
        CGContextSetLineWidth(UIGraphicsGetCurrentContext(), _width);


        if (_isErasing) {
            CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeClear);
        }
        else {
            CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), _red, _green, _blue, _alpha);
            CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeNormal);
        }

        CGContextMoveToPoint(UIGraphicsGetCurrentContext(), _lastPoint.x, _lastPoint.y);
        CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), _lastPoint.x, _lastPoint.y);
        CGContextStrokePath(UIGraphicsGetCurrentContext());
        CGContextFlush(UIGraphicsGetCurrentContext());
        self.tempImageView.image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    }




    UIGraphicsBeginImageContext(self.drawImageView.frame.size);
    [self.drawImageView.image drawInRect:CGRectMake(0, 0, size.width, size.height) blendMode:kCGBlendModeNormal alpha:1.0];
    [self.tempImageView.image drawInRect:CGRectMake(0, 0, size.width, size.height) blendMode:kCGBlendModeNormal alpha:_alpha];
    self.drawImageView.image = UIGraphicsGetImageFromCurrentImageContext();
    self.tempImageView.image = nil;
    UIGraphicsEndImageContext();
}
OMGPOP
  • 1,995
  • 8
  • 52
  • 95
  • Why do you swap a tempImage and drawingImage for? Just curious? –  Jan 05 '17 at 18:12
  • 2
    Instead of swapping image views, just draw the clear onto the “main image view” instead. That is how i solved this same problem. – jforward5 Jun 23 '19 at 15:43
3

If anybody is still intereted this is how it worked for me in Swift 3. Thanks for the help OMGPOP

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    swiped = false
    hideAllButtons()
    if let touch = touches.first {
        lastPoint = touch.location(in: self.view)
    }

    if isErasing {
        self.tempImageView.image = self.mainImageView.image
        self.mainImageView.image = nil
    }
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    swiped = true
    if let touch = touches.first {
        let currentPoint = touch.location(in: view)
        drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint)

        lastPoint = currentPoint
    }

}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    showAllButtons()
    if !swiped {
        drawLineFrom(fromPoint: lastPoint, toPoint: lastPoint)
    }

    UIGraphicsBeginImageContext(mainImageView.frame.size)
    mainImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: .normal, alpha: 1.0)
    tempImageView.image?.draw(in:  CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: .normal, alpha: opacity)
    mainImageView.image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    tempImageView.image = nil
}

// Draws a line between two points on the view
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
    UIGraphicsBeginImageContext(view.frame.size)
    let context = UIGraphicsGetCurrentContext()
    tempImageView.image?.draw(in: CGRect(x:0, y:0, width: view.frame.size.width, height: view.frame.size.height))

    context?.move(to: fromPoint)
    context?.addLine(to: toPoint)

    context?.setLineCap(.round)
    context?.setLineWidth(brushWidth)

    if isErasing {
        context?.setBlendMode(.clear)
    } else {
        context?.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0)
        context?.setBlendMode(.normal)
    }
    context?.strokePath()

    tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
    tempImageView.alpha = opacity
    UIGraphicsEndImageContext()

}
benhofmann
  • 331
  • 3
  • 12
  • This isn't working. It is creating problem like my question http://stackoverflow.com/questions/43137955/image-gets-blurry-and-zoomed-out-when-erasing – Jitendra Modi Apr 05 '17 at 06:26
1

Here's how I got the eraser working. In the touchesEnded function I'm drawing on the context with the following code:

//draw the main image on the context
self.mainImage.image?.draw(in: CGRect(x:0, y:0, width:self.view.frame.size.width, height:self.view.frame.size.height), blendMode: .normal, alpha: 1.0)
    if(erasing == false) {
        //draw the current stroke normally since we're not erasing
        self.tempDrawImage.image?.draw(in: CGRect(x:0, y:0, width:self.view.frame.size.width, height:self.view.frame.size.height), blendMode: .normal, alpha: opacity!)
    } else {
        //let's create a mask of what we would like to erase by using the destinationIn function which according to the docs is the following
        //R = D*Sa
        //Destination * Source_Alpha
        self.tempDrawImage.image?.draw(in: CGRect(x:0, y:0, width:self.view.frame.size.width, height:self.view.frame.size.height), blendMode: .destinationIn, alpha: 1.0)
        //Let's apply this mask to the current drawing using XOR blending which removes any drawing underneath our mask but leaves everything else alone
        self.mainImage.image?.draw(in: CGRect(x:0, y:0, width:self.view.frame.size.width, height:self.view.frame.size.height), blendMode: .xor, alpha: 1.0)
    }
    //let's apply the changes we've made to our mainImage
    self.mainImage.image = UIGraphicsGetImageFromCurrentImageContext();
    self.tempDrawImage.image = nil;

To get a better idea of how it works you can comment out the draw calls individually to see how they look.

You might find some aliasing issues where you're getting little bits left behind on the edge of strokes. To remove that when drawing in erase mode turn of alisasing for the context:

let ctx = UIGraphicsGetCurrentContext()
    ctx?.setShouldAntialias(false)
MoK
  • 11
  • 4
1

I'm using one "canvas" view and the following function to achieve "erase effect". My canvas is transparent and under it, I have background UIImageView with a photo on top of which I draw lines. After I'm done with drawing onto my canvas and the user wants the final drawing, I merge both canvas and the background. This does the job of erasing previously drawn lines:

private func drawStroke(context: CGContext?, touch: UITouch, width: CGFloat, color: UIColor) {
        guard let context = context else { return }
        let previousLocation = touch.previousLocation(in: self)
        let location = touch.location(in: self)

        context.setBlendMode(color == .clear ? .clear : .normal)
        context.setLineWidth(width)
        context.setStrokeColor(color.cgColor)
        context.setLineCap(.round)

        context.move(to: previousLocation)
        context.addLine(to: location)
        context.strokePath()
    }
bojan
  • 870
  • 9
  • 7
-1

Why don't you paint/draw on a separate view ?

TopView = Paint + gesture ...., BottomView = background image.

erase color = ClearColor (transparent)

At the end, if you want to save your composition, up to you to combine both pictures.

Armand DOHM
  • 1,121
  • 1
  • 7
  • 9
  • as the tutorial pointed out, it's much simpler to have 2 views to handle opacity – OMGPOP Mar 02 '14 at 18:57
  • "You could, but the dual UIImageViews are used to preserve opacity. When you’re drawing on tempDrawImage, the opacity is set to 1.0 (fully opaque). However, when you merge tempDrawImage with mainImage, the tempDrawImage opacity is set to the configured value, thus giving the brush stroke the opacity we want. If you were to draw directly on mainImage, it would be incredibly difficult to draw brush strokes with different opacity values." – OMGPOP Mar 02 '14 at 18:58