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*] =