开发者

How to implement undo in a drawing app

Below is the code snippet of painting. I can do undo in a vector drawing jus开发者_如何学Pythont like storing points and remove the highest one from mutable array then redrawing.However, it does not function properly in a raster drawing.

If I use UIGraphicsGetCurrentContext() as a context reference, undo works well. But the context of CGBitmapContextCreate() does not when issue undo action.

- (id)initWithFrame:(CGRect)frame {  
    objArray = [[NSMutableArray alloc] init];

    CGColorSpaceRef  colorSpace = CGColorSpaceCreateDeviceRGB();

    canvas = CGBitmapContextCreate(NULL, drawImage.frame.size.width, drawImage.frame.size.height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colorSpace);
    }
    return self;
}

 

- (void)drawRect:(CGRect)rect {

    CGContextRef context = UIGraphicsGetCurrentContext();

    CGImageRef imgRef = CGBitmapContextCreateImage(canvas);

    CGRect r = self.bounds;
    CGContextDrawImage(context, CGRectMake(0, 0, r.size.width, r.size.height), imgRef); 

    if(ok) {
        for (int i = 0; i < [objArray count]; i++) {
            CGPoint point = [[[objArray objectAtIndex: i] objectAtIndex:0] CGPointValue];
            CGContextMoveToPoint(canvas, point.x, point.y);

            for (int j = 0; j < [[objArray objectAtIndex:i] count]; j++) {                
                point = [[[objArray objectAtIndex: i] objectAtIndex:j] CGPointValue];
                CGContextAddLineToPoint(canvas, point.x, point.y);
                CGContextStrokePath(**canvas**);
                CGContextMoveToPoint(**canvas**, point.x, point.y);
            }            
        }
    }
    CGImageRelease(imgRef); 
}

 

- (void)undo:(id) sender {
    NSLog(@"click");
    if([objArray count] > 0)
        [objArray removeLastObject];
    ok = YES;
    [self setNeedsDisplay];     
}

 

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

    NSMutableArray *points = [NSMutableArray array];

    UITouch *touch = nil;

    if (touchPoint) {
        touch = [touches member:touchPoint];
    }

    end = [touch locationInView:self];

    [points addObject:[NSValue valueWithCGPoint:start]];

    [points addObject:[NSValue valueWithCGPoint:end]];

    [objArray addObject:points];

    CGContextMoveToPoint(**canvas**, start.x, start.y);

    CGContextAddLineToPoint(**canvas**, end.x, end.y);  
    CGContextSetLineCap(**canvas**, kCGLineCapRound);

    CGContextSetLineWidth(**canvas**, 40.0);

    CGContextStrokePath(**canvas**); 

    start = end;

    [self setNeedsDisplay];
}


With raster drawing you're changing the pixels in the canvas each time, there are no objects like there are in a vector drawing.

As a result the only "state" you have is the canvas itself. In order to allow for undo you actually need to save a copy of the canvas before each change. Right before you make the change you'll copy the old bitmap context and then make the change. If the user chooses to undo then you'll just copy the saved context over the normal one. If you want to allow for multiple undos you'll have to save multiple copies.

Obviously this can become memory intensive. Technically you don't actually have to save the whole canvas, just the part that has changes on it, with a record of the position of the changed section. If changes are small then you'll save quite a bit of memory, but some changes can affect the whole canvas, not saving anything.

You could potentially save even more memory with algorithms that store changed pixels, but the processing overhead isn't likely worth it.


Assumming you're storing the image in an Image object, create a stack:

Stack undoStack = ... Stack redoStack = ...

The high memory solution

As the user makes changes you to the image, you can store the next image (w the changes), and the next and the next and so on. When the user wants to undo, you restore the images by popping from the undoStack and pushing onto the redo stack:

void undo(){
    redoStack.push(undoStack.pop());
}

To redo, use the same process, but backwards.

The low memory solution

The concent is the same as above, but now instead of storing the whole image, you can XOR the modified image with the previous one (or with the original one) and store only the pixels that have changed and coordinates at which these changes occur. You might even consider quad tree packing of this new XORed image to save memory if the changes are not great.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜