如何使用 UIKit 更快地渲染具有效果的图像
How to render an image with effect faster with UIKit
我正在制作一个 iOS 应用程序,其中有一个过程可以用多个 UIImageView 切换很多图片(一个循环来设置带有一堆图像的 UIImageView 的图像 属性)。有时一些图像需要一些图形效果,比如乘法。
最简单的方法是使用 CIFilter 来做这件事,但问题是 iOS 上的 CALayer 不支持 "filters" 属性,因此您需要应用设置 "image" 属性 之前对图像的效果。但是当你频繁刷新屏幕时,这真的太慢了。
所以接下来我尝试直接使用 Core Graphics 与 UIGraphics 上下文和 kCGBlendModeMultiply 进行乘法运算。这确实比使用 CIFilter 快得多,但是由于您必须在渲染图像之前应用乘法运算,所以在尝试渲染具有乘法效果的图像时,您仍然可以感觉到程序运行速度比渲染普通图像慢。
我的猜测是,这两种方法的根本问题是你必须用 GPU 处理图像的效果,然后用 CPU 得到结果图像,最后用 CPU 渲染结果图像GPU,这意味着 CPU 和 GPU 之间的数据传输浪费了很多时间,所以我尝试将 super class 从 UIImageView 更改为 UIView 并将 CGGraphics 上下文代码实现到 drawRect 方法,然后当我设置了 "image" 属性 我在 didSet 中调用了 setNeedsDisplay 方法。但这并不是很好......实际上每次它调用 setNeedsDisplay 程序变得更慢,甚至比使用 CIFilter 还要慢,可能是因为有几个视图显示。
我想我可能可以用 OpenGL 解决这个问题,但我想知道我是否可以只用 UIKit 解决这个问题?
据我了解,您必须对不同的图像进行相同的更改。因此初始初始化的时间对您来说并不重要,但应尽快处理每张图像。首先,在背景 queue/thread 中生成新图像 至关重要 。
快速process/generate图片有两个好方法:
使用 CoreImage 的 CIFilter
使用GPUImage库
如果您使用了 CoreImage,请检查您是否正确使用了 CIFilter 和 CIContext。 CIContext 创建需要相当多的时间,但它可以在不同的 CIFilters 和图像之间 SHARED - 所以你应该只创建 CIContext 一次! CIFilter 也可以在不同的图像之间 SHARED,但由于它不是线程安全的,你应该为每个线程设置一个单独的 CIFilter。
在我的代码中有以下内容:
+ (UIImage*)roundShadowImageForImage:(UIImage*)image {
static CIFilter *_filter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
NSLog(@"CIContext and CIFilter generating...");
_context = [CIContext contextWithOptions:@{ kCIContextUseSoftwareRenderer: @NO,
kCIContextWorkingColorSpace : [NSNull null] }];
CIImage *roundShadowImage = [CIImage imageWithCGImage:[[self class] roundShadowImage].CGImage];
CIImage *maskImage = [CIImage imageWithCGImage:[[self class] roundWhiteImage].CGImage];
_filter = [CIFilter filterWithName:@"CIBlendWithAlphaMask"
keysAndValues:
kCIInputBackgroundImageKey, roundShadowImage,
kCIInputMaskImageKey, maskImage, nil];
NSLog(@"CIContext and CIFilter are generated");
});
if (image == nil) {
return nil;
}
NSAssert(_filter, @"Error: CIFilter for cover images is not generated");
CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
// CIContext and CIImage objects are immutable, which means each can be shared safely among threads
CIFilter *filterForThread = [_filter copy]; // CIFilter could not be shared between different threads.
CGAffineTransform imageTransform = CGAffineTransformIdentity;
if (!CGSizeEqualToSize(imageSize, coverSize)) {
NSLog(@"Cover image. Resizing image %@ to required size %@", NSStringFromCGSize(imageSize), NSStringFromCGSize(coverSize));
CGFloat scaleFactor = MAX(coverSide / imageSize.width, coverSide / imageSize.height);
imageTransform = CGAffineTransformMakeScale(scaleFactor, scaleFactor);
}
imageTransform = CGAffineTransformTranslate(imageTransform, extraBorder, extraBorder);
CIImage *ciImage = [CIImage imageWithCGImage:image.CGImage];
ciImage = [ciImage imageByApplyingTransform:imageTransform];
if (image.hasAlpha) {
CIImage *ciWhiteImage = [CIImage imageWithCGImage:[self whiteImage].CGImage];
CIFilter *filter = [CIFilter filterWithName:@"CISourceOverCompositing"
keysAndValues:
kCIInputBackgroundImageKey, ciWhiteImage,
kCIInputImageKey, ciImage, nil];
[filterForThread setValue:filter.outputImage forKey:kCIInputImageKey];
}
else
{
[filterForThread setValue:ciImage forKey:kCIInputImageKey];
}
CIImage *outputCIImage = [filterForThread outputImage];
CGImageRef cgimg = [_context createCGImage:outputCIImage fromRect:[outputCIImage extent]];
UIImage *newImage = [UIImage imageWithCGImage:cgimg];
CGImageRelease(cgimg);
return newImage;
}
如果您对速度仍然不满意,请尝试GPUImage这是一个非常好的库,它也非常快,因为它使用 OpenGL 进行图像生成。
我正在制作一个 iOS 应用程序,其中有一个过程可以用多个 UIImageView 切换很多图片(一个循环来设置带有一堆图像的 UIImageView 的图像 属性)。有时一些图像需要一些图形效果,比如乘法。
最简单的方法是使用 CIFilter 来做这件事,但问题是 iOS 上的 CALayer 不支持 "filters" 属性,因此您需要应用设置 "image" 属性 之前对图像的效果。但是当你频繁刷新屏幕时,这真的太慢了。
所以接下来我尝试直接使用 Core Graphics 与 UIGraphics 上下文和 kCGBlendModeMultiply 进行乘法运算。这确实比使用 CIFilter 快得多,但是由于您必须在渲染图像之前应用乘法运算,所以在尝试渲染具有乘法效果的图像时,您仍然可以感觉到程序运行速度比渲染普通图像慢。
我的猜测是,这两种方法的根本问题是你必须用 GPU 处理图像的效果,然后用 CPU 得到结果图像,最后用 CPU 渲染结果图像GPU,这意味着 CPU 和 GPU 之间的数据传输浪费了很多时间,所以我尝试将 super class 从 UIImageView 更改为 UIView 并将 CGGraphics 上下文代码实现到 drawRect 方法,然后当我设置了 "image" 属性 我在 didSet 中调用了 setNeedsDisplay 方法。但这并不是很好......实际上每次它调用 setNeedsDisplay 程序变得更慢,甚至比使用 CIFilter 还要慢,可能是因为有几个视图显示。
我想我可能可以用 OpenGL 解决这个问题,但我想知道我是否可以只用 UIKit 解决这个问题?
据我了解,您必须对不同的图像进行相同的更改。因此初始初始化的时间对您来说并不重要,但应尽快处理每张图像。首先,在背景 queue/thread 中生成新图像 至关重要 。 快速process/generate图片有两个好方法:
使用 CoreImage 的 CIFilter
使用GPUImage库
如果您使用了 CoreImage,请检查您是否正确使用了 CIFilter 和 CIContext。 CIContext 创建需要相当多的时间,但它可以在不同的 CIFilters 和图像之间 SHARED - 所以你应该只创建 CIContext 一次! CIFilter 也可以在不同的图像之间 SHARED,但由于它不是线程安全的,你应该为每个线程设置一个单独的 CIFilter。
在我的代码中有以下内容:
+ (UIImage*)roundShadowImageForImage:(UIImage*)image {
static CIFilter *_filter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
NSLog(@"CIContext and CIFilter generating...");
_context = [CIContext contextWithOptions:@{ kCIContextUseSoftwareRenderer: @NO,
kCIContextWorkingColorSpace : [NSNull null] }];
CIImage *roundShadowImage = [CIImage imageWithCGImage:[[self class] roundShadowImage].CGImage];
CIImage *maskImage = [CIImage imageWithCGImage:[[self class] roundWhiteImage].CGImage];
_filter = [CIFilter filterWithName:@"CIBlendWithAlphaMask"
keysAndValues:
kCIInputBackgroundImageKey, roundShadowImage,
kCIInputMaskImageKey, maskImage, nil];
NSLog(@"CIContext and CIFilter are generated");
});
if (image == nil) {
return nil;
}
NSAssert(_filter, @"Error: CIFilter for cover images is not generated");
CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
// CIContext and CIImage objects are immutable, which means each can be shared safely among threads
CIFilter *filterForThread = [_filter copy]; // CIFilter could not be shared between different threads.
CGAffineTransform imageTransform = CGAffineTransformIdentity;
if (!CGSizeEqualToSize(imageSize, coverSize)) {
NSLog(@"Cover image. Resizing image %@ to required size %@", NSStringFromCGSize(imageSize), NSStringFromCGSize(coverSize));
CGFloat scaleFactor = MAX(coverSide / imageSize.width, coverSide / imageSize.height);
imageTransform = CGAffineTransformMakeScale(scaleFactor, scaleFactor);
}
imageTransform = CGAffineTransformTranslate(imageTransform, extraBorder, extraBorder);
CIImage *ciImage = [CIImage imageWithCGImage:image.CGImage];
ciImage = [ciImage imageByApplyingTransform:imageTransform];
if (image.hasAlpha) {
CIImage *ciWhiteImage = [CIImage imageWithCGImage:[self whiteImage].CGImage];
CIFilter *filter = [CIFilter filterWithName:@"CISourceOverCompositing"
keysAndValues:
kCIInputBackgroundImageKey, ciWhiteImage,
kCIInputImageKey, ciImage, nil];
[filterForThread setValue:filter.outputImage forKey:kCIInputImageKey];
}
else
{
[filterForThread setValue:ciImage forKey:kCIInputImageKey];
}
CIImage *outputCIImage = [filterForThread outputImage];
CGImageRef cgimg = [_context createCGImage:outputCIImage fromRect:[outputCIImage extent]];
UIImage *newImage = [UIImage imageWithCGImage:cgimg];
CGImageRelease(cgimg);
return newImage;
}
如果您对速度仍然不满意,请尝试GPUImage这是一个非常好的库,它也非常快,因为它使用 OpenGL 进行图像生成。