Skip to content

Instantly share code, notes, and snippets.

@Matt54
Created August 16, 2024 04:47
Show Gist options
  • Save Matt54/a9f9978719c0ac40af721f36087439b3 to your computer and use it in GitHub Desktop.
Save Matt54/a9f9978719c0ac40af721f36087439b3 to your computer and use it in GitHub Desktop.
Feathering effect applied to an image on a Window and also a RealityView
import SwiftUI
import RealityKit
#Preview("Window View") {
ImagePlaneView()
}
#Preview("RealityView") {
ImagePlaneRealityView()
}
struct ImagePlaneView: View {
@State var featheredImage: UIImage?
var body: some View {
ZStack {
if let featheredImage {
Image(uiImage: featheredImage)
.resizable()
.aspectRatio(contentMode: .fit)
}
}
.background(.thinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 50))
.task {
let cgImage = try! await downloadImageAndConvertToCGImage()
let featheredCGImage = applyFeatheringEffect(to: cgImage)
featheredImage = UIImage(cgImage: featheredCGImage)
}
}
}
struct ImagePlaneRealityView: View {
var body: some View {
RealityView { content in
// create image plane
let cgImage = try! await downloadImageAndConvertToCGImage()
let featheredImage = applyFeatheringEffect(to: cgImage)
let texture = try! await TextureResource(image: featheredImage, options: .init(semantic: nil))
var imageMaterial = UnlitMaterial()
imageMaterial.color.texture = .init(.init(texture))
imageMaterial.blending = .transparent(opacity: 1.0) // otherwise we are opaque
imageMaterial.faceCulling = .none
let meshResource = MeshResource.generatePlane(width: 0.3, height: 0.3)
let imageEntity = ModelEntity(mesh: meshResource, materials: [imageMaterial])
content.add(imageEntity)
// make a background plane
let backgroundMesh = MeshResource.generatePlane(width: 0.3, height: 0.3, cornerRadius: 0.03)
var backgroundMaterial = UnlitMaterial(color: .init(white: 0.5, alpha: 1.0))
backgroundMaterial.blending = .transparent(opacity: 0.5)
backgroundMaterial.faceCulling = .none
let backgroundPlane = ModelEntity(mesh: backgroundMesh, materials: [backgroundMaterial])
content.add(backgroundPlane)
// make sure the image is in front
let sortGroup = ModelSortGroup(depthPass: .postPass)
imageEntity.components.set(ModelSortGroupComponent(group: sortGroup, order: 1))
backgroundPlane.components.set(ModelSortGroupComponent(group: sortGroup, order: 0))
}
}
}
func downloadImageAndConvertToCGImage(from url: URL = URL(string: "https://picsum.photos/800")!) async throws -> CGImage {
let (data, _) = try await URLSession.shared.data(from: url)
let image = UIImage(data: data)!
let cgImage = image.cgImage!
return cgImage
}
func applyFeatheringEffect(to image: CGImage) -> CGImage {
let width = image.width
let height = image.height
let featherSize = min(width, height) / 8 // Increased feather size for smoother transition
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
guard let context = CGContext(data: nil,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: 0,
space: colorSpace,
bitmapInfo: bitmapInfo) else {
return image
}
// Draw the original image
context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
// Create gradients for fading
let edgeGradient = CGGradient(colorsSpace: colorSpace,
colors: [UIColor.white.withAlphaComponent(0).cgColor,
UIColor.white.cgColor] as CFArray,
locations: [0, 1])!
let cornerGradient = CGGradient(colorsSpace: colorSpace,
colors: [UIColor.white.withAlphaComponent(0).cgColor,
UIColor.white.withAlphaComponent(0.5).cgColor,
UIColor.white.cgColor] as CFArray,
locations: [0, 0.5, 1])!
// Apply the gradients
context.saveGState()
context.setBlendMode(.destinationIn)
// Corner gradients (extended to overlap with edge gradients)
let cornerRadius = sqrt(2) * CGFloat(featherSize)
// Top-left corner
context.drawRadialGradient(cornerGradient,
startCenter: CGPoint(x: 0, y: 0),
startRadius: 0,
endCenter: CGPoint(x: 0, y: 0),
endRadius: cornerRadius,
options: [])
// Top-right corner
context.drawRadialGradient(cornerGradient,
startCenter: CGPoint(x: width, y: 0),
startRadius: 0,
endCenter: CGPoint(x: width, y: 0),
endRadius: cornerRadius,
options: [])
// Bottom-left corner
context.drawRadialGradient(cornerGradient,
startCenter: CGPoint(x: 0, y: height),
startRadius: 0,
endCenter: CGPoint(x: 0, y: height),
endRadius: cornerRadius,
options: [])
// Bottom-right corner
context.drawRadialGradient(cornerGradient,
startCenter: CGPoint(x: width, y: height),
startRadius: 0,
endCenter: CGPoint(x: width, y: height),
endRadius: cornerRadius,
options: [])
// Edge gradients
// Left edge
context.drawLinearGradient(edgeGradient,
start: CGPoint(x: 0, y: 0),
end: CGPoint(x: CGFloat(featherSize), y: 0),
options: [])
// Right edge
context.drawLinearGradient(edgeGradient,
start: CGPoint(x: width, y: 0),
end: CGPoint(x: width - featherSize, y: 0),
options: [])
// Top edge
context.drawLinearGradient(edgeGradient,
start: CGPoint(x: 0, y: 0),
end: CGPoint(x: 0, y: featherSize),
options: [])
// Bottom edge
context.drawLinearGradient(edgeGradient,
start: CGPoint(x: 0, y: height),
end: CGPoint(x: 0, y: height - featherSize),
options: [])
context.restoreGState()
guard let featheredImage = context.makeImage() else {
return image
}
return featheredImage
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment