Simulating Scratchy Analog Film
Degrade the quality of an image to make it look like dated, analog film.
Overview
The sepiaTone() filter changes the tint of an image to a reddish-brownish hue resembling old analog photographs. You can enhance the effect by applying random specks and scratches.
[Image]
The following steps leverage built-in Core Image filters to tint and texture an image to look as if it were analog film:
Apply the sepiaTone() filter.
Create randomly varying white specks to simulate grain.
Create randomly varying dark scratches to simulate scratchy film.
Composite the speckle image and scratches onto the sepia-toned image.
Apply the Sepia Tone Filter to the Original Image
Tint the original image by applying the sepiaTone() filter.
CIFilter<CISepiaTone> *sepiaFilter = CIFilter.sepiaToneFilter;
sepiaFilter.inputImage = inputImage;
sepiaFilter.intensity = 1.0;
CIImage *sepiaCIImage = sepiaFilter.outputImage;Simulate Grain by Creating Randomly Varying Specks
You can use the output of the randomGenerator() filter to generate images containing random noise. Even though the noise pattern isn’t customizable in size, you can extend and crop it to fit the image.
The filter takes no inputs.
CIFilter<CIRandomGenerator> *colorNoise = CIFilter.randomGeneratorFilter;
CIImage* noiseImage = colorNoise.outputImage;Next, apply a whitening effect by chaining the noise output to a colorMatrix() filter. This built-in filter multiplies the noise color values individually and applies a bias to each component. For white grain, apply whitening to the y-component of RGB and no bias.
CIVector *whitenVector = [CIVector vectorWithX:0 Y:1 Z:0 W:0];
CIVector *fineGrain = [CIVector vectorWithX:0 Y:0.005 Z:0 W:0];
CIVector *zeroVector = [CIVector vectorWithX:0 Y:0 Z:0 W: 0];
CIFilter<CIColorMatrix> *whiteningFilter = CIFilter.colorMatrixFilter;
whiteningFilter.inputImage = noiseImage;
whiteningFilter.RVector = whitenVector;
whiteningFilter.RVector = whitenVector;
whiteningFilter.BVector = whitenVector;
whiteningFilter.AVector = fineGrain;
whiteningFilter.biasVector = zeroVector;
CIImage *whiteSpecks = whiteningFilter.outputImage;The whiteSpecks resulting from this filter have the appearance of spotty grain when viewed as an image.
[Image]
Create the grainy image by compositing the whitened noise as input over the sepia-toned source image using the sourceOverCompositing() filter.
CIFilter<CICompositeOperation> *speckCompositor = CIFilter.sourceOverCompositingFilter;
speckCompositor.inputImage = whiteSpecks;
speckCompositor.backgroundImage = sepiaCIImage;
CIImage *speckledImage = speckCompositor.outputImage;Simulate Scratch by Scaling Randomly Varying Noise
The process for applying random-looking scratches is the same as the technique used in the white grain: color the output of the randomGenerator() filter.
To make the speckle resemble scratches, scale the random noise output vertically by applying a scaling CGAffineTransform.
CGAffineTransform verticalScale = CGAffineTransformMakeScale(1.5, 25);
CIImage* transformedNoise = [noiseImage imageByApplyingTransform:verticalScale];Previously, you whitened the speckle image by applying the CIColorMatrix filter evenly across all color components. For the dark scratches, instead focus on only the red component, setting the other vector inputs to zero. This time, instead of multiplying the green, blue, and alpha channels, add bias (0, 1, 1, 1).
CIFilter<CIColorMatrix>* darkeningFilter = CIFilter.colorMatrixFilter;
CIVector* darkenVector = [CIVector vectorWithX:4 Y:0 Z:0 W:0];
CIVector* darkenBias = [CIVector vectorWithX:0 Y:1 Z:1 W:1];
darkeningFilter.inputImage = noiseImage;
darkeningFilter.RVector = darkenVector;
darkeningFilter.GVector = zeroVector;
darkeningFilter.BVector = zeroVector;
darkeningFilter.AVector = zeroVector;
darkeningFilter.biasVector = darkenBias;
CIImage* randomScratches = darkeningFilter.outputImage;The resulting scratches are cyan, so grayscale them using the minimumComponent() filter, which takes the minimum of the RGB values to produce a grayscale image.
CIFilter<CIMinimumComponent> *grayscaleFilter = CIFilter.minimumComponentFilter;
grayscaleFilter.inputImage = randomScratches;
CIImage *darkScratches = grayscaleFilter.outputImage;The grayscale filter produces random lines that resemble dark scratches.
[Image]
Composite the Specks and Scratches to the Sepia Image
Now that the components are set, you can add the scratches to the grainy sepia image produced earlier. However, unlike the grainy texture, the scratches impact the image multiplicatively. Instead of the sourceOverCompositing() filter, which composites source over background, use the multiplyCompositing() filter to compose the scratches multiplicatively. Set the scratched image as the filter’s input image, and tab the speckle-composited sepia image as the input background image.
CIFilter<CICompositeOperation> *oldFilmCompositor = CIFilter.multiplyCompositingFilter;
oldFilmCompositor.inputImage = darkScratches;
oldFilmCompositor.backgroundImage = speckledImage;
CIImage *oldFilmImage = oldFilmCompositor.outputImage;Since the noise images had different dimensions than the source image, crop the composited result to the original image size to remove excess beyond the original extent.
CIImage* finalImage = [oldFilmImage imageByCroppingToRect:inputImage.extent];The cropped image represents the final result: a sepia-toned image with simulated grain and scratches composited to give it an analog film appearance.
[Image]