Programmatic "fuzzy" style background for UIView

3k Views Asked by At

Of course, it's trivial to set a plain color for a background:

enter image description here

These days, instead of using "plain gray", it is popular to use a "fuzzy" or "cloudy" background, as a design feature in apps.

For example, here's a couple "fuzzy" backgrounds - it's just a plain color with perhaps some noise and maybe blur on that.

You can see backgrounds something like this all over, consider popular feed apps (whassapp etc). It's a "fad" of our day.

enter image description here

enter image description here

It occurred to me, it would be fantastic if you could do this in code in Swift

Note: starting with a PNG is not an elegant solution:

Hopefully it is possible to generate everything programmatically from scratch.

It would be great if the Inspector had a slider in the IBDesignable style, "Add faddish 'grainy' background..." - Should be possible in the new era!

5

There are 5 best solutions below

3
On BEST ANSWER

This will get you started, based on something I wrote a long time ago:

enter image description here

@IBInspectable properties:

  • noiseColor: the noise/grain color, this is applied over the view's backgroundColor
  • noiseMinAlpha: the minimum alpha the randomized noise can be
  • noiseMaxAlpha: the maximum alpha the randomized noise can be
  • noisePasses: how many times to apply the noise, more passes will be slower but can result in a better noise effect
  • noiseSpacing: how common the randomized noise occurs, higher spacing means the noise will be less frequent

Explanation:

When any of the designable noise properties change the view is flagged for redraw. In the draw function the UIImage is generated (or pulled from NSCache if available).

In the generation method each pixel is iterated over and if the pixel should be noise (depending on the spacing parameter), the noise color is applied with a randomized alpha channel. This is done as many times as the number of passes.

.

// 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
    }
}
0
On

We use great component KGNoise. It is really easy to use. I think it can help you

KGNoise generates random black and white pixels into a static 128x128 image that is then tiled to fill the space. The random pixels are seeded with a value that has been chosen to look the most random, this also means that the noise will look consistent between app launches.

2
On

While the question asks for a "programmatic" solution, it comes to mind that what you are trying to do and refer as "fuzzy" sounds a lot like UIBlurEffect, UIVisualEffectView and UIVibrancyEffect which were introduced in iOS 8.

enter image description here

In order to use these, you can drag a UIVisualEffectView on your Storyboard scene to add a blur or vibrancy effect to a specific part of the screen.

If you would like to have an entire scene appearing with the visual effect on top of the previous scene, you should configure the following:

  1. Set either the View Controller or presentation segue to Presentation = Over Current Context and make the background color of the "fuzzy"

enter image description here

  1. Set the background color of the presented view controller to clearColor.

  2. Embed the entire content of the presented view controller inside a UIVisualEffectView

With that, you can get effects like this:

enter image description here

9
On

You could easily build something up using GPUImage. It comes with a huge set of blurs, noise generators and filters.. You can connect them together in sequence and build up complex GPU accelerated effects.

To give you an good starting point. Here's a quick dirty prototype of a function that uses GPUImage to do something like what you want. If you set 'orUseNoise' to YES it will create a blurred image based on perlin noise INSTEAD if the image. Tweak the values pointed out to change the desired effect.

- (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;
}

This is a simple stack of:

GPUImagePicture -> GPUImagePerlinNoiseFilter -> GPUImageGaussianBlurFilter

..with a bit of handling code to make into an image properly.

You can try changing the stack to use some of the many other filters.

NOTE: Even if you use the noise instead of the image. You will still need to provide an image until you cut that part out.

1
On

I agree with answer about GPUImage and since you don't want to provide image, you could create blank image like this:

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)
}

The main advantage of using GPUImage is speed.