【问题标题】:How to render an image with effect faster with UIKit如何使用 UIKit 更快地渲染具有效果的图像
【发布时间】:2015-04-20 08:29:11
【问题描述】:

我正在制作一个 iOS 应用程序,它有一个使用多个 UIImageViews 切换大量图片的过程(一个循环来设置 UIImageView 的图像属性和一堆图像)。有时有些图像需要一些图形效果,比如乘法。

最简单的方法是使用 CIFilter 来执行此操作,但问题是 iOS 上的 CALayer 不支持“filters”属性,因此您需要在设置“image”属性之前将效果应用于图像。但是当你非常频繁地刷新屏幕时,这真的太慢了​​。

接下来我尝试直接使用 Core Graphics 与 UIGraphics 上下文和 kCGBlendModeMultiply 进行乘法运算。这确实比使用 CIFilter 快得多,但是由于您必须在渲染图像之前应用乘法,因此在尝试渲染具有乘法效果的图像时,您仍然会感觉程序运行速度比渲染普通图像要慢。

我的猜测是这两种方法的根本问题是你必须用GPU对图像进行效果处理,然后用CPU得到结果图像,最后用GPU渲染结果图像,这意味着数据CPU和GPU之间的传输浪费了很多时间,所以我尝试将超类从UIImageView更改为UIView并将CGGraphics上下文代码实现为drawRect方法,然后当我设置“image”属性时我在didSet中调用setNeedsDisplay方法。但这并不能很好地工作......实际上每次调用 setNeedsDisplay 程序都会变得比使用 CIFilter 慢得多,可能是因为显示了多个视图。

我想也许我可以用 OpenGL 解决这个问题,但我想知道我是否可以只用 UIKit 解决这个问题?

【问题讨论】:

    标签: ios uiimageview


    【解决方案1】:

    据我了解,您必须对不同的图像进行相同的更改。因此,初始初始化的时间对您来说并不重要,但应尽快处理每个图像。首先,在后台队列/线程中生成新图像是关键。 有两种快速处理/生成图像的好方法:

    1. 使用 CoreImage 中的 CIFilter

    2. 使用 GPUImage 库

    如果您使用过 CoreImage,请检查您是否正确使用了 CIFilter 和 CIContext。 CIContext 创建需要相当多的时间,但它可能在不同的 CIFilter 和图像之间共享 - 所以你应该只创建一次 CIContext! CIFilter 也可以在不同图像之间共享,但由于它不是线程安全的,因此您应该为每个线程设置一个单独的 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进行图像生成。

    【讨论】:

    • 非常感谢。但是由于“setValue:forKey:”的过程需要很多时间,不幸的是,使用 CIFilter 生成 1 个过滤图像的成本比使用 Core Graphics 的要多。那些带有过滤器的图像不是相同的图像,所以每次降低速度时我都必须执行“setValue:forKey”。不过感谢您对 GPUImage 的建议,我稍后再试。
    • @lovee 对比图像处理(Core Graphics、CIFilter、GPUImage...)方法“setValue:forKey:”什么都不做。
    • @lovee CIFilter/CIContext 初始化需要一些时间,但每个图像的处理速度非常快。您将在 GPUImage 中获得相同的效果 - 着色器初始化需要一段时间,但对于每个图像,它的工作速度非常快。
    • 感谢您的评论,很抱歉误解了您的代码。我检查了每个步骤的处理时间,我认为setValue: forKey: 方法花费了很多时间,但并没有我想象的那么多。并且通过将过滤器对象的数量限制为 1 并将其用于所有图像,它变得比以前更快。但不幸的是,它仍然不够快......无论如何真的很感谢您的回复。
    猜你喜欢
    • 2013-10-04
    • 2015-01-31
    • 1970-01-01
    • 2013-12-07
    • 2013-01-28
    • 1970-01-01
    • 1970-01-01
    • 2017-07-28
    • 1970-01-01
    相关资源
    最近更新 更多