Simulating Scratchy Analog Film

Degrade the quality of an image to make it look like dated, scratchy analog film.


The CISepiaTone 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.

Figure 1

Combine filtered white noise with dark scratches to the CISepiaTone filter to create an old analog film effect

Compositing scratchy analog film by compositing results from CIFilter objects

The following steps leverage built-in Core Image filters to tint and texture an image to look as if it were analog film:

  1. Apply the CISepiaTone filter.

  2. Create randomly varying white specks to simulate grain.

  3. Create randomly varying dark scratches to simulate scratchy film.

  4. 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 CISepiaTone filter.

CIFilter* sepiaFilter = [CIFilter filterWithName:@"CISepiaTone"];
[sepiaFilter setValue:inputImage forKey:kCIInputImageKey];
[sepiaFilter setValue:@(1.0) forKey:kCIInputIntensityKey];
CIImage* sepiaCIImage = sepiaFilter.outputImage;

The output of this filter will become the background for compositing the grain image.

Simulate Grain by Creating Randomly Varying Speckle

You can use the output of the CIRandomGenerator filter as a basis for random noise images. Even though the noise pattern is not customizable in size, it can be extended and cropped to fit the image.

The filter takes no inputs.

CIFilter* coloredNoise = [CIFilter filterWithName:@"CIRandomGenerator"];
CIImage* noiseImage = coloredNoise.outputImage;

Next, apply a whitening effect by chaining the noise output to a CIColorMatrix 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.

CIFilter* whiteningFilter = [CIFilter filterWithName:@"CIColorMatrix"];
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];
[whiteningFilter setValue:noiseImage forKey:kCIInputImageKey];
[whiteningFilter setValue:whitenVector forKey:@"inputRVector"];
[whiteningFilter setValue:whitenVector forKey:@"inputGVector"];
[whiteningFilter setValue:whitenVector forKey:@"inputBVector"];
[whiteningFilter setValue:fineGrain forKey:@"inputAVector"];
[whiteningFilter setValue:zeroVector forKey:@"inputBiasVector"];
CIImage* whiteSpecks = whiteningFilter.outputImage;

The whiteSpecks resulting from this filter have the appearance of spotty grain when viewed as an image.

Figure 2

White speckle grain created from a whitening CIColorMatrix applied to CIRandomGenerator noise

Image of white dots on a transaprent background, used to simulate grain on an old photo

Create the grainy image by compositing the whitened noise as input over the sepia-toned source image using the CISourceOverCompositing filter.

CIFilter* speckCompositor = [CIFilter filterWithName:@"CISourceOverCompositing"];
[speckCompositor setValue:whiteSpecks forKey:kCIInputImageKey];
[speckCompositor setValue:sepiaCIImage forKey:kCIInputBackgroundImageKey];
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 CIRandomGenerator 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* darkeningFilter = [CIFilter filterWithName:@"CIColorMatrix"];
CIVector* darkenVector = [CIVector vectorWithX:4 Y:0 Z:0 W:0];
CIVector* darkenBias = [CIVector vectorWithX:0 Y:1 Z:1 W:1];
[darkeningFilter setValue:transformedNoise forKey:kCIInputImageKey];
[darkeningFilter setValue:darkenVector forKey:@"inputRVector"];
[darkeningFilter setValue:zeroVector forKey:@"inputGVector"];
[darkeningFilter setValue:zeroVector forKey:@"inputBVector"];
[darkeningFilter setValue:zeroVector forKey:@"inputAVector"];
[darkeningFilter setValue:darkenBias forKey:@"inputBiasVector"];
CIImage* randomScratches = darkeningFilter.outputImage;

The resulting scratches are cyan-colored, so grayscale them using the CIMinimumComponentFilter, which takes the minimum of the RGB values to produce a grayscale image.

CIFilter* grayscaleFilter = [CIFilter filterWithName:@"CIMinimumComponent"];
[grayscaleFilter setValue:randomScratches forKey:kCIInputImageKey];
CIImage* darkScratches = grayscaleFilter.outputImage;

The grayscale filter produces random lines that resemble dark scratches.

Figure 3

Dark scratches created from a darkening CIColorMatrix applied to CIRandomGenerator noise

Image of black lines on a white background, used to simulate scratches on an old photo

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 CISourceOverCompositing filter, which composites source over background, use the CIMultiplyCompositing 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* oldFilmCompositor = [CIFilter filterWithName:@"CIMultiplyCompositing"];
[oldFilmCompositor setValue:darkScratches forKey:kCIInputImageKey];
[oldFilmCompositor setValue:speckledImage forKey:kCIInputBackgroundImageKey];
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.

Figure 4

Final result of CIMultiplyCompositing the source image with grain and scratches.

Sepia-toned image of fruit augmented with speckle grain and dark scratches.

See Also

Filter Recipes

Applying a Chroma Key Effect

Replace a color in one image with the background from another.

Selectively Focusing on an Image

Focus on a part of an image by applying Gaussian blur and gradient masks.

Customizing Image Transitions

Transition between images in creative ways using Core Image filters.