I am trying to mimic a vintage film grain effect as done by apple here. the problem lies in overlaying the grain image, when I composite it and crop it to the size of the original image, I am getting a bunch of weird patterns - this is consistent across multiple images and sizes, I have applied the grain image to a plain image to help show the effect.
As you can see, there is a consistent vertical and horizontal pattern, almost like a grid. Why is this happening and how can I prevent it from happening?
Grain generator function:
private func applyRandomGrain() {
/// generate grain & convert to grayscale
let grainGenerator = CIFilter.randomGenerator()
let grayscale = CIFilter.minimumComponent()
grayscale.inputImage = grainGenerator.outputImage
/// adjust brightness of grain image
let exposureFilter = CIFilter.exposureAdjust()
exposureFilter.inputImage = grayscale.outputImage
exposureFilter.ev = Float(settings.grainExposure)
/// adjust intensity of grain image with opacity
let alphaVector = CIVector(x: 0, y: 0, z: 0, w: settings.grainOpacity)
let alphaFilter = CIFilter.colorMatrix()
alphaFilter.inputImage = exposureFilter.outputImage
alphaFilter.aVector = alphaVector
guard let inputImage = outputImage else {
return
}
/// apply grain image to original image
let composite = CIFilter.sourceOverCompositing()
composite.backgroundImage = inputImage
composite.inputImage = alphaFilter.outputImage
guard let imageWithGrain = composite.outputImage else {
return
}
outputImage = imageWithGrain.cropped(to: inputImage)
}
EDIT: The example image is 1920x2560 - I have observed that the larger the image the more of a pattern there is. At a 100x100 image, there is no pattern.
The pattern generated by CIRandomGenerator
is repeating. From the sample site you linked:
The image output from
CIRandomGenerator
is always the same; even if you reseed your random number generator, the image output from this filter is always the same 512x512 pattern. However, it is suitable for giving the appearance of randomness.
This means every 512 pixels the pattern repeats. That's why you don't see the effect as strongly for smaller images.
If you want "true", non-repeating randomness, you have to implement your own filter kernel. For instance, one that implements Perlin noise.
Alternatively, if you don't mind using an internal filter from Apple (and that's a big if), you can use the CIPhotoGrain
filter that does exactly that: simulate a photo grain effect caused by high ISO values.
let filter = CIFilter(name: "CIPhotoGrain")!
filter.setValue(image, forKey: kCIInputImageKey)
filter.setValue(42, forKey: "inputSeed") // random seed
filter.setValue(1.0, forKey: "inputAmount")
filter.setValue(2000, forKey: "inputISO")
let output = filter.outputImage