简体   繁体   中英

Draw rectangle using Core Graphics with live preview

I'm creating a simple drawing application and would like some help with drawing rectangles based on the user's touch events. I'm able to draw a rectangle using Core Graphics from the point in touchesBegan to the point in touchesEnded. This rectangle is then displayed once the touchesEnded event is fired.

However, I would like to be able to do this in a 'live' fashion. Meaning, the rectangle is updated and displayed as the user drags their finger. Is this possible? Any help would be greatly appreciated, thank you!

UPDATE:

I was able to get this to work by using the following code. It works perfectly fine for small images, however it get's very slow for large images. I realize my solution is hugely inefficient and was wondering if anyone could suggest a better solution. Thanks.

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        UITouch *touch = [touches anyObject];
        _firstPoint = [touch locationInView:self.view];
        _firstPoint.y -= 40;
        _lastPoint = _firstPoint;
    }

    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {
        UITouch *touch = [touches anyObject];
        CGPoint currentPoint = [touch locationInView:self.view];
        currentPoint.y -= 40;

        [self drawSquareFrom:_firstPoint to:currentPoint];

        _lastPoint = currentPoint;
    }

    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    {
        UITouch *touch = [touches anyObject];
        CGPoint currentPoint = [touch locationInView:self.view];
        currentPoint.y -= 40;

        [self drawSquareFrom:_firstPoint to:currentPoint];

        [_modifiedImage release];
        _modifiedImage = [[UIImage alloc] initWithCGImage:_imageView.image.CGImage];
    }

    - (void)drawSquareFrom:(CGPoint)firstPoint to:(CGPoint)lastPoint
    {
        _imageView.image = _modifiedImage;

        CGFloat width  = lastPoint.x - firstPoint.x;
        CGFloat height = lastPoint.y - firstPoint.y;

        _path = [UIBezierPath bezierPathWithRect:CGRectMake(firstPoint.x, firstPoint.y, width, height)];

        UIGraphicsBeginImageContext( _originalImage.size );
        [_imageView.image drawInRect:CGRectMake(0, 0, _originalImage.size.width, _originalImage.size.height)];

        _path.lineWidth = 10;
        [[UIColor redColor] setStroke];
        [[UIColor clearColor] setFill];
        [_path fill];
        [_path stroke];

        _imageView.image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    }

Just use two Contexts. In one you just "preview" draw the Rectangle and you can clear it on TouchesMoved. On TouchesEnd you finally draw on your original context and then on the Image.

What you are doing right now is creating a paired CGImage and CGBitmapContext for every call to drawSquareFrom:to: and disposing them each time. Instead, create a single paired CGImage and CGBitmapContext that you reuse for each call.

-(void) drawSquareFrom:(CGPoint)from to:(CGPoint)to {
    CGContextRef context = [self offscreenContext];
    CGRect draw , full = [self offscreenContextBounds];

    draw.origin = from;
    draw.size.width = to.x - from.x;
    draw.size.height = to.y - from.y;
    draw = CGRectStandardize( draw );

    [[UIColor redColor] setStroke];
    [[UIColor clearColor] setFill];
    CGContextClearRect( context , full );
    CGContextFillRect( context , draw );
    CGContextStrokeRectWithWidth( context , draw , 10 );
    [_imageView setNeedsDisplay];
}

You create a paired CGImage and CGContext like this, although there are some ... to fill in. All the myX variables are intended to be class members.

-(CGContextRef) offscreenContext {
    if ( nil == myContext ) {
        size_t width = 400;
        size_t height = 300;
        CFMutableDataRef data = CFDataCreateMutable( NULL , width * height * 4 ); // 4 is bytes per pixel
        CGDataProviderRef provider = CGDataProviderCreateWithCFData( data );
        CGImageRef image = CGImageCreate( width , height , ... , provider , ... );
        CGBitmapContextRef context = CGBitmapContextCreate( CFDataGetMutableBytePtr( data ) , width , height , ... );
        CFRelease( data ); // retained by provider I think
        CGDataProviderRelease( provider ); // retained by image

        myImage = image;
        myContext = context;
        myContextFrame.origin = CGPointZero;
        myContextFrame.size.width = width;
        myContextFrame.size.height = height;
        _imageView.image = [UIImage imageWithCGImage:image];
    }

    return myContext;
}
-(CGRect) offscreenContextBounds {
    return myContextFrame;
}

This sets the _imageView.image to the newly created image. In drawSquareFrom:to: I assume that setNeedsDisplay is sufficient to draw the changes to made, but it may be that you need to assign a new UIImage each time that wraps the same CGImage.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM