5

所以我有一个基本的绘图应用程序可以让我画线。我绘制到屏幕外位图,然后将图像呈现在drawRect. 它可以工作,但是太慢了,在你用手指绘制后大约半秒更新。我从本教程http://www.youtube.com/watch?v=UfWeMIL-Nu8&feature=relmfu中获取了代码并对其进行了改编 ,正如您在评论中看到的那样,人们也说它太慢了,但那家伙没有t 回应。

那么我怎样才能加快速度呢?或者有更好的方法吗?任何指针将不胜感激。

这是我的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。

4

7 回答 7

7

我试图修复您的代码,但是由于您似乎只发布了其中的一半,因此我无法使其正常工作(复制+粘贴代码会导致很多错误,更不用说开始对其进行性能调整了)。

但是,您可以使用一些技巧来极大地提高性能。

第一个,可能也是最引人注目的,是-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(您可以这样做)。但是,然后您在绘图矩形中绘制该保存/缓存的图像。所以,再一次,它在每一帧都被重绘。更好的方法是让绘图视图透明,然后在其后面使用 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];
于 2012-07-07T13:50:57.297 回答
5

它之所以慢是因为每一帧你都在创建一个位图并试图绘制它。

您要求更好的方法吗?您是否查看过iOS 上绘图应用程序的 Apple 示例代码?如果你不喜欢这样,那么你总是可以使用cocos2d,它提供了一个 CCRenderTexture 类(和示例代码)。

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

于 2012-07-02T08:41:47.813 回答
2

使用这种方法,我想您应该考虑将后台线程用于图像渲染的所有艰苦工作,而将主线程仅用于 UI 更新,即

__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

于 2012-07-03T22:41:26.863 回答
1

那么我认为你需要改变你的逻辑。借助此链接 http://devmag.org.za/2011/04/05/bzier-curves-a-tutorial/ ,您可能会得到一些非常好的想法,如果您认为您没有时间理解,那么您可以直接访问此代码https://github.com/levinunnink/Smooth-Line-View :) 我希望这会对您有很大帮助。

于 2012-07-04T09:55:21.847 回答
0

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

于 2012-07-05T13:37:43.400 回答
0

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

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

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

于 2012-07-05T19:08:00.620 回答
0

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

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

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

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

4)修改矩阵中的值,更新需要更新的瓦片。

注意:这肯定不是最好的选择。这只是一个替代方案。我提到它是因为我认为它与您现在所拥有的很接近。它在 iPhone 3GS 上对我有用。如果您的目标是 >= iPhone 4 ,那么您应该没问题。

问候,

乔治

于 2012-07-06T07:52:28.830 回答