diff --git a/.gitignore b/.gitignore index e43b0f9..e9f3e7b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +xcuserdata/ diff --git a/Diffusion-macOS/ContentView.swift b/Diffusion-macOS/ContentView.swift new file mode 100644 index 0000000..004aacf --- /dev/null +++ b/Diffusion-macOS/ContentView.swift @@ -0,0 +1,100 @@ +// +// ContentView.swift +// Diffusion-macOS +// +// Created by Cyril Zakka on 1/12/23. +// See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE +// + +import SwiftUI +import ImageIO + + +// AppKit version that uses NSImage, NSSavePanel +struct ShareButtons: View { + var image: CGImage + var name: String + + var filename: String { + name.replacingOccurrences(of: " ", with: "_") + } + + func showSavePanel() -> URL? { + let savePanel = NSSavePanel() + savePanel.allowedContentTypes = [.png] + savePanel.canCreateDirectories = true + savePanel.isExtensionHidden = false + savePanel.title = "Save your image" + savePanel.message = "Choose a folder and a name to store the image." + savePanel.nameFieldLabel = "File name:" + savePanel.nameFieldStringValue = filename + + let response = savePanel.runModal() + return response == .OK ? savePanel.url : nil + } + + func savePNG(cgImage: CGImage, path: URL) { + let image = NSImage(cgImage: cgImage, size: .zero) + let imageRepresentation = NSBitmapImageRep(data: image.tiffRepresentation!) + guard let pngData = imageRepresentation?.representation(using: .png, properties: [:]) else { + print("Error generating PNG data") + return + } + do { + try pngData.write(to: path) + } catch { + print("Error saving: \(error)") + } + } + + var body: some View { + let imageView = Image(image, scale: 1, label: Text(name)) + HStack { + ShareLink(item: imageView, preview: SharePreview(name, image: imageView)) + Button() { + if let url = showSavePanel() { + savePNG(cgImage: image, path: url) + } + } label: { + Label("Save…", systemImage: "square.and.arrow.down") + } + } + } +} + +struct ContentView: View { + @StateObject var generation = GenerationContext() + + func toolbar() -> any View { + if case .complete(let prompt, let cgImage, _) = generation.state, let cgImage = cgImage { + return ShareButtons(image: cgImage, name: prompt) + } else { + let prompt = DEFAULT_PROMPT + let cgImage = NSImage(imageLiteralResourceName: "placeholder").cgImage(forProposedRect: nil, context: nil, hints: nil)! + return ShareButtons(image: cgImage, name: prompt) + } + } + + var body: some View { + NavigationSplitView { + ControlsView() + .navigationSplitViewColumnWidth(min: 250, ideal: 300) + } detail: { + GeneratedImageView() + .aspectRatio(contentMode: .fit) + .frame(width: 512, height: 512) + .cornerRadius(15) + .toolbar { + AnyView(toolbar()) + } + + } + .environmentObject(generation) + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/Diffusion-macOS/ControlsView.swift b/Diffusion-macOS/ControlsView.swift new file mode 100644 index 0000000..8a48ba6 --- /dev/null +++ b/Diffusion-macOS/ControlsView.swift @@ -0,0 +1,151 @@ +// +// PromptView.swift +// Diffusion-macOS +// +// Created by Cyril Zakka on 1/12/23. +// See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE +// + +import Combine +import SwiftUI +import CompactSlider + +enum PipelineState { + case downloading(Double) + case uncompressing + case loading + case ready + case failed(Error) +} + +struct ControlsView: View { + @EnvironmentObject var generation: GenerationContext + + static let models = ModelInfo.MODELS + static let modelNames = models.map { $0.modelVersion } + + @State private var model = Settings.shared.currentModel.modelVersion + @State private var disclosedPrompt = true + + // TODO: refactor download with similar code in Loading.swift (iOS) + @State private var stateSubscriber: Cancellable? + @State private var pipelineState: PipelineState = .downloading(0) + + func modelDidChange(model: ModelInfo) { + print("Loading model \(model)") + Settings.shared.currentModel = model + + pipelineState = .downloading(0) + Task.init { + let loader = PipelineLoader(model: model) + stateSubscriber = loader.statePublisher.sink { state in + DispatchQueue.main.async { + switch state { + case .downloading(let progress): + pipelineState = .downloading(progress) + case .uncompressing: + pipelineState = .uncompressing + case .readyOnDisk: + pipelineState = .loading + default: + break + } + } + } + do { + generation.pipeline = try await loader.prepare() + pipelineState = .ready + } catch { + print("Could not load model, error: \(error)") + pipelineState = .failed(error) + } + } + } + + var body: some View { + VStack(alignment: .leading) { + + Label("Adjustments", systemImage: "gearshape.2") + .font(.headline) + .fontWeight(.bold) + Divider() + + ScrollView { + Group { + DisclosureGroup { + Picker("", selection: $model) { + ForEach(Self.modelNames, id: \.self) { + Text($0) + } + } + .onChange(of: model) { theModel in + guard let model = ModelInfo.from(modelVersion: theModel) else { return } + modelDidChange(model: model) + } + } label: { + Label("Model", systemImage: "cpu").foregroundColor(.secondary) + } + + Divider() + + DisclosureGroup(isExpanded: $disclosedPrompt) { + Group { + TextField("Positive prompt", text: $generation.positivePrompt, + axis: .vertical).lineLimit(5) + .textFieldStyle(.squareBorder) + .listRowInsets(EdgeInsets(top: 0, leading: -20, bottom: 0, trailing: 20)) + TextField("Negative prompt", text: $generation.negativePrompt, + axis: .vertical).lineLimit(5) + .textFieldStyle(.squareBorder) + }.padding(.leading, 10) + } label: { + Label("Prompts", systemImage: "text.quote").foregroundColor(.secondary) + } + + Divider() + + DisclosureGroup { + CompactSlider(value: $generation.steps, in: 0...150, step: 5) { + Text("Steps") + Spacer() + Text("\(Int(generation.steps))") + }.padding(.leading, 10) + } label: { + Label("Step count", systemImage: "square.3.layers.3d.down.left").foregroundColor(.secondary) + } + Divider() + +// DisclosureGroup() { +// CompactSlider(value: $generation.numImages, in: 0...10, step: 1) { +// Text("Number of Images") +// Spacer() +// Text("\(Int(generation.numImages))") +// }.padding(.leading, 10) +// } label: { +// Label("Number of images", systemImage: "photo.stack").foregroundColor(.secondary) +// } +// Divider() + + DisclosureGroup() { + let sliderLabel = generation.seed < 0 ? "Random Seed" : "Seed" + CompactSlider(value: $generation.seed, in: -1...1000, step: 1) { + Text(sliderLabel) + Spacer() + Text("\(Int(generation.seed))") + }.padding(.leading, 10) + } label: { + Label("Seed", systemImage: "leaf").foregroundColor(.secondary) + } + } + } + + StatusView(pipelineState: $pipelineState) + } + .padding() + .onAppear { + print(PipelineLoader.models) + modelDidChange(model: ModelInfo.from(modelVersion: model) ?? ModelInfo.v2Base) + } + } +} + diff --git a/Diffusion-macOS/Diffusion_macOS.entitlements b/Diffusion-macOS/Diffusion_macOS.entitlements new file mode 100644 index 0000000..a046386 --- /dev/null +++ b/Diffusion-macOS/Diffusion_macOS.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + + diff --git a/Diffusion-macOS/Diffusion_macOSApp.swift b/Diffusion-macOS/Diffusion_macOSApp.swift new file mode 100644 index 0000000..623c1c4 --- /dev/null +++ b/Diffusion-macOS/Diffusion_macOSApp.swift @@ -0,0 +1,20 @@ +// +// Diffusion_macOSApp.swift +// Diffusion-macOS +// +// Created by Cyril Zakka on 1/12/23. +// See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE +// + +import SwiftUI + +@main +struct Diffusion_macOSApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} + +let runningOnMac = true diff --git a/Diffusion-macOS/GeneratedImageView.swift b/Diffusion-macOS/GeneratedImageView.swift new file mode 100644 index 0000000..bea93d8 --- /dev/null +++ b/Diffusion-macOS/GeneratedImageView.swift @@ -0,0 +1,37 @@ +// +// GeneratedImageView.swift +// Diffusion +// +// Created by Pedro Cuenca on 18/1/23. +// See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE +// + +import SwiftUI + +struct GeneratedImageView: View { + @EnvironmentObject var generation: GenerationContext + + var body: some View { + switch generation.state { + case .startup: return AnyView(Image("placeholder").resizable()) + case .running(let progress): + guard let progress = progress, progress.stepCount > 0 else { + // The first time it takes a little bit before generation starts + return AnyView(ProgressView()) + } + let step = Int(progress.step) + 1 + let fraction = Double(step) / Double(progress.stepCount) + let label = "Step \(step) of \(progress.stepCount)" + return AnyView(ProgressView(label, value: fraction, total: 1).padding()) + case .complete(_, let image, _): + guard let theImage = image else { + return AnyView(Image(systemName: "exclamationmark.triangle").resizable()) + } + + return AnyView(Image(theImage, scale: 1, label: Text("generated")) + .resizable() + .clipShape(RoundedRectangle(cornerRadius: 20)) + ) + } + } +} diff --git a/Diffusion-macOS/Preview Content/Preview Assets.xcassets/Contents.json b/Diffusion-macOS/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Diffusion-macOS/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Diffusion-macOS/StatusView.swift b/Diffusion-macOS/StatusView.swift new file mode 100644 index 0000000..7262345 --- /dev/null +++ b/Diffusion-macOS/StatusView.swift @@ -0,0 +1,90 @@ +// +// StatusView.swift +// Diffusion-macOS +// +// Created by Cyril Zakka on 1/12/23. +// See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE +// + +import SwiftUI + +struct StatusView: View { + @EnvironmentObject var generation: GenerationContext + var pipelineState: Binding + + func submit() { + if case .running = generation.state { return } + Task { + generation.state = .running(nil) + let interval: TimeInterval? + let image: CGImage? + (image, interval) = await generation.generate() ?? (nil, nil) + generation.state = .complete(generation.positivePrompt, image, interval) + } + } + + func generationStatusView() -> any View { + switch generation.state { + case .startup: return EmptyView() + case .running(let progress): + guard let progress = progress, progress.stepCount > 0 else { + // The first time it takes a little bit before generation starts + return HStack { + Text("Preparing model…") + Spacer() + } + } + let step = Int(progress.step) + 1 + let fraction = Double(step) / Double(progress.stepCount) + return HStack { + Text("Generating \(Int(round(100*fraction)))%") + Spacer() + } + case .complete(_, let image, let interval): + guard let _ = image else { + return HStack { + Text("Safety checker triggered, please try a different prompt or seed") + Spacer() + } + } + + return HStack { + let intervalString = String(format: "Time: %.1fs", interval ?? 0) + Text(intervalString) + Spacer() + }.frame(maxHeight: 25) + } + } + + var body: some View { + switch pipelineState.wrappedValue { + case .downloading(let progress): + ProgressView("Downloading…", value: progress*100, total: 110).padding() + case .uncompressing: + ProgressView("Uncompressing…", value: 100, total: 110).padding() + case .loading: + ProgressView("Loading…", value: 105, total: 110).padding() + case .ready: + VStack { + Button { + submit() + } label: { + Text("Generate") + .frame(maxWidth: .infinity) + .frame(height: 50) + } + .buttonStyle(.borderedProminent) + + AnyView(generationStatusView()) + } + case .failed: + Text("Pipeline loading error") + } + } +} + +struct StatusView_Previews: PreviewProvider { + static var previews: some View { + StatusView(pipelineState: .constant(.downloading(0.2))) + } +} diff --git a/Diffusion.xcodeproj/project.pbxproj b/Diffusion.xcodeproj/project.pbxproj index 4bbbe0b..dc9d76f 100644 --- a/Diffusion.xcodeproj/project.pbxproj +++ b/Diffusion.xcodeproj/project.pbxproj @@ -12,6 +12,19 @@ EBB5BA5829425E17003A2A5B /* Path in Frameworks */ = {isa = PBXBuildFile; productRef = EBB5BA5729425E17003A2A5B /* Path */; }; EBB5BA5A29426E06003A2A5B /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB5BA5929426E06003A2A5B /* Downloader.swift */; }; EBB5BA5D294504DE003A2A5B /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = EBB5BA5C294504DE003A2A5B /* ZIPFoundation */; }; + EBDD7DAA29731F6C00C1C4B2 /* Pipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE75601293E91E200806B32 /* Pipeline.swift */; }; + EBDD7DAB29731F7500C1C4B2 /* PipelineLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB5BA5229425BEE003A2A5B /* PipelineLoader.swift */; }; + EBDD7DAD29731FB300C1C4B2 /* Path in Frameworks */ = {isa = PBXBuildFile; productRef = EBDD7DAC29731FB300C1C4B2 /* Path */; }; + EBDD7DAF29731FB300C1C4B2 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = EBDD7DAE29731FB300C1C4B2 /* ZIPFoundation */; }; + EBDD7DB129731FB300C1C4B2 /* StableDiffusion in Frameworks */ = {isa = PBXBuildFile; productRef = EBDD7DB029731FB300C1C4B2 /* StableDiffusion */; }; + EBDD7DB32973200200C1C4B2 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBDD7DB22973200200C1C4B2 /* Utils.swift */; }; + EBDD7DB42973200200C1C4B2 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBDD7DB22973200200C1C4B2 /* Utils.swift */; }; + EBDD7DB52973201800C1C4B2 /* ModelInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE3FF4B295E1EFE00E921AA /* ModelInfo.swift */; }; + EBDD7DB62973206600C1C4B2 /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB5BA5929426E06003A2A5B /* Downloader.swift */; }; + EBDD7DB82976AAFE00C1C4B2 /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBDD7DB72976AAFE00C1C4B2 /* State.swift */; }; + EBDD7DB92976AAFE00C1C4B2 /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBDD7DB72976AAFE00C1C4B2 /* State.swift */; }; + EBDD7DBD2977FFB300C1C4B2 /* GeneratedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBDD7DBB2977FFB300C1C4B2 /* GeneratedImageView.swift */; }; + EBDD7DC02978642200C1C4B2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EBE755CC293E37DD00806B32 /* Assets.xcassets */; }; EBE3FF4C295E1EFE00E921AA /* ModelInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE3FF4B295E1EFE00E921AA /* ModelInfo.swift */; }; EBE755C9293E37DD00806B32 /* DiffusionApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE755C8293E37DD00806B32 /* DiffusionApp.swift */; }; EBE755CB293E37DD00806B32 /* TextToImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE755CA293E37DD00806B32 /* TextToImage.swift */; }; @@ -22,6 +35,12 @@ EBE755E7293E37DE00806B32 /* DiffusionUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE755E6293E37DE00806B32 /* DiffusionUITestsLaunchTests.swift */; }; EBE75602293E91E200806B32 /* Pipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE75601293E91E200806B32 /* Pipeline.swift */; }; EBE756092941178600806B32 /* Loading.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE756082941178600806B32 /* Loading.swift */; }; + F15520242971093300DC009B /* Diffusion_macOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15520232971093300DC009B /* Diffusion_macOSApp.swift */; }; + F15520262971093300DC009B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15520252971093300DC009B /* ContentView.swift */; }; + F155202B2971093400DC009B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F155202A2971093400DC009B /* Preview Assets.xcassets */; }; + F1552031297109C300DC009B /* ControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1552030297109C300DC009B /* ControlsView.swift */; }; + F155203429710B3600DC009B /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F155203329710B3600DC009B /* StatusView.swift */; }; + F155203C297118E700DC009B /* CompactSlider in Frameworks */ = {isa = PBXBuildFile; productRef = F155203B297118E700DC009B /* CompactSlider */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -45,6 +64,10 @@ EB33A51E2954E1BC00B16357 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; EBB5BA5229425BEE003A2A5B /* PipelineLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PipelineLoader.swift; sourceTree = ""; }; EBB5BA5929426E06003A2A5B /* Downloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Downloader.swift; sourceTree = ""; }; + EBDD7DB22973200200C1C4B2 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; + EBDD7DB72976AAFE00C1C4B2 /* State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = State.swift; sourceTree = ""; }; + EBDD7DBA2976F03600C1C4B2 /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = config/debug.xcconfig; sourceTree = ""; }; + EBDD7DBB2977FFB300C1C4B2 /* GeneratedImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneratedImageView.swift; sourceTree = ""; }; EBE3FF4A295DFE2400E921AA /* common.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = common.xcconfig; path = config/common.xcconfig; sourceTree = ""; }; EBE3FF4B295E1EFE00E921AA /* ModelInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelInfo.swift; sourceTree = ""; }; EBE4438729488DCA00CDA605 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -62,6 +85,13 @@ EBE755E6293E37DE00806B32 /* DiffusionUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffusionUITestsLaunchTests.swift; sourceTree = ""; }; EBE75601293E91E200806B32 /* Pipeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pipeline.swift; sourceTree = ""; }; EBE756082941178600806B32 /* Loading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Loading.swift; sourceTree = ""; }; + F15520212971093300DC009B /* Diffusers.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Diffusers.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F15520232971093300DC009B /* Diffusion_macOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Diffusion_macOSApp.swift; sourceTree = ""; }; + F15520252971093300DC009B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + F155202A2971093400DC009B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + F155202C2971093400DC009B /* Diffusion_macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Diffusion_macOS.entitlements; sourceTree = ""; }; + F1552030297109C300DC009B /* ControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlsView.swift; sourceTree = ""; }; + F155203329710B3600DC009B /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -89,6 +119,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F155201E2971093300DC009B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F155203C297118E700DC009B /* CompactSlider in Frameworks */, + EBDD7DB129731FB300C1C4B2 /* StableDiffusion in Frameworks */, + EBDD7DAD29731FB300C1C4B2 /* Path in Frameworks */, + EBDD7DAF29731FB300C1C4B2 /* ZIPFoundation in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -105,10 +146,12 @@ isa = PBXGroup; children = ( EBE3FF4A295DFE2400E921AA /* common.xcconfig */, + EBDD7DBA2976F03600C1C4B2 /* debug.xcconfig */, EBE4438729488DCA00CDA605 /* README.md */, EBE443892948953600CDA605 /* LICENSE */, EBE755FF293E910800806B32 /* Packages */, EBE755C7293E37DD00806B32 /* Diffusion */, + F15520222971093300DC009B /* Diffusion-macOS */, EBE755D9293E37DE00806B32 /* DiffusionTests */, EBE755E3293E37DE00806B32 /* DiffusionUITests */, EBE755C6293E37DD00806B32 /* Products */, @@ -122,6 +165,7 @@ EBE755C5293E37DD00806B32 /* Diffusion.app */, EBE755D6293E37DE00806B32 /* DiffusionTests.xctest */, EBE755E0293E37DE00806B32 /* DiffusionUITests.xctest */, + F15520212971093300DC009B /* Diffusers.app */, ); name = Products; sourceTree = ""; @@ -134,7 +178,9 @@ EBE7560A29411A5E00806B32 /* Views */, EBB5BA5929426E06003A2A5B /* Downloader.swift */, EBE755C8293E37DD00806B32 /* DiffusionApp.swift */, + EBDD7DB22973200200C1C4B2 /* Utils.swift */, EBE3FF4B295E1EFE00E921AA /* ModelInfo.swift */, + EBDD7DB72976AAFE00C1C4B2 /* State.swift */, EBE755CC293E37DD00806B32 /* Assets.xcassets */, EBE755CE293E37DD00806B32 /* Diffusion.entitlements */, EBE755CF293E37DD00806B32 /* Preview Content */, @@ -190,6 +236,28 @@ path = Views; sourceTree = ""; }; + F15520222971093300DC009B /* Diffusion-macOS */ = { + isa = PBXGroup; + children = ( + F15520232971093300DC009B /* Diffusion_macOSApp.swift */, + F15520252971093300DC009B /* ContentView.swift */, + EBDD7DBB2977FFB300C1C4B2 /* GeneratedImageView.swift */, + F1552030297109C300DC009B /* ControlsView.swift */, + F155203329710B3600DC009B /* StatusView.swift */, + F155202C2971093400DC009B /* Diffusion_macOS.entitlements */, + F15520292971093400DC009B /* Preview Content */, + ); + path = "Diffusion-macOS"; + sourceTree = ""; + }; + F15520292971093400DC009B /* Preview Content */ = { + isa = PBXGroup; + children = ( + F155202A2971093400DC009B /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -251,6 +319,29 @@ productReference = EBE755E0293E37DE00806B32 /* DiffusionUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; + F15520202971093300DC009B /* Diffusion-macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = F155202F2971093400DC009B /* Build configuration list for PBXNativeTarget "Diffusion-macOS" */; + buildPhases = ( + F155201D2971093300DC009B /* Sources */, + F155201E2971093300DC009B /* Frameworks */, + F155201F2971093300DC009B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Diffusion-macOS"; + packageProductDependencies = ( + F155203B297118E700DC009B /* CompactSlider */, + EBDD7DAC29731FB300C1C4B2 /* Path */, + EBDD7DAE29731FB300C1C4B2 /* ZIPFoundation */, + EBDD7DB029731FB300C1C4B2 /* StableDiffusion */, + ); + productName = "Diffusion-macOS"; + productReference = F15520212971093300DC009B /* Diffusers.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -258,7 +349,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1410; + LastSwiftUpdateCheck = 1420; LastUpgradeCheck = 1410; TargetAttributes = { EBE755C4293E37DD00806B32 = { @@ -272,6 +363,9 @@ CreatedOnToolsVersion = 14.1; TestTargetID = EBE755C4293E37DD00806B32; }; + F15520202971093300DC009B = { + CreatedOnToolsVersion = 14.2; + }; }; }; buildConfigurationList = EBE755C0293E37DD00806B32 /* Build configuration list for PBXProject "Diffusion" */; @@ -287,6 +381,7 @@ EBB5BA5629425E17003A2A5B /* XCRemoteSwiftPackageReference "Path.swift" */, EBB5BA5B294504DE003A2A5B /* XCRemoteSwiftPackageReference "ZIPFoundation" */, EB33A51B2954D89F00B16357 /* XCRemoteSwiftPackageReference "ml-stable-diffusion" */, + F155203A297118E600DC009B /* XCRemoteSwiftPackageReference "CompactSlider" */, ); productRefGroup = EBE755C6293E37DD00806B32 /* Products */; projectDirPath = ""; @@ -295,6 +390,7 @@ EBE755C4293E37DD00806B32 /* Diffusion */, EBE755D5293E37DE00806B32 /* DiffusionTests */, EBE755DF293E37DE00806B32 /* DiffusionUITests */, + F15520202971093300DC009B /* Diffusion-macOS */, ); }; /* End PBXProject section */ @@ -323,6 +419,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F155201F2971093300DC009B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EBDD7DC02978642200C1C4B2 /* Assets.xcassets in Resources */, + F155202B2971093400DC009B /* Preview Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -335,8 +440,10 @@ EBB5BA5A29426E06003A2A5B /* Downloader.swift in Sources */, EBE3FF4C295E1EFE00E921AA /* ModelInfo.swift in Sources */, EBE756092941178600806B32 /* Loading.swift in Sources */, + EBDD7DB82976AAFE00C1C4B2 /* State.swift in Sources */, EBB5BA5329425BEE003A2A5B /* PipelineLoader.swift in Sources */, EBE755C9293E37DD00806B32 /* DiffusionApp.swift in Sources */, + EBDD7DB32973200200C1C4B2 /* Utils.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -357,6 +464,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F155201D2971093300DC009B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EBDD7DAB29731F7500C1C4B2 /* PipelineLoader.swift in Sources */, + EBDD7DAA29731F6C00C1C4B2 /* Pipeline.swift in Sources */, + F15520262971093300DC009B /* ContentView.swift in Sources */, + EBDD7DB92976AAFE00C1C4B2 /* State.swift in Sources */, + EBDD7DB42973200200C1C4B2 /* Utils.swift in Sources */, + F1552031297109C300DC009B /* ControlsView.swift in Sources */, + EBDD7DB62973206600C1C4B2 /* Downloader.swift in Sources */, + F155203429710B3600DC009B /* StatusView.swift in Sources */, + F15520242971093300DC009B /* Diffusion_macOSApp.swift in Sources */, + EBDD7DB52973201800C1C4B2 /* ModelInfo.swift in Sources */, + EBDD7DBD2977FFB300C1C4B2 /* GeneratedImageView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -484,7 +609,7 @@ }; EBE755EB293E37DE00806B32 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = EBE3FF4A295DFE2400E921AA /* common.xcconfig */; + baseConfigurationReference = EBDD7DBA2976F03600C1C4B2 /* debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -511,7 +636,6 @@ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.1; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.pcuenca.DiffusionApp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -552,7 +676,6 @@ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.1; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.pcuenca.DiffusionApp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -654,6 +777,65 @@ }; name = Release; }; + F155202D2971093400DC009B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EBDD7DBA2976F03600C1C4B2 /* debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CODE_SIGN_ENTITLEMENTS = "Diffusion-macOS/Diffusion_macOS.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Diffusion-macOS/Preview Content\""; + DEVELOPMENT_TEAM = 2EADP68M95; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.1; + MARKETING_VERSION = 1.0; + SDKROOT = macosx; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + F155202E2971093400DC009B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EBE3FF4A295DFE2400E921AA /* common.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CODE_SIGN_ENTITLEMENTS = "Diffusion-macOS/Diffusion_macOS.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Diffusion-macOS/Preview Content\""; + DEVELOPMENT_TEAM = 2EADP68M95; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.1; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.huggingface.Diffusers; + SDKROOT = macosx; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -693,6 +875,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F155202F2971093400DC009B /* Build configuration list for PBXNativeTarget "Diffusion-macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F155202D2971093400DC009B /* Debug */, + F155202E2971093400DC009B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ @@ -720,6 +911,14 @@ minimumVersion = 0.9.9; }; }; + F155203A297118E600DC009B /* XCRemoteSwiftPackageReference "CompactSlider" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/buh/CompactSlider.git"; + requirement = { + branch = main; + kind = branch; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -738,6 +937,26 @@ package = EBB5BA5B294504DE003A2A5B /* XCRemoteSwiftPackageReference "ZIPFoundation" */; productName = ZIPFoundation; }; + EBDD7DAC29731FB300C1C4B2 /* Path */ = { + isa = XCSwiftPackageProductDependency; + package = EBB5BA5629425E17003A2A5B /* XCRemoteSwiftPackageReference "Path.swift" */; + productName = Path; + }; + EBDD7DAE29731FB300C1C4B2 /* ZIPFoundation */ = { + isa = XCSwiftPackageProductDependency; + package = EBB5BA5B294504DE003A2A5B /* XCRemoteSwiftPackageReference "ZIPFoundation" */; + productName = ZIPFoundation; + }; + EBDD7DB029731FB300C1C4B2 /* StableDiffusion */ = { + isa = XCSwiftPackageProductDependency; + package = EB33A51B2954D89F00B16357 /* XCRemoteSwiftPackageReference "ml-stable-diffusion" */; + productName = StableDiffusion; + }; + F155203B297118E700DC009B /* CompactSlider */ = { + isa = XCSwiftPackageProductDependency; + package = F155203A297118E600DC009B /* XCRemoteSwiftPackageReference "CompactSlider" */; + productName = CompactSlider; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = EBE755BD293E37DD00806B32 /* Project object */; diff --git a/Diffusion.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Diffusion.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..6f69852 --- /dev/null +++ b/Diffusion.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,50 @@ +{ + "pins" : [ + { + "identity" : "compactslider", + "kind" : "remoteSourceControl", + "location" : "https://github.com/buh/CompactSlider.git", + "state" : { + "branch" : "main", + "revision" : "3cb37fb7385913835b6844c6af2680c64000dcd2" + } + }, + { + "identity" : "ml-stable-diffusion", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/ml-stable-diffusion", + "state" : { + "branch" : "main", + "revision" : "fb1fa01c9d30e9b2e02a8b7ed35d905e272a0262" + } + }, + { + "identity" : "path.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mxcl/Path.swift.git", + "state" : { + "revision" : "9c6f807b0a76be0e27aecc908bc6f173400d839e", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "fddd1c00396eed152c45a46bea9f47b98e59301d", + "version" : "1.2.0" + } + }, + { + "identity" : "zipfoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/weichsel/ZIPFoundation.git", + "state" : { + "revision" : "43ec568034b3731101dbf7670765d671c30f54f3", + "version" : "0.9.16" + } + } + ], + "version" : 2 +} diff --git a/Diffusion.xcodeproj/project.xcworkspace/xcuserdata/cyril.xcuserdatad/UserInterfaceState.xcuserstate b/Diffusion.xcodeproj/project.xcworkspace/xcuserdata/cyril.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..42119b3 Binary files /dev/null and b/Diffusion.xcodeproj/project.xcworkspace/xcuserdata/cyril.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Diffusion.xcodeproj/xcuserdata/cyril.xcuserdatad/xcschemes/xcschememanagement.plist b/Diffusion.xcodeproj/xcuserdata/cyril.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..7cfdf33 --- /dev/null +++ b/Diffusion.xcodeproj/xcuserdata/cyril.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,19 @@ + + + + + SchemeUserState + + Diffusion-macOS.xcscheme_^#shared#^_ + + orderHint + 1 + + Diffusion.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/Diffusion/Assets.xcassets/AppIcon.appiconset/256x256@2x.png b/Diffusion/Assets.xcassets/AppIcon.appiconset/256x256@2x.png new file mode 100644 index 0000000..c3bd11b Binary files /dev/null and b/Diffusion/Assets.xcassets/AppIcon.appiconset/256x256@2x.png differ diff --git a/Diffusion/Assets.xcassets/AppIcon.appiconset/512x512@2x.png b/Diffusion/Assets.xcassets/AppIcon.appiconset/512x512@2x.png new file mode 100644 index 0000000..fea6e64 Binary files /dev/null and b/Diffusion/Assets.xcassets/AppIcon.appiconset/512x512@2x.png differ diff --git a/Diffusion/Assets.xcassets/AppIcon.appiconset/Contents.json b/Diffusion/Assets.xcassets/AppIcon.appiconset/Contents.json index be97d4c..f2216a9 100644 --- a/Diffusion/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Diffusion/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "labrador-x2.png", + "filename" : "diffusers_on_white_1024.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" @@ -42,17 +42,19 @@ "size" : "256x256" }, { + "filename" : "256x256@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { + "filename" : "256x256@2x.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { - "filename" : "labrador-x2 1.png", + "filename" : "512x512@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" diff --git a/Diffusion/Assets.xcassets/AppIcon.appiconset/diffusers_on_white_1024.png b/Diffusion/Assets.xcassets/AppIcon.appiconset/diffusers_on_white_1024.png new file mode 100644 index 0000000..3a83840 Binary files /dev/null and b/Diffusion/Assets.xcassets/AppIcon.appiconset/diffusers_on_white_1024.png differ diff --git a/Diffusion/Assets.xcassets/AppIcon.appiconset/labrador-x2 1.png b/Diffusion/Assets.xcassets/AppIcon.appiconset/labrador-x2 1.png deleted file mode 100644 index 1034ce2..0000000 Binary files a/Diffusion/Assets.xcassets/AppIcon.appiconset/labrador-x2 1.png and /dev/null differ diff --git a/Diffusion/Assets.xcassets/AppIcon.appiconset/labrador-x2.png b/Diffusion/Assets.xcassets/AppIcon.appiconset/labrador-x2.png deleted file mode 100644 index 1034ce2..0000000 Binary files a/Diffusion/Assets.xcassets/AppIcon.appiconset/labrador-x2.png and /dev/null differ diff --git a/Diffusion/DiffusionApp.swift b/Diffusion/DiffusionApp.swift index a07e3f9..d8cdc30 100644 --- a/Diffusion/DiffusionApp.swift +++ b/Diffusion/DiffusionApp.swift @@ -17,7 +17,4 @@ struct DiffusionApp: App { } } -// A couple of helpers - -extension String: Error {} let runningOnMac = ProcessInfo.processInfo.isMacCatalystApp diff --git a/Diffusion/Info.plist b/Diffusion/Info.plist index 0b50ca2..2ee5146 100644 --- a/Diffusion/Info.plist +++ b/Diffusion/Info.plist @@ -4,5 +4,7 @@ NSPhotoLibraryAddUsageDescription To be able to save generated images to your Photo Library, you’ll need to allow this. + CFBundleDisplayName + Diffusers diff --git a/Diffusion/ModelInfo.swift b/Diffusion/ModelInfo.swift index 9365bb2..88b68c1 100644 --- a/Diffusion/ModelInfo.swift +++ b/Diffusion/ModelInfo.swift @@ -60,22 +60,37 @@ extension ModelInfo { // TODO: repo does not exist yet static let v14Base = ModelInfo( modelId: "pcuenq/coreml-stable-diffusion-v1-4", - modelVersion: "1.4" + modelVersion: "CompVis/stable-diffusion-v1-4" ) static let v15Base = ModelInfo( modelId: "pcuenq/coreml-stable-diffusion-v1-5", - modelVersion: "1.5" + modelVersion: "runwayml/stable-diffusion-v1-5" ) static let v2Base = ModelInfo( modelId: "pcuenq/coreml-stable-diffusion-2-base", - modelVersion: "2-base" + modelVersion: "stabilityai/stable-diffusion-2-base" ) static let v21Base = ModelInfo( modelId: "pcuenq/coreml-stable-diffusion-2-1-base", - modelVersion: "2.1-base", + modelVersion: "stabilityai/stable-diffusion-2-1-base", supportsEncoder: true ) + + static let MODELS = [ +// ModelInfo.v14Base, + ModelInfo.v15Base, + ModelInfo.v2Base, + ModelInfo.v21Base + ] + + static func from(modelVersion: String) -> ModelInfo? { + ModelInfo.MODELS.first(where: {$0.modelVersion == modelVersion}) + } + + static func from(modelId: String) -> ModelInfo? { + ModelInfo.MODELS.first(where: {$0.modelId == modelId}) + } } diff --git a/Diffusion/Pipeline/Pipeline.swift b/Diffusion/Pipeline/Pipeline.swift index caacd9e..570dc9f 100644 --- a/Diffusion/Pipeline/Pipeline.swift +++ b/Diffusion/Pipeline/Pipeline.swift @@ -29,12 +29,13 @@ class Pipeline { self.pipeline = pipeline } - func generate(prompt: String, scheduler: StableDiffusionScheduler, numInferenceSteps stepCount: Int = 50, seed: UInt32? = nil) throws -> (CGImage, TimeInterval) { + func generate(prompt: String, negativePrompt: String = "", scheduler: StableDiffusionScheduler, numInferenceSteps stepCount: Int = 50, seed: UInt32? = nil) throws -> (CGImage, TimeInterval) { let beginDate = Date() print("Generating...") let theSeed = seed ?? UInt32.random(in: 0.. (CGImage, TimeInterval)? { + guard let pipeline = pipeline else { return nil } + let seed = self.seed >= 0 ? UInt32(self.seed) : nil + return try? pipeline.generate(prompt: positivePrompt, negativePrompt: negativePrompt, scheduler: scheduler, numInferenceSteps: Int(steps), seed: seed) + } +} + +class Settings { + static let shared = Settings() + + let defaults = UserDefaults.standard + + enum Keys: String { + case model + } + + private init() { + defaults.register(defaults: [ + Keys.model.rawValue: ModelInfo.v2Base.modelId + ]) + } + + var currentModel: ModelInfo { + set { + defaults.set(newValue.modelId, forKey: Keys.model.rawValue) + } + get { + guard let modelId = defaults.string(forKey: Keys.model.rawValue) else { return DEFAULT_MODEL } + return ModelInfo.from(modelId: modelId) ?? DEFAULT_MODEL + } + } +} diff --git a/Diffusion/Utils.swift b/Diffusion/Utils.swift new file mode 100644 index 0000000..cd0db8e --- /dev/null +++ b/Diffusion/Utils.swift @@ -0,0 +1,11 @@ +// +// Utils.swift +// Diffusion +// +// Created by Pedro Cuenca on 14/1/23. +// See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE +// + +import Foundation + +extension String: Error {} diff --git a/Diffusion/Views/Loading.swift b/Diffusion/Views/Loading.swift index 58e997c..a298f56 100644 --- a/Diffusion/Views/Loading.swift +++ b/Diffusion/Views/Loading.swift @@ -11,12 +11,8 @@ import Combine let model = ModelInfo.v2Base -class DiffusionGlobals: ObservableObject { - @Published var pipeline: Pipeline? = nil -} - struct LoadingView: View { - @StateObject var context = DiffusionGlobals() + @StateObject var generation = GenerationContext() @State private var preparationPhase = "Downloading…" @State private var downloadProgress: Double = 0 @@ -41,7 +37,7 @@ struct LoadingView: View { } } .animation(.easeIn, value: currentView) - .environmentObject(context) + .environmentObject(generation) .onAppear { Task.init { let loader = PipelineLoader(model: model) @@ -63,7 +59,7 @@ struct LoadingView: View { } } do { - context.pipeline = try await loader.prepare() + generation.pipeline = try await loader.prepare() self.currentView = .textToImage } catch { self.currentView = .error("Could not load model, error: \(error)") diff --git a/Diffusion/Views/TextToImage.swift b/Diffusion/Views/TextToImage.swift index 779b97b..e05fdc5 100644 --- a/Diffusion/Views/TextToImage.swift +++ b/Diffusion/Views/TextToImage.swift @@ -10,21 +10,6 @@ import SwiftUI import Combine import StableDiffusion -// TODO: bind to UI controls -let scheduler = StableDiffusionScheduler.dpmSolverMultistepScheduler -let steps = 25 -let seed: UInt32? = nil - -func generate(pipeline: Pipeline?, prompt: String) async -> (CGImage, TimeInterval)? { - guard let pipeline = pipeline else { return nil } - return try? pipeline.generate(prompt: prompt, scheduler: scheduler, numInferenceSteps: steps, seed: seed) -} - -enum GenerationState { - case startup - case running(StableDiffusionProgress?) - case idle(String, TimeInterval?) -} /// Presents "Share" + "Save" buttons on Mac; just "Share" on iOS/iPadOS. /// This is because I didn't find a way for "Share" to show a Save option when running on macOS. @@ -67,7 +52,6 @@ struct ShareButtons: View { } struct ImageWithPlaceholder: View { - var image: Binding var state: Binding var body: some View { @@ -82,8 +66,8 @@ struct ImageWithPlaceholder: View { let fraction = Double(step) / Double(progress.stepCount) let label = "Step \(step) of \(progress.stepCount)" return AnyView(ProgressView(label, value: fraction, total: 1).padding()) - case .idle(let lastPrompt, let interval): - guard let theImage = image.wrappedValue else { + case .complete(let lastPrompt, let image, let interval): + guard let theImage = image else { return AnyView(Image(systemName: "exclamationmark.triangle").resizable()) } @@ -107,28 +91,23 @@ struct ImageWithPlaceholder: View { } struct TextToImage: View { - @EnvironmentObject var context: DiffusionGlobals - - @State private var prompt = "Labrador in the style of Vermeer" - @State private var image: CGImage? = nil - @State private var state: GenerationState = .startup - - @State private var progressSubscriber: Cancellable? + @EnvironmentObject var generation: GenerationContext func submit() { - if case .running = state { return } + if case .running = generation.state { return } Task { - state = .running(nil) + generation.state = .running(nil) let interval: TimeInterval? - (image, interval) = await generate(pipeline: context.pipeline, prompt: prompt) ?? (nil, nil) - state = .idle(prompt, interval) + let image: CGImage? + (image, interval) = await generation.generate() ?? (nil, nil) + generation.state = .complete(generation.positivePrompt, image, interval) } } var body: some View { VStack { HStack { - TextField("Prompt", text: $prompt) + TextField("Prompt", text: $generation.positivePrompt) .textFieldStyle(.roundedBorder) .onSubmit { submit() @@ -139,16 +118,10 @@ struct TextToImage: View { .padding() .buttonStyle(.borderedProminent) } - ImageWithPlaceholder(image: $image, state: $state) + ImageWithPlaceholder(state: $generation.state) .scaledToFit() Spacer() } .padding() - .onAppear { - progressSubscriber = context.pipeline!.progressPublisher.sink { progress in - guard let progress = progress else { return } - state = .running(progress) - } - } } } diff --git a/DiffusionTests/DiffusionTests.swift b/DiffusionTests/DiffusionTests.swift index 8636847..30cd371 100644 --- a/DiffusionTests/DiffusionTests.swift +++ b/DiffusionTests/DiffusionTests.swift @@ -3,6 +3,7 @@ // DiffusionTests // // Created by Pedro Cuenca on December 2022. +// See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE // import XCTest diff --git a/DiffusionUITests/DiffusionUITests.swift b/DiffusionUITests/DiffusionUITests.swift index 79cbb2f..4dd7ce6 100644 --- a/DiffusionUITests/DiffusionUITests.swift +++ b/DiffusionUITests/DiffusionUITests.swift @@ -3,6 +3,7 @@ // DiffusionUITests // // Created by Pedro Cuenca on December 2022. +// See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE // import XCTest diff --git a/DiffusionUITests/DiffusionUITestsLaunchTests.swift b/DiffusionUITests/DiffusionUITestsLaunchTests.swift index f301f0d..5fde4d7 100644 --- a/DiffusionUITests/DiffusionUITestsLaunchTests.swift +++ b/DiffusionUITests/DiffusionUITestsLaunchTests.swift @@ -3,6 +3,7 @@ // DiffusionUITests // // Created by Pedro Cuenca on December 2022. +// See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE // import XCTest diff --git a/config/common.xcconfig b/config/common.xcconfig index 6586cde..be26145 100644 --- a/config/common.xcconfig +++ b/config/common.xcconfig @@ -3,13 +3,14 @@ // Diffusion // // Created by Pedro Cuenca on 202212. +// See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE // // Configuration settings file format documentation can be found at: // https://help.apple.com/xcode/#/dev745c5c974 -// Update if you fork this repo -DEVELOPMENT_TEAM = ZWDJQ796RU +PRODUCT_NAME = Diffusers -// Disable code-signing for macOS -CODE_SIGN_IDENTITY[sdk=macos*] = +// Update if you fork this repo +DEVELOPMENT_TEAM = 2EADP68M95 +PRODUCT_BUNDLE_IDENTIFIER = com.huggingface.Diffusers diff --git a/config/debug.xcconfig b/config/debug.xcconfig new file mode 100644 index 0000000..03c680e --- /dev/null +++ b/config/debug.xcconfig @@ -0,0 +1,12 @@ +// +// debug.xcconfig +// Diffusion +// +// Created by Pedro Cuenca on 17/1/23. +// See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE +// + +#include "common.xcconfig" + +// Disable code-signing for macOS +CODE_SIGN_IDENTITY[sdk=macos*] =