给定UIImage
和CGRect
,绘制与CGRect
对应的图像部分(没有缩放)的最有效方式(在内存和时间上)是什么?
作为参考,我现在就是这样做的:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect frameRect = CGRectMake(frameOrigin.x + rect.origin.x, frameOrigin.y + rect.origin.y, rect.size.width, rect.size.height);
CGImageRef imageRef = CGImageCreateWithImageInRect(image_.CGImage, frameRect);
CGContextTranslateCTM(context, 0, rect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, rect, imageRef);
CGImageRelease(imageRef);
}
不幸的是,对于中等大小的图像和高setNeedsDisplay
频率,这似乎非常慢。使用UIImageView
的框架和clipToBounds
可以产生更好的结果(灵活性较低)。
答案 0 :(得分:78)
我猜你这样做是为了在屏幕上显示部分图像,因为你提到了UIImageView
。优化问题总是需要专门定义。
实际上,如果您的目标只是剪切图像的矩形区域(不是太大),那么UIImageView
clipsToBounds
setNeedsDisplay
是存档目标的最快/最简单的方法之一。此外,您无需发送UIImageView
消息。
或者您可以尝试将UIView
置于空transform
内并在容器视图中设置剪裁。使用此技术,您可以通过在2D(缩放,旋转,平移)中设置CALayer
属性来自由转换图像。
如果您需要进行3D转换,您仍然可以将masksToBounds
与CALayer
属性一起使用,但使用UIView
会给您带来的额外性能通常不会太大。
无论如何,您需要了解所有低级细节才能正确使用它们进行优化。
CALayer
只是OpenGL
之上的一个薄层,它是在 OpenGL 之上实现的,它是 GPU 的虚拟直接接口>。这意味着UIKit正在被GPU加速。
因此,如果您正确使用它们(我的意思是,在设计限制范围内),它的执行效果与普通UIView
一样。如果您只使用几个图像来显示,那么CALayer
实现可以获得可接受的性能,因为它可以完全加速底层 OpenGL (这意味着GPU加速)。
无论如何,如果你需要极端优化数百个带有微调像素着色器的动画精灵,你应该直接使用OpenGL,因为UIImageView
缺乏很多优化选项较低的水平。无论如何,至少为了优化UI的东西,要比Apple更好,这是非常难的。
CGImage
慢?你应该知道的是GPU加速。在所有最近的计算机中,只有GPU才能实现快速的图形性能。然后,重点是您使用的方法是否在GPU之上实现。
IMO,CGImage
绘图方法未使用GPU实现。
我想我在Apple的文档中提到了这一点,但我不记得在哪里。所以我不确定这一点。无论如何,我认为CGImage
是在CPU中实现的,因为,
CATiledLayer
的初始设计意图应该是CPU。所以它似乎是在CPU中完成的。在CPU中完成的图形操作比在GPU中慢很多。
简单地剪裁图像和合成图像层对GPU来说是非常简单和便宜的操作(与CPU相比),因此您可以期望UIKit库将利用它,因为整个UIKit是在OpenGL之上实现的。
因为优化是一种关于微观管理的工作,具体数字和小事实非常重要。什么是中等大小? iOS上的OpenGL通常将最大纹理大小限制为1024x1024像素(在最近的版本中可能更大)。如果您的图像大于此图像,它将无法工作,或者性能会大大降低(我认为UIImageView针对极限范围内的图像进行了优化)。
如果你需要使用剪辑显示巨大的图像,你必须使用另一个优化,如CGImage
,这是一个完全不同的故事。
除非你想知道OpenGL的每一个细节,否则不要去OpenGL。它需要充分了解低级图形,至少需要100倍的代码。
虽然不太可能发生,但{{1}}东西(或其他任何东西)不需要仅仅停留在CPU中。不要忘记检查您正在使用的API的基本技术。尽管如此,GPU的东西与CPU有很大不同,然后API人员通常会明确地提及它们。
答案 1 :(得分:5)
如果您不仅可以设置UIImageView的图像,而且还可以设置在UIImage中显示的左上角偏移量,那么最终会更快,从精灵地图集创建的图像少得多。也许这是可能的。
与此同时,我在我的应用程序中使用的实用程序类中创建了这些有用的函数。它从另一个UIImage的一部分创建一个UIImage,可以使用标准的UIImageOrientation值来指定旋转,缩放和翻转选项。
我的应用在初始化期间会创建大量的UIImages,这必然需要时间。但是在选择某个选项卡之前不需要某些图像。为了给出更快加载的外观,我可以在启动时生成的单独线程中创建它们,然后等到完成后再选择该选项卡。
+ (UIImage*)imageByCropping:(UIImage *)imageToCrop toRect:(CGRect)aperture {
return [ChordCalcController imageByCropping:imageToCrop toRect:aperture withOrientation:UIImageOrientationUp];
}
// Draw a full image into a crop-sized area and offset to produce a cropped, rotated image
+ (UIImage*)imageByCropping:(UIImage *)imageToCrop toRect:(CGRect)aperture withOrientation:(UIImageOrientation)orientation {
// convert y coordinate to origin bottom-left
CGFloat orgY = aperture.origin.y + aperture.size.height - imageToCrop.size.height,
orgX = -aperture.origin.x,
scaleX = 1.0,
scaleY = 1.0,
rot = 0.0;
CGSize size;
switch (orientation) {
case UIImageOrientationRight:
case UIImageOrientationRightMirrored:
case UIImageOrientationLeft:
case UIImageOrientationLeftMirrored:
size = CGSizeMake(aperture.size.height, aperture.size.width);
break;
case UIImageOrientationDown:
case UIImageOrientationDownMirrored:
case UIImageOrientationUp:
case UIImageOrientationUpMirrored:
size = aperture.size;
break;
default:
assert(NO);
return nil;
}
switch (orientation) {
case UIImageOrientationRight:
rot = 1.0 * M_PI / 2.0;
orgY -= aperture.size.height;
break;
case UIImageOrientationRightMirrored:
rot = 1.0 * M_PI / 2.0;
scaleY = -1.0;
break;
case UIImageOrientationDown:
scaleX = scaleY = -1.0;
orgX -= aperture.size.width;
orgY -= aperture.size.height;
break;
case UIImageOrientationDownMirrored:
orgY -= aperture.size.height;
scaleY = -1.0;
break;
case UIImageOrientationLeft:
rot = 3.0 * M_PI / 2.0;
orgX -= aperture.size.height;
break;
case UIImageOrientationLeftMirrored:
rot = 3.0 * M_PI / 2.0;
orgY -= aperture.size.height;
orgX -= aperture.size.width;
scaleY = -1.0;
break;
case UIImageOrientationUp:
break;
case UIImageOrientationUpMirrored:
orgX -= aperture.size.width;
scaleX = -1.0;
break;
}
// set the draw rect to pan the image to the right spot
CGRect drawRect = CGRectMake(orgX, orgY, imageToCrop.size.width, imageToCrop.size.height);
// create a context for the new image
UIGraphicsBeginImageContextWithOptions(size, NO, imageToCrop.scale);
CGContextRef gc = UIGraphicsGetCurrentContext();
// apply rotation and scaling
CGContextRotateCTM(gc, rot);
CGContextScaleCTM(gc, scaleX, scaleY);
// draw the image to our clipped context using the offset rect
CGContextDrawImage(gc, drawRect, imageToCrop.CGImage);
// pull the image from our cropped context
UIImage *cropped = UIGraphicsGetImageFromCurrentImageContext();
// pop the context to get back to the default
UIGraphicsEndImageContext();
// Note: this is autoreleased
return cropped;
}
答案 2 :(得分:3)
在 UIImageView 中移动大图像的非常简单的方法如下。
让我们得到大小(100,400)的图像,代表一个图像的4个状态,一个在另一个之下。我们想要显示尺寸为(100,100)的方形 UIImageView 中偏移Y = 100的第二张图片。 解决方案是:
UIImageView *iView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
CGRect contentFrame = CGRectMake(0, 0.25, 1, 0.25);
iView.layer.contentsRect = contentFrame;
iView.image = [UIImage imageNamed:@"NAME"];
此处 contentFrame 是相对于真实 UIImage 大小的规范化框架。 所以,“0”表示我们从左边界开始可见的图像部分, “0.25”表示我们有垂直偏移100, “1”表示我们要显示图像的全宽, 最后,“0.25”表示我们只想显示高度的1/4部分图像。
因此,在本地图像坐标中,我们显示以下框架
CGRect visibleAbsoluteFrame = CGRectMake(0*100, 0.25*400, 1*100, 0.25*400)
or CGRectMake(0, 100, 100, 100);
答案 3 :(得分:1)
不是创建新图像(因为它分配内存而成本高昂),而是如何使用CGContextClipToRect
?
答案 4 :(得分:0)
最快的方法是使用图像蒙版:与要遮罩的图像大小相同的图像,但具有某个像素图案,指示渲染时要遮盖的图像部分 - >
// maskImage is used to block off the portion that you do not want rendered
// note that rect is not actually used because the image mask defines the rect that is rendered
-(void) drawRect:(CGRect)rect maskImage:(UIImage*)maskImage {
UIGraphicsBeginImageContext(image_.size);
[maskImage drawInRect:image_.bounds];
maskImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRef maskRef = maskImage.CGImage;
CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
CGImageGetHeight(maskRef),
CGImageGetBitsPerComponent(maskRef),
CGImageGetBitsPerPixel(maskRef),
CGImageGetBytesPerRow(maskRef),
CGImageGetDataProvider(maskRef), NULL, false);
CGImageRef maskedImageRef = CGImageCreateWithMask([image_ CGImage], mask);
image_ = [UIImage imageWithCGImage:maskedImageRef scale:1.0f orientation:image_.imageOrientation];
CGImageRelease(mask);
CGImageRelease(maskedImageRef);
}