Tutorials › Launch Lab Notes › Chapter 24

Four Stages, One Still

LabChapter 24 of the Launch Lab Notes20 minAugust 20, 2026Intermediate

A filter chain is easy to hide inside one UIImage. This bench refuses to hide it.

One Harbor still. Four columns. ORIGINAL is the control — no CIFilter. Everything to its right takes the previous CIImage as input. Sepia intensity is the live knob.

Filmstrip: ORIGINAL stays unfiltered while NOIR → SEPIA → BLOOM update as sepia intensity cycles.

Rest — sepia 0.15

-mode rest

SEPIA 0.15 pinned. Strip ORIGINAL control, NOIR photoEffectNoir, SEPIA intensity 0.15, BLOOM on sepia out. PIPE source → noir → sepia → bloom.

ORIGINAL keeps the dusk gradient and yellow beacon. NOIR is grayscale. SEPIA at 0.15 is only a hint of warmth on that grayscale. BLOOM softens the highlights of that sepia output.

Mid — sepia 0.55

-mode mid

SEPIA 0.55 pinned. ORIGINAL unchanged color; NOIR gray; SEPIA warmer brown; BLOOM glows on sepia.

ORIGINAL and NOIR do not move — they do not read the sepia knob. SEPIA and BLOOM both warm because bloom is fed by sepia’s output, not by the original.

Hot — sepia 1.00

-mode hot

SEPIA 1.00 pinned. SEPIA full brown; BLOOM halo on HARBOR text and beacon. ORIGINAL still full color.
StageFilterObserved
ORIGINALnone (control)full-color Harbor still
NOIRphotoEffectNoirgrayscale
SEPIAsepiaTone @ 1.00full brown on noir
BLOOMbloom on sepia outglow on text + beacon
noir.inputImage = original
sepia.inputImage = noir.outputImage
sepia.intensity = Float(sepiaAmount)
bloom.inputImage = sepia.outputImage
Order is the product

Sepia-after-noir is not noir-after-sepia. The strip makes the order a picture: if ORIGINAL ever gained a tint when the knob moved, the control would be lying.

Mini-exercise

Swap the chain so sepia runs on the original and noir runs after. Capture -mode hot and compare the SEPIA column to today’s brown-on-gray.

Challenges

  1. Add a fifth stage: CIColorControls contrast on bloom output.
  2. Show a parallel strip that applies each filter to ORIGINAL independently (not cumulative) and label the difference.
  3. Drive sepia from a TimelineView instead of DispatchQueue asyncAfter.
  4. Export the BLOOM stage to the photo library via CIContext.writePNGRepresentation.
  5. Unit-test that ORIGINAL’s rendered pixels match the source when sepia is 1.0.

Key Points

Next up: Metal shaders in SwiftUI — colorEffect / distortionEffect / layerEffect on the same view, one uniform.

SwiftUI
SwiftUI tutorials for building native app screens, layouts, navigation, and state-driven interfaces.
Swift
Swift fundamentals for app developers who want to understand the language behind real iOS and macOS apps.
Ship iOS
Shipping workflows for iOS apps.
📚 Go deeper with LIPAI WANG’s hands-on Udemy bootcampsBrowse all courses →
← Ch 23: Full Res vs ThumbnailsCh 25: One Uniform, Three Effects→
SwiftUIUltimate SwiftUI SeriesSwiftUI tutorials for building native app screens, layouts, navigation, and state-driven interfaces.SwiftUltimate Swift SeriesSwift fundamentals for app developers who want to understand the language behind real iOS and macOS apps.Ship iOSShip iOS Apps SeriesShipping workflows for iOS apps: signing, TestFlight, App Store Connect, CI, and release hygiene.

Ship your apps faster

When you're ready to publish your Swift app to the App Store, Simple App Shipper handles metadata, screenshots, TestFlight, and submissions — all in one place.

Try Simple App Shipper
5 free articles remainingSubscribe for unlimited access