在 UITableViewCell 中模糊图像时出现性能问题
Performance Issue while blurring image in UITableViewCell
我的习惯 UITableViewCell
中有一个 UIImageView
。包含的图像应该是模糊的。我知道 UIVisualEffectsView
但首先这在 iOS8
之前不可用,其次模糊对于我的用例来说有点重。
这就是我想出这个解决方案的原因:
示例单元格ForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = @"showCell";
DEShowCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
[tableView registerNib:[UINib nibWithNibName:@"DEShowCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:cellIdentifier];
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
}
[cell setBackgroundImageWithBlur:[UIImage imageNamed:@"sampleBanner"]];
return cell;
}
我的自定义单元格:
-(void)setBackgroundImageWithBlur:(UIImage *)image {
[self.backgroundImageView setImage:[self blurWithCoreImage:image]];
}
- (UIImage *)blurWithCoreImage:(UIImage *)sourceImage
{
CIImage *inputImage = [CIImage imageWithCGImage:sourceImage.CGImage];
// Apply Affine-Clamp filter to stretch the image so that it does not
// look shrunken when gaussian blur is applied
CGAffineTransform transform = CGAffineTransformIdentity;
CIFilter *clampFilter = [CIFilter filterWithName:@"CIAffineClamp"];
[clampFilter setValue:inputImage forKey:@"inputImage"];
[clampFilter setValue:[NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)] forKey:@"inputTransform"];
// Apply gaussian blur filter with radius of 30
CIFilter *gaussianBlurFilter = [CIFilter filterWithName: @"CIGaussianBlur"];
[gaussianBlurFilter setValue:clampFilter.outputImage forKey: @"inputImage"];
[gaussianBlurFilter setValue:@10 forKey:@"inputRadius"];
CIContext *context = [CIContext contextWithOptions:nil];
CGImageRef cgImage = [context createCGImage:gaussianBlurFilter.outputImage fromRect:[inputImage extent]];
// Set up output context.
UIGraphicsBeginImageContext(self.frame.size);
CGContextRef outputContext = UIGraphicsGetCurrentContext();
// Invert image coordinates
CGContextScaleCTM(outputContext, 1.0, -1.0);
CGContextTranslateCTM(outputContext, 0, -self.frame.size.height);
// Draw base image.
CGContextDrawImage(outputContext, self.frame, cgImage);
// Apply white tint
CGContextSaveGState(outputContext);
CGContextSetFillColorWithColor(outputContext, [UIColor colorWithWhite:1 alpha:0.2].CGColor);
CGContextFillRect(outputContext, self.frame);
CGContextRestoreGState(outputContext);
// Output image is ready.
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
不幸的是,当我尝试滚动 UITableView
时,这给我带来了巨大的性能问题。
所以我问我如何解决?我可以使用一些库来做像 GPUImage
这样的模糊,我想这会更快,但我不知道这是否会产生很大的不同。
我认为 UITableView
将包含大约 20-60 行。
还有其他想法,例如缓存或其他吗?
没有理由继续计算模糊的背景图像。因为都用同一个,模糊一次,配置cell的时候赋值:
if (cell == nil)
{
[tableView registerNib:[UINib nibWithNibName:@"DEShowCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:cellIdentifier];
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// Inside the initial configuration; not outside.
[cell.backgroundImageView setImage:self.blurredBackgroundImage];
}
设置self.blurredBackgroundImage
一次:
- (void)viewDidLoad {
self.blurredBackgroundImage = [self blurWithCoreImage:[UIImage imageNamed:...]];
}
当然,鉴于此代码,我可能只是在 PhotoShop 中模糊图像并将其保存为资源。没有理由以编程方式模糊静态资源(除非您在其他地方使用相同的图像并且它足够大以至于磁盘 space 很重要)。
我将使用 FXBlurView 并设置 dynamic = NO。它具有不错的性能。
如果您确实希望使用 CoreImage,则不应不断地创建要使用它的上下文。您应该创建一次上下文并重新使用它,因为不这样做会导致此类性能问题。
您可以通过使用 time profiler 工具分析您的代码来确定这是否是上述代码中的问题,或者是否是其他问题。为此,只需从 Xcode 中的 "Product" 菜单中选择 select "Profile",然后在它运行时选择 "Time Profiler"。然后您将能够看到占用最多时间的调用的堆栈跟踪。
WWDC 2013 code samples、iOS_UIImageEffects 之一介绍了如何使用 vImage 高性能框架来模糊图像。当我测试它时,它比 CoreImage 快得多。
但是在回答您有关缓存的问题时,是的,绝对可以使用缓存。使用 NSCache
作为主缓存,使用持久内存作为二级缓存。
顺便说一句,您似乎为每个单元格模糊了相同的图像。如果是这样,您应该将其模糊一次,然后对所有单元格重复使用该模糊图像。
同样,您正在多次注册笔尖。预先注册一次。
我的习惯 UITableViewCell
中有一个 UIImageView
。包含的图像应该是模糊的。我知道 UIVisualEffectsView
但首先这在 iOS8
之前不可用,其次模糊对于我的用例来说有点重。
这就是我想出这个解决方案的原因:
示例单元格ForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = @"showCell";
DEShowCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
[tableView registerNib:[UINib nibWithNibName:@"DEShowCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:cellIdentifier];
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
}
[cell setBackgroundImageWithBlur:[UIImage imageNamed:@"sampleBanner"]];
return cell;
}
我的自定义单元格:
-(void)setBackgroundImageWithBlur:(UIImage *)image {
[self.backgroundImageView setImage:[self blurWithCoreImage:image]];
}
- (UIImage *)blurWithCoreImage:(UIImage *)sourceImage
{
CIImage *inputImage = [CIImage imageWithCGImage:sourceImage.CGImage];
// Apply Affine-Clamp filter to stretch the image so that it does not
// look shrunken when gaussian blur is applied
CGAffineTransform transform = CGAffineTransformIdentity;
CIFilter *clampFilter = [CIFilter filterWithName:@"CIAffineClamp"];
[clampFilter setValue:inputImage forKey:@"inputImage"];
[clampFilter setValue:[NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)] forKey:@"inputTransform"];
// Apply gaussian blur filter with radius of 30
CIFilter *gaussianBlurFilter = [CIFilter filterWithName: @"CIGaussianBlur"];
[gaussianBlurFilter setValue:clampFilter.outputImage forKey: @"inputImage"];
[gaussianBlurFilter setValue:@10 forKey:@"inputRadius"];
CIContext *context = [CIContext contextWithOptions:nil];
CGImageRef cgImage = [context createCGImage:gaussianBlurFilter.outputImage fromRect:[inputImage extent]];
// Set up output context.
UIGraphicsBeginImageContext(self.frame.size);
CGContextRef outputContext = UIGraphicsGetCurrentContext();
// Invert image coordinates
CGContextScaleCTM(outputContext, 1.0, -1.0);
CGContextTranslateCTM(outputContext, 0, -self.frame.size.height);
// Draw base image.
CGContextDrawImage(outputContext, self.frame, cgImage);
// Apply white tint
CGContextSaveGState(outputContext);
CGContextSetFillColorWithColor(outputContext, [UIColor colorWithWhite:1 alpha:0.2].CGColor);
CGContextFillRect(outputContext, self.frame);
CGContextRestoreGState(outputContext);
// Output image is ready.
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
不幸的是,当我尝试滚动 UITableView
时,这给我带来了巨大的性能问题。
所以我问我如何解决?我可以使用一些库来做像 GPUImage
这样的模糊,我想这会更快,但我不知道这是否会产生很大的不同。
我认为 UITableView
将包含大约 20-60 行。
还有其他想法,例如缓存或其他吗?
没有理由继续计算模糊的背景图像。因为都用同一个,模糊一次,配置cell的时候赋值:
if (cell == nil)
{
[tableView registerNib:[UINib nibWithNibName:@"DEShowCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:cellIdentifier];
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// Inside the initial configuration; not outside.
[cell.backgroundImageView setImage:self.blurredBackgroundImage];
}
设置self.blurredBackgroundImage
一次:
- (void)viewDidLoad {
self.blurredBackgroundImage = [self blurWithCoreImage:[UIImage imageNamed:...]];
}
当然,鉴于此代码,我可能只是在 PhotoShop 中模糊图像并将其保存为资源。没有理由以编程方式模糊静态资源(除非您在其他地方使用相同的图像并且它足够大以至于磁盘 space 很重要)。
我将使用 FXBlurView 并设置 dynamic = NO。它具有不错的性能。
如果您确实希望使用 CoreImage,则不应不断地创建要使用它的上下文。您应该创建一次上下文并重新使用它,因为不这样做会导致此类性能问题。
您可以通过使用 time profiler 工具分析您的代码来确定这是否是上述代码中的问题,或者是否是其他问题。为此,只需从 Xcode 中的 "Product" 菜单中选择 select "Profile",然后在它运行时选择 "Time Profiler"。然后您将能够看到占用最多时间的调用的堆栈跟踪。
WWDC 2013 code samples、iOS_UIImageEffects 之一介绍了如何使用 vImage 高性能框架来模糊图像。当我测试它时,它比 CoreImage 快得多。
但是在回答您有关缓存的问题时,是的,绝对可以使用缓存。使用 NSCache
作为主缓存,使用持久内存作为二级缓存。
顺便说一句,您似乎为每个单元格模糊了相同的图像。如果是这样,您应该将其模糊一次,然后对所有单元格重复使用该模糊图像。
同样,您正在多次注册笔尖。预先注册一次。