12

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!

Fattie
  • 27,874
  • 70
  • 431
  • 719

5 Answers5

16

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
    }
}
SomeGuy
  • 9,670
  • 3
  • 32
  • 35
  • beautiful IBDesignable solution. – Fattie Jun 09 '16 at 16:58
  • 1
    This method is not the fastest one. You can allocate buffer and then create Bitmap Context with that buffer and fill it directly, instead of calling UIRectFill. – kelin Jun 15 '16 at 12:28
  • Hi SomeGuy, can we set this Fuzzy effect to `CALayer` instead of `UIView`. I have a `CAGradientLayer` and I want to show the Fuzzy effect on that. – Harsha Sep 03 '20 at 07:43
2

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.

Blind Ninja
  • 1,063
  • 13
  • 28
2

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.

Andres Canella
  • 3,706
  • 1
  • 35
  • 47
  • 1
    @JoeBlow I added a quick code sample. It has both with and without image. – Andres Canella Oct 02 '15 at 14:08
  • The app will crash even if you're using the noise instead of the image because the case is not handled. You will need to replace GPUImagePicture in that case. So just always provide an image even if you're not using via 'orUseNoise'. – Andres Canella Oct 02 '15 at 16:53
  • 1
    I added a bit more description the answer. Lower setBlurRadiusInPixels to see what the noise is doing. higher gaussFilter numbers will render slower. GPUImage is a safe bet for performance. You don't need to make the into a UIImage to use. Check out all the GPUImage docs, hope this helps as a good starting point. – Andres Canella Oct 02 '15 at 17:03
  • You don't need to start with an image if you set 'orUseNoise' to YES. You just need to drop any image in there so it does not crash but the image will not be used as it will be covered completely by the noise. I'm swamped with a lot of work, just did not have the time to clean that case up:) – Andres Canella Oct 02 '15 at 17:22
  • Ah ok, *You just need to drop any image in there so it does not crash* got it now .. – Fattie Oct 02 '15 at 17:24
  • Alternately, you can capture what you have behind your view, input it as an image to this function with a tint which is how we use it here: https://dl.dropboxusercontent.com/u/22105205/IMG_2039.PNG – Andres Canella Oct 02 '15 at 17:39
  • I wish I could pay the bounty to everyone, but SO doesn't allow it! Andres answer has the most info and code and was the first to give some GPUImage code - so thanks!! – Fattie Oct 03 '15 at 18:05
  • Thanks Joe, glad to help out! I also have the same method for the blur using Quartz instead of GPUImage in case you want it. – Andres Canella Oct 03 '15 at 18:25
  • how was this given the bounty when it isn't from scratch (requires GPUImage), requires an input image, and is not IBDesignable? (all as per the question) – SomeGuy Oct 04 '15 at 14:55
1

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.

John Tracid
  • 3,836
  • 3
  • 22
  • 33
0

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

Eneko Alonso
  • 18,884
  • 9
  • 62
  • 84