如何在绘图应用程序中实现撤消

时间:2011-01-06 10:38:33

标签: iphone objective-c

以下是绘画的代码片段。我可以在矢量绘图中撤消,就像存储点一样,从可变数组中删除最高的一个然后重绘。但是,它在光栅绘图中无法正常工作。

如果我使用UIGraphicsGetCurrentContext()作为上下文引用,则undo工作正常。但是,当发出撤消操作时,CGBitmapContextCreate()的上下文不会出现。

- (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];
}

2 个答案:

答案 0 :(得分:2)

使用光栅绘制每次都会更改画布中的像素,没有像矢量绘图中那样的对象。

因此,你拥有的唯一“状态”就是画布本身。为了允许撤消,您实际上需要在每次更改之前保存画布的副本。在进行更改之前,您将复制旧的位图上下文,然后进行更改。如果用户选择撤消,那么您只需将保存的上下文复制到正常上下文中。如果要允许多个撤消,则必须保存多个副本。

显然,这可能会占用大量内存。从技术上讲,你实际上并不需要保存整个画布,只需要保存整个画布的部分,并记录已更改部分的位置。如果更改很小,那么你将节省相当多的内存,但是一些更改会影响整个画布,而不会保存任何内容。

使用存储更改像素的算法可以节省更多内存,但处理开销可能不值得。

答案 1 :(得分:0)

假设您将图像存储在Image对象中,请创建一个堆栈:

  

堆栈undoStack = ...   Stack redoStack = ...

高内存解决方案

当用户对图像进行更改时,您可以存储下一个图像(w更改),下一个图像和下一个图像,依此类推。当用户想要撤消时,您可以通过从undoStack弹出并推送到重做堆栈来恢复图像:

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

要重做,请使用相同的过程,但要向后。

低内存解决方案

焦点与上面相同,但现在不是存储整个图像,而是可以将修改后的图像与前一个图像(或原始图像)进行异或,并仅存储已更改的像素和这些像素的坐标发生了变化。如果更改不是很好,您甚至可以考虑对这个新的XORed图像进行四叉树打包以节省内存。