使用iOS 7的快照API模糊屏幕

4

我相信NDA已经解除,所以我可以问这个问题。我有一个UIView子类:

BlurView *blurredView = ((BlurView *)[self.view snapshotViewAfterScreenUpdates:NO]);
blurredView.frame = self.view.frame;
[self.view addSubview:blurredView];

迄今为止,它在捕获屏幕方面表现良好,但现在我想模糊该视图。我应该如何操作?从我所了解的,我需要捕获视图(上下文?!)的当前内容并将其转换为CIImage(不是吗?),然后对其应用CIGaussianBlur,并将其绘制回视图中。
我应该怎么做?
附注:该视图没有动画,因此在性能方面应该没问题。
编辑:这是我到目前为止所拥有的。问题是我无法将快照捕获到UIImage中,我得到的是黑屏。但是如果我直接将视图添加为子视图,我可以看到快照在那里。
// Snapshot
UIView *view = [self.view snapshotViewAfterScreenUpdates:NO];

// Convert to UIImage
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

// Apply the UIImage to a UIImageView
BlurView *blurredView = [[BlurView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
[self.view addSubview:blurredView];
blurredView.imageView.image = img;

// Black screen -.-

BlurView.m:

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];

    if (self) {
        // Initialization code
        self.imageView = [[UIImageView alloc] init];
        self.imageView.frame = CGRectMake(20, 20, 200, 200);
        [self addSubview:self.imageView];
    }
    return self;
}

1
在WWDC2013的示例代码中有一个UIImageView类别。 - Kyle Fang
我已经检查过了,但它使用资产中的图像作为源,而我需要使用快照。这一点我无法弄清楚。 - Nikolay Dyankov
4个回答

13

这个问题只有一半被回答了,所以我认为值得补充一下。

UIScreen的问题在于

- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates

以及UIView的

- (UIView *)resizableSnapshotViewFromRect:(CGRect)rect 
                      afterScreenUpdates:(BOOL)afterUpdates 
                           withCapInsets:(UIEdgeInsets)capInsets

问题是您无法从它们中派生UIImage - 出现“黑屏”问题。

在iOS7中,苹果提供了一个用于提取UIImages的第三方API,该方法位于UIView上。

- (BOOL)drawViewHierarchyInRect:(CGRect)rect 
             afterScreenUpdates:(BOOL)afterUpdates  

它不像snapshotView那么快,但与renderInContext相比还算可以(在Apple提供的示例中,它比renderInContext快五倍,比snapshotView慢三倍)

示例用法:

 UIGraphicsBeginImageContextWithOptions(image.size, NULL, 0);
 [view drawViewHierarchyInRect:rect];
 UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
 UIGraphicsEndImageContext();  

然后需要获得一个模糊版本。

 UIImage* lightImage = [newImage applyLightEffect];  

其中applyLightEffect是苹果UIImage+ImageEffects分类中的一种“模糊”类方法,这在答案中有提到(答案中链接到此代码示例的链接已失效,但此链接可以带你到正确的页面:你需要的文件是)。

主要参考资料是WWDC2013会议第226节:在iOS上实现引人入胜的UI

顺便说一句,在苹果的renderInContext参考文档中有一个有趣的注释,暗示了黑屏问题……

重要提示:此方法在OS X v10.5的实现不支持整个核心动画组合模型。QCCompositionLayer、CAOpenGLLayer和QTMovieLayer层没有被渲染。此外,不会呈现使用3D变换的图层,也不会呈现指定backgroundFilters、filters、compositingFilter或mask值的图层。未来的OS X版本可能会添加对渲染这些图层和属性的支持。

自10.5以来,这个注释还没有更新过,所以我认为“未来版本”仍可能有一段时间,我们可以将新的CASnapshotLayer(或其他什么)添加到列表中。


我用这种方法并没有得到任何模糊的效果。使用UIVisualEffect可以获得更好的效果:https://dev59.com/hlwY5IYBdhLWcg3wpJP_#40016528 - Senseful

9

WWDC的样例代码ios_uiimageeffects

有一个名为UIImage+ImageEffectsUIImage类别。

这是它的API:

- (UIImage *)applyLightEffect;
- (UIImage *)applyExtraLightEffect;
- (UIImage *)applyDarkEffect;
- (UIImage *)applyTintEffectWithColor:(UIColor *)tintColor;

- (UIImage *)applyBlurWithRadius:(CGFloat)blurRadius 
                       tintColor:(UIColor *)tintColor 
           saturationDeltaFactor:(CGFloat)saturationDeltaFactor 
                       maskImage:(UIImage *)maskImage;

由于法律原因,我无法在这里展示实现方式,但是其中有一个演示项目。使用起来应该很容易上手。

3
这只回答了我问题的一半。我仍需要获取UIImage,以应用这些效果,但此方法给我一个黑色的图像:https://dev59.com/s2855IYBdhLWcg3wcz_y - Nikolay Dyankov
如果你只需要图片,那么就不需要使用快照API了。直接在self.view上渲染图片即可。 - Kyle Fang
那么如果我不截屏,我从哪里获取图像呢? - Nikolay Dyankov
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, self.view.opaque, 0.0); 然后 [self.view.layer renderInContext:UIGraphicsGetCurrentContext] - Kyle Fang
@NikolayDyankov,你找到解决这个黑屏问题的方法了吗? - m8labs
1
@NikolayDyankov我认为这永远不会起作用。我认为这不起作用(黑屏)的原因是快照返回的视图实际上不是一个完整的视图。如果您通过调试器进行步骤,则会看到它的类型为“_UIReplicantView”。这使我想到它只是另一个视图的优化副本,并没有与“完整”UIView相同的“层”特征... - MobileVet

4

为了总结如何使用foundry的示例代码来实现这一点,请使用以下方法:

我想略微模糊整个屏幕,所以我将使用主屏幕边界。

CGRect screenCaptureRect = [UIScreen mainScreen].bounds;
UIView *viewWhereYouWantToScreenCapture = [[UIApplication sharedApplication] keyWindow];

//screen capture code
UIGraphicsBeginImageContextWithOptions(screenCaptureRect.size, NO, [UIScreen mainScreen].scale);
[viewWhereYouWantToScreenCapture drawViewHierarchyInRect:screenCaptureRect afterScreenUpdates:NO];
UIImage *capturedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

//blur code
UIColor *tintColor = [UIColor colorWithWhite:1.0 alpha:0];
UIImage *blurredImage = [capturedImage applyBlurWithRadius:1.5 tintColor:tintColor saturationDeltaFactor:1.2 maskImage:nil];
//or use [capturedImage applyLightAffect] but I thought that was too much for me

//use blurredImage in whatever way you so desire!

关于屏幕截图部分的注意事项

UIGraphicsBeginImageContextWithOptions() 的第二个参数是不透明度。除非您没有任何其他 alpha 值,否则应该使用 NO。如果您返回 YES,屏幕截图将不考虑透明度值,因此速度会更快,但可能会出现错误。

UIGraphicsBeginImageContextWithOptions() 的第三个参数是比例。 可能要像我一样放入设备的比例,以确保区分视网膜和非视网膜。 但我还没有真正测试过这一点,我认为 0.0f 也可以工作。

drawViewHierarchyInRect:afterScreenUpdates: 要注意返回的屏幕更新 BOOL 值。如果我在后台之前没有放置 NO,当我回到前台时,应用程序会出现故障。不过,如果您不离开应用程序,则可能可以使用 YES

模糊注意事项

这里有一个非常轻微的模糊效果。更改 blurRadius 将使其更加模糊,并且您可以更改色调颜色和透明度以产生各种其他效果。

另外,您需要添加一个类别才能使模糊方法工作...

如何添加 UIImage+ImageEffects 类别

您需要下载 UIImage+ImageEffects 类别才能进行模糊处理。 登录后从这里下载: https://developer.apple.com/downloads/index.action?name=WWDC%202013

搜索 "UIImageEffects",然后找到它。只需提取出 2 个必要的文件并将它们添加到您的项目中。UIImage+ImageEffects.h 和 UIImage+ImageEffects.m。

另外,因为我有一个不是使用 xCode 5 创建的项目,所以我必须在我的构建设置中启用模块。要执行此操作,请转到目标构建设置并搜索 "modules",然后确保 "Enable Modules" 和 "Link Frameworks Automatically" 都设置为 yes,否则会出现新类别的编译器错误。

祝您成功实现模糊效果!


0

查看WWDC 2013示例应用程序“轻松运行”。

模糊效果是通过类别实现的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接