Skip to content

Instantly share code, notes, and snippets.

@dejager
Created October 21, 2025 17:49
Show Gist options
  • Select an option

  • Save dejager/e27aa55b2276ff77f60b33042a6ecc2b to your computer and use it in GitHub Desktop.

Select an option

Save dejager/e27aa55b2276ff77f60b33042a6ecc2b to your computer and use it in GitHub Desktop.
import SwiftUI
import CoreImage
import CoreImage.CIFilterBuiltins
struct MirrorBlurGradientDemo: View {
@State private var overlayColor = Color(red: 0.483, green: 0.137, blue: 0.149)
@State private var blurRadius: CGFloat = 60
@State private var blurFadeFraction: CGFloat = 0.77
@State private var bottomBleed: CGFloat = 118
private let ciContext = CIContext(options: [.workingColorSpace: NSNull()])
var body: some View {
VStack(spacing: 16) {
Spacer()
}
.safeAreaInset(edge: .bottom) {
VStack(alignment: .leading, spacing: 10) {
Text("Blur radius: \(Int(blurRadius))")
Slider(value: $blurRadius, in: 0...60)
Text("Blur fade fraction: \(String(format: "%.2f", blurFadeFraction))")
Slider(value: $blurFadeFraction, in: 0.2...0.9)
Text("Bottom bleed: \(Int(bottomBleed))")
Slider(value: $bottomBleed, in: 0...200)
ColorPicker("Overlay color", selection: $overlayColor)
}
.safeAreaPadding()
}
.background(alignment: .top) {
if let ui = UIImage(named: "image"),
let out = renderPipeline(input: ui,
overlayColor: UIColor(overlayColor),
blurRadius: blurRadius,
blurFadeFraction: blurFadeFraction,
bottomBleed: bottomBleed) {
Image(uiImage: out)
.resizable()
.scaledToFill()
.ignoresSafeArea()
.containerRelativeFrame(.vertical, alignment: .top) { length, _ in
length * 0.66
}
} else {
Text("🤷‍♂️")
.padding()
}
}
.frame(maxHeight: .infinity)
.navigationTitle("Core Image Pipeline Mirror + Gradient + Blur")
}
// MARK: - Core Image Pipeline
private func renderPipeline(input: UIImage,
overlayColor: UIColor,
blurRadius: CGFloat,
blurFadeFraction: CGFloat,
bottomBleed: CGFloat) -> UIImage? {
guard var base = CIImage(image: input) else { return nil }
base = base.oriented(.up)
let w = base.extent.width
let h = base.extent.height
let H = h * 2 + bottomBleed
let outputExtent = CGRect(x: 0, y: 0, width: w, height: H)
let reflectionExtent = CGRect(x: 0, y: bottomBleed, width: w, height: h)
let flipped = base
.transformed(by: CGAffineTransform(scaleX: 1, y: -1))
.transformed(by: CGAffineTransform(translationX: 0, y: h + bottomBleed))
let top = base
.transformed(by: CGAffineTransform(translationX: 0, y: h + bottomBleed))
let clear = CIImage(color: .clear).cropped(to: outputExtent)
let stack = top.composited(over: flipped).composited(over: clear).cropped(to: outputExtent)
let gradient = CIFilter.linearGradient()
gradient.point0 = CGPoint(x: w / 2, y: reflectionExtent.minY)
gradient.point1 = CGPoint(x: w / 2, y: reflectionExtent.maxY)
gradient.color0 = CIColor(color: overlayColor)
gradient.color1 = CIColor(red: 0, green: 0, blue: 0, alpha: 0)
let gradientOverlay = gradient.outputImage!.cropped(to: reflectionExtent)
let withGradient = gradientOverlay.composited(over: stack)
let clamped = withGradient.clampedToExtent()
let fadeHeight = max(h * 2 * blurFadeFraction, 1)
let maskGradient = CIFilter.linearGradient()
maskGradient.point0 = CGPoint(x: w / 2, y: 0)
maskGradient.point1 = CGPoint(x: w / 2, y: fadeHeight)
maskGradient.color0 = CIColor(red: 1, green: 1, blue: 1, alpha: 1)
maskGradient.color1 = CIColor(red: 0, green: 0, blue: 0, alpha: 1)
guard let mask = maskGradient.outputImage?.cropped(to: outputExtent) else { return nil }
guard let blur = CIFilter(name: "CIMaskedVariableBlur") else { return nil }
blur.setValue(clamped, forKey: kCIInputImageKey)
blur.setValue(mask, forKey: "inputMask")
blur.setValue(blurRadius, forKey: kCIInputRadiusKey)
guard let blurred = blur.outputImage?.cropped(to: outputExtent) else { return nil }
guard let cg = ciContext.createCGImage(blurred, from: outputExtent) else { return nil }
return UIImage(cgImage: cg, scale: input.scale, orientation: .up)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment