kiran
kiran

Reputation: 4409

Draw Brush Is Not Completly Drawing When Moving Diagonally?

When drawing from currentPoint.x and currentPoint.y to lastPoint.x and lastPoint.y in diagonally right upside or right downwards it's giving me gaps (spaces) in the drawn line as shown in below image.

sample screenshot

 -(void)brushType{
            UIGraphicsBeginImageContext(drawImage.frame.size);
            CGContextRef Mycontext  = UIGraphicsGetCurrentContext();
            int  x, cx, deltax, xstep,y, cy, deltay, ystep,error, st, dupe;
            int x0, y0, x1, y1;

            x0 = currentPoint.x;
            y0 = currentPoint.y;
            x1 = lastPoint.x;
            y1 = lastPoint.y;
            // find largest delta for pixel steps
            st =(abs(y1 - y0) > abs(x1 - x0));
            // if deltay > deltax then swap x,y    
            if (st) {

                (x0 ^= y0); 
                (y0 ^= x0); 
                (x0 ^= y0); //swap(x0, y0);
                (x1 ^= y1); 
                (y1 ^= x1);
                (x1 ^= y1); // swap(x1, y1);
          }
            deltax = abs(x1 - x0);
            deltay = abs(y1 - y0);
            error = (deltax / 4);
           y = y0;
           if (x0 > x1) {
                xstep = -1;
            }
            else {
                xstep = 1;
            }
            if (y0 > y1) { 
                ystep = -1;
            }
            else { 
                ystep = 1; 
            }

            for ((x = x0); (x != (x1 + xstep)); (x += xstep))
            {
                (cx = x);
                (cy = y); // copy of x, copy of y

                // if x,y swapped above, swap them back now
                if (st) { 
                    (cx ^= cy); 
                    (cy ^= cx); 
                    (cx ^= cy); 
                }

                (dupe = 0); // initialize no dupe

                if(!dupe) {

                    CGContextSetRGBStrokeColor(Mycontext, 0.0, 0.0, 0.0, 1.00f);               
                    CGContextMoveToPoint(Mycontext, cx+brushSize*4,cy-brushSize/2);
                    CGContextAddLineToPoint(Mycontext, cx-brushSize/2, cy+brushSize*4);
          }

              (error -= deltay); // converge toward end of line
                      if (error < 0) { // not done yet
                    (y += ystep);
                    (error += deltax);}
              }
           CGContextStrokePath(Mycontext);
      [drawImage.image drawInRect:CGRectMake(0, 0, drawImage.frame.size.width, drawImage.frame.size.height)];  
            drawImage.image = UIGraphicsGetImageFromCurrentImageContext();    
            UIGraphicsEndImageContext();
            lastPoint = currentPoint;

        }

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

     brushSize = 4;

    mouseSwiped = YES;
    UITouch *touch = [touches anyObject];   
    currentPoint = [touch locationInView:drawImage];
    currentPoint.y -= 20; 

    [self brushType];


}

If anyone has an idea please helpe me to fix this issue!

Upvotes: 1

Views: 284

Answers (1)

rob mayoff
rob mayoff

Reputation: 385890

I see a bunch of problems with your function, most of which are poor style. You are using XOR-swap, which makes the code hard to read. You declare all of your variables at the top of the method, making it harder to understand the lifetime of each variable. You put unnecessary parentheses all over the place, making the code harder to read. You call CGContextSetRGBStrokeColor repeatedly in your loop, but you only need to set the stroke color once. So first let's rewrite your method to fix those problems:

static inline void swap(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
}

-(void)brushType {
    int x0 = currentPoint.x;
    int y0 = currentPoint.y;
    int x1 = lastPoint.x;
    int y1 = lastPoint.y;
    int deltax = abs(x1 - x0);
    int deltay = abs(y1 - y0);

    int needSwap = deltay > deltax;
    if (needSwap) {
        swap(&x0, &y0);
        swap(&x1, &y1);
        swap(&deltax, &deltay);
    }

    int error = deltax / 4;
    int y = y0;
    int xstep = x0 > x1 ? -1 : 1;
    int ystep = y0 > y1 ? -1 : 1;

    CGSize size = self.drawImage.bounds.size;
    UIGraphicsBeginImageContext(size); {
        CGContextRef gc  = UIGraphicsGetCurrentContext();
        for (int x = x0; x != x1 + xstep; x += xstep)
        {
            int cx = x;
            int cy = y;
            if (needSwap)
                swap(&cx, &cy);

            CGContextMoveToPoint(gc, cx + brushSize*4, cy - brushSize/2);
            CGContextAddLineToPoint(gc, cx - brushSize/2, cy + brushSize*4);

            error -= deltay; // converge toward end of line
            if (error < 0) { // not done yet
                y += ystep;
                error += deltax;
            }
        }
        [UIColor.blackColor setStroke];
        CGContextStrokePath(gc);
        [self.drawImage.image drawInRect:CGRectMake(0, 0, size.width, size.height)];
        self.drawImage.image = UIGraphicsGetImageFromCurrentImageContext();    
    } UIGraphicsEndImageContext();
    lastPoint = currentPoint;
}

So now it's easier to understand that you're stepping along the straight line from lastPoint to currentPoint. If that line is primarily horizontal, you increment x by 1 (or -1) at each step, and increment y as necessary to stay close to the true straight line. If the line is primarily vertical, you swap x and y.

Here's the problem. Suppose the straight line is at a 45 degree angle. You're going to increment x by 1 and y by 1 at every step. Pythagoras tells us that you're moving a distance of sqrt(2) ≈ 1.4142 points per step. Since your “brush” is stroked with the default stroke width of 1 point, there's a gap between adjacent stamps of the brush.

The correct way to fix this is to stop using ints and error correction terms to compute the points along the line. Core Graphics and UIKit use CGFloats anyway, so by using ints you're not only getting less accurate results, you're triggering extra conversions from CGFloat to int and back.

What you need to do is store x and y as CGFloat, and increment them both at each step so that the distance between brush stamps is 1.

-(void)brushType {
    CGFloat dx = currentPoint.x - lastPoint.x;
    CGFloat dy = currentPoint.y - lastPoint.y;
    CGFloat length = hypotf(dx, dy);
    dx /= length;
    dy /= length;

    CGSize size = self.drawImage.bounds.size;
    // Bonus!  This works correctly on Retina devices!
    UIGraphicsBeginImageContextWithOptions(size, NO, self.drawImage.window.screen.scale); {
        CGContextRef gc  = UIGraphicsGetCurrentContext();

        for (CGFloat i = 0; i < length; ++i) {
            CGFloat x = lastPoint.x + i * dx;
            CGFloat y = lastPoint.y + i * dy;
            CGContextMoveToPoint(gc, x + brushSize * 4, y - brushSize / 2);
            CGContextAddLineToPoint(gc, x - brushSize / 2, y + brushSize * 4);
        }

        [UIColor.blackColor setStroke];
        CGContextSetLineWidth(gc, 1.01);
        CGContextStrokePath(gc);
        [self.drawImage.image drawAtPoint:CGPointZero];
        self.drawImage.image = UIGraphicsGetImageFromCurrentImageContext();
    } UIGraphicsEndImageContext();
    lastPoint = currentPoint;
}

If you use this version of the method, you'll find that it looks much better, but still not quite perfect:

better screenshot

The problem, I believe, is that diagonal strokes are drawn using anti-aliasing, and anti-aliasing is an approximation. If you drawn two width-1 lines 1 point apart, the partial colorings that each line contributes to the common pixels don't add up perfectly.

An easy, cheesy way to fix it is to make your stroke width a little wider. Just add this before CGContextStrokePath:

        CGContextSetLineWidth(gc, 1.1);

Result:

best screen shot

Upvotes: 2

Related Questions