Skip to content

Instantly share code, notes, and snippets.

@frzi
Last active August 19, 2025 10:45
Show Gist options
  • Select an option

  • Save frzi/f13203d42a47c3d167b27135d98d6098 to your computer and use it in GitHub Desktop.

Select an option

Save frzi/f13203d42a47c3d167b27135d98d6098 to your computer and use it in GitHub Desktop.
LQIP view for SwiftUI
/**
* MIT License
* Copyright (c) 2025 frzi (github.com/frzi)
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import SwiftUI
/// Low Quality Image Placeholder view.
///
/// Renders a blurry image based on a 32-bit unsigned integer. Intended to be used with something like `AsyncImage`.
/// ```swift
/// AsyncImage(url: url) { phase in
/// if let image = phase.image {
/// image
/// }
/// else {
/// ImagePlaceholder(value: 0x0104654c)
/// }
/// }
/// ```
public struct ImagePlaceholder: View {
private static func unpackColors(_ value: UInt32) -> (Color, Color, Color) {
func unpack11bit(_ val: UInt32) -> Color {
let r = Double(val >> 7) / Double(0b1111)
let g = Double((val >> 3) & 0b1111) / Double(0b1111)
let b = Double(val & 0b111) / Double(0b111)
return Color(.sRGB, red: r, green: g, blue: b)
}
func unpack10bit(_ val: UInt32) -> Color {
let r = Double(val >> 7) / Double(0b111)
let g = Double((val >> 3) & 0b1111) / Double(0b1111)
let b = Double(val & 0b111) / Double(0b111)
return Color(.sRGB, red: r, green: g, blue: b)
}
let color0 = unpack11bit(value >> 21)
let color1 = unpack11bit((value >> 10) & 0b11111111111)
let color2 = unpack10bit(value & 0b1111111111)
return (color0, color1, color2)
}
private let colors: (Color, Color, Color)
/// Initialize the `ImagePlaceholder` with an UInt32 value.
///
/// ```swift
/// ImagePlaceholder(value: 0x41370307)
/// ```
public init(value: UInt32) {
self.colors = Self.unpackColors(value)
}
/// Initialize the `ImagePlaceholder` with a hex string.
///
/// ```swift
/// ImagePlaceholder(hex: "#41370307")
/// ```
/// - Note:A leading # in the hex string is optional.
public init(hex: String) {
let hex = hex.starts(with: "#") ? String(hex.dropFirst()) : hex
if let uint = UInt32(hex, radix: 16) {
self.colors = Self.unpackColors(uint)
}
else {
self.colors = (.clear, .clear, .clear)
}
}
private func easedGradientStops(_ color: Color) -> [Gradient.Stop] {
let curve = UnitCurve.bezier(
startControlPoint: UnitPoint(x: 0.4, y: 0),
endControlPoint: UnitPoint(x: 0.6, y: 1)
)
return stride(from: 0.0, through: 1.0, by: 0.1)
.map { pos in
Gradient.Stop(color: color.opacity(1 - curve.value(at: pos)), location: pos)
}
}
private func render(context: inout GraphicsContext, size: CGSize) {
context
.fill(
Path(CGRect(x: 0, y: 0, width: size.width, height: size.height)),
with: .color(colors.0)
)
context
.fill(
Path(CGRect(x: 0, y: 0, width: size.width, height: size.height)),
with: .radialGradient(
Gradient(stops: easedGradientStops(colors.1)),
center: CGPoint(x: size.width * 0.5, y: size.height * 0.5),
startRadius: 0,
endRadius: max(size.height, size.width) * 0.9,
options: [.linearColor]
)
)
context
.fill(
Path(CGRect(x: 0, y: 0, width: size.width, height: size.height)),
with: .radialGradient(
Gradient(stops: easedGradientStops(colors.2)),
center: CGPoint(x: size.width * 0.9, y: size.height),
startRadius: 5,
endRadius: max(size.height, size.width) * 0.9,
options: [.linearColor]
)
)
}
public var body: some View {
Canvas(renderer: render)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment