UIView的程序化“模糊”背景样式

12

当然,为背景设置纯色很简单:

输入图像描述

现在,与其使用“纯灰色”,使用“模糊”或“云朵状”的背景作为应用程序的设计特色更受欢迎。

例如,这里有几个“模糊”背景 - 它只是一个平面颜色,可能加上一些噪点和模糊处理。

您可以在各种地方看到类似于这样的背景,比如流行的订阅应用程序(whassapp等)。这是我们这个时代的“时尚潮流”。

输入图像描述

输入图像描述

我想到了一个绝妙的想法,如果你能在Swift代码中实现这个功能就太好了。

请注意:从PNG开始并不是优雅的解决方案:

希望能够通过编程从头开始生成所有内容。

如果检查器在IBDesignable样式中有一个滑块,“添加时尚的‘颗粒状’背景...”就太好了 - 在新时代应该是可能的!

6个回答

16

以下内容是基于我很久以前写的东西,可以帮助你入门:

enter image description here

@IBInspectable 属性:

  • noiseColor: 噪点颜色,将应用于视图的 backgroundColor
  • noiseMinAlpha: 随机噪点最小 alpha 值
  • noiseMaxAlpha: 随机噪点最大 alpha 值
  • noisePasses: 应用噪点的次数,次数越多速度越慢,但可能会产生更好的噪点效果
  • noiseSpacing: 随机噪点出现的频率,间隔越大则噪点出现越少

说明:

当任何可设计的噪点属性更改时,视图将被标记为需要重绘。在绘制函数中,将生成 UIImage(或从 NSCache 中获取,如果可用)。

在生成方法中,每个像素都会被迭代,如果该像素应该是噪点(取决于间隔参数),则使用随机的 alpha 通道应用噪点颜色。这将根据通过次数进行多次处理。

.

// NoiseView.swift
import UIKit

let noiseImageCache = NSCache()

@IBDesignable class NoiseView: UIView {

    let noiseImageSize = CGSizeMake(128, 128)

    @IBInspectable var noiseColor: UIColor = UIColor.blackColor() {
        didSet { setNeedsDisplay() }
    }
    @IBInspectable var noiseMinAlpha: CGFloat = 0 {
        didSet { setNeedsDisplay() }
    }
    @IBInspectable var noiseMaxAlpha: CGFloat = 1 {
        didSet { setNeedsDisplay() }
    }
    @IBInspectable var noisePasses: Int = 1 {
        didSet {
            noisePasses = max(0, noisePasses)
            setNeedsDisplay()
        }
    }
    @IBInspectable var noiseSpacing: Int = 1 {
        didSet {
            noiseSpacing = max(1, noiseSpacing)
            setNeedsDisplay()
        }
    }

    override func drawRect(rect: CGRect) {
        super.drawRect(rect)

        UIColor(patternImage: currentUIImage()).set()
        UIRectFillUsingBlendMode(bounds, .Normal)
    }

    private func currentUIImage() -> UIImage {

        //  Key based on all parameters
        let cacheKey = "\(noiseImageSize),\(noiseColor),\(noiseMinAlpha),\(noiseMaxAlpha),\(noisePasses)"

        var image = noiseImageCache.objectForKey(cacheKey) as! UIImage!

        if image == nil {
            image = generatedUIImage()

            #if !TARGET_INTERFACE_BUILDER
                noiseImageCache.setObject(image, forKey: cacheKey)
            #endif
        }

        return image
    }

    private func generatedUIImage() -> UIImage {

        UIGraphicsBeginImageContextWithOptions(noiseImageSize, false, 0)

        let accuracy: CGFloat = 1000.0

        for _ in 0..<noisePasses {
            for y in 0..<Int(noiseImageSize.height) {
                for x in 0..<Int(noiseImageSize.width) {
                    if random() % noiseSpacing == 0 {
                        let alpha = (CGFloat(random() % Int((noiseMaxAlpha - noiseMinAlpha) * accuracy)) / accuracy) + noiseMinAlpha
                        noiseColor.colorWithAlphaComponent(alpha).set()
                        UIRectFill(CGRectMake(CGFloat(x), CGFloat(y), 1, 1))
                    }
                }
            }
        }

        let image = UIGraphicsGetImageFromCurrentImageContext() as UIImage

        UIGraphicsEndImageContext()

        return image
    }
}

漂亮的IBDesignable解决方案。 - Fattie
1
这种方法不是最快的。你可以分配缓冲区,然后使用该缓冲区创建位图上下文,并直接填充它,而不是调用UIRectFill。 - kelin
嗨,SomeGuy,我们能否将这个模糊效果设置到 CALayer 而不是 UIView 上。我有一个 CAGradientLayer,我想在上面显示模糊效果。 - Harsha

4

在Swift 3中

import UIKit

let noiseImageCache = NSCache<AnyObject, AnyObject>()

@IBDesignable class NoiseView: UIView {

    let noiseImageSize = CGSize(width: 128.0, height: 128.0)

    @IBInspectable var noiseColor: UIColor = UIColor.black {
        didSet { setNeedsDisplay() }
    }
    @IBInspectable var noiseMinAlpha: CGFloat = 0 {
        didSet { setNeedsDisplay() }
    }
    @IBInspectable var noiseMaxAlpha: CGFloat = 0.5 {
        didSet { setNeedsDisplay() }
    }
    @IBInspectable var noisePasses: Int = 3 {
        didSet {
            noisePasses = max(0, noisePasses)
            setNeedsDisplay()
        }
    }
    @IBInspectable var noiseSpacing: Int = 1 {
        didSet {
            noiseSpacing = max(1, noiseSpacing)
            setNeedsDisplay()
        }
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        UIColor(patternImage: currentUIImage()).set()
        UIRectFillUsingBlendMode(bounds, .normal)
    }

    private func currentUIImage() -> UIImage {

        //  Key based on all parameters
        let cacheKey = "\(noiseImageSize),\(noiseColor),\(noiseMinAlpha),\(noiseMaxAlpha),\(noisePasses)"
        var image = noiseImageCache.object(forKey: cacheKey as AnyObject) as? UIImage

        if image == nil {
            image = generatedUIImage()

            #if !TARGET_INTERFACE_BUILDER
                noiseImageCache.setObject(image!, forKey: cacheKey as AnyObject)
            #endif
        }
        return image!
    }

    private func generatedUIImage() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(noiseImageSize, false, 0)
        let accuracy: CGFloat = 1000.0
        for _ in 0..<noisePasses {
            for y in 0..<Int(noiseImageSize.height) {
                for x in 0..<Int(noiseImageSize.width) {
                    if Int(arc4random()) % noiseSpacing == 0 {
                        let alpha = (CGFloat(arc4random() % UInt32((noiseMaxAlpha - noiseMinAlpha) * accuracy)) / accuracy) + noiseMinAlpha
                        noiseColor.withAlphaComponent(alpha).set()
                        UIRectFill(CGRect(x: x, y: y, width: 1, height: 1))
                    }
                }
            }
        }
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image!
    }
}

2
我们使用了一个很棒的组件KGNoise,它非常容易使用。我认为这可以帮助你。
KGNoise会生成随机黑白像素到静态的128x128图片中,然后将其平铺以填满空间。随机像素被种植有最随机的值,这也意味着噪点在应用程序启动时看起来一致。

2
你可以轻松地使用GPUImage创建一些东西。它带有大量的模糊、噪声生成器和滤镜。你可以将它们连接在一起,建立复杂的GPU加速效果。
为了给你一个好的起点,这里有一个快速而简单的原型函数,它使用GPUImage来实现你想要的功能。如果将'orUseNoise'设置为YES,它将基于柏林噪声而不是图像创建模糊图像。调整指出的值以改变所需的效果。
- (UIImage *)blurWithGPUImage:(UIImage *)sourceImage orUseNoise:(bool) useNoise {
    GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithImage:sourceImage];

    GPUImageGaussianBlurFilter *gaussFilter = [[GPUImageGaussianBlurFilter alloc] init];
    [gaussFilter setBlurRadiusInPixels:6];                                      //<<-------TWEAK
    [gaussFilter setBlurPasses:1];                                              //<<-------TWEAK

    if(useNoise) {
        GPUImagePerlinNoiseFilter* perlinNouse = [[GPUImagePerlinNoiseFilter alloc] init];
        [perlinNouse setColorStart:(GPUVector4){1.0, 1.0, 1.0f, 1.0}];          //<<-------TWEAK
        [perlinNouse setColorFinish:(GPUVector4){0.5,0.5, 0.5f, 1.0}];          //<<-------TWEAK
        [perlinNouse setScale:200];                                             //<<-------TWEAK
        [stillImageSource addTarget:perlinNouse];
        [perlinNouse addTarget:gaussFilter];
    } else {
        [stillImageSource addTarget:gaussFilter];
    }

    [gaussFilter useNextFrameForImageCapture];
    [stillImageSource processImage];

    UIImage *outputImage = [gaussFilter imageFromCurrentFramebuffer];

    // Set up output context.
    UIGraphicsBeginImageContext(self.view.frame.size);
    CGContextRef outputContext = UIGraphicsGetCurrentContext();

    // Invert image coordinates
    CGContextScaleCTM(outputContext, 1.0, -1.0);
    CGContextTranslateCTM(outputContext, 0, -self.view.frame.size.height);

    // Draw base image.
    CGContextDrawImage(outputContext, self.view.frame, outputImage.CGImage);

    // Apply tint
    CGContextSaveGState(outputContext);
    UIColor* tint = [UIColor colorWithWhite:1.0f alpha:0.6];                    //<<-------TWEAK
    CGContextSetFillColorWithColor(outputContext, tint.CGColor);
    CGContextFillRect(outputContext, self.view.frame);
    CGContextRestoreGState(outputContext);

    // Output image
    outputImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return outputImage;
}

这是一个简单的堆栈:
GPUImagePicture -> GPUImagePerlinNoiseFilter -> GPUImageGaussianBlurFilter 还有一些处理代码,使其能够正确地输出图像。
你可以尝试更改堆栈以使用其他滤镜。
注意:即使你使用噪声而不是图像,你仍需要提供一个图像,直到你将该部分剪切掉为止。

1
@JoeBlow 我添加了一个快速的代码示例。它包含有和没有图片两种情况。 - Andres Canella
即使您使用噪声而不是图像,应用程序也会崩溃,因为该情况未得到处理。在这种情况下,您需要替换GPUImagePicture。因此,即使您不使用“orUseNoise”,也请始终提供图像。 - Andres Canella
1
我在答案中添加了更多的描述。将setBlurRadiusInPixels降低以查看噪声的效果。更高的gaussFilter数字会导致渲染速度变慢。GPUImage是性能的保证。您不需要将其转换为UIImage即可使用。请查看所有GPUImage文档,希望这可以作为一个良好的起点帮助到您。 - Andres Canella
如果将“orUseNoise”设置为YES,则无需从图像开始。您只需要放置任何图像,以便它不会崩溃,但是该图像将不会被使用,因为它将完全被噪声覆盖。我忙于很多工作,没有时间清理那个案例 :) - Andres Canella
啊,好的,你只需要把任何图像放在那里,这样它就不会崩溃了,现在我明白了。 - Fattie
显示剩余4条评论

1

我同意有关GPUImage的答案,如果您不想提供图像,可以创建如下的空白图像:

func createNoiseImage(size: CGSize, color: UIColor) -> UIImage {
    UIGraphicsBeginImageContext(size)
    let context = UIGraphicsGetCurrentContext()

    CGContextSetFillColorWithColor(context, color.CGColor)
    CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height))

    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext();

    let filter = GPUImagePerlinNoiseFilter()
    return filter.imageByFilteringImage(image)
}

使用GPUImage的主要优点是速度。

非常正确 - 从某种程度上来说,这是最完整的答案!! - Fattie

0
虽然问题要求“编程”解决方案,但我想到你试图做的和所谓的“模糊”听起来很像在iOS 8中引入的UIBlurEffectUIVisualEffectViewUIVibrancyEffect

enter image description here

为了使用这些功能,您可以将UIVisualEffectView拖到Storyboard场景中,以向屏幕的特定部分添加模糊或活力效果。
如果您想要整个场景出现在先前场景的视觉效果之上,您应该进行以下配置:
  1. 将视图控制器或呈现segue设置为Presentation = Over Current Context,并使“模糊”的背景颜色。

enter image description here

  1. 将呈现的视图控制器的背景颜色设置为clearColor

  2. 将呈现的视图控制器的整个内容嵌入到一个UIVisualEffectView中。

这样,您就可以获得如下效果:

enter image description here


1
这些是在iOS 8中引入的,而不是iOS 7。 - SomeGuy
你说得对,我已经更新了我的答案以反映这一点。 - Eneko Alonso

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