Diffusion/Common/DiffusionImage.swift (95 lines of code) (raw):
//
// DiffusionImage.swift
// Diffusion
//
// Created by Dolmere on 03/07/2023.
//
import SwiftUI
import StableDiffusion
import CoreTransferable
/// Tracking for a `DiffusionImage` generation state.
enum DiffusionImageState {
case generating
case waiting
case complete
}
/// Generic custom error to use when an image generation fails.
enum DiffusionImageError: Error {
case invalidDiffusionImage
}
/// Combination of a `DiffusionImage` and its associated `DiffusionImageState`
struct DiffusionImageWrapper {
var diffusionImageState: DiffusionImageState = .waiting
var diffusionImage: DiffusionImage? = nil
}
/// Model class to hold a generated image and the "recipe" data that was used to generate it
final class DiffusionImage: NSObject, Identifiable, NSCoding, NSSecureCoding {
let id: UUID
let cgImage: CGImage
let seed: UInt32
let steps: Double
let positivePrompt: String
let negativePrompt: String
let guidanceScale: Double
let disableSafety: Bool
/// Local enum represented with a String to conform to NSSecureCoding
let scheduler: StableDiffusionScheduler
/// This is a composed `String` built from the numeric `Seed` and the user supplied `positivePrompt` limited to the first 200 character and with whitespace replaced with underscore characters.
var generatedFilename: String {
return "\(seed)-\(positivePrompt)".first200Safe
}
/// The location on the file system where this generated image is stored.
var fileURL: URL
init(id: UUID, cgImage: CGImage, seed: UInt32, steps: Double, positivePrompt: String, negativePrompt: String, guidanceScale: Double, disableSafety: Bool, scheduler: StableDiffusionScheduler) {
let genname = "\(seed)-\(positivePrompt)".first200Safe
self.id = id
self.cgImage = cgImage
self.seed = seed
self.steps = steps
self.positivePrompt = positivePrompt
self.negativePrompt = negativePrompt
self.guidanceScale = guidanceScale
self.disableSafety = disableSafety
self.scheduler = scheduler
// Initially set the fileURL to the top level applicationDirectory to allow running the completed instance func save() where the fileURL will be updated to the correct location.
self.fileURL = URL.applicationDirectory
// init the instance fully before executing an instance function
super.init()
if let url = save(cgImage: cgImage, filename: genname) {
self.fileURL = url
} else {
fatalError("Fatal error init of DiffusionImage, cannot create image file at \(genname)")
}
}
func encode(with coder: NSCoder) {
coder.encode(id, forKey: "id")
coder.encode(seed, forKey: "seed")
coder.encode(steps, forKey: "steps")
coder.encode(positivePrompt, forKey: "positivePrompt")
coder.encode(negativePrompt, forKey: "negativePrompt")
coder.encode(guidanceScale, forKey: "guidanceScale")
coder.encode(disableSafety, forKey: "disableSafety")
coder.encode(scheduler, forKey: "scheduler")
// Encode cgImage as data
if let data = pngRepresentation() {
coder.encode(data, forKey: "cgImage")
}
}
required init?(coder: NSCoder) {
guard let id = coder.decodeObject(forKey: "id") as? UUID else {
return nil
}
self.id = id
self.seed = UInt32(coder.decodeInt32(forKey: "seed"))
self.steps = coder.decodeDouble(forKey: "steps")
self.positivePrompt = coder.decodeObject(forKey: "positivePrompt") as? String ?? ""
self.negativePrompt = coder.decodeObject(forKey: "negativePrompt") as? String ?? ""
self.guidanceScale = coder.decodeDouble(forKey: "guidanceScale")
self.disableSafety = coder.decodeBool(forKey: "disableSafety")
self.scheduler = coder.decodeObject(forKey: "scheduler") as? StableDiffusionScheduler ?? StableDiffusionScheduler.dpmSolverMultistepScheduler
let genname = "\(seed)-\(positivePrompt)".first200Safe
// Decode cgImage from data
if let imageData = coder.decodeObject(forKey: "cgImage") as? Data {
guard let img = CGImage.fromData(imageData) else { fatalError("Fatal error loading data with missing cgImage in object") }
self.cgImage = img
} else {
fatalError("Fatal error loading data with missing cgImage in object")
}
self.fileURL = URL.applicationDirectory
super.init()
if let url = save(cgImage: cgImage, filename: genname) {
self.fileURL = url
} else {
fatalError("Fatal error init of DiffusionImage, cannot create image file at \(genname)")
}
}
// MARK: - Equatable
static func == (lhs: DiffusionImage, rhs: DiffusionImage) -> Bool {
return lhs.id == rhs.id
}
// MARK: - NSSecureCoding
static var supportsSecureCoding: Bool {
return true
}
}