使用CGBitmapContext绘制得太慢了

时间:2012-06-29 12:05:21

标签: iphone objective-c ios ipad drawrect

所以我在这个过程中有一个基本的绘图应用程序,允许我绘制线条。我绘制到屏幕外的位图,然后在drawRect中显示图像。它工作但速度太慢,用你的手指画出来后更新大约半秒钟。我从这个教程http://www.youtube.com/watch?v=UfWeMIL-Nu8&feature=relmfu中获取了代码并对其进行了调整,正如您在评论中所看到的那样,人们也说它太慢但是这个人没有回应。

那么我怎样才能加快速度呢?或者有更好的方法吗?任何指针都将受到赞赏。

继承我DrawView.m中的代码。

-(id)initWithCoder:(NSCoder *)aDecoder {
     if ((self=[super initWithCoder:aDecoder])) {
         [self setUpBuffer];
     }

     return self;
}

-(void)setUpBuffer {
     CGContextRelease(offscreenBuffer);

     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

     offscreenBuffer = CGBitmapContextCreate(NULL, self.bounds.size.width, self.bounds.size.height, 8, self.bounds.size.width*4, colorSpace, kCGImageAlphaPremultipliedLast);
     CGColorSpaceRelease(colorSpace);

     CGContextTranslateCTM(offscreenBuffer, 0, self.bounds.size.height);
     CGContextScaleCTM(offscreenBuffer, 1.0, -1.0);
}


-(void)drawToBuffer:(CGPoint)coordA :(CGPoint)coordB :(UIColor *)penColor :(int)thickness {

     CGContextBeginPath(offscreenBuffer);
     CGContextMoveToPoint(offscreenBuffer, coordA.x,coordA.y);
     CGContextAddLineToPoint(offscreenBuffer, coordB.x,coordB.y);
     CGContextSetLineWidth(offscreenBuffer, thickness);
     CGContextSetLineCap(offscreenBuffer, kCGLineCapRound);
     CGContextSetStrokeColorWithColor(offscreenBuffer, [penColor CGColor]);
     CGContextStrokePath(offscreenBuffer);

}

- (void)drawRect:(CGRect)rect {
    CGImageRef cgImage = CGBitmapContextCreateImage(offscreenBuffer);
    UIImage *image =[[UIImage alloc] initWithCGImage:cgImage];
    CGImageRelease(cgImage);
    [image drawInRect:self.bounds];

}

在模拟器上完美运行但不是设备,我想这与处理器速度有关。

我正在使用ARC。

7 个答案:

答案 0 :(得分:6)

我试图修复你的代码,但是因为你似乎只发布了一半的代码我无法使它工作(复制+粘贴代码导致很多错误,更不用说启动性能调整了)。

但是,您可以使用一些提示来大幅提高性能。

第一个,也可能是最引人注目的是-setNeedsDisplayInRect:而不是-setNeedsDisplay。这意味着它只会重绘已更改的小矩形。对于具有1024 * 768 * 4像素的iPad 3来说,这是一项很多工作。将每帧减少到大约20 * 20或更低将大幅提高性能。

CGRect rect;
rect.origin.x = minimum(coordA.x, coordB.x) - (thickness * 0.5);
rect.size.width = (maximum(coordA.x, coordB.x) + (thickness * 0.5)) - rect.origin.x;
rect.origin.y = minimum(coordA.y, coordB.y) - (thickness * 0.5);
rect.size.height = (maximum(coordA.y, coordB.y) + (thickness * 0.5)) - rect.origin.y;
[self setNeedsDisplayInRect:rect];

你可以做的另一个重大改进是只绘制当前触摸的CGPath(你这样做)。但是,然后您在绘制rect中绘制已保存/缓存的图像。所以,再次,每帧重绘一次。更好的方法是使绘图视图透明,然后使用后面的UIImageView。 UIImageView是在iOS上显示图像的最佳方式。

- DrawView (1 finger)
   -drawRect:
- BackgroundView (the image of the old touches)
   -self.image

绘制视图本身只会绘制当前触摸仅每次更改的部分。当用户抬起手指时,您可以将其缓存到UIImage,在当前背景/缓存UIImageView的图像上绘制它,并将imageView.image设置为新图像。

组合图像时的最后一点涉及将2个全屏图像绘制到屏幕外CGContext中,因此如果在主线程上完成则会导致延迟,相反应该在后台线程中完成,然后将结果推回到主线。

* touch starts *
- DrawView : draw current touch
* touch ends *
- 'background thread' : combine backgroundView.image and DrawView.drawRect
    * thread finished *
    send resulting UIImage to main queue and set backgroundView.image to it;
    Clear DrawView's current path that is now in the cache;

所有这些组合可以制作一个非常流畅的60fps绘图应用程序。但是,视图不会像我们希望的那样快速更新,因此在更快地移动图形时绘图看起来是锯齿状的。这可以通过使用UIBezierPath而不是CGPath来改进。

CGPoint lastPoint = [touch previousLocationInView:self];
CGPoint mid = midPoint(currentPoint, lastPoint);
-[UIBezierPath addQuadCurveToPoint:mid controlPoint:lastPoint];

答案 1 :(得分:5)

它很慢的原因是因为每个帧都在创建一个位图并试图绘制它。

你问过更好的方法吗?你看过the apple sample code for a drawing app on iOS了吗?如果你不喜欢,那么你总是可以使用提供CCRenderTexture类的cocos2d(以及示例代码)。

目前,您正在使用一种您已经知道效率不高的方法。

答案 2 :(得分:2)

通过这种方法,我认为你应该考虑将后台线程用于图像渲染的所有艰苦工作,并且仅针对UI更新使用主线程,i。即

__block UIImage *__imageBuffer = nil;

- (UIImage *)drawSomeImage
{
    UIGraphicsBeginImageContext(self.bounds);

    // draw image with CoreGraphics

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return image;
}

- (void)updateUI
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // prepare image on background thread

        __imageBuffer = [self drawSomeImage];

        dispatch_async(dispatch_get_main_queue(), ^{

            // calling drawRect with prepared image

            [self setNeedsDisplay];

        });
    });
}

- (void)drawRect
{
    // draw image buffer on current context

    [__imageBuffer drawInRect:self.bounds];
}

我省略了一些使优化更清晰的细节。更好的是切换到UIImageView。通过这种方式,您可以摆脱至关重要的- (void)drawDect方法,并在图像准备就绪时更新UIImageView的图像属性。

答案 3 :(得分:1)

我觉得你需要改变你的逻辑。借助此链接,您可以获得一些非常好的主意 http://devmag.org.za/2011/04/05/bzier-curves-a-tutorial/ 如果你认为你没有时间去理解那么你可以直接使用这段代码https://github.com/levinunnink/Smooth-Line-View :)我跳这会对你有所帮助。

答案 4 :(得分:0)

使用CgLayer缓存路径,阅读文档,最适合优化。

答案 5 :(得分:0)

无论您提出的方法效率太低,因为每次移动手指时创建图像都是不合适的。

如果它只需要绘制路径,那么将CGMutablePathref作为成员变量, 并在绘制rect中使用CGPath函数移动到指定的点。

更重要的是,在刷新视图时,调用setNeedsDisplayInRect仅传递您需要绘制的区域。希望它对你有用。

答案 6 :(得分:0)

我做了一件完全像这样的事情。查看AppStore上的Pixelate应用程序。为了绘制,我在代码中使用了tile。毕竟,当您点击屏幕并绘制一些东西时,您需要重新绘制整个图像,这是一个非常繁重的操作。如果你喜欢Pixelate的移动方式,我就是这样做的:

1)将我的图像分割成n x m个图块。那是我可以改变这些值并获得更大/更小的瓷砖。在最坏的情况下(用户点击4个图块的交叉点),您必须重新绘制这4个图块。不是整个图像。

2)制作一个三维矩阵,其中我正在存储每个图块的像素信息。因此matrix[0][0][0]是第一个图块的第一个像素的红色值(每个像素具有RGB或RGBA值,具体取决于您使用的是png还是jpgs)。

3)获取用户按下的位置并计算需要修改的图块。

4)修改矩阵中的值并更新需要更新的图块。

注意:这肯定不是最好的选择。这只是一个选择。我之所以提到它是因为我认为它与你现在拥有的很接近。它适用于iPhone 3GS。如果你的目标是> = iPhone 4,那么你应该还不错。

此致

乔治