├── .gitignore ├── Diffusion-macOS ├── Capabilities.swift ├── ContentView.swift ├── ControlsView.swift ├── DiffusionImage+macOS.swift ├── Diffusion_macOS.entitlements ├── Diffusion_macOSApp.swift ├── GeneratedImageView.swift ├── HelpContent.swift ├── Info.plist ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── StatusView.swift └── Utils_macOS.swift ├── Diffusion.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcuserdata │ │ └── cyril.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── cyril.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── Diffusion ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── .DS_Store │ │ ├── 256x256@2x.png │ │ ├── 512x512@2x.png │ │ ├── Contents.json │ │ └── diffusers_on_white_1024.png │ ├── Contents.json │ └── placeholder.imageset │ │ ├── Contents.json │ │ └── labrador.png ├── Common │ ├── DiffusionImage.swift │ ├── Downloader.swift │ ├── ModelInfo.swift │ ├── Pipeline │ │ ├── Pipeline.swift │ │ └── PipelineLoader.swift │ ├── State.swift │ ├── Utils.swift │ └── Views │ │ └── PromptTextField.swift ├── Diffusion.entitlements ├── DiffusionApp.swift ├── DiffusionImage+iOS.swift ├── Info.plist ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Utils_iOS.swift └── Views │ ├── Loading.swift │ └── TextToImage.swift ├── DiffusionTests └── DiffusionTests.swift ├── DiffusionUITests ├── DiffusionUITests.swift └── DiffusionUITestsLaunchTests.swift ├── LICENSE ├── README.md ├── config ├── common.xcconfig └── debug.xcconfig └── screenshot.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | xcuserdata/ 3 | -------------------------------------------------------------------------------- /Diffusion-macOS/Capabilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Capabilities.swift 3 | // Diffusion-macOS 4 | // 5 | // Created by Pedro Cuenca on 20/2/23. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import Foundation 10 | 11 | let runningOnMac = true 12 | let deviceHas6GBOrMore = true 13 | let deviceHas8GBOrMore = true 14 | let BENCHMARK = false 15 | 16 | let deviceSupportsQuantization = { 17 | if #available(macOS 14, *) { 18 | return true 19 | } else { 20 | return false 21 | } 22 | }() 23 | 24 | 25 | #if canImport(MLCompute) 26 | import MLCompute 27 | let _hasANE = MLCDevice.ane() != nil 28 | #else 29 | let _hasANE = false 30 | #endif 31 | 32 | final class Capabilities { 33 | static let hasANE = _hasANE 34 | 35 | // According to my tests this is a good proxy to estimate whether CPU+GPU 36 | // or CPU+NE works better. Things may become more complicated if we 37 | // choose all compute units. 38 | static var performanceCores: Int = { 39 | var ncores: Int32 = 0 40 | var bytes = MemoryLayout.size 41 | 42 | // In M1/M2 perflevel0 refers to the performance cores and perflevel1 are the efficiency cores 43 | // In Intel there's only one performance level 44 | let result = sysctlbyname("hw.perflevel0.physicalcpu", &ncores, &bytes, nil, 0) 45 | guard result == 0 else { return 0 } 46 | return Int(ncores) 47 | }() 48 | } 49 | -------------------------------------------------------------------------------- /Diffusion-macOS/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Diffusion-macOS 4 | // 5 | // Created by Cyril Zakka on 1/12/23. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import SwiftUI 10 | import ImageIO 11 | 12 | 13 | // AppKit version that uses NSImage, NSSavePanel 14 | struct ShareButtons: View { 15 | var image: CGImage 16 | var name: String 17 | 18 | var filename: String { 19 | name.replacingOccurrences(of: " ", with: "_") 20 | } 21 | 22 | func showSavePanel() -> URL? { 23 | let savePanel = NSSavePanel() 24 | savePanel.allowedContentTypes = [.png] 25 | savePanel.canCreateDirectories = true 26 | savePanel.isExtensionHidden = false 27 | savePanel.title = "Save your image" 28 | savePanel.message = "Choose a folder and a name to store the image." 29 | savePanel.nameFieldLabel = "File name:" 30 | savePanel.nameFieldStringValue = filename 31 | 32 | let response = savePanel.runModal() 33 | return response == .OK ? savePanel.url : nil 34 | } 35 | 36 | func savePNG(cgImage: CGImage, path: URL) { 37 | let image = NSImage(cgImage: cgImage, size: .zero) 38 | let imageRepresentation = NSBitmapImageRep(data: image.tiffRepresentation!) 39 | guard let pngData = imageRepresentation?.representation(using: .png, properties: [:]) else { 40 | print("Error generating PNG data") 41 | return 42 | } 43 | do { 44 | try pngData.write(to: path) 45 | } catch { 46 | print("Error saving: \(error)") 47 | } 48 | } 49 | 50 | var body: some View { 51 | let imageView = Image(image, scale: 1, label: Text(name)) 52 | HStack { 53 | ShareLink(item: imageView, preview: SharePreview(name, image: imageView)) 54 | Button() { 55 | if let url = showSavePanel() { 56 | savePNG(cgImage: image, path: url) 57 | } 58 | } label: { 59 | Label("Save…", systemImage: "square.and.arrow.down") 60 | } 61 | } 62 | } 63 | } 64 | 65 | struct ContentView: View { 66 | @StateObject var generation = GenerationContext() 67 | 68 | func toolbar() -> any View { 69 | if case .complete(let prompt, let cgImage, _, _) = generation.state, let cgImage = cgImage { 70 | // TODO: share seed too 71 | return ShareButtons(image: cgImage, name: prompt) 72 | } else { 73 | let prompt = DEFAULT_PROMPT 74 | let cgImage = NSImage(imageLiteralResourceName: "placeholder").cgImage(forProposedRect: nil, context: nil, hints: nil)! 75 | return ShareButtons(image: cgImage, name: prompt) 76 | } 77 | } 78 | 79 | var body: some View { 80 | NavigationSplitView { 81 | ControlsView() 82 | .navigationSplitViewColumnWidth(min: 250, ideal: 300) 83 | } detail: { 84 | GeneratedImageView() 85 | .aspectRatio(contentMode: .fit) 86 | .frame(width: 512, height: 512) 87 | .cornerRadius(15) 88 | .toolbar { 89 | AnyView(toolbar()) 90 | } 91 | 92 | } 93 | .environmentObject(generation) 94 | } 95 | } 96 | 97 | struct ContentView_Previews: PreviewProvider { 98 | static var previews: some View { 99 | ContentView() 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Diffusion-macOS/ControlsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PromptView.swift 3 | // Diffusion-macOS 4 | // 5 | // Created by Cyril Zakka on 1/12/23. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import Combine 10 | import SwiftUI 11 | import CompactSlider 12 | 13 | /// Track a StableDiffusion Pipeline's readiness. This include actively downloading from the internet, uncompressing the downloaded zip file, actively loading into memory, ready to use or an Error state. 14 | enum PipelineState { 15 | case downloading(Double) 16 | case uncompressing 17 | case loading 18 | case ready 19 | case failed(Error) 20 | } 21 | 22 | /// Mimics the native appearance, but labels are clickable. 23 | /// To be removed (adding gestures to all labels) if we observe any UI shenanigans. 24 | struct LabelToggleDisclosureGroupStyle: DisclosureGroupStyle { 25 | func makeBody(configuration: Configuration) -> some View { 26 | VStack { 27 | HStack { 28 | Button { 29 | withAnimation { 30 | configuration.isExpanded.toggle() 31 | } 32 | } label: { 33 | Image(systemName: configuration.isExpanded ? "chevron.down" : "chevron.right").frame(width:8, height: 8) 34 | }.buttonStyle(.plain).font(.footnote).fontWeight(.semibold).foregroundColor(.gray) 35 | configuration.label.onTapGesture { 36 | withAnimation { 37 | configuration.isExpanded.toggle() 38 | } 39 | } 40 | Spacer() 41 | } 42 | if configuration.isExpanded { 43 | configuration.content 44 | } 45 | } 46 | } 47 | } 48 | 49 | struct ControlsView: View { 50 | @EnvironmentObject var generation: GenerationContext 51 | 52 | static let models = ModelInfo.MODELS 53 | 54 | @State private var model = Settings.shared.currentModel.modelVersion 55 | @State private var disclosedModel = true 56 | @State private var disclosedPrompt = true 57 | @State private var disclosedGuidance = false 58 | @State private var disclosedSteps = false 59 | @State private var disclosedPreview = false 60 | @State private var disclosedSeed = false 61 | @State private var disclosedAdvanced = false 62 | 63 | // TODO: refactor download with similar code in Loading.swift (iOS) 64 | @State private var stateSubscriber: Cancellable? 65 | @State private var pipelineState: PipelineState = .downloading(0) 66 | @State private var pipelineLoader: PipelineLoader? = nil 67 | 68 | // TODO: make this computed, and observable, and easy to read 69 | @State private var mustShowSafetyCheckerDisclaimer = false 70 | @State private var mustShowModelDownloadDisclaimer = false // When changing advanced settings 71 | 72 | @State private var showModelsHelp = false 73 | @State private var showPromptsHelp = false 74 | @State private var showGuidanceHelp = false 75 | @State private var showStepsHelp = false 76 | @State private var showPreviewHelp = false 77 | @State private var showSeedHelp = false 78 | @State private var showAdvancedHelp = false 79 | @State private var positiveTokenCount: Int = 0 80 | @State private var negativeTokenCount: Int = 0 81 | 82 | let maxSeed: UInt32 = UInt32.max 83 | private var textFieldLabelSeed: String { generation.seed < 1 ? "Random Seed" : "Seed" } 84 | 85 | var modelFilename: String? { 86 | guard let pipelineLoader = pipelineLoader else { return nil } 87 | let selectedURL = pipelineLoader.compiledURL 88 | guard FileManager.default.fileExists(atPath: selectedURL.path) else { return nil } 89 | return selectedURL.path 90 | } 91 | 92 | fileprivate func updateSafetyCheckerState() { 93 | mustShowSafetyCheckerDisclaimer = generation.disableSafety && !Settings.shared.safetyCheckerDisclaimerShown 94 | } 95 | 96 | fileprivate func updateComputeUnitsState() { 97 | Settings.shared.userSelectedComputeUnits = generation.computeUnits 98 | modelDidChange(model: Settings.shared.currentModel) 99 | } 100 | 101 | fileprivate func resetComputeUnitsState() { 102 | generation.computeUnits = Settings.shared.userSelectedComputeUnits ?? ModelInfo.defaultComputeUnits 103 | } 104 | 105 | fileprivate func modelDidChange(model: ModelInfo) { 106 | guard pipelineLoader?.model != model || pipelineLoader?.computeUnits != generation.computeUnits else { 107 | print("Reusing same model \(model) with units \(generation.computeUnits)") 108 | return 109 | } 110 | 111 | if !model.supportsNeuralEngine && generation.computeUnits == .cpuAndNeuralEngine { 112 | // Reset compute units to GPU if Neural Engine is not supported 113 | Settings.shared.userSelectedComputeUnits = .cpuAndGPU 114 | resetComputeUnitsState() 115 | print("Neural Engine not supported for model \(model), switching to GPU") 116 | } else { 117 | resetComputeUnitsState() 118 | } 119 | 120 | Settings.shared.currentModel = model 121 | 122 | pipelineLoader?.cancel() 123 | pipelineState = .downloading(0) 124 | Task.init { 125 | let loader = PipelineLoader(model: model, computeUnits: generation.computeUnits, maxSeed: maxSeed) 126 | self.pipelineLoader = loader 127 | stateSubscriber = loader.statePublisher.sink { state in 128 | DispatchQueue.main.async { 129 | switch state { 130 | case .downloading(let progress): 131 | pipelineState = .downloading(progress) 132 | case .uncompressing: 133 | pipelineState = .uncompressing 134 | case .readyOnDisk: 135 | pipelineState = .loading 136 | case .failed(let error): 137 | pipelineState = .failed(error) 138 | default: 139 | break 140 | } 141 | } 142 | } 143 | do { 144 | generation.pipeline = try await loader.prepare() 145 | pipelineState = .ready 146 | } catch { 147 | print("Could not load model, error: \(error)") 148 | pipelineState = .failed(error) 149 | } 150 | } 151 | } 152 | 153 | fileprivate func isModelDownloaded(_ model: ModelInfo, computeUnits: ComputeUnits? = nil) -> Bool { 154 | PipelineLoader(model: model, computeUnits: computeUnits ?? generation.computeUnits).ready 155 | } 156 | 157 | fileprivate func modelLabel(_ model: ModelInfo) -> Text { 158 | let downloaded = isModelDownloaded(model) 159 | let prefix = downloaded ? "● " : "◌ " //"○ " 160 | return Text(prefix).foregroundColor(downloaded ? .accentColor : .secondary) + Text(model.modelVersion) 161 | } 162 | 163 | fileprivate func prompts() -> some View { 164 | VStack { 165 | Spacer() 166 | PromptTextField(text: $generation.positivePrompt, isPositivePrompt: true, model: $model) 167 | .onChange(of: generation.positivePrompt) { prompt in 168 | Settings.shared.prompt = prompt 169 | } 170 | .padding(.top, 5) 171 | Spacer() 172 | PromptTextField(text: $generation.negativePrompt, isPositivePrompt: false, model: $model) 173 | .onChange(of: generation.negativePrompt) { negativePrompt in 174 | Settings.shared.negativePrompt = negativePrompt 175 | } 176 | .padding(.bottom, 5) 177 | Spacer() 178 | } 179 | .frame(maxHeight: .infinity) 180 | } 181 | 182 | var body: some View { 183 | VStack(alignment: .leading) { 184 | 185 | Label("Generation Options", systemImage: "gearshape.2") 186 | .font(.headline) 187 | .fontWeight(.bold) 188 | Divider() 189 | 190 | ScrollView { 191 | Group { 192 | DisclosureGroup(isExpanded: $disclosedModel) { 193 | let revealOption = "-- reveal --" 194 | Picker("", selection: $model) { 195 | ForEach(Self.models, id: \.modelVersion) { 196 | modelLabel($0) 197 | } 198 | Text("Reveal in Finder…").tag(revealOption) 199 | } 200 | .onChange(of: model) { selection in 201 | guard selection != revealOption else { 202 | // The reveal option has been requested - open the models folder in Finder 203 | NSWorkspace.shared.selectFile(modelFilename, inFileViewerRootedAtPath: PipelineLoader.models.path) 204 | model = Settings.shared.currentModel.modelVersion 205 | return 206 | } 207 | guard let model = ModelInfo.from(modelVersion: selection) else { return } 208 | modelDidChange(model: model) 209 | } 210 | } label: { 211 | HStack { 212 | Label("Model from Hub", systemImage: "cpu").foregroundColor(.secondary) 213 | Spacer() 214 | if disclosedModel { 215 | Button { 216 | showModelsHelp.toggle() 217 | } label: { 218 | Image(systemName: "info.circle") 219 | } 220 | .buttonStyle(.plain) 221 | // Or maybe use .sheet instead 222 | .sheet(isPresented: $showModelsHelp) { 223 | modelsHelp($showModelsHelp) 224 | } 225 | } 226 | }.foregroundColor(.secondary) 227 | } 228 | Divider() 229 | 230 | DisclosureGroup(isExpanded: $disclosedPrompt) { 231 | Group { 232 | prompts() 233 | }.padding(.leading, 10) 234 | } label: { 235 | HStack { 236 | Label("Prompts", systemImage: "text.quote").foregroundColor(.secondary) 237 | Spacer() 238 | if disclosedPrompt { 239 | Button { 240 | showPromptsHelp.toggle() 241 | } label: { 242 | Image(systemName: "info.circle") 243 | } 244 | .buttonStyle(.plain) 245 | // Or maybe use .sheet instead 246 | .popover(isPresented: $showPromptsHelp, arrowEdge: .trailing) { 247 | promptsHelp($showPromptsHelp) 248 | } 249 | } 250 | }.foregroundColor(.secondary) 251 | } 252 | Divider() 253 | 254 | let guidanceScaleValue = generation.guidanceScale.formatted("%.1f") 255 | DisclosureGroup(isExpanded: $disclosedGuidance) { 256 | CompactSlider(value: $generation.guidanceScale, in: 0...20, step: 0.5) { 257 | Text("Guidance Scale") 258 | Spacer() 259 | Text(guidanceScaleValue) 260 | } 261 | .onChange(of: generation.guidanceScale) { guidanceScale in 262 | Settings.shared.guidanceScale = guidanceScale 263 | } 264 | .padding(.leading, 10) 265 | } label: { 266 | HStack { 267 | Label("Guidance Scale", systemImage: "scalemass").foregroundColor(.secondary) 268 | Spacer() 269 | if disclosedGuidance { 270 | Button { 271 | showGuidanceHelp.toggle() 272 | } label: { 273 | Image(systemName: "info.circle") 274 | } 275 | .buttonStyle(.plain) 276 | // Or maybe use .sheet instead 277 | .popover(isPresented: $showGuidanceHelp, arrowEdge: .trailing) { 278 | guidanceHelp($showGuidanceHelp) 279 | } 280 | } else { 281 | Text(guidanceScaleValue) 282 | } 283 | }.foregroundColor(.secondary) 284 | } 285 | 286 | DisclosureGroup(isExpanded: $disclosedSteps) { 287 | CompactSlider(value: $generation.steps, in: 1...150, step: 1) { 288 | Text("Steps") 289 | Spacer() 290 | Text("\(Int(generation.steps))") 291 | } 292 | .onChange(of: generation.steps) { steps in 293 | Settings.shared.stepCount = steps 294 | } 295 | .padding(.leading, 10) 296 | } label: { 297 | HStack { 298 | Label("Step count", systemImage: "square.3.layers.3d.down.left").foregroundColor(.secondary) 299 | Spacer() 300 | if disclosedSteps { 301 | Button { 302 | showStepsHelp.toggle() 303 | } label: { 304 | Image(systemName: "info.circle") 305 | } 306 | .buttonStyle(.plain) 307 | .popover(isPresented: $showStepsHelp, arrowEdge: .trailing) { 308 | stepsHelp($showStepsHelp) 309 | } 310 | } else { 311 | Text("\(Int(generation.steps))") 312 | } 313 | }.foregroundColor(.secondary) 314 | } 315 | 316 | DisclosureGroup(isExpanded: $disclosedPreview) { 317 | CompactSlider(value: $generation.previews, in: 0...25, step: 1) { 318 | Text("Previews") 319 | Spacer() 320 | Text("\(Int(generation.previews))") 321 | } 322 | .onChange(of: generation.previews) { previews in 323 | Settings.shared.previewCount = previews 324 | } 325 | .padding(.leading, 10) 326 | } label: { 327 | HStack { 328 | Label("Preview count", systemImage: "eye.square").foregroundColor(.secondary) 329 | Spacer() 330 | if disclosedPreview { 331 | Button { 332 | showPreviewHelp.toggle() 333 | } label: { 334 | Image(systemName: "info.circle") 335 | } 336 | .buttonStyle(.plain) 337 | .popover(isPresented: $showPreviewHelp, arrowEdge: .trailing) { 338 | previewHelp($showPreviewHelp) 339 | } 340 | } else { 341 | Text("\(Int(generation.previews))") 342 | } 343 | }.foregroundColor(.secondary) 344 | } 345 | 346 | DisclosureGroup(isExpanded: $disclosedSeed) { 347 | discloseSeedContent() 348 | .padding(.leading, 10) 349 | } label: { 350 | HStack { 351 | Label(textFieldLabelSeed, systemImage: "leaf").foregroundColor(.secondary) 352 | Spacer() 353 | if disclosedSeed { 354 | Button { 355 | showSeedHelp.toggle() 356 | } label: { 357 | Image(systemName: "info.circle") 358 | } 359 | .buttonStyle(.plain) 360 | .popover(isPresented: $showSeedHelp, arrowEdge: .trailing) { 361 | seedHelp($showSeedHelp) 362 | } 363 | } else { 364 | Text(generation.seed.formatted(.number.grouping(.never))) 365 | } 366 | } 367 | .foregroundColor(.secondary) 368 | } 369 | 370 | if Capabilities.hasANE { 371 | Divider() 372 | let isNeuralEngineDisabled = !(ModelInfo.from(modelVersion: model)?.supportsNeuralEngine ?? true) 373 | DisclosureGroup(isExpanded: $disclosedAdvanced) { 374 | HStack { 375 | Picker(selection: $generation.computeUnits, label: Text("Use")) { 376 | Text("GPU").tag(ComputeUnits.cpuAndGPU) 377 | Text("Neural Engine\(isNeuralEngineDisabled ? " (unavailable)" : "")") 378 | .foregroundColor(isNeuralEngineDisabled ? .secondary : .primary) 379 | .tag(ComputeUnits.cpuAndNeuralEngine) 380 | Text("GPU and Neural Engine").tag(ComputeUnits.all) 381 | }.pickerStyle(.radioGroup).padding(.leading) 382 | Spacer() 383 | } 384 | .onChange(of: generation.computeUnits) { units in 385 | guard let currentModel = ModelInfo.from(modelVersion: model) else { return } 386 | if isNeuralEngineDisabled && units == .cpuAndNeuralEngine { 387 | resetComputeUnitsState() 388 | return 389 | } 390 | let variantDownloaded = isModelDownloaded(currentModel, computeUnits: units) 391 | if variantDownloaded { 392 | updateComputeUnitsState() 393 | } else { 394 | mustShowModelDownloadDisclaimer.toggle() 395 | } 396 | } 397 | .alert("Download Required", isPresented: $mustShowModelDownloadDisclaimer, actions: { 398 | Button("Cancel", role: .destructive) { resetComputeUnitsState() } 399 | Button("Download", role: .cancel) { updateComputeUnitsState() } 400 | }, message: { 401 | Text("This setting requires a new version of the selected model.") 402 | }) 403 | } label: { 404 | HStack { 405 | Label("Advanced", systemImage: "terminal").foregroundColor(.secondary) 406 | Spacer() 407 | if disclosedAdvanced { 408 | Button { 409 | showAdvancedHelp.toggle() 410 | } label: { 411 | Image(systemName: "info.circle") 412 | } 413 | .buttonStyle(.plain) 414 | .popover(isPresented: $showAdvancedHelp, arrowEdge: .trailing) { 415 | advancedHelp($showAdvancedHelp) 416 | } 417 | } 418 | }.foregroundColor(.secondary) 419 | } 420 | } 421 | } 422 | } 423 | .disclosureGroupStyle(LabelToggleDisclosureGroupStyle()) 424 | 425 | Toggle("Disable Safety Checker", isOn: $generation.disableSafety).onChange(of: generation.disableSafety) { value in 426 | updateSafetyCheckerState() 427 | } 428 | .popover(isPresented: $mustShowSafetyCheckerDisclaimer) { 429 | VStack { 430 | Text("You have disabled the safety checker").font(.title).padding(.top) 431 | Text(""" 432 | Please, ensure that you abide \ 433 | by the conditions of the Stable Diffusion license and do not expose \ 434 | unfiltered results to the public. 435 | """) 436 | .lineLimit(nil) 437 | .padding(.all, 5) 438 | Button { 439 | Settings.shared.safetyCheckerDisclaimerShown = true 440 | updateSafetyCheckerState() 441 | } label: { 442 | Text("I Accept").frame(maxWidth: 200) 443 | } 444 | .padding(.bottom) 445 | } 446 | .frame(minWidth: 400, idealWidth: 400, maxWidth: 400) 447 | .fixedSize() 448 | } 449 | Divider() 450 | 451 | StatusView(pipelineState: $pipelineState) 452 | } 453 | .padding() 454 | .onAppear { 455 | modelDidChange(model: ModelInfo.from(modelVersion: model) ?? ModelInfo.v2Base) 456 | } 457 | } 458 | 459 | fileprivate func discloseSeedContent() -> some View { 460 | let seedBinding = Binding( 461 | get: { 462 | String(generation.seed) 463 | }, 464 | set: { newValue in 465 | if let seed = UInt32(newValue) { 466 | generation.seed = seed 467 | Settings.shared.seed = seed 468 | } else { 469 | generation.seed = 0 470 | Settings.shared.seed = 0 471 | } 472 | } 473 | ) 474 | 475 | return HStack { 476 | TextField("", text: seedBinding) 477 | .multilineTextAlignment(.trailing) 478 | .onChange(of: seedBinding.wrappedValue, perform: { newValue in 479 | if let seed = UInt32(newValue) { 480 | generation.seed = seed 481 | Settings.shared.seed = seed 482 | } else { 483 | generation.seed = 0 484 | Settings.shared.seed = 0 485 | } 486 | }) 487 | .onReceive(Just(seedBinding.wrappedValue)) { newValue in 488 | let filtered = newValue.filter { "0123456789".contains($0) } 489 | if filtered != newValue { 490 | seedBinding.wrappedValue = filtered 491 | } 492 | } 493 | Stepper("", value: $generation.seed, in: 0...UInt32.max) 494 | } 495 | } 496 | } 497 | -------------------------------------------------------------------------------- /Diffusion-macOS/DiffusionImage+macOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiffusionImage+macOS.swift 3 | // Diffusion-macOS 4 | // 5 | // Created by Dolmere and Pedro Cuenca on 30/07/2023. 6 | // 7 | 8 | import SwiftUI 9 | import UniformTypeIdentifiers 10 | 11 | extension DiffusionImage { 12 | 13 | /// Instance func to place the generated image on the file system and return the `fileURL` where it is stored. 14 | func save(cgImage: CGImage, filename: String?) -> URL? { 15 | 16 | let nsImage = NSImage(cgImage: cgImage, size: NSSize(width: cgImage.width, height: cgImage.height)) 17 | 18 | 19 | let appSupportURL = Settings.shared.tempStorageURL() 20 | let fn = filename ?? "diffusion_generated_image" 21 | let fileURL = appSupportURL 22 | .appendingPathComponent(fn) 23 | .appendingPathExtension("png") 24 | 25 | // Save the image as a temporary file 26 | if let tiffData = nsImage.tiffRepresentation, 27 | let bitmap = NSBitmapImageRep(data: tiffData), 28 | let pngData = bitmap.representation(using: .png, properties: [:]) { 29 | do { 30 | try pngData.write(to: fileURL) 31 | return fileURL 32 | } catch { 33 | print("Error saving image to temporary file: \(error)") 34 | } 35 | } 36 | return nil 37 | } 38 | 39 | /// Returns a `Data` representation of this generated image in PNG format or nil if there is an error converting the image data. 40 | func pngRepresentation() -> Data? { 41 | let bitmapRep = NSBitmapImageRep(cgImage: cgImage) 42 | return bitmapRep.representation(using: .png, properties: [:]) 43 | } 44 | } 45 | 46 | extension DiffusionImage: NSItemProviderWriting { 47 | 48 | // MARK: - NSItemProviderWriting 49 | 50 | static var writableTypeIdentifiersForItemProvider: [String] { 51 | return [UTType.data.identifier, UTType.png.identifier, UTType.fileURL.identifier] 52 | } 53 | 54 | func itemProviderVisibilityForRepresentation(withTypeIdentifier typeIdentifier: String) -> NSItemProviderRepresentationVisibility { 55 | return .all 56 | } 57 | 58 | func itemProviderRepresentation(forTypeIdentifier typeIdentifier: String) throws -> NSItemProvider { 59 | print("itemProviderRepresentation(forTypeIdentifier") 60 | print(typeIdentifier) 61 | let data = try NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: true) 62 | let itemProvider = NSItemProvider() 63 | itemProvider.registerDataRepresentation(forTypeIdentifier: typeIdentifier, visibility: NSItemProviderRepresentationVisibility.all) { completion in 64 | completion(data, nil) 65 | return nil 66 | } 67 | return itemProvider 68 | } 69 | 70 | func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping @Sendable (Data?, Error?) -> Void) -> Progress? { 71 | if typeIdentifier == NSPasteboard.PasteboardType.fileURL.rawValue { 72 | let data = fileURL.dataRepresentation 73 | completionHandler(data, nil) 74 | } else if typeIdentifier == UTType.png.identifier { 75 | let data = pngRepresentation() 76 | completionHandler(data, nil) 77 | } else { 78 | // Indicate that the specified typeIdentifier is not supported 79 | let error = NSError(domain: "com.huggingface.diffusion", code: 0, userInfo: [NSLocalizedDescriptionKey: "Unsupported typeIdentifier"]) 80 | completionHandler(nil, error) 81 | } 82 | return nil 83 | } 84 | 85 | } 86 | 87 | extension DiffusionImage: NSPasteboardWriting { 88 | 89 | // MARK: - NSPasteboardWriting 90 | 91 | func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] { 92 | return [ 93 | NSPasteboard.PasteboardType.fileURL, 94 | NSPasteboard.PasteboardType(rawValue: UTType.png.identifier) 95 | ] 96 | } 97 | 98 | func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? { 99 | if type == NSPasteboard.PasteboardType.fileURL { 100 | 101 | // Return the file's data' representation 102 | return fileURL.dataRepresentation 103 | 104 | } else if type.rawValue == UTType.png.identifier { 105 | 106 | // Return a PNG data representation 107 | return pngRepresentation() 108 | } 109 | 110 | return nil 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Diffusion-macOS/Diffusion_macOS.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Diffusion-macOS/Diffusion_macOSApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Diffusion_macOSApp.swift 3 | // Diffusion-macOS 4 | // 5 | // Created by Cyril Zakka on 1/12/23. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import SwiftUI 10 | 11 | @main 12 | struct Diffusion_macOSApp: App { 13 | var body: some Scene { 14 | WindowGroup { 15 | ContentView() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Diffusion-macOS/GeneratedImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GeneratedImageView.swift 3 | // Diffusion 4 | // 5 | // Created by Pedro Cuenca on 18/1/23. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct GeneratedImageView: View { 12 | @EnvironmentObject var generation: GenerationContext 13 | 14 | var body: some View { 15 | switch generation.state { 16 | case .startup: return AnyView(Image("placeholder").resizable()) 17 | case .running(let progress): 18 | guard let progress = progress, progress.stepCount > 0 else { 19 | // The first time it takes a little bit before generation starts 20 | return AnyView(ProgressView()) 21 | } 22 | 23 | let step = Int(progress.step) + 1 24 | let fraction = Double(step) / Double(progress.stepCount) 25 | let label = "Step \(step) of \(progress.stepCount)" 26 | 27 | return AnyView(VStack { 28 | Group { 29 | if let safeImage = generation.previewImage { 30 | Image(safeImage, scale: 1, label: Text("generated")) 31 | .resizable() 32 | .clipShape(RoundedRectangle(cornerRadius: 20)) 33 | } 34 | } 35 | HStack { 36 | ProgressView(label, value: fraction, total: 1).padding() 37 | Button { 38 | generation.cancelGeneration() 39 | } label: { 40 | Image(systemName: "x.circle.fill").foregroundColor(.gray) 41 | } 42 | .buttonStyle(.plain) 43 | } 44 | }) 45 | case .complete(_, let image, _, _): 46 | guard let theImage = image else { 47 | return AnyView(Image(systemName: "exclamationmark.triangle").resizable()) 48 | } 49 | 50 | return AnyView( 51 | Image(theImage, scale: 1, label: Text("generated")) 52 | .resizable() 53 | .clipShape(RoundedRectangle(cornerRadius: 20)) 54 | .contextMenu { 55 | Button { 56 | NSPasteboard.general.clearContents() 57 | let nsimage = NSImage(cgImage: theImage, size: NSSize(width: theImage.width, height: theImage.height)) 58 | NSPasteboard.general.writeObjects([nsimage]) 59 | } label: { 60 | Text("Copy Photo") 61 | } 62 | } 63 | ) 64 | case .failed(_): 65 | return AnyView(Image(systemName: "exclamationmark.triangle").resizable()) 66 | case .userCanceled: 67 | return AnyView(Text("Generation canceled")) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Diffusion-macOS/HelpContent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HelpContent.swift 3 | // Diffusion-macOS 4 | // 5 | // Created by Pedro Cuenca on 7/2/23. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import SwiftUI 10 | 11 | func helpContent(title: String, description: Text, showing: Binding, width: Double = 400) -> some View { 12 | VStack { 13 | Text(title) 14 | .font(.title3) 15 | .padding(.top, 10) 16 | .padding(.all, 5) 17 | description 18 | .lineLimit(nil) 19 | .padding(.bottom, 5) 20 | .padding([.leading, .trailing], 15) 21 | Button { 22 | showing.wrappedValue.toggle() 23 | } label: { 24 | Text("Dismiss").frame(maxWidth: 200) 25 | } 26 | .padding(.bottom) 27 | } 28 | .frame(minWidth: width, idealWidth: width, maxWidth: width) 29 | } 30 | 31 | func helpContent(title: String, description: String, showing: Binding, width: Double = 400) -> some View { 32 | helpContent(title: title, description: Text(description), showing: showing) 33 | } 34 | 35 | func helpContent(title: String, description: AttributedString, showing: Binding, width: Double = 400) -> some View { 36 | helpContent(title: title, description: Text(description), showing: showing) 37 | } 38 | 39 | 40 | func modelsHelp(_ showing: Binding) -> some View { 41 | let description = try! AttributedString(markdown: 42 | """ 43 | Diffusers launches with a set of 5 models that can be downloaded from the Hugging Face Hub: 44 | 45 | **[Stable Diffusion 1.4](https://huggingface.co/CompVis/stable-diffusion-v1-4)** 46 | 47 | This is the original Stable Diffusion model that changed the landscape of AI image generation. For more details, visit the [model card](https://huggingface.co/CompVis/stable-diffusion-v1-4) or click on the title above. 48 | 49 | **[Stable Diffusion 1.5](https://huggingface.co/runwayml/stable-diffusion-v1-5)** 50 | 51 | Same architecture as 1.4, but trained on additional images with a focus on aesthetics. 52 | 53 | **[Stable Diffusion 2](https://huggingface.co/StabilityAI/stable-diffusion-2-base)** 54 | 55 | Improved model, heavily retrained on millions of additional images. This version corresponds to the [`stable-diffusion-2-base`](https://huggingface.co/StabilityAI/stable-diffusion-2-base) version of the model (trained on 512 x 512 images). 56 | 57 | **[Stable Diffusion 2.1](https://huggingface.co/stabilityai/stable-diffusion-2-1-base)** 58 | 59 | The last reference in the Stable Diffusion family. Works great with _negative prompts_. 60 | 61 | **[OFA small v0](https://huggingface.co/OFA-Sys/small-stable-diffusion-v0)** 62 | 63 | This is a special so-called _distilled_ model, half the size of the others. It runs faster and requires less RAM, try it out if you find generation slow! 64 | 65 | """, options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace)) 66 | return helpContent(title: "Available Models", description: description, showing: showing, width: 600) 67 | } 68 | 69 | func promptsHelp(_ showing: Binding) -> some View { 70 | let description = try! AttributedString(markdown: 71 | """ 72 | **Prompt** is the description of what you want, and **negative prompt** is what you _don't want_. 73 | 74 | Use the negative prompt to tweak a previous generation (by removing unwanted items), or to provide hints for the model. 75 | 76 | Many people like to use negative prompts such as "ugly, bad quality" to make the model try harder. \ 77 | Or consider excluding terms like "3d" or "realistic" if you're after particular drawing styles. 78 | 79 | """, options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace)) 80 | return helpContent(title: "Prompt and Negative Prompt", description: description, showing: showing, width: 600) 81 | } 82 | 83 | func guidanceHelp(_ showing: Binding) -> some View { 84 | let description = 85 | """ 86 | Indicates how much the image should resemble the prompt. 87 | 88 | Low values produce more varied results, while excessively high ones \ 89 | may result in image artifacts such as posterization. 90 | 91 | Values between 7 and 10 are usually good choices, but they affect \ 92 | differently to different models. 93 | 94 | Feel free to experiment! 95 | """ 96 | return helpContent(title: "Guidance Scale", description: description, showing: showing) 97 | } 98 | 99 | func stepsHelp(_ showing: Binding) -> some View { 100 | let description = 101 | """ 102 | How many times to go through the diffusion process. 103 | 104 | Quality increases the more steps you choose, but marginal improvements \ 105 | get increasingly smaller. 106 | 107 | 🧨 Diffusers currently uses the super efficient DPM Solver scheduler, \ 108 | which produces great results in just 20 or 25 steps 🤯 109 | """ 110 | return helpContent(title: "Inference Steps", description: description, showing: showing) 111 | } 112 | 113 | func previewHelp(_ showing: Binding) -> some View { 114 | let description = 115 | """ 116 | This number controls how many previews to display throughout the image generation process. 117 | 118 | Using more previews can be useful if you want more visibility into how \ 119 | generation is progressing. 120 | 121 | However, computing each preview takes some time and can slow down \ 122 | generation. If the process is too slow you can reduce the preview count, \ 123 | which will result in less visibility of intermediate steps during generation. 124 | 125 | You can try different values to see what works best for your hardware. 126 | 127 | For the absolute fastest generation times, use 0 previews. 128 | """ 129 | return helpContent(title: "Preview Count", description: description, showing: showing) 130 | } 131 | 132 | func seedHelp(_ showing: Binding) -> some View { 133 | let description = 134 | """ 135 | This is a number that allows you to reproduce a previous generation. 136 | 137 | Use it like this: select a seed and write a prompt, then generate an image. \ 138 | Next, maybe add a negative prompt or tweak the prompt slightly, and see how the result changes. \ 139 | Rinse and repeat until you are satisfied, or select a new seed to start over. 140 | 141 | Set the value to 0 for a random seed to be chosen for you. 142 | """ 143 | return helpContent(title: "Generation Seed", description: description, showing: showing) 144 | } 145 | 146 | func advancedHelp(_ showing: Binding) -> some View { 147 | let description = 148 | """ 149 | This section allows you to try different optimization settings. 150 | 151 | Diffusers will try to select the best configuration for you, but it may not always be optimal \ 152 | for your computer. You can experiment with these settings to verify the combination that works faster \ 153 | in your system. 154 | 155 | Please, note that these settings may trigger downloads of additional model variants. 156 | """ 157 | return helpContent(title: "Advanced Model Settings", description: description, showing: showing) 158 | } 159 | -------------------------------------------------------------------------------- /Diffusion-macOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ITSAppUsesNonExemptEncryption 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Diffusion-macOS/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Diffusion-macOS/StatusView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatusView.swift 3 | // Diffusion-macOS 4 | // 5 | // Created by Cyril Zakka on 1/12/23. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct StatusView: View { 12 | @EnvironmentObject var generation: GenerationContext 13 | var pipelineState: Binding 14 | 15 | @State private var showErrorPopover = false 16 | 17 | func submit() { 18 | if case .running = generation.state { return } 19 | Task { 20 | generation.state = .running(nil) 21 | do { 22 | let result = try await generation.generate() 23 | if result.userCanceled { 24 | generation.state = .userCanceled 25 | } else { 26 | generation.state = .complete(generation.positivePrompt, result.image, result.lastSeed, result.interval) 27 | } 28 | } catch { 29 | generation.state = .failed(error) 30 | } 31 | } 32 | } 33 | 34 | func errorWithDetails(_ message: String, error: Error) -> any View { 35 | HStack { 36 | Text(message) 37 | Spacer() 38 | Button { 39 | showErrorPopover.toggle() 40 | } label: { 41 | Image(systemName: "info.circle") 42 | }.buttonStyle(.plain) 43 | .popover(isPresented: $showErrorPopover) { 44 | VStack { 45 | Text(verbatim: "\(error)") 46 | .lineLimit(nil) 47 | .padding(.all, 5) 48 | Button { 49 | showErrorPopover.toggle() 50 | } label: { 51 | Text("Dismiss").frame(maxWidth: 200) 52 | } 53 | .padding(.bottom) 54 | } 55 | .frame(minWidth: 400, idealWidth: 400, maxWidth: 400) 56 | .fixedSize() 57 | } 58 | } 59 | } 60 | 61 | func generationStatusView() -> any View { 62 | switch generation.state { 63 | case .startup: return EmptyView() 64 | case .running(let progress): 65 | guard let progress = progress, progress.stepCount > 0 else { 66 | // The first time it takes a little bit before generation starts 67 | return HStack { 68 | Text("Preparing model…") 69 | Spacer() 70 | } 71 | } 72 | let step = Int(progress.step) + 1 73 | let fraction = Double(step) / Double(progress.stepCount) 74 | return HStack { 75 | Text("Generating \(Int(round(100*fraction)))%") 76 | Spacer() 77 | } 78 | case .complete(_, let image, let lastSeed, let interval): 79 | guard let _ = image else { 80 | return HStack { 81 | Text("Safety checker triggered, please try a different prompt or seed.") 82 | Spacer() 83 | } 84 | } 85 | 86 | return HStack { 87 | let intervalString = String(format: "Time: %.1fs", interval ?? 0) 88 | Text(intervalString) 89 | Spacer() 90 | if generation.seed != lastSeed { 91 | 92 | Text(String("Seed: \(formatLargeNumber(lastSeed))")) 93 | Button("Set") { 94 | generation.seed = lastSeed 95 | } 96 | } 97 | }.frame(maxHeight: 25) 98 | case .failed(let error): 99 | return errorWithDetails("Generation error", error: error) 100 | case .userCanceled: 101 | return HStack { 102 | Text("Generation canceled.") 103 | Spacer() 104 | } 105 | } 106 | } 107 | 108 | var body: some View { 109 | switch pipelineState.wrappedValue { 110 | case .downloading(let progress): 111 | ProgressView("Downloading…", value: progress*100, total: 110).padding() 112 | case .uncompressing: 113 | ProgressView("Uncompressing…", value: 100, total: 110).padding() 114 | case .loading: 115 | ProgressView("Loading…", value: 105, total: 110).padding() 116 | case .ready: 117 | VStack { 118 | Button { 119 | submit() 120 | } label: { 121 | Text("Generate") 122 | .frame(maxWidth: .infinity) 123 | .frame(height: 50) 124 | } 125 | .buttonStyle(.borderedProminent) 126 | 127 | AnyView(generationStatusView()) 128 | } 129 | case .failed(let error): 130 | AnyView(errorWithDetails("Pipeline loading error", error: error)) 131 | } 132 | } 133 | } 134 | 135 | struct StatusView_Previews: PreviewProvider { 136 | static var previews: some View { 137 | StatusView(pipelineState: .constant(.downloading(0.2))) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Diffusion-macOS/Utils_macOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils_macOS.swift 3 | // Diffusion-macOS 4 | // 5 | // Created by Dolmere on 31/07/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension CGImage { 11 | static func fromData(_ imageData: Data) -> CGImage? { 12 | if let image = NSBitmapImageRep(data: imageData)?.cgImage { 13 | return image 14 | } 15 | return nil 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Diffusion.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 16AFDD4F2C1B7D6200536A62 /* StableDiffusion in Frameworks */ = {isa = PBXBuildFile; productRef = 16AFDD4E2C1B7D6200536A62 /* StableDiffusion */; }; 11 | 16AFDD512C1B7D6700536A62 /* StableDiffusion in Frameworks */ = {isa = PBXBuildFile; productRef = 16AFDD502C1B7D6700536A62 /* StableDiffusion */; }; 12 | 8C4B32042A770C1D0090EF17 /* DiffusionImage+macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4B32032A770C1D0090EF17 /* DiffusionImage+macOS.swift */; }; 13 | 8C4B32062A770C300090EF17 /* DiffusionImage+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4B32052A770C300090EF17 /* DiffusionImage+iOS.swift */; }; 14 | 8C4B32082A77F90C0090EF17 /* Utils_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4B32072A77F90C0090EF17 /* Utils_iOS.swift */; }; 15 | 8C4B320A2A77F9160090EF17 /* Utils_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4B32092A77F9160090EF17 /* Utils_macOS.swift */; }; 16 | 8CD8A53A2A456EF800BD8A98 /* PromptTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CD8A5392A456EF800BD8A98 /* PromptTextField.swift */; }; 17 | 8CD8A53C2A476E2C00BD8A98 /* PromptTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CD8A5392A456EF800BD8A98 /* PromptTextField.swift */; }; 18 | 8CEEB7D92A54C88C00C23829 /* DiffusionImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CEEB7D82A54C88C00C23829 /* DiffusionImage.swift */; }; 19 | 8CEEB7DA2A54C88C00C23829 /* DiffusionImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CEEB7D82A54C88C00C23829 /* DiffusionImage.swift */; }; 20 | EB067F872992E561004D1AD9 /* HelpContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB067F862992E561004D1AD9 /* HelpContent.swift */; }; 21 | EB560F0429A3C20800C0F8B8 /* Capabilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB560F0329A3C20800C0F8B8 /* Capabilities.swift */; }; 22 | EBB5BA5329425BEE003A2A5B /* PipelineLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB5BA5229425BEE003A2A5B /* PipelineLoader.swift */; }; 23 | EBB5BA5A29426E06003A2A5B /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB5BA5929426E06003A2A5B /* Downloader.swift */; }; 24 | EBB5BA5D294504DE003A2A5B /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = EBB5BA5C294504DE003A2A5B /* ZIPFoundation */; }; 25 | EBDD7DAA29731F6C00C1C4B2 /* Pipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE75601293E91E200806B32 /* Pipeline.swift */; }; 26 | EBDD7DAB29731F7500C1C4B2 /* PipelineLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB5BA5229425BEE003A2A5B /* PipelineLoader.swift */; }; 27 | EBDD7DAF29731FB300C1C4B2 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = EBDD7DAE29731FB300C1C4B2 /* ZIPFoundation */; }; 28 | EBDD7DB32973200200C1C4B2 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBDD7DB22973200200C1C4B2 /* Utils.swift */; }; 29 | EBDD7DB42973200200C1C4B2 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBDD7DB22973200200C1C4B2 /* Utils.swift */; }; 30 | EBDD7DB52973201800C1C4B2 /* ModelInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE3FF4B295E1EFE00E921AA /* ModelInfo.swift */; }; 31 | EBDD7DB62973206600C1C4B2 /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB5BA5929426E06003A2A5B /* Downloader.swift */; }; 32 | EBDD7DB82976AAFE00C1C4B2 /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBDD7DB72976AAFE00C1C4B2 /* State.swift */; }; 33 | EBDD7DB92976AAFE00C1C4B2 /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBDD7DB72976AAFE00C1C4B2 /* State.swift */; }; 34 | EBDD7DBD2977FFB300C1C4B2 /* GeneratedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBDD7DBB2977FFB300C1C4B2 /* GeneratedImageView.swift */; }; 35 | EBDD7DC02978642200C1C4B2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EBE755CC293E37DD00806B32 /* Assets.xcassets */; }; 36 | EBE3FF4C295E1EFE00E921AA /* ModelInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE3FF4B295E1EFE00E921AA /* ModelInfo.swift */; }; 37 | EBE755C9293E37DD00806B32 /* DiffusionApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE755C8293E37DD00806B32 /* DiffusionApp.swift */; }; 38 | EBE755CB293E37DD00806B32 /* TextToImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE755CA293E37DD00806B32 /* TextToImage.swift */; }; 39 | EBE755CD293E37DD00806B32 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EBE755CC293E37DD00806B32 /* Assets.xcassets */; }; 40 | EBE755D1293E37DD00806B32 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EBE755D0293E37DD00806B32 /* Preview Assets.xcassets */; }; 41 | EBE755DB293E37DE00806B32 /* DiffusionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE755DA293E37DE00806B32 /* DiffusionTests.swift */; }; 42 | EBE755E5293E37DE00806B32 /* DiffusionUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE755E4293E37DE00806B32 /* DiffusionUITests.swift */; }; 43 | EBE755E7293E37DE00806B32 /* DiffusionUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE755E6293E37DE00806B32 /* DiffusionUITestsLaunchTests.swift */; }; 44 | EBE75602293E91E200806B32 /* Pipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE75601293E91E200806B32 /* Pipeline.swift */; }; 45 | EBE756092941178600806B32 /* Loading.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE756082941178600806B32 /* Loading.swift */; }; 46 | F15520242971093300DC009B /* Diffusion_macOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15520232971093300DC009B /* Diffusion_macOSApp.swift */; }; 47 | F15520262971093300DC009B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15520252971093300DC009B /* ContentView.swift */; }; 48 | F155202B2971093400DC009B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F155202A2971093400DC009B /* Preview Assets.xcassets */; }; 49 | F1552031297109C300DC009B /* ControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1552030297109C300DC009B /* ControlsView.swift */; }; 50 | F155203429710B3600DC009B /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F155203329710B3600DC009B /* StatusView.swift */; }; 51 | F155203C297118E700DC009B /* CompactSlider in Frameworks */ = {isa = PBXBuildFile; productRef = F155203B297118E700DC009B /* CompactSlider */; }; 52 | /* End PBXBuildFile section */ 53 | 54 | /* Begin PBXContainerItemProxy section */ 55 | EBE755D7293E37DE00806B32 /* PBXContainerItemProxy */ = { 56 | isa = PBXContainerItemProxy; 57 | containerPortal = EBE755BD293E37DD00806B32 /* Project object */; 58 | proxyType = 1; 59 | remoteGlobalIDString = EBE755C4293E37DD00806B32; 60 | remoteInfo = Diffusion; 61 | }; 62 | EBE755E1293E37DE00806B32 /* PBXContainerItemProxy */ = { 63 | isa = PBXContainerItemProxy; 64 | containerPortal = EBE755BD293E37DD00806B32 /* Project object */; 65 | proxyType = 1; 66 | remoteGlobalIDString = EBE755C4293E37DD00806B32; 67 | remoteInfo = Diffusion; 68 | }; 69 | /* End PBXContainerItemProxy section */ 70 | 71 | /* Begin PBXFileReference section */ 72 | 8C4B32032A770C1D0090EF17 /* DiffusionImage+macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiffusionImage+macOS.swift"; sourceTree = ""; }; 73 | 8C4B32052A770C300090EF17 /* DiffusionImage+iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiffusionImage+iOS.swift"; sourceTree = ""; }; 74 | 8C4B32072A77F90C0090EF17 /* Utils_iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils_iOS.swift; sourceTree = ""; }; 75 | 8C4B32092A77F9160090EF17 /* Utils_macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils_macOS.swift; sourceTree = ""; }; 76 | 8CD8A5392A456EF800BD8A98 /* PromptTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromptTextField.swift; sourceTree = ""; }; 77 | 8CEEB7D82A54C88C00C23829 /* DiffusionImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiffusionImage.swift; sourceTree = ""; }; 78 | EB067F862992E561004D1AD9 /* HelpContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpContent.swift; sourceTree = ""; }; 79 | EB33A51E2954E1BC00B16357 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 80 | EB560F0329A3C20800C0F8B8 /* Capabilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Capabilities.swift; sourceTree = ""; }; 81 | EB560F0529A4090D00C0F8B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 82 | EBB5BA5229425BEE003A2A5B /* PipelineLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PipelineLoader.swift; sourceTree = ""; }; 83 | EBB5BA5929426E06003A2A5B /* Downloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Downloader.swift; sourceTree = ""; }; 84 | EBDD7DB22973200200C1C4B2 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 85 | EBDD7DB72976AAFE00C1C4B2 /* State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = State.swift; sourceTree = ""; }; 86 | EBDD7DBA2976F03600C1C4B2 /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = config/debug.xcconfig; sourceTree = ""; }; 87 | EBDD7DBB2977FFB300C1C4B2 /* GeneratedImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneratedImageView.swift; sourceTree = ""; }; 88 | EBE3FF4A295DFE2400E921AA /* common.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = common.xcconfig; path = config/common.xcconfig; sourceTree = ""; }; 89 | EBE3FF4B295E1EFE00E921AA /* ModelInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelInfo.swift; sourceTree = ""; }; 90 | EBE4438729488DCA00CDA605 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 91 | EBE443892948953600CDA605 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 92 | EBE755C5293E37DD00806B32 /* Diffusion.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Diffusion.app; sourceTree = BUILT_PRODUCTS_DIR; }; 93 | EBE755C8293E37DD00806B32 /* DiffusionApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffusionApp.swift; sourceTree = ""; }; 94 | EBE755CA293E37DD00806B32 /* TextToImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextToImage.swift; sourceTree = ""; }; 95 | EBE755CC293E37DD00806B32 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 96 | EBE755CE293E37DD00806B32 /* Diffusion.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Diffusion.entitlements; sourceTree = ""; }; 97 | EBE755D0293E37DD00806B32 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 98 | EBE755D6293E37DE00806B32 /* DiffusionTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DiffusionTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 99 | EBE755DA293E37DE00806B32 /* DiffusionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffusionTests.swift; sourceTree = ""; }; 100 | EBE755E0293E37DE00806B32 /* DiffusionUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DiffusionUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 101 | EBE755E4293E37DE00806B32 /* DiffusionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffusionUITests.swift; sourceTree = ""; }; 102 | EBE755E6293E37DE00806B32 /* DiffusionUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffusionUITestsLaunchTests.swift; sourceTree = ""; }; 103 | EBE75601293E91E200806B32 /* Pipeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pipeline.swift; sourceTree = ""; }; 104 | EBE756082941178600806B32 /* Loading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Loading.swift; sourceTree = ""; }; 105 | F15520212971093300DC009B /* Diffusers.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Diffusers.app; sourceTree = BUILT_PRODUCTS_DIR; }; 106 | F15520232971093300DC009B /* Diffusion_macOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Diffusion_macOSApp.swift; sourceTree = ""; }; 107 | F15520252971093300DC009B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 108 | F155202A2971093400DC009B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 109 | F155202C2971093400DC009B /* Diffusion_macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Diffusion_macOS.entitlements; sourceTree = ""; }; 110 | F1552030297109C300DC009B /* ControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlsView.swift; sourceTree = ""; }; 111 | F155203329710B3600DC009B /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = ""; }; 112 | /* End PBXFileReference section */ 113 | 114 | /* Begin PBXFrameworksBuildPhase section */ 115 | EBE755C2293E37DD00806B32 /* Frameworks */ = { 116 | isa = PBXFrameworksBuildPhase; 117 | buildActionMask = 2147483647; 118 | files = ( 119 | EBB5BA5D294504DE003A2A5B /* ZIPFoundation in Frameworks */, 120 | 16AFDD512C1B7D6700536A62 /* StableDiffusion in Frameworks */, 121 | ); 122 | runOnlyForDeploymentPostprocessing = 0; 123 | }; 124 | EBE755D3293E37DE00806B32 /* Frameworks */ = { 125 | isa = PBXFrameworksBuildPhase; 126 | buildActionMask = 2147483647; 127 | files = ( 128 | ); 129 | runOnlyForDeploymentPostprocessing = 0; 130 | }; 131 | EBE755DD293E37DE00806B32 /* Frameworks */ = { 132 | isa = PBXFrameworksBuildPhase; 133 | buildActionMask = 2147483647; 134 | files = ( 135 | ); 136 | runOnlyForDeploymentPostprocessing = 0; 137 | }; 138 | F155201E2971093300DC009B /* Frameworks */ = { 139 | isa = PBXFrameworksBuildPhase; 140 | buildActionMask = 2147483647; 141 | files = ( 142 | F155203C297118E700DC009B /* CompactSlider in Frameworks */, 143 | 16AFDD4F2C1B7D6200536A62 /* StableDiffusion in Frameworks */, 144 | EBDD7DAF29731FB300C1C4B2 /* ZIPFoundation in Frameworks */, 145 | ); 146 | runOnlyForDeploymentPostprocessing = 0; 147 | }; 148 | /* End PBXFrameworksBuildPhase section */ 149 | 150 | /* Begin PBXGroup section */ 151 | 8CD8A53B2A476E1C00BD8A98 /* Views */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | 8CD8A5392A456EF800BD8A98 /* PromptTextField.swift */, 155 | ); 156 | path = Views; 157 | sourceTree = ""; 158 | }; 159 | 8CF53E022A44AE0400E6358B /* Common */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | EBB5BA5929426E06003A2A5B /* Downloader.swift */, 163 | EBE3FF4B295E1EFE00E921AA /* ModelInfo.swift */, 164 | EBDD7DB72976AAFE00C1C4B2 /* State.swift */, 165 | EBDD7DB22973200200C1C4B2 /* Utils.swift */, 166 | 8CEEB7D82A54C88C00C23829 /* DiffusionImage.swift */, 167 | EBB5BA5129425B07003A2A5B /* Pipeline */, 168 | 8CD8A53B2A476E1C00BD8A98 /* Views */, 169 | ); 170 | name = Common; 171 | path = Diffusion/Common; 172 | sourceTree = ""; 173 | }; 174 | EBB5BA5129425B07003A2A5B /* Pipeline */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | EBE75601293E91E200806B32 /* Pipeline.swift */, 178 | EBB5BA5229425BEE003A2A5B /* PipelineLoader.swift */, 179 | ); 180 | path = Pipeline; 181 | sourceTree = ""; 182 | }; 183 | EBE755BC293E37DD00806B32 = { 184 | isa = PBXGroup; 185 | children = ( 186 | EBE3FF4A295DFE2400E921AA /* common.xcconfig */, 187 | EBDD7DBA2976F03600C1C4B2 /* debug.xcconfig */, 188 | EBE4438729488DCA00CDA605 /* README.md */, 189 | EBE443892948953600CDA605 /* LICENSE */, 190 | EBE755FF293E910800806B32 /* Packages */, 191 | 8CF53E022A44AE0400E6358B /* Common */, 192 | EBE755C7293E37DD00806B32 /* Diffusion */, 193 | F15520222971093300DC009B /* Diffusion-macOS */, 194 | EBE755D9293E37DE00806B32 /* DiffusionTests */, 195 | EBE755E3293E37DE00806B32 /* DiffusionUITests */, 196 | EBE755C6293E37DD00806B32 /* Products */, 197 | EBE75603293E93BB00806B32 /* Frameworks */, 198 | ); 199 | sourceTree = ""; 200 | }; 201 | EBE755C6293E37DD00806B32 /* Products */ = { 202 | isa = PBXGroup; 203 | children = ( 204 | EBE755C5293E37DD00806B32 /* Diffusion.app */, 205 | EBE755D6293E37DE00806B32 /* DiffusionTests.xctest */, 206 | EBE755E0293E37DE00806B32 /* DiffusionUITests.xctest */, 207 | F15520212971093300DC009B /* Diffusers.app */, 208 | ); 209 | name = Products; 210 | sourceTree = ""; 211 | }; 212 | EBE755C7293E37DD00806B32 /* Diffusion */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | EB33A51E2954E1BC00B16357 /* Info.plist */, 216 | EBE7560A29411A5E00806B32 /* Views */, 217 | EBE755C8293E37DD00806B32 /* DiffusionApp.swift */, 218 | EBE755CC293E37DD00806B32 /* Assets.xcassets */, 219 | EBE755CE293E37DD00806B32 /* Diffusion.entitlements */, 220 | EBE755CF293E37DD00806B32 /* Preview Content */, 221 | 8C4B32052A770C300090EF17 /* DiffusionImage+iOS.swift */, 222 | 8C4B32072A77F90C0090EF17 /* Utils_iOS.swift */, 223 | ); 224 | path = Diffusion; 225 | sourceTree = ""; 226 | }; 227 | EBE755CF293E37DD00806B32 /* Preview Content */ = { 228 | isa = PBXGroup; 229 | children = ( 230 | EBE755D0293E37DD00806B32 /* Preview Assets.xcassets */, 231 | ); 232 | path = "Preview Content"; 233 | sourceTree = ""; 234 | }; 235 | EBE755D9293E37DE00806B32 /* DiffusionTests */ = { 236 | isa = PBXGroup; 237 | children = ( 238 | EBE755DA293E37DE00806B32 /* DiffusionTests.swift */, 239 | ); 240 | path = DiffusionTests; 241 | sourceTree = ""; 242 | }; 243 | EBE755E3293E37DE00806B32 /* DiffusionUITests */ = { 244 | isa = PBXGroup; 245 | children = ( 246 | EBE755E4293E37DE00806B32 /* DiffusionUITests.swift */, 247 | EBE755E6293E37DE00806B32 /* DiffusionUITestsLaunchTests.swift */, 248 | ); 249 | path = DiffusionUITests; 250 | sourceTree = ""; 251 | }; 252 | EBE755FF293E910800806B32 /* Packages */ = { 253 | isa = PBXGroup; 254 | children = ( 255 | ); 256 | name = Packages; 257 | sourceTree = ""; 258 | }; 259 | EBE75603293E93BB00806B32 /* Frameworks */ = { 260 | isa = PBXGroup; 261 | children = ( 262 | ); 263 | name = Frameworks; 264 | sourceTree = ""; 265 | }; 266 | EBE7560A29411A5E00806B32 /* Views */ = { 267 | isa = PBXGroup; 268 | children = ( 269 | EBE756082941178600806B32 /* Loading.swift */, 270 | EBE755CA293E37DD00806B32 /* TextToImage.swift */, 271 | ); 272 | path = Views; 273 | sourceTree = ""; 274 | }; 275 | F15520222971093300DC009B /* Diffusion-macOS */ = { 276 | isa = PBXGroup; 277 | children = ( 278 | EB560F0529A4090D00C0F8B8 /* Info.plist */, 279 | F15520232971093300DC009B /* Diffusion_macOSApp.swift */, 280 | F15520252971093300DC009B /* ContentView.swift */, 281 | EBDD7DBB2977FFB300C1C4B2 /* GeneratedImageView.swift */, 282 | F1552030297109C300DC009B /* ControlsView.swift */, 283 | F155203329710B3600DC009B /* StatusView.swift */, 284 | EB067F862992E561004D1AD9 /* HelpContent.swift */, 285 | EB560F0329A3C20800C0F8B8 /* Capabilities.swift */, 286 | 8C4B32032A770C1D0090EF17 /* DiffusionImage+macOS.swift */, 287 | 8C4B32092A77F9160090EF17 /* Utils_macOS.swift */, 288 | F155202C2971093400DC009B /* Diffusion_macOS.entitlements */, 289 | F15520292971093400DC009B /* Preview Content */, 290 | ); 291 | path = "Diffusion-macOS"; 292 | sourceTree = ""; 293 | }; 294 | F15520292971093400DC009B /* Preview Content */ = { 295 | isa = PBXGroup; 296 | children = ( 297 | F155202A2971093400DC009B /* Preview Assets.xcassets */, 298 | ); 299 | path = "Preview Content"; 300 | sourceTree = ""; 301 | }; 302 | /* End PBXGroup section */ 303 | 304 | /* Begin PBXNativeTarget section */ 305 | EBE755C4293E37DD00806B32 /* Diffusion */ = { 306 | isa = PBXNativeTarget; 307 | buildConfigurationList = EBE755EA293E37DE00806B32 /* Build configuration list for PBXNativeTarget "Diffusion" */; 308 | buildPhases = ( 309 | EBE755C1293E37DD00806B32 /* Sources */, 310 | EBE755C2293E37DD00806B32 /* Frameworks */, 311 | EBE755C3293E37DD00806B32 /* Resources */, 312 | ); 313 | buildRules = ( 314 | ); 315 | dependencies = ( 316 | EBF61AB32A2F976600482CF3 /* PBXTargetDependency */, 317 | ); 318 | name = Diffusion; 319 | packageProductDependencies = ( 320 | EBB5BA5C294504DE003A2A5B /* ZIPFoundation */, 321 | 16AFDD502C1B7D6700536A62 /* StableDiffusion */, 322 | ); 323 | productName = Diffusion; 324 | productReference = EBE755C5293E37DD00806B32 /* Diffusion.app */; 325 | productType = "com.apple.product-type.application"; 326 | }; 327 | EBE755D5293E37DE00806B32 /* DiffusionTests */ = { 328 | isa = PBXNativeTarget; 329 | buildConfigurationList = EBE755ED293E37DE00806B32 /* Build configuration list for PBXNativeTarget "DiffusionTests" */; 330 | buildPhases = ( 331 | EBE755D2293E37DE00806B32 /* Sources */, 332 | EBE755D3293E37DE00806B32 /* Frameworks */, 333 | EBE755D4293E37DE00806B32 /* Resources */, 334 | ); 335 | buildRules = ( 336 | ); 337 | dependencies = ( 338 | EBE755D8293E37DE00806B32 /* PBXTargetDependency */, 339 | ); 340 | name = DiffusionTests; 341 | productName = DiffusionTests; 342 | productReference = EBE755D6293E37DE00806B32 /* DiffusionTests.xctest */; 343 | productType = "com.apple.product-type.bundle.unit-test"; 344 | }; 345 | EBE755DF293E37DE00806B32 /* DiffusionUITests */ = { 346 | isa = PBXNativeTarget; 347 | buildConfigurationList = EBE755F0293E37DE00806B32 /* Build configuration list for PBXNativeTarget "DiffusionUITests" */; 348 | buildPhases = ( 349 | EBE755DC293E37DE00806B32 /* Sources */, 350 | EBE755DD293E37DE00806B32 /* Frameworks */, 351 | EBE755DE293E37DE00806B32 /* Resources */, 352 | ); 353 | buildRules = ( 354 | ); 355 | dependencies = ( 356 | EBE755E2293E37DE00806B32 /* PBXTargetDependency */, 357 | ); 358 | name = DiffusionUITests; 359 | productName = DiffusionUITests; 360 | productReference = EBE755E0293E37DE00806B32 /* DiffusionUITests.xctest */; 361 | productType = "com.apple.product-type.bundle.ui-testing"; 362 | }; 363 | F15520202971093300DC009B /* Diffusion-macOS */ = { 364 | isa = PBXNativeTarget; 365 | buildConfigurationList = F155202F2971093400DC009B /* Build configuration list for PBXNativeTarget "Diffusion-macOS" */; 366 | buildPhases = ( 367 | EB7FE72529954BFF009056BD /* ShellScript */, 368 | F155201D2971093300DC009B /* Sources */, 369 | F155201E2971093300DC009B /* Frameworks */, 370 | F155201F2971093300DC009B /* Resources */, 371 | ); 372 | buildRules = ( 373 | ); 374 | dependencies = ( 375 | EB0199492A31FEAF00B133E2 /* PBXTargetDependency */, 376 | ); 377 | name = "Diffusion-macOS"; 378 | packageProductDependencies = ( 379 | F155203B297118E700DC009B /* CompactSlider */, 380 | EBDD7DAE29731FB300C1C4B2 /* ZIPFoundation */, 381 | 16AFDD4E2C1B7D6200536A62 /* StableDiffusion */, 382 | ); 383 | productName = "Diffusion-macOS"; 384 | productReference = F15520212971093300DC009B /* Diffusers.app */; 385 | productType = "com.apple.product-type.application"; 386 | }; 387 | /* End PBXNativeTarget section */ 388 | 389 | /* Begin PBXProject section */ 390 | EBE755BD293E37DD00806B32 /* Project object */ = { 391 | isa = PBXProject; 392 | attributes = { 393 | BuildIndependentTargetsInParallel = 1; 394 | LastSwiftUpdateCheck = 1420; 395 | LastUpgradeCheck = 1410; 396 | TargetAttributes = { 397 | EBE755C4293E37DD00806B32 = { 398 | CreatedOnToolsVersion = 14.1; 399 | }; 400 | EBE755D5293E37DE00806B32 = { 401 | CreatedOnToolsVersion = 14.1; 402 | TestTargetID = EBE755C4293E37DD00806B32; 403 | }; 404 | EBE755DF293E37DE00806B32 = { 405 | CreatedOnToolsVersion = 14.1; 406 | TestTargetID = EBE755C4293E37DD00806B32; 407 | }; 408 | F15520202971093300DC009B = { 409 | CreatedOnToolsVersion = 14.2; 410 | }; 411 | }; 412 | }; 413 | buildConfigurationList = EBE755C0293E37DD00806B32 /* Build configuration list for PBXProject "Diffusion" */; 414 | compatibilityVersion = "Xcode 14.0"; 415 | developmentRegion = en; 416 | hasScannedForEncodings = 0; 417 | knownRegions = ( 418 | en, 419 | Base, 420 | ); 421 | mainGroup = EBE755BC293E37DD00806B32; 422 | packageReferences = ( 423 | EBB5BA5B294504DE003A2A5B /* XCRemoteSwiftPackageReference "ZIPFoundation" */, 424 | F155203A297118E600DC009B /* XCRemoteSwiftPackageReference "CompactSlider" */, 425 | 16AFDD4D2C1B7D4800536A62 /* XCRemoteSwiftPackageReference "ml-stable-diffusion" */, 426 | ); 427 | productRefGroup = EBE755C6293E37DD00806B32 /* Products */; 428 | projectDirPath = ""; 429 | projectRoot = ""; 430 | targets = ( 431 | EBE755C4293E37DD00806B32 /* Diffusion */, 432 | EBE755D5293E37DE00806B32 /* DiffusionTests */, 433 | EBE755DF293E37DE00806B32 /* DiffusionUITests */, 434 | F15520202971093300DC009B /* Diffusion-macOS */, 435 | ); 436 | }; 437 | /* End PBXProject section */ 438 | 439 | /* Begin PBXResourcesBuildPhase section */ 440 | EBE755C3293E37DD00806B32 /* Resources */ = { 441 | isa = PBXResourcesBuildPhase; 442 | buildActionMask = 2147483647; 443 | files = ( 444 | EBE755D1293E37DD00806B32 /* Preview Assets.xcassets in Resources */, 445 | EBE755CD293E37DD00806B32 /* Assets.xcassets in Resources */, 446 | ); 447 | runOnlyForDeploymentPostprocessing = 0; 448 | }; 449 | EBE755D4293E37DE00806B32 /* Resources */ = { 450 | isa = PBXResourcesBuildPhase; 451 | buildActionMask = 2147483647; 452 | files = ( 453 | ); 454 | runOnlyForDeploymentPostprocessing = 0; 455 | }; 456 | EBE755DE293E37DE00806B32 /* Resources */ = { 457 | isa = PBXResourcesBuildPhase; 458 | buildActionMask = 2147483647; 459 | files = ( 460 | ); 461 | runOnlyForDeploymentPostprocessing = 0; 462 | }; 463 | F155201F2971093300DC009B /* Resources */ = { 464 | isa = PBXResourcesBuildPhase; 465 | buildActionMask = 2147483647; 466 | files = ( 467 | EBDD7DC02978642200C1C4B2 /* Assets.xcassets in Resources */, 468 | F155202B2971093400DC009B /* Preview Assets.xcassets in Resources */, 469 | ); 470 | runOnlyForDeploymentPostprocessing = 0; 471 | }; 472 | /* End PBXResourcesBuildPhase section */ 473 | 474 | /* Begin PBXShellScriptBuildPhase section */ 475 | EB7FE72529954BFF009056BD /* ShellScript */ = { 476 | isa = PBXShellScriptBuildPhase; 477 | alwaysOutOfDate = 1; 478 | buildActionMask = 12; 479 | files = ( 480 | ); 481 | inputFileListPaths = ( 482 | ); 483 | inputPaths = ( 484 | ); 485 | outputFileListPaths = ( 486 | ); 487 | outputPaths = ( 488 | ); 489 | runOnlyForDeploymentPostprocessing = 0; 490 | shellPath = /bin/sh; 491 | shellScript = "if [ \"$CONFIGURATION\" != \"Debug\" ]\nthen \n version=`date +%Y%m%d.%H%M%S`\n CONFIG_FILE=${SRCROOT}/config/common.xcconfig\n sed -e 's/^CURRENT_PROJECT_VERSION.*/CURRENT_PROJECT_VERSION = '\"$version\"'/' ${CONFIG_FILE} > ${CONFIG_FILE}.tmp\n [[ -s ${CONFIG_FILE}.tmp ]] && {\n mv ${CONFIG_FILE}.tmp ${CONFIG_FILE}\n }\nfi\n"; 492 | }; 493 | /* End PBXShellScriptBuildPhase section */ 494 | 495 | /* Begin PBXSourcesBuildPhase section */ 496 | EBE755C1293E37DD00806B32 /* Sources */ = { 497 | isa = PBXSourcesBuildPhase; 498 | buildActionMask = 2147483647; 499 | files = ( 500 | EBE75602293E91E200806B32 /* Pipeline.swift in Sources */, 501 | EBE755CB293E37DD00806B32 /* TextToImage.swift in Sources */, 502 | EBB5BA5A29426E06003A2A5B /* Downloader.swift in Sources */, 503 | 8C4B32062A770C300090EF17 /* DiffusionImage+iOS.swift in Sources */, 504 | 8CEEB7D92A54C88C00C23829 /* DiffusionImage.swift in Sources */, 505 | EBE3FF4C295E1EFE00E921AA /* ModelInfo.swift in Sources */, 506 | EBE756092941178600806B32 /* Loading.swift in Sources */, 507 | 8C4B32082A77F90C0090EF17 /* Utils_iOS.swift in Sources */, 508 | EBDD7DB82976AAFE00C1C4B2 /* State.swift in Sources */, 509 | EBB5BA5329425BEE003A2A5B /* PipelineLoader.swift in Sources */, 510 | 8CD8A53C2A476E2C00BD8A98 /* PromptTextField.swift in Sources */, 511 | EBE755C9293E37DD00806B32 /* DiffusionApp.swift in Sources */, 512 | EBDD7DB32973200200C1C4B2 /* Utils.swift in Sources */, 513 | ); 514 | runOnlyForDeploymentPostprocessing = 0; 515 | }; 516 | EBE755D2293E37DE00806B32 /* Sources */ = { 517 | isa = PBXSourcesBuildPhase; 518 | buildActionMask = 2147483647; 519 | files = ( 520 | EBE755DB293E37DE00806B32 /* DiffusionTests.swift in Sources */, 521 | ); 522 | runOnlyForDeploymentPostprocessing = 0; 523 | }; 524 | EBE755DC293E37DE00806B32 /* Sources */ = { 525 | isa = PBXSourcesBuildPhase; 526 | buildActionMask = 2147483647; 527 | files = ( 528 | EBE755E7293E37DE00806B32 /* DiffusionUITestsLaunchTests.swift in Sources */, 529 | EBE755E5293E37DE00806B32 /* DiffusionUITests.swift in Sources */, 530 | ); 531 | runOnlyForDeploymentPostprocessing = 0; 532 | }; 533 | F155201D2971093300DC009B /* Sources */ = { 534 | isa = PBXSourcesBuildPhase; 535 | buildActionMask = 2147483647; 536 | files = ( 537 | EBDD7DAB29731F7500C1C4B2 /* PipelineLoader.swift in Sources */, 538 | EBDD7DAA29731F6C00C1C4B2 /* Pipeline.swift in Sources */, 539 | F15520262971093300DC009B /* ContentView.swift in Sources */, 540 | EBDD7DB92976AAFE00C1C4B2 /* State.swift in Sources */, 541 | EB067F872992E561004D1AD9 /* HelpContent.swift in Sources */, 542 | 8C4B320A2A77F9160090EF17 /* Utils_macOS.swift in Sources */, 543 | EBDD7DB42973200200C1C4B2 /* Utils.swift in Sources */, 544 | 8CD8A53A2A456EF800BD8A98 /* PromptTextField.swift in Sources */, 545 | F1552031297109C300DC009B /* ControlsView.swift in Sources */, 546 | EBDD7DB62973206600C1C4B2 /* Downloader.swift in Sources */, 547 | F155203429710B3600DC009B /* StatusView.swift in Sources */, 548 | EB560F0429A3C20800C0F8B8 /* Capabilities.swift in Sources */, 549 | F15520242971093300DC009B /* Diffusion_macOSApp.swift in Sources */, 550 | EBDD7DB52973201800C1C4B2 /* ModelInfo.swift in Sources */, 551 | 8C4B32042A770C1D0090EF17 /* DiffusionImage+macOS.swift in Sources */, 552 | EBDD7DBD2977FFB300C1C4B2 /* GeneratedImageView.swift in Sources */, 553 | 8CEEB7DA2A54C88C00C23829 /* DiffusionImage.swift in Sources */, 554 | ); 555 | runOnlyForDeploymentPostprocessing = 0; 556 | }; 557 | /* End PBXSourcesBuildPhase section */ 558 | 559 | /* Begin PBXTargetDependency section */ 560 | EB0199492A31FEAF00B133E2 /* PBXTargetDependency */ = { 561 | isa = PBXTargetDependency; 562 | productRef = EB0199482A31FEAF00B133E2 /* StableDiffusion */; 563 | }; 564 | EBE755D8293E37DE00806B32 /* PBXTargetDependency */ = { 565 | isa = PBXTargetDependency; 566 | target = EBE755C4293E37DD00806B32 /* Diffusion */; 567 | targetProxy = EBE755D7293E37DE00806B32 /* PBXContainerItemProxy */; 568 | }; 569 | EBE755E2293E37DE00806B32 /* PBXTargetDependency */ = { 570 | isa = PBXTargetDependency; 571 | target = EBE755C4293E37DD00806B32 /* Diffusion */; 572 | targetProxy = EBE755E1293E37DE00806B32 /* PBXContainerItemProxy */; 573 | }; 574 | EBF61AB32A2F976600482CF3 /* PBXTargetDependency */ = { 575 | isa = PBXTargetDependency; 576 | productRef = EBF61AB22A2F976600482CF3 /* StableDiffusion */; 577 | }; 578 | /* End PBXTargetDependency section */ 579 | 580 | /* Begin XCBuildConfiguration section */ 581 | EBE755E8293E37DE00806B32 /* Debug */ = { 582 | isa = XCBuildConfiguration; 583 | buildSettings = { 584 | ALWAYS_SEARCH_USER_PATHS = NO; 585 | CLANG_ANALYZER_NONNULL = YES; 586 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 587 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 588 | CLANG_ENABLE_MODULES = YES; 589 | CLANG_ENABLE_OBJC_ARC = YES; 590 | CLANG_ENABLE_OBJC_WEAK = YES; 591 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 592 | CLANG_WARN_BOOL_CONVERSION = YES; 593 | CLANG_WARN_COMMA = YES; 594 | CLANG_WARN_CONSTANT_CONVERSION = YES; 595 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 596 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 597 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 598 | CLANG_WARN_EMPTY_BODY = YES; 599 | CLANG_WARN_ENUM_CONVERSION = YES; 600 | CLANG_WARN_INFINITE_RECURSION = YES; 601 | CLANG_WARN_INT_CONVERSION = YES; 602 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 603 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 604 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 605 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 606 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 607 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 608 | CLANG_WARN_STRICT_PROTOTYPES = YES; 609 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 610 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 611 | CLANG_WARN_UNREACHABLE_CODE = YES; 612 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 613 | COPY_PHASE_STRIP = NO; 614 | DEBUG_INFORMATION_FORMAT = dwarf; 615 | ENABLE_STRICT_OBJC_MSGSEND = YES; 616 | ENABLE_TESTABILITY = YES; 617 | GCC_C_LANGUAGE_STANDARD = gnu11; 618 | GCC_DYNAMIC_NO_PIC = NO; 619 | GCC_NO_COMMON_BLOCKS = YES; 620 | GCC_OPTIMIZATION_LEVEL = 0; 621 | GCC_PREPROCESSOR_DEFINITIONS = ( 622 | "DEBUG=1", 623 | "$(inherited)", 624 | ); 625 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 626 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 627 | GCC_WARN_UNDECLARED_SELECTOR = YES; 628 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 629 | GCC_WARN_UNUSED_FUNCTION = YES; 630 | GCC_WARN_UNUSED_VARIABLE = YES; 631 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 632 | MTL_FAST_MATH = YES; 633 | ONLY_ACTIVE_ARCH = YES; 634 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 635 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 636 | }; 637 | name = Debug; 638 | }; 639 | EBE755E9293E37DE00806B32 /* Release */ = { 640 | isa = XCBuildConfiguration; 641 | buildSettings = { 642 | ALWAYS_SEARCH_USER_PATHS = NO; 643 | CLANG_ANALYZER_NONNULL = YES; 644 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 645 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 646 | CLANG_ENABLE_MODULES = YES; 647 | CLANG_ENABLE_OBJC_ARC = YES; 648 | CLANG_ENABLE_OBJC_WEAK = YES; 649 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 650 | CLANG_WARN_BOOL_CONVERSION = YES; 651 | CLANG_WARN_COMMA = YES; 652 | CLANG_WARN_CONSTANT_CONVERSION = YES; 653 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 654 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 655 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 656 | CLANG_WARN_EMPTY_BODY = YES; 657 | CLANG_WARN_ENUM_CONVERSION = YES; 658 | CLANG_WARN_INFINITE_RECURSION = YES; 659 | CLANG_WARN_INT_CONVERSION = YES; 660 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 661 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 662 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 663 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 664 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 665 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 666 | CLANG_WARN_STRICT_PROTOTYPES = YES; 667 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 668 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 669 | CLANG_WARN_UNREACHABLE_CODE = YES; 670 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 671 | COPY_PHASE_STRIP = NO; 672 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 673 | ENABLE_NS_ASSERTIONS = NO; 674 | ENABLE_STRICT_OBJC_MSGSEND = YES; 675 | GCC_C_LANGUAGE_STANDARD = gnu11; 676 | GCC_NO_COMMON_BLOCKS = YES; 677 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 678 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 679 | GCC_WARN_UNDECLARED_SELECTOR = YES; 680 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 681 | GCC_WARN_UNUSED_FUNCTION = YES; 682 | GCC_WARN_UNUSED_VARIABLE = YES; 683 | MTL_ENABLE_DEBUG_INFO = NO; 684 | MTL_FAST_MATH = YES; 685 | SWIFT_COMPILATION_MODE = wholemodule; 686 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 687 | }; 688 | name = Release; 689 | }; 690 | EBE755EB293E37DE00806B32 /* Debug */ = { 691 | isa = XCBuildConfiguration; 692 | baseConfigurationReference = EBDD7DBA2976F03600C1C4B2 /* debug.xcconfig */; 693 | buildSettings = { 694 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 695 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 696 | CODE_SIGN_ENTITLEMENTS = Diffusion/Diffusion.entitlements; 697 | CODE_SIGN_STYLE = Automatic; 698 | CURRENT_PROJECT_VERSION = 1; 699 | DEVELOPMENT_ASSET_PATHS = "\"Diffusion/Preview Content\""; 700 | ENABLE_HARDENED_RUNTIME = YES; 701 | ENABLE_PREVIEWS = YES; 702 | GENERATE_INFOPLIST_FILE = YES; 703 | INFOPLIST_FILE = Diffusion/Info.plist; 704 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 705 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 706 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 707 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 708 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 709 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 710 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 711 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 712 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 713 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 714 | IPHONEOS_DEPLOYMENT_TARGET = 16.2; 715 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 716 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 717 | MACOSX_DEPLOYMENT_TARGET = 13.1; 718 | PRODUCT_NAME = "$(TARGET_NAME)"; 719 | SDKROOT = auto; 720 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 721 | SUPPORTS_MACCATALYST = YES; 722 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 723 | SWIFT_EMIT_LOC_STRINGS = YES; 724 | SWIFT_VERSION = 5.0; 725 | TARGETED_DEVICE_FAMILY = "1,2"; 726 | }; 727 | name = Debug; 728 | }; 729 | EBE755EC293E37DE00806B32 /* Release */ = { 730 | isa = XCBuildConfiguration; 731 | baseConfigurationReference = EBE3FF4A295DFE2400E921AA /* common.xcconfig */; 732 | buildSettings = { 733 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 734 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 735 | CODE_SIGN_ENTITLEMENTS = Diffusion/Diffusion.entitlements; 736 | CODE_SIGN_STYLE = Automatic; 737 | CURRENT_PROJECT_VERSION = 1; 738 | DEVELOPMENT_ASSET_PATHS = "\"Diffusion/Preview Content\""; 739 | ENABLE_HARDENED_RUNTIME = YES; 740 | ENABLE_PREVIEWS = YES; 741 | GENERATE_INFOPLIST_FILE = YES; 742 | INFOPLIST_FILE = Diffusion/Info.plist; 743 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 744 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 745 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 746 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 747 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 748 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 749 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 750 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 751 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 752 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 753 | IPHONEOS_DEPLOYMENT_TARGET = 16.2; 754 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 755 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 756 | MACOSX_DEPLOYMENT_TARGET = 13.1; 757 | PRODUCT_NAME = "$(TARGET_NAME)"; 758 | SDKROOT = auto; 759 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 760 | SUPPORTS_MACCATALYST = YES; 761 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 762 | SWIFT_EMIT_LOC_STRINGS = YES; 763 | SWIFT_VERSION = 5.0; 764 | TARGETED_DEVICE_FAMILY = "1,2"; 765 | }; 766 | name = Release; 767 | }; 768 | EBE755EE293E37DE00806B32 /* Debug */ = { 769 | isa = XCBuildConfiguration; 770 | buildSettings = { 771 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 772 | BUNDLE_LOADER = "$(TEST_HOST)"; 773 | CODE_SIGN_STYLE = Automatic; 774 | CURRENT_PROJECT_VERSION = 1; 775 | DEVELOPMENT_TEAM = ZWDJQ796RU; 776 | GENERATE_INFOPLIST_FILE = YES; 777 | IPHONEOS_DEPLOYMENT_TARGET = 16.1; 778 | MACOSX_DEPLOYMENT_TARGET = 13.0; 779 | MARKETING_VERSION = 1.0; 780 | PRODUCT_BUNDLE_IDENTIFIER = com.pcuenca.DiffusionTests; 781 | PRODUCT_NAME = "$(TARGET_NAME)"; 782 | SDKROOT = auto; 783 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 784 | SWIFT_EMIT_LOC_STRINGS = NO; 785 | SWIFT_VERSION = 5.0; 786 | TARGETED_DEVICE_FAMILY = "1,2"; 787 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Diffusion.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Diffusion"; 788 | }; 789 | name = Debug; 790 | }; 791 | EBE755EF293E37DE00806B32 /* Release */ = { 792 | isa = XCBuildConfiguration; 793 | buildSettings = { 794 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 795 | BUNDLE_LOADER = "$(TEST_HOST)"; 796 | CODE_SIGN_STYLE = Automatic; 797 | CURRENT_PROJECT_VERSION = 1; 798 | DEVELOPMENT_TEAM = ZWDJQ796RU; 799 | GENERATE_INFOPLIST_FILE = YES; 800 | IPHONEOS_DEPLOYMENT_TARGET = 16.1; 801 | MACOSX_DEPLOYMENT_TARGET = 13.0; 802 | MARKETING_VERSION = 1.0; 803 | PRODUCT_BUNDLE_IDENTIFIER = com.pcuenca.DiffusionTests; 804 | PRODUCT_NAME = "$(TARGET_NAME)"; 805 | SDKROOT = auto; 806 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 807 | SWIFT_EMIT_LOC_STRINGS = NO; 808 | SWIFT_VERSION = 5.0; 809 | TARGETED_DEVICE_FAMILY = "1,2"; 810 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Diffusion.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Diffusion"; 811 | }; 812 | name = Release; 813 | }; 814 | EBE755F1293E37DE00806B32 /* Debug */ = { 815 | isa = XCBuildConfiguration; 816 | buildSettings = { 817 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 818 | CODE_SIGN_STYLE = Automatic; 819 | CURRENT_PROJECT_VERSION = 1; 820 | DEVELOPMENT_TEAM = ZWDJQ796RU; 821 | GENERATE_INFOPLIST_FILE = YES; 822 | IPHONEOS_DEPLOYMENT_TARGET = 16.1; 823 | MACOSX_DEPLOYMENT_TARGET = 13.0; 824 | MARKETING_VERSION = 1.0; 825 | PRODUCT_BUNDLE_IDENTIFIER = com.pcuenca.DiffusionUITests; 826 | PRODUCT_NAME = "$(TARGET_NAME)"; 827 | SDKROOT = auto; 828 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 829 | SWIFT_EMIT_LOC_STRINGS = NO; 830 | SWIFT_VERSION = 5.0; 831 | TARGETED_DEVICE_FAMILY = "1,2"; 832 | TEST_TARGET_NAME = Diffusion; 833 | }; 834 | name = Debug; 835 | }; 836 | EBE755F2293E37DE00806B32 /* Release */ = { 837 | isa = XCBuildConfiguration; 838 | buildSettings = { 839 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 840 | CODE_SIGN_STYLE = Automatic; 841 | CURRENT_PROJECT_VERSION = 1; 842 | DEVELOPMENT_TEAM = ZWDJQ796RU; 843 | GENERATE_INFOPLIST_FILE = YES; 844 | IPHONEOS_DEPLOYMENT_TARGET = 16.1; 845 | MACOSX_DEPLOYMENT_TARGET = 13.0; 846 | MARKETING_VERSION = 1.0; 847 | PRODUCT_BUNDLE_IDENTIFIER = com.pcuenca.DiffusionUITests; 848 | PRODUCT_NAME = "$(TARGET_NAME)"; 849 | SDKROOT = auto; 850 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 851 | SWIFT_EMIT_LOC_STRINGS = NO; 852 | SWIFT_VERSION = 5.0; 853 | TARGETED_DEVICE_FAMILY = "1,2"; 854 | TEST_TARGET_NAME = Diffusion; 855 | }; 856 | name = Release; 857 | }; 858 | F155202D2971093400DC009B /* Debug */ = { 859 | isa = XCBuildConfiguration; 860 | baseConfigurationReference = EBDD7DBA2976F03600C1C4B2 /* debug.xcconfig */; 861 | buildSettings = { 862 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 863 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 864 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; 865 | CODE_SIGN_ENTITLEMENTS = "Diffusion-macOS/Diffusion_macOS.entitlements"; 866 | CODE_SIGN_STYLE = Automatic; 867 | COMBINE_HIDPI_IMAGES = YES; 868 | DEVELOPMENT_ASSET_PATHS = "\"Diffusion-macOS/Preview Content\""; 869 | DEVELOPMENT_TEAM = 2EADP68M95; 870 | ENABLE_PREVIEWS = YES; 871 | GENERATE_INFOPLIST_FILE = YES; 872 | INFOPLIST_FILE = "Diffusion-macOS/Info.plist"; 873 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; 874 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 875 | LD_RUNPATH_SEARCH_PATHS = ( 876 | "$(inherited)", 877 | "@executable_path/../Frameworks", 878 | ); 879 | MACOSX_DEPLOYMENT_TARGET = 14.0; 880 | SDKROOT = macosx; 881 | SWIFT_EMIT_LOC_STRINGS = YES; 882 | SWIFT_VERSION = 5.0; 883 | }; 884 | name = Debug; 885 | }; 886 | F155202E2971093400DC009B /* Release */ = { 887 | isa = XCBuildConfiguration; 888 | baseConfigurationReference = EBE3FF4A295DFE2400E921AA /* common.xcconfig */; 889 | buildSettings = { 890 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 891 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 892 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; 893 | CODE_SIGN_ENTITLEMENTS = "Diffusion-macOS/Diffusion_macOS.entitlements"; 894 | CODE_SIGN_STYLE = Automatic; 895 | COMBINE_HIDPI_IMAGES = YES; 896 | DEVELOPMENT_ASSET_PATHS = "\"Diffusion-macOS/Preview Content\""; 897 | DEVELOPMENT_TEAM = 2EADP68M95; 898 | ENABLE_PREVIEWS = YES; 899 | GENERATE_INFOPLIST_FILE = YES; 900 | INFOPLIST_FILE = "Diffusion-macOS/Info.plist"; 901 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; 902 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 903 | LD_RUNPATH_SEARCH_PATHS = ( 904 | "$(inherited)", 905 | "@executable_path/../Frameworks", 906 | ); 907 | MACOSX_DEPLOYMENT_TARGET = 14.0; 908 | PRODUCT_BUNDLE_IDENTIFIER = com.huggingface.Diffusers; 909 | SDKROOT = macosx; 910 | SWIFT_EMIT_LOC_STRINGS = YES; 911 | SWIFT_VERSION = 5.0; 912 | }; 913 | name = Release; 914 | }; 915 | /* End XCBuildConfiguration section */ 916 | 917 | /* Begin XCConfigurationList section */ 918 | EBE755C0293E37DD00806B32 /* Build configuration list for PBXProject "Diffusion" */ = { 919 | isa = XCConfigurationList; 920 | buildConfigurations = ( 921 | EBE755E8293E37DE00806B32 /* Debug */, 922 | EBE755E9293E37DE00806B32 /* Release */, 923 | ); 924 | defaultConfigurationIsVisible = 0; 925 | defaultConfigurationName = Release; 926 | }; 927 | EBE755EA293E37DE00806B32 /* Build configuration list for PBXNativeTarget "Diffusion" */ = { 928 | isa = XCConfigurationList; 929 | buildConfigurations = ( 930 | EBE755EB293E37DE00806B32 /* Debug */, 931 | EBE755EC293E37DE00806B32 /* Release */, 932 | ); 933 | defaultConfigurationIsVisible = 0; 934 | defaultConfigurationName = Release; 935 | }; 936 | EBE755ED293E37DE00806B32 /* Build configuration list for PBXNativeTarget "DiffusionTests" */ = { 937 | isa = XCConfigurationList; 938 | buildConfigurations = ( 939 | EBE755EE293E37DE00806B32 /* Debug */, 940 | EBE755EF293E37DE00806B32 /* Release */, 941 | ); 942 | defaultConfigurationIsVisible = 0; 943 | defaultConfigurationName = Release; 944 | }; 945 | EBE755F0293E37DE00806B32 /* Build configuration list for PBXNativeTarget "DiffusionUITests" */ = { 946 | isa = XCConfigurationList; 947 | buildConfigurations = ( 948 | EBE755F1293E37DE00806B32 /* Debug */, 949 | EBE755F2293E37DE00806B32 /* Release */, 950 | ); 951 | defaultConfigurationIsVisible = 0; 952 | defaultConfigurationName = Release; 953 | }; 954 | F155202F2971093400DC009B /* Build configuration list for PBXNativeTarget "Diffusion-macOS" */ = { 955 | isa = XCConfigurationList; 956 | buildConfigurations = ( 957 | F155202D2971093400DC009B /* Debug */, 958 | F155202E2971093400DC009B /* Release */, 959 | ); 960 | defaultConfigurationIsVisible = 0; 961 | defaultConfigurationName = Release; 962 | }; 963 | /* End XCConfigurationList section */ 964 | 965 | /* Begin XCRemoteSwiftPackageReference section */ 966 | 16AFDD4D2C1B7D4800536A62 /* XCRemoteSwiftPackageReference "ml-stable-diffusion" */ = { 967 | isa = XCRemoteSwiftPackageReference; 968 | repositoryURL = "https://github.com/argmaxinc/ml-stable-diffusion.git"; 969 | requirement = { 970 | branch = main; 971 | kind = branch; 972 | }; 973 | }; 974 | EBB5BA5B294504DE003A2A5B /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = { 975 | isa = XCRemoteSwiftPackageReference; 976 | repositoryURL = "https://github.com/weichsel/ZIPFoundation.git"; 977 | requirement = { 978 | kind = upToNextMajorVersion; 979 | minimumVersion = 0.9.9; 980 | }; 981 | }; 982 | F155203A297118E600DC009B /* XCRemoteSwiftPackageReference "CompactSlider" */ = { 983 | isa = XCRemoteSwiftPackageReference; 984 | repositoryURL = "https://github.com/buh/CompactSlider.git"; 985 | requirement = { 986 | branch = main; 987 | kind = branch; 988 | }; 989 | }; 990 | /* End XCRemoteSwiftPackageReference section */ 991 | 992 | /* Begin XCSwiftPackageProductDependency section */ 993 | 16AFDD4E2C1B7D6200536A62 /* StableDiffusion */ = { 994 | isa = XCSwiftPackageProductDependency; 995 | package = 16AFDD4D2C1B7D4800536A62 /* XCRemoteSwiftPackageReference "ml-stable-diffusion" */; 996 | productName = StableDiffusion; 997 | }; 998 | 16AFDD502C1B7D6700536A62 /* StableDiffusion */ = { 999 | isa = XCSwiftPackageProductDependency; 1000 | package = 16AFDD4D2C1B7D4800536A62 /* XCRemoteSwiftPackageReference "ml-stable-diffusion" */; 1001 | productName = StableDiffusion; 1002 | }; 1003 | EB0199482A31FEAF00B133E2 /* StableDiffusion */ = { 1004 | isa = XCSwiftPackageProductDependency; 1005 | productName = StableDiffusion; 1006 | }; 1007 | EBB5BA5C294504DE003A2A5B /* ZIPFoundation */ = { 1008 | isa = XCSwiftPackageProductDependency; 1009 | package = EBB5BA5B294504DE003A2A5B /* XCRemoteSwiftPackageReference "ZIPFoundation" */; 1010 | productName = ZIPFoundation; 1011 | }; 1012 | EBDD7DAE29731FB300C1C4B2 /* ZIPFoundation */ = { 1013 | isa = XCSwiftPackageProductDependency; 1014 | package = EBB5BA5B294504DE003A2A5B /* XCRemoteSwiftPackageReference "ZIPFoundation" */; 1015 | productName = ZIPFoundation; 1016 | }; 1017 | EBF61AB22A2F976600482CF3 /* StableDiffusion */ = { 1018 | isa = XCSwiftPackageProductDependency; 1019 | productName = StableDiffusion; 1020 | }; 1021 | F155203B297118E700DC009B /* CompactSlider */ = { 1022 | isa = XCSwiftPackageProductDependency; 1023 | package = F155203A297118E600DC009B /* XCRemoteSwiftPackageReference "CompactSlider" */; 1024 | productName = CompactSlider; 1025 | }; 1026 | /* End XCSwiftPackageProductDependency section */ 1027 | }; 1028 | rootObject = EBE755BD293E37DD00806B32 /* Project object */; 1029 | } 1030 | -------------------------------------------------------------------------------- /Diffusion.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Diffusion.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Diffusion.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "e97aab54879429ea40e58df49ffe4eef5228d95a28a7cf4d5dca9204c33564e1", 3 | "pins" : [ 4 | { 5 | "identity" : "compactslider", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/buh/CompactSlider.git", 8 | "state" : { 9 | "branch" : "main", 10 | "revision" : "31a4db8ef10f32e574be35399b10b00e05d27e38" 11 | } 12 | }, 13 | { 14 | "identity" : "ml-stable-diffusion", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/argmaxinc/ml-stable-diffusion.git", 17 | "state" : { 18 | "branch" : "main", 19 | "revision" : "d1f0604fab5345011e0b9f5b87ee0c155612565f" 20 | } 21 | }, 22 | { 23 | "identity" : "swift-argument-parser", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/apple/swift-argument-parser.git", 26 | "state" : { 27 | "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", 28 | "version" : "1.4.0" 29 | } 30 | }, 31 | { 32 | "identity" : "zipfoundation", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/weichsel/ZIPFoundation.git", 35 | "state" : { 36 | "revision" : "b979e8b52c7ae7f3f39fa0182e738e9e7257eb78", 37 | "version" : "0.9.18" 38 | } 39 | } 40 | ], 41 | "version" : 3 42 | } 43 | -------------------------------------------------------------------------------- /Diffusion.xcodeproj/project.xcworkspace/xcuserdata/cyril.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huggingface/swift-coreml-diffusers/a561fae8b47707efa68f70e2fa1fea5ab4462ab9/Diffusion.xcodeproj/project.xcworkspace/xcuserdata/cyril.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Diffusion.xcodeproj/xcuserdata/cyril.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Diffusion-macOS.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | Diffusion.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Diffusion/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Diffusion/Assets.xcassets/AppIcon.appiconset/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huggingface/swift-coreml-diffusers/a561fae8b47707efa68f70e2fa1fea5ab4462ab9/Diffusion/Assets.xcassets/AppIcon.appiconset/.DS_Store -------------------------------------------------------------------------------- /Diffusion/Assets.xcassets/AppIcon.appiconset/256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huggingface/swift-coreml-diffusers/a561fae8b47707efa68f70e2fa1fea5ab4462ab9/Diffusion/Assets.xcassets/AppIcon.appiconset/256x256@2x.png -------------------------------------------------------------------------------- /Diffusion/Assets.xcassets/AppIcon.appiconset/512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huggingface/swift-coreml-diffusers/a561fae8b47707efa68f70e2fa1fea5ab4462ab9/Diffusion/Assets.xcassets/AppIcon.appiconset/512x512@2x.png -------------------------------------------------------------------------------- /Diffusion/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "diffusers_on_white_1024.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | }, 9 | { 10 | "idiom" : "mac", 11 | "scale" : "1x", 12 | "size" : "16x16" 13 | }, 14 | { 15 | "idiom" : "mac", 16 | "scale" : "2x", 17 | "size" : "16x16" 18 | }, 19 | { 20 | "idiom" : "mac", 21 | "scale" : "1x", 22 | "size" : "32x32" 23 | }, 24 | { 25 | "idiom" : "mac", 26 | "scale" : "2x", 27 | "size" : "32x32" 28 | }, 29 | { 30 | "idiom" : "mac", 31 | "scale" : "1x", 32 | "size" : "128x128" 33 | }, 34 | { 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "idiom" : "mac", 41 | "scale" : "1x", 42 | "size" : "256x256" 43 | }, 44 | { 45 | "filename" : "256x256@2x.png", 46 | "idiom" : "mac", 47 | "scale" : "2x", 48 | "size" : "256x256" 49 | }, 50 | { 51 | "filename" : "256x256@2x.png", 52 | "idiom" : "mac", 53 | "scale" : "1x", 54 | "size" : "512x512" 55 | }, 56 | { 57 | "filename" : "512x512@2x.png", 58 | "idiom" : "mac", 59 | "scale" : "2x", 60 | "size" : "512x512" 61 | } 62 | ], 63 | "info" : { 64 | "author" : "xcode", 65 | "version" : 1 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Diffusion/Assets.xcassets/AppIcon.appiconset/diffusers_on_white_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huggingface/swift-coreml-diffusers/a561fae8b47707efa68f70e2fa1fea5ab4462ab9/Diffusion/Assets.xcassets/AppIcon.appiconset/diffusers_on_white_1024.png -------------------------------------------------------------------------------- /Diffusion/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Diffusion/Assets.xcassets/placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "labrador.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Diffusion/Assets.xcassets/placeholder.imageset/labrador.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huggingface/swift-coreml-diffusers/a561fae8b47707efa68f70e2fa1fea5ab4462ab9/Diffusion/Assets.xcassets/placeholder.imageset/labrador.png -------------------------------------------------------------------------------- /Diffusion/Common/DiffusionImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiffusionImage.swift 3 | // Diffusion 4 | // 5 | // Created by Dolmere on 03/07/2023. 6 | // 7 | 8 | import SwiftUI 9 | import StableDiffusion 10 | import CoreTransferable 11 | 12 | /// Tracking for a `DiffusionImage` generation state. 13 | enum DiffusionImageState { 14 | case generating 15 | case waiting 16 | case complete 17 | } 18 | 19 | /// Generic custom error to use when an image generation fails. 20 | enum DiffusionImageError: Error { 21 | case invalidDiffusionImage 22 | } 23 | 24 | /// Combination of a `DiffusionImage` and its associated `DiffusionImageState` 25 | struct DiffusionImageWrapper { 26 | var diffusionImageState: DiffusionImageState = .waiting 27 | var diffusionImage: DiffusionImage? = nil 28 | } 29 | 30 | /// Model class to hold a generated image and the "recipe" data that was used to generate it 31 | final class DiffusionImage: NSObject, Identifiable, NSCoding, NSSecureCoding { 32 | 33 | let id: UUID 34 | let cgImage: CGImage 35 | let seed: UInt32 36 | let steps: Double 37 | let positivePrompt: String 38 | let negativePrompt: String 39 | let guidanceScale: Double 40 | let disableSafety: Bool 41 | /// Local enum represented with a String to conform to NSSecureCoding 42 | let scheduler: StableDiffusionScheduler 43 | 44 | /// This is a composed `String` built from the numeric `Seed` and the user supplied `positivePrompt` limited to the first 200 character and with whitespace replaced with underscore characters. 45 | var generatedFilename: String { 46 | return "\(seed)-\(positivePrompt)".first200Safe 47 | } 48 | 49 | /// The location on the file system where this generated image is stored. 50 | var fileURL: URL 51 | 52 | init(id: UUID, cgImage: CGImage, seed: UInt32, steps: Double, positivePrompt: String, negativePrompt: String, guidanceScale: Double, disableSafety: Bool, scheduler: StableDiffusionScheduler) { 53 | let genname = "\(seed)-\(positivePrompt)".first200Safe 54 | self.id = id 55 | self.cgImage = cgImage 56 | self.seed = seed 57 | self.steps = steps 58 | self.positivePrompt = positivePrompt 59 | self.negativePrompt = negativePrompt 60 | self.guidanceScale = guidanceScale 61 | self.disableSafety = disableSafety 62 | self.scheduler = scheduler 63 | // Initially set the fileURL to the top level applicationDirectory to allow running the completed instance func save() where the fileURL will be updated to the correct location. 64 | self.fileURL = URL.applicationDirectory 65 | // init the instance fully before executing an instance function 66 | super.init() 67 | if let url = save(cgImage: cgImage, filename: genname) { 68 | self.fileURL = url 69 | } else { 70 | fatalError("Fatal error init of DiffusionImage, cannot create image file at \(genname)") 71 | } 72 | } 73 | 74 | func encode(with coder: NSCoder) { 75 | coder.encode(id, forKey: "id") 76 | coder.encode(seed, forKey: "seed") 77 | coder.encode(steps, forKey: "steps") 78 | coder.encode(positivePrompt, forKey: "positivePrompt") 79 | coder.encode(negativePrompt, forKey: "negativePrompt") 80 | coder.encode(guidanceScale, forKey: "guidanceScale") 81 | coder.encode(disableSafety, forKey: "disableSafety") 82 | coder.encode(scheduler, forKey: "scheduler") 83 | // Encode cgImage as data 84 | if let data = pngRepresentation() { 85 | coder.encode(data, forKey: "cgImage") 86 | } 87 | } 88 | 89 | required init?(coder: NSCoder) { 90 | guard let id = coder.decodeObject(forKey: "id") as? UUID else { 91 | return nil 92 | } 93 | 94 | self.id = id 95 | self.seed = UInt32(coder.decodeInt32(forKey: "seed")) 96 | self.steps = coder.decodeDouble(forKey: "steps") 97 | self.positivePrompt = coder.decodeObject(forKey: "positivePrompt") as? String ?? "" 98 | self.negativePrompt = coder.decodeObject(forKey: "negativePrompt") as? String ?? "" 99 | self.guidanceScale = coder.decodeDouble(forKey: "guidanceScale") 100 | self.disableSafety = coder.decodeBool(forKey: "disableSafety") 101 | self.scheduler = coder.decodeObject(forKey: "scheduler") as? StableDiffusionScheduler ?? StableDiffusionScheduler.dpmSolverMultistepScheduler 102 | let genname = "\(seed)-\(positivePrompt)".first200Safe 103 | 104 | // Decode cgImage from data 105 | if let imageData = coder.decodeObject(forKey: "cgImage") as? Data { 106 | guard let img = CGImage.fromData(imageData) else { fatalError("Fatal error loading data with missing cgImage in object") } 107 | self.cgImage = img 108 | } else { 109 | fatalError("Fatal error loading data with missing cgImage in object") 110 | } 111 | self.fileURL = URL.applicationDirectory 112 | super.init() 113 | if let url = save(cgImage: cgImage, filename: genname) { 114 | self.fileURL = url 115 | } else { 116 | fatalError("Fatal error init of DiffusionImage, cannot create image file at \(genname)") 117 | } 118 | } 119 | 120 | // MARK: - Equatable 121 | 122 | static func == (lhs: DiffusionImage, rhs: DiffusionImage) -> Bool { 123 | return lhs.id == rhs.id 124 | } 125 | 126 | // MARK: - NSSecureCoding 127 | 128 | static var supportsSecureCoding: Bool { 129 | return true 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Diffusion/Common/Downloader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Downloader.swift 3 | // Diffusion 4 | // 5 | // Created by Pedro Cuenca on December 2022. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import Foundation 10 | import Combine 11 | 12 | class Downloader: NSObject, ObservableObject { 13 | private(set) var destination: URL 14 | 15 | enum DownloadState { 16 | case notStarted 17 | case downloading(Double) 18 | case completed(URL) 19 | case failed(Error) 20 | } 21 | 22 | private(set) lazy var downloadState: CurrentValueSubject = CurrentValueSubject(.notStarted) 23 | private var stateSubscriber: Cancellable? 24 | 25 | private var urlSession: URLSession? = nil 26 | 27 | init(from url: URL, to destination: URL, using authToken: String? = nil) { 28 | self.destination = destination 29 | super.init() 30 | 31 | var config = URLSessionConfiguration.default 32 | #if !os(macOS) 33 | // .background allows downloads to proceed in the background 34 | // helpful for devices that may not keep the app in the foreground for the download duration 35 | config = URLSessionConfiguration.background(withIdentifier: "net.pcuenca.diffusion.download") 36 | config.isDiscretionary = false 37 | config.sessionSendsLaunchEvents = true 38 | #endif 39 | urlSession = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue()) 40 | downloadState.value = .downloading(0) 41 | urlSession?.getAllTasks { tasks in 42 | // If there's an existing pending background task with the same URL, let it proceed. 43 | guard tasks.filter({ $0.originalRequest?.url == url }).isEmpty else { 44 | print("Already downloading \(url)") 45 | return 46 | } 47 | print("Starting download of \(url)") 48 | 49 | var request = URLRequest(url: url) 50 | if let authToken = authToken { 51 | request.setValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization") 52 | } 53 | 54 | self.urlSession?.downloadTask(with: request).resume() 55 | } 56 | } 57 | 58 | @discardableResult 59 | func waitUntilDone() throws -> URL { 60 | // It's either this, or stream the bytes ourselves (add to a buffer, save to disk, etc; boring and finicky) 61 | let semaphore = DispatchSemaphore(value: 0) 62 | stateSubscriber = downloadState.sink { state in 63 | switch state { 64 | case .completed: semaphore.signal() 65 | case .failed: semaphore.signal() 66 | default: break 67 | } 68 | } 69 | semaphore.wait() 70 | 71 | switch downloadState.value { 72 | case .completed(let url): return url 73 | case .failed(let error): throw error 74 | default: throw("Should never happen, lol") 75 | } 76 | } 77 | 78 | func cancel() { 79 | urlSession?.invalidateAndCancel() 80 | } 81 | } 82 | 83 | extension Downloader: URLSessionDelegate, URLSessionDownloadDelegate { 84 | func urlSession(_: URLSession, downloadTask: URLSessionDownloadTask, didWriteData _: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { 85 | downloadState.value = .downloading(Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)) 86 | } 87 | 88 | func urlSession(_: URLSession, downloadTask _: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { 89 | guard FileManager.default.fileExists(atPath: location.path) else { 90 | downloadState.value = .failed("Invalid download location received: \(location)") 91 | return 92 | } 93 | do { 94 | try FileManager.default.moveItem(at: location, to: destination) 95 | downloadState.value = .completed(destination) 96 | } catch { 97 | downloadState.value = .failed(error) 98 | } 99 | } 100 | 101 | func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { 102 | if let error = error { 103 | downloadState.value = .failed(error) 104 | } else if let response = task.response as? HTTPURLResponse { 105 | print("HTTP response status code: \(response.statusCode)") 106 | // let headers = response.allHeaderFields 107 | // print("HTTP response headers: \(headers)") 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Diffusion/Common/ModelInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelInfo.swift 3 | // Diffusion 4 | // 5 | // Created by Pedro Cuenca on 29/12/22. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import CoreML 10 | 11 | enum AttentionVariant: String { 12 | case original 13 | case splitEinsum 14 | case splitEinsumV2 15 | } 16 | 17 | extension AttentionVariant { 18 | var defaultComputeUnits: MLComputeUnits { self == .original ? .cpuAndGPU : .cpuAndNeuralEngine } 19 | } 20 | 21 | struct ModelInfo { 22 | /// Hugging Face model Id that contains .zip archives with compiled Core ML models 23 | let modelId: String 24 | 25 | /// Arbitrary string for presentation purposes. Something like "2.1-base" 26 | let modelVersion: String 27 | 28 | /// Suffix of the archive containing the ORIGINAL attention variant. Usually something like "original_compiled" 29 | let originalAttentionSuffix: String 30 | 31 | /// Suffix of the archive containing the SPLIT_EINSUM attention variant. Usually something like "split_einsum_compiled" 32 | let splitAttentionSuffix: String 33 | 34 | /// Suffix of the archive containing the SPLIT_EINSUM_V2 attention variant. Usually something like "split_einsum_v2_compiled" 35 | let splitAttentionV2Suffix: String 36 | 37 | /// Whether the archive contains ANE optimized models 38 | let supportsNeuralEngine: Bool 39 | 40 | /// Whether the archive contains the VAE Encoder (for image to image tasks). Not yet in use. 41 | let supportsEncoder: Bool 42 | 43 | /// Is attention v2 supported? (Ideally, we should know by looking at the repo contents) 44 | let supportsAttentionV2: Bool 45 | 46 | /// Are weights quantized? This is only used to decide whether to use `reduceMemory` 47 | let quantized: Bool 48 | 49 | /// Whether this is a Stable Diffusion XL model 50 | // TODO: retrieve from remote config 51 | let isXL: Bool 52 | 53 | /// Whether this is a Stable Diffusion 3 model 54 | // TODO: retrieve from remote config 55 | let isSD3: Bool 56 | 57 | //TODO: refactor all these properties 58 | init(modelId: String, modelVersion: String, 59 | originalAttentionSuffix: String = "original_compiled", 60 | splitAttentionSuffix: String = "split_einsum_compiled", 61 | splitAttentionV2Suffix: String = "split_einsum_v2_compiled", 62 | supportsNeuralEngine: Bool = true, 63 | supportsEncoder: Bool = false, 64 | supportsAttentionV2: Bool = false, 65 | quantized: Bool = false, 66 | isXL: Bool = false, 67 | isSD3: Bool = false) { 68 | self.modelId = modelId 69 | self.modelVersion = modelVersion 70 | self.originalAttentionSuffix = originalAttentionSuffix 71 | self.splitAttentionSuffix = splitAttentionSuffix 72 | self.splitAttentionV2Suffix = splitAttentionV2Suffix 73 | self.supportsNeuralEngine = supportsNeuralEngine 74 | self.supportsEncoder = supportsEncoder 75 | self.supportsAttentionV2 = supportsAttentionV2 76 | self.quantized = quantized 77 | self.isXL = isXL 78 | self.isSD3 = isSD3 79 | } 80 | } 81 | 82 | extension ModelInfo { 83 | //TODO: set compute units instead and derive variant from it 84 | static var defaultAttention: AttentionVariant { 85 | guard runningOnMac else { return .splitEinsum } 86 | #if os(macOS) 87 | guard Capabilities.hasANE else { return .original } 88 | return Capabilities.performanceCores >= 8 ? .original : .splitEinsum 89 | #else 90 | return .splitEinsum 91 | #endif 92 | } 93 | 94 | static var defaultComputeUnits: MLComputeUnits { defaultAttention.defaultComputeUnits } 95 | 96 | var bestAttention: AttentionVariant { 97 | if !runningOnMac && supportsAttentionV2 { return .splitEinsumV2 } 98 | return ModelInfo.defaultAttention 99 | } 100 | var defaultComputeUnits: MLComputeUnits { bestAttention.defaultComputeUnits } 101 | 102 | func modelURL(for variant: AttentionVariant) -> URL { 103 | // Pattern: https://huggingface.co/pcuenq/coreml-stable-diffusion/resolve/main/coreml-stable-diffusion-v1-5_original_compiled.zip 104 | let suffix: String 105 | switch variant { 106 | case .original: suffix = originalAttentionSuffix 107 | case .splitEinsum: suffix = splitAttentionSuffix 108 | case .splitEinsumV2: suffix = splitAttentionV2Suffix 109 | } 110 | let repo = modelId.split(separator: "/").last! 111 | return URL(string: "https://huggingface.co/\(modelId)/resolve/main/\(repo)_\(suffix).zip")! 112 | } 113 | 114 | /// Best variant for the current platform. 115 | /// Currently using `split_einsum` for iOS and simple performance heuristics for macOS. 116 | var bestURL: URL { modelURL(for: bestAttention) } 117 | 118 | var reduceMemory: Bool { 119 | // Enable on iOS devices, except when using quantization 120 | if runningOnMac { return false } 121 | if isXL { return !deviceHas8GBOrMore } 122 | return !(quantized && deviceHas6GBOrMore) 123 | } 124 | } 125 | 126 | extension ModelInfo { 127 | static let v14Base = ModelInfo( 128 | modelId: "pcuenq/coreml-stable-diffusion-1-4", 129 | modelVersion: "CompVis SD 1.4" 130 | ) 131 | 132 | static let v14Palettized = ModelInfo( 133 | modelId: "apple/coreml-stable-diffusion-1-4-palettized", 134 | modelVersion: "CompVis SD 1.4 [6 bit]", 135 | supportsEncoder: true, 136 | supportsAttentionV2: true, 137 | quantized: true 138 | ) 139 | 140 | static let v15Base = ModelInfo( 141 | modelId: "pcuenq/coreml-stable-diffusion-v1-5", 142 | modelVersion: "RunwayML SD 1.5" 143 | ) 144 | 145 | static let v15Palettized = ModelInfo( 146 | modelId: "apple/coreml-stable-diffusion-v1-5-palettized", 147 | modelVersion: "RunwayML SD 1.5 [6 bit]", 148 | supportsEncoder: true, 149 | supportsAttentionV2: true, 150 | quantized: true 151 | ) 152 | 153 | static let v2Base = ModelInfo( 154 | modelId: "pcuenq/coreml-stable-diffusion-2-base", 155 | modelVersion: "StabilityAI SD 2.0", 156 | supportsEncoder: true 157 | ) 158 | 159 | static let v2Palettized = ModelInfo( 160 | modelId: "apple/coreml-stable-diffusion-2-base-palettized", 161 | modelVersion: "StabilityAI SD 2.0 [6 bit]", 162 | supportsEncoder: true, 163 | supportsAttentionV2: true, 164 | quantized: true 165 | ) 166 | 167 | static let v21Base = ModelInfo( 168 | modelId: "pcuenq/coreml-stable-diffusion-2-1-base", 169 | modelVersion: "StabilityAI SD 2.1", 170 | supportsEncoder: true 171 | ) 172 | 173 | static let v21Palettized = ModelInfo( 174 | modelId: "apple/coreml-stable-diffusion-2-1-base-palettized", 175 | modelVersion: "StabilityAI SD 2.1 [6 bit]", 176 | supportsEncoder: true, 177 | supportsAttentionV2: true, 178 | quantized: true 179 | ) 180 | 181 | static let ofaSmall = ModelInfo( 182 | modelId: "pcuenq/coreml-small-stable-diffusion-v0", 183 | modelVersion: "OFA-Sys/small-stable-diffusion-v0" 184 | ) 185 | 186 | static let xl = ModelInfo( 187 | modelId: "apple/coreml-stable-diffusion-xl-base", 188 | modelVersion: "SDXL base (1024, macOS)", 189 | supportsEncoder: true, 190 | isXL: true 191 | ) 192 | 193 | static let xlWithRefiner = ModelInfo( 194 | modelId: "apple/coreml-stable-diffusion-xl-base-with-refiner", 195 | modelVersion: "SDXL with refiner (1024, macOS)", 196 | supportsEncoder: true, 197 | isXL: true 198 | ) 199 | 200 | static let xlmbp = ModelInfo( 201 | modelId: "apple/coreml-stable-diffusion-mixed-bit-palettization", 202 | modelVersion: "SDXL base (1024, macOS) [4.5 bit]", 203 | supportsEncoder: true, 204 | quantized: true, 205 | isXL: true 206 | ) 207 | 208 | static let xlmbpChunked = ModelInfo( 209 | modelId: "apple/coreml-stable-diffusion-xl-base-ios", 210 | modelVersion: "SDXL base (768, iOS) [4 bit]", 211 | supportsEncoder: false, 212 | quantized: true, 213 | isXL: true 214 | ) 215 | 216 | static let sd3 = ModelInfo( 217 | modelId: "argmaxinc/coreml-stable-diffusion-3-medium", 218 | modelVersion: "SD3 medium (512, macOS)", 219 | supportsNeuralEngine: false, // TODO: support SD3 on ANE 220 | supportsEncoder: false, 221 | quantized: false, 222 | isSD3: true 223 | ) 224 | 225 | static let sd3highres = ModelInfo( 226 | modelId: "argmaxinc/coreml-stable-diffusion-3-medium-1024-t5", 227 | modelVersion: "SD3 medium (1024, T5, macOS)", 228 | supportsNeuralEngine: false, // TODO: support SD3 on ANE 229 | supportsEncoder: false, 230 | quantized: false, 231 | isSD3: true 232 | ) 233 | 234 | static let MODELS: [ModelInfo] = { 235 | if deviceSupportsQuantization { 236 | var models = [ 237 | ModelInfo.v14Base, 238 | ModelInfo.v14Palettized, 239 | ModelInfo.v15Base, 240 | ModelInfo.v15Palettized, 241 | ModelInfo.v2Base, 242 | ModelInfo.v2Palettized, 243 | ModelInfo.v21Base, 244 | ModelInfo.v21Palettized 245 | ] 246 | if runningOnMac { 247 | models.append(contentsOf: [ 248 | ModelInfo.xl, 249 | ModelInfo.xlWithRefiner, 250 | ModelInfo.xlmbp, 251 | ModelInfo.sd3, 252 | ModelInfo.sd3highres, 253 | ]) 254 | } else { 255 | models.append(ModelInfo.xlmbpChunked) 256 | } 257 | return models 258 | } else { 259 | return [ 260 | ModelInfo.v14Base, 261 | ModelInfo.v15Base, 262 | ModelInfo.v2Base, 263 | ModelInfo.v21Base, 264 | ] 265 | } 266 | }() 267 | 268 | static func from(modelVersion: String) -> ModelInfo? { 269 | ModelInfo.MODELS.first(where: {$0.modelVersion == modelVersion}) 270 | } 271 | 272 | static func from(modelId: String) -> ModelInfo? { 273 | ModelInfo.MODELS.first(where: {$0.modelId == modelId}) 274 | } 275 | } 276 | 277 | extension ModelInfo : Equatable { 278 | static func ==(lhs: ModelInfo, rhs: ModelInfo) -> Bool { lhs.modelId == rhs.modelId } 279 | } 280 | -------------------------------------------------------------------------------- /Diffusion/Common/Pipeline/Pipeline.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Pipeline.swift 3 | // Diffusion 4 | // 5 | // Created by Pedro Cuenca on December 2022. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import Foundation 10 | import CoreML 11 | import Combine 12 | 13 | import StableDiffusion 14 | 15 | struct StableDiffusionProgress { 16 | var progress: StableDiffusionPipeline.Progress 17 | 18 | var step: Int { progress.step } 19 | var stepCount: Int { progress.stepCount } 20 | 21 | var currentImages: [CGImage?] 22 | 23 | init(progress: StableDiffusionPipeline.Progress, previewIndices: [Bool]) { 24 | self.progress = progress 25 | self.currentImages = [nil] 26 | 27 | // Since currentImages is a computed property, only access the preview image if necessary 28 | if progress.step < previewIndices.count, previewIndices[progress.step] { 29 | self.currentImages = progress.currentImages 30 | } 31 | } 32 | } 33 | 34 | struct GenerationResult { 35 | var image: CGImage? 36 | var lastSeed: UInt32 37 | var interval: TimeInterval? 38 | var userCanceled: Bool 39 | var itsPerSecond: Double? 40 | } 41 | 42 | class Pipeline { 43 | let pipeline: StableDiffusionPipelineProtocol 44 | let maxSeed: UInt32 45 | 46 | var isXL: Bool { 47 | if #available(macOS 14.0, iOS 17.0, *) { 48 | return (pipeline as? StableDiffusionXLPipeline) != nil 49 | } 50 | return false 51 | } 52 | 53 | var isSD3: Bool { 54 | if #available(macOS 14.0, iOS 17.0, *) { 55 | return (pipeline as? StableDiffusion3Pipeline) != nil 56 | } 57 | return false 58 | } 59 | 60 | var progress: StableDiffusionProgress? = nil { 61 | didSet { 62 | progressPublisher.value = progress 63 | } 64 | } 65 | lazy private(set) var progressPublisher: CurrentValueSubject = CurrentValueSubject(progress) 66 | 67 | private var canceled = false 68 | 69 | init(_ pipeline: StableDiffusionPipelineProtocol, maxSeed: UInt32 = UInt32.max) { 70 | self.pipeline = pipeline 71 | self.maxSeed = maxSeed 72 | } 73 | 74 | func generate( 75 | prompt: String, 76 | negativePrompt: String = "", 77 | scheduler: StableDiffusionScheduler, 78 | numInferenceSteps stepCount: Int = 50, 79 | seed: UInt32 = 0, 80 | numPreviews previewCount: Int = 5, 81 | guidanceScale: Float = 7.5, 82 | disableSafety: Bool = false 83 | ) throws -> GenerationResult { 84 | let beginDate = Date() 85 | canceled = false 86 | let theSeed = seed > 0 ? seed : UInt32.random(in: 1...maxSeed) 87 | let sampleTimer = SampleTimer() 88 | sampleTimer.start() 89 | 90 | var config = StableDiffusionPipeline.Configuration(prompt: prompt) 91 | config.negativePrompt = negativePrompt 92 | config.stepCount = stepCount 93 | config.seed = theSeed 94 | config.guidanceScale = guidanceScale 95 | config.disableSafety = disableSafety 96 | config.schedulerType = scheduler.asStableDiffusionScheduler() 97 | config.useDenoisedIntermediates = true 98 | if isXL { 99 | config.encoderScaleFactor = 0.13025 100 | config.decoderScaleFactor = 0.13025 101 | config.schedulerTimestepSpacing = .karras 102 | } 103 | 104 | if isSD3 { 105 | config.encoderScaleFactor = 1.5305 106 | config.decoderScaleFactor = 1.5305 107 | config.decoderShiftFactor = 0.0609 108 | config.schedulerTimestepShift = 3.0 109 | } 110 | 111 | // Evenly distribute previews based on inference steps 112 | let previewIndices = previewIndices(stepCount, previewCount) 113 | 114 | let images = try pipeline.generateImages(configuration: config) { progress in 115 | sampleTimer.stop() 116 | handleProgress(StableDiffusionProgress(progress: progress, 117 | previewIndices: previewIndices), 118 | sampleTimer: sampleTimer) 119 | if progress.stepCount != progress.step { 120 | sampleTimer.start() 121 | } 122 | return !canceled 123 | } 124 | let interval = Date().timeIntervalSince(beginDate) 125 | print("Got images: \(images) in \(interval)") 126 | 127 | // Unwrap the 1 image we asked for, nil means safety checker triggered 128 | let image = images.compactMap({ $0 }).first 129 | return GenerationResult(image: image, lastSeed: theSeed, interval: interval, userCanceled: canceled, itsPerSecond: 1.0/sampleTimer.median) 130 | } 131 | 132 | func handleProgress(_ progress: StableDiffusionProgress, sampleTimer: SampleTimer) { 133 | self.progress = progress 134 | } 135 | 136 | func setCancelled() { 137 | canceled = true 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Diffusion/Common/Pipeline/PipelineLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PipelineLoader.swift 3 | // Diffusion 4 | // 5 | // Created by Pedro Cuenca on December 2022. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | 10 | import CoreML 11 | import Combine 12 | 13 | import ZIPFoundation 14 | import StableDiffusion 15 | 16 | class PipelineLoader { 17 | static let models = Settings.shared.applicationSupportURL().appendingPathComponent("hf-diffusion-models") 18 | let model: ModelInfo 19 | let computeUnits: ComputeUnits 20 | let maxSeed: UInt32 21 | 22 | private var downloadSubscriber: Cancellable? 23 | 24 | init(model: ModelInfo, computeUnits: ComputeUnits? = nil, maxSeed: UInt32 = UInt32.max) { 25 | self.model = model 26 | self.computeUnits = computeUnits ?? model.defaultComputeUnits 27 | self.maxSeed = maxSeed 28 | state = .undetermined 29 | setInitialState() 30 | } 31 | 32 | enum PipelinePreparationPhase { 33 | case undetermined 34 | case waitingToDownload 35 | case downloading(Double) 36 | case downloaded 37 | case uncompressing 38 | case readyOnDisk 39 | case loaded 40 | case failed(Error) 41 | } 42 | 43 | var state: PipelinePreparationPhase { 44 | didSet { 45 | statePublisher.value = state 46 | } 47 | } 48 | private(set) lazy var statePublisher: CurrentValueSubject = CurrentValueSubject(state) 49 | private(set) var downloader: Downloader? = nil 50 | 51 | func setInitialState() { 52 | if ready { 53 | state = .readyOnDisk 54 | return 55 | } 56 | if downloaded { 57 | state = .downloaded 58 | return 59 | } 60 | state = .waitingToDownload 61 | } 62 | } 63 | 64 | extension PipelineLoader { 65 | // Unused. Kept for debugging purposes. --pcuenca 66 | static func removeAll() { 67 | // Delete the parent models folder as it will be recreated when it's needed again 68 | do { 69 | try FileManager.default.removeItem(at: models) 70 | } catch { 71 | print("Failed to delete: \(models), error: \(error.localizedDescription)") 72 | } 73 | } 74 | } 75 | 76 | 77 | extension PipelineLoader { 78 | func cancel() { downloader?.cancel() } 79 | } 80 | 81 | extension PipelineLoader { 82 | var url: URL { 83 | return model.modelURL(for: variant) 84 | } 85 | 86 | var filename: String { 87 | return url.lastPathComponent 88 | } 89 | 90 | var downloadedURL: URL { PipelineLoader.models.appendingPathComponent(filename) } 91 | 92 | var uncompressURL: URL { PipelineLoader.models } 93 | 94 | var packagesFilename: String { (filename as NSString).deletingPathExtension } 95 | 96 | var compiledURL: URL { downloadedURL.deletingLastPathComponent().appendingPathComponent(packagesFilename) } 97 | 98 | var downloaded: Bool { 99 | return FileManager.default.fileExists(atPath: downloadedURL.path) 100 | } 101 | 102 | var ready: Bool { 103 | return FileManager.default.fileExists(atPath: compiledURL.path) 104 | } 105 | 106 | var variant: AttentionVariant { 107 | switch computeUnits { 108 | case .cpuOnly : return .original // Not supported yet 109 | case .cpuAndGPU : return .original 110 | case .cpuAndNeuralEngine: return model.supportsAttentionV2 ? .splitEinsumV2 : .splitEinsum 111 | case .all : return model.isSD3 ? .original : .splitEinsum 112 | @unknown default: 113 | fatalError("Unknown MLComputeUnits") 114 | } 115 | } 116 | 117 | func prepare() async throws -> Pipeline { 118 | do { 119 | do { 120 | try FileManager.default.createDirectory(atPath: PipelineLoader.models.path, withIntermediateDirectories: true, attributes: nil) 121 | } catch { 122 | print("Error creating PipelineLoader.models path: \(error)") 123 | } 124 | 125 | try await download() 126 | try await unzip() 127 | let pipeline = try await load(url: compiledURL) 128 | return Pipeline(pipeline, maxSeed: maxSeed) 129 | } catch { 130 | state = .failed(error) 131 | throw error 132 | } 133 | } 134 | 135 | @discardableResult 136 | func download() async throws -> URL { 137 | if ready || downloaded { return downloadedURL } 138 | 139 | let downloader = Downloader(from: url, to: downloadedURL) 140 | self.downloader = downloader 141 | downloadSubscriber = downloader.downloadState.sink { state in 142 | if case .downloading(let progress) = state { 143 | self.state = .downloading(progress) 144 | } 145 | } 146 | try downloader.waitUntilDone() 147 | return downloadedURL 148 | } 149 | 150 | func unzip() async throws { 151 | guard downloaded else { return } 152 | state = .uncompressing 153 | do { 154 | try FileManager().unzipItem(at: downloadedURL, to: uncompressURL) 155 | } catch { 156 | // Cleanup if error occurs while unzipping 157 | try FileManager.default.removeItem(at: uncompressURL) 158 | throw error 159 | } 160 | try FileManager.default.removeItem(at: downloadedURL) 161 | state = .readyOnDisk 162 | } 163 | 164 | func load(url: URL) async throws -> StableDiffusionPipelineProtocol { 165 | let beginDate = Date() 166 | let configuration = MLModelConfiguration() 167 | configuration.computeUnits = computeUnits 168 | let pipeline: StableDiffusionPipelineProtocol 169 | if model.isXL { 170 | if #available(macOS 14.0, iOS 17.0, *) { 171 | pipeline = try StableDiffusionXLPipeline(resourcesAt: url, 172 | configuration: configuration, 173 | reduceMemory: model.reduceMemory) 174 | } else { 175 | throw "Stable Diffusion XL requires macOS 14" 176 | } 177 | 178 | } else if model.isSD3 { 179 | if #available(macOS 14.0, iOS 17.0, *) { 180 | pipeline = try StableDiffusion3Pipeline(resourcesAt: url, 181 | configuration: configuration, 182 | reduceMemory: model.reduceMemory) 183 | } else { 184 | throw "Stable Diffusion 3 requires macOS 14" 185 | } 186 | } else { 187 | pipeline = try StableDiffusionPipeline(resourcesAt: url, 188 | controlNet: [], 189 | configuration: configuration, 190 | disableSafety: false, 191 | reduceMemory: model.reduceMemory) 192 | } 193 | try pipeline.loadResources() 194 | print("Pipeline loaded in \(Date().timeIntervalSince(beginDate))") 195 | state = .loaded 196 | return pipeline 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /Diffusion/Common/State.swift: -------------------------------------------------------------------------------- 1 | // 2 | // State.swift 3 | // Diffusion 4 | // 5 | // Created by Pedro Cuenca on 17/1/23. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import Combine 10 | import SwiftUI 11 | import StableDiffusion 12 | import CoreML 13 | 14 | let DEFAULT_MODEL = ModelInfo.sd3 15 | let DEFAULT_PROMPT = "Labrador in the style of Vermeer" 16 | 17 | enum GenerationState { 18 | case startup 19 | case running(StableDiffusionProgress?) 20 | case complete(String, CGImage?, UInt32, TimeInterval?) 21 | case userCanceled 22 | case failed(Error) 23 | } 24 | 25 | typealias ComputeUnits = MLComputeUnits 26 | 27 | /// Schedulers compatible with StableDiffusionPipeline. This is a local implementation of the StableDiffusionScheduler enum as a String represetation to allow for compliance with NSSecureCoding. 28 | public enum StableDiffusionScheduler: String { 29 | /// Scheduler that uses a pseudo-linear multi-step (PLMS) method 30 | case pndmScheduler 31 | /// Scheduler that uses a second order DPM-Solver++ algorithm 32 | case dpmSolverMultistepScheduler 33 | /// Scheduler for rectified flow based multimodal diffusion transformer models 34 | case discreteFlowScheduler 35 | 36 | func asStableDiffusionScheduler() -> StableDiffusion.StableDiffusionScheduler { 37 | switch self { 38 | case .pndmScheduler: return StableDiffusion.StableDiffusionScheduler.pndmScheduler 39 | case .dpmSolverMultistepScheduler: return StableDiffusion.StableDiffusionScheduler.dpmSolverMultistepScheduler 40 | case .discreteFlowScheduler: return StableDiffusion.StableDiffusionScheduler.discreteFlowScheduler 41 | } 42 | } 43 | } 44 | 45 | class GenerationContext: ObservableObject { 46 | let scheduler = StableDiffusionScheduler.dpmSolverMultistepScheduler 47 | 48 | @Published var pipeline: Pipeline? = nil { 49 | didSet { 50 | if let pipeline = pipeline { 51 | progressSubscriber = pipeline 52 | .progressPublisher 53 | .receive(on: DispatchQueue.main) 54 | .sink { progress in 55 | guard let progress = progress else { return } 56 | self.updatePreviewIfNeeded(progress) 57 | self.state = .running(progress) 58 | } 59 | } 60 | } 61 | } 62 | @Published var state: GenerationState = .startup 63 | 64 | @Published var positivePrompt = Settings.shared.prompt 65 | @Published var negativePrompt = Settings.shared.negativePrompt 66 | 67 | // FIXME: Double to support the slider component 68 | @Published var steps: Double = Settings.shared.stepCount 69 | @Published var numImages: Double = 1.0 70 | @Published var seed: UInt32 = Settings.shared.seed 71 | @Published var guidanceScale: Double = Settings.shared.guidanceScale 72 | @Published var previews: Double = runningOnMac ? Settings.shared.previewCount : 0.0 73 | @Published var disableSafety = false 74 | @Published var previewImage: CGImage? = nil 75 | 76 | @Published var computeUnits: ComputeUnits = Settings.shared.userSelectedComputeUnits ?? ModelInfo.defaultComputeUnits 77 | 78 | private var progressSubscriber: Cancellable? 79 | 80 | private func updatePreviewIfNeeded(_ progress: StableDiffusionProgress) { 81 | if previews == 0 || progress.step == 0 { 82 | previewImage = nil 83 | } 84 | 85 | if previews > 0, let newImage = progress.currentImages.first, newImage != nil { 86 | previewImage = newImage 87 | } 88 | } 89 | 90 | func generate() async throws -> GenerationResult { 91 | guard let pipeline = pipeline else { throw "No pipeline" } 92 | return try pipeline.generate( 93 | prompt: positivePrompt, 94 | negativePrompt: negativePrompt, 95 | scheduler: scheduler, 96 | numInferenceSteps: Int(steps), 97 | seed: seed, 98 | numPreviews: Int(previews), 99 | guidanceScale: Float(guidanceScale), 100 | disableSafety: disableSafety 101 | ) 102 | } 103 | 104 | func cancelGeneration() { 105 | pipeline?.setCancelled() 106 | } 107 | } 108 | 109 | class Settings { 110 | static let shared = Settings() 111 | 112 | let defaults = UserDefaults.standard 113 | 114 | enum Keys: String { 115 | case model 116 | case safetyCheckerDisclaimer 117 | case computeUnits 118 | case prompt 119 | case negativePrompt 120 | case guidanceScale 121 | case stepCount 122 | case previewCount 123 | case seed 124 | } 125 | 126 | private init() { 127 | defaults.register(defaults: [ 128 | Keys.model.rawValue: ModelInfo.v2Base.modelId, 129 | Keys.safetyCheckerDisclaimer.rawValue: false, 130 | Keys.computeUnits.rawValue: -1, // Use default 131 | Keys.prompt.rawValue: DEFAULT_PROMPT, 132 | Keys.negativePrompt.rawValue: "", 133 | Keys.guidanceScale.rawValue: 7.5, 134 | Keys.stepCount.rawValue: 25, 135 | Keys.previewCount.rawValue: 5, 136 | Keys.seed.rawValue: 0 137 | ]) 138 | } 139 | 140 | var currentModel: ModelInfo { 141 | set { 142 | defaults.set(newValue.modelId, forKey: Keys.model.rawValue) 143 | } 144 | get { 145 | guard let modelId = defaults.string(forKey: Keys.model.rawValue) else { return DEFAULT_MODEL } 146 | return ModelInfo.from(modelId: modelId) ?? DEFAULT_MODEL 147 | } 148 | } 149 | 150 | var prompt: String { 151 | set { 152 | defaults.set(newValue, forKey: Keys.prompt.rawValue) 153 | } 154 | get { 155 | return defaults.string(forKey: Keys.prompt.rawValue) ?? DEFAULT_PROMPT 156 | } 157 | } 158 | 159 | var negativePrompt: String { 160 | set { 161 | defaults.set(newValue, forKey: Keys.negativePrompt.rawValue) 162 | } 163 | get { 164 | return defaults.string(forKey: Keys.negativePrompt.rawValue) ?? "" 165 | } 166 | } 167 | 168 | var guidanceScale: Double { 169 | set { 170 | defaults.set(newValue, forKey: Keys.guidanceScale.rawValue) 171 | } 172 | get { 173 | return defaults.double(forKey: Keys.guidanceScale.rawValue) 174 | } 175 | } 176 | 177 | var stepCount: Double { 178 | set { 179 | defaults.set(newValue, forKey: Keys.stepCount.rawValue) 180 | } 181 | get { 182 | return defaults.double(forKey: Keys.stepCount.rawValue) 183 | } 184 | } 185 | 186 | var previewCount: Double { 187 | set { 188 | defaults.set(newValue, forKey: Keys.previewCount.rawValue) 189 | } 190 | get { 191 | return defaults.double(forKey: Keys.previewCount.rawValue) 192 | } 193 | } 194 | 195 | var seed: UInt32 { 196 | set { 197 | defaults.set(String(newValue), forKey: Keys.seed.rawValue) 198 | } 199 | get { 200 | if let seedString = defaults.string(forKey: Keys.seed.rawValue), let seedValue = UInt32(seedString) { 201 | return seedValue 202 | } 203 | return 0 204 | } 205 | } 206 | 207 | var safetyCheckerDisclaimerShown: Bool { 208 | set { 209 | defaults.set(newValue, forKey: Keys.safetyCheckerDisclaimer.rawValue) 210 | } 211 | get { 212 | return defaults.bool(forKey: Keys.safetyCheckerDisclaimer.rawValue) 213 | } 214 | } 215 | 216 | /// Returns the option selected by the user, if overridden 217 | /// `nil` means: guess best 218 | var userSelectedComputeUnits: ComputeUnits? { 219 | set { 220 | // Any value other than the supported ones would cause `get` to return `nil` 221 | defaults.set(newValue?.rawValue ?? -1, forKey: Keys.computeUnits.rawValue) 222 | } 223 | get { 224 | let current = defaults.integer(forKey: Keys.computeUnits.rawValue) 225 | guard current != -1 else { return nil } 226 | return ComputeUnits(rawValue: current) 227 | } 228 | } 229 | 230 | public func applicationSupportURL() -> URL { 231 | let fileManager = FileManager.default 232 | guard let appDirectoryURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else { 233 | // To ensure we don't return an optional - if the user domain application support cannot be accessed use the top level application support directory 234 | return URL.applicationSupportDirectory 235 | } 236 | 237 | do { 238 | // Create the application support directory if it doesn't exist 239 | try fileManager.createDirectory(at: appDirectoryURL, withIntermediateDirectories: true, attributes: nil) 240 | return appDirectoryURL 241 | } catch { 242 | print("Error creating application support directory: \(error)") 243 | return fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! 244 | } 245 | } 246 | 247 | func tempStorageURL() -> URL { 248 | 249 | let tmpDir = applicationSupportURL().appendingPathComponent("hf-diffusion-tmp") 250 | 251 | // Create directory if it doesn't exist 252 | if !FileManager.default.fileExists(atPath: tmpDir.path) { 253 | do { 254 | try FileManager.default.createDirectory(at: tmpDir, withIntermediateDirectories: true, attributes: nil) 255 | } catch { 256 | print("Failed to create temporary directory: \(error)") 257 | return FileManager.default.temporaryDirectory 258 | } 259 | } 260 | 261 | return tmpDir 262 | } 263 | 264 | } 265 | -------------------------------------------------------------------------------- /Diffusion/Common/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // Diffusion 4 | // 5 | // Created by Pedro Cuenca on 14/1/23. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import Foundation 10 | 11 | extension String: Error {} 12 | 13 | extension Double { 14 | func formatted(_ format: String) -> String { 15 | return String(format: "\(format)", self) 16 | } 17 | } 18 | 19 | extension String { 20 | var first200Safe: String { 21 | let endIndex = index(startIndex, offsetBy: Swift.min(200, count)) 22 | let substring = String(self[startIndex.. [Bool] { 48 | // Ensure valid parameters 49 | guard numInferenceSteps > 0, numPreviews > 0 else { 50 | return [Bool](repeating: false, count: numInferenceSteps) 51 | } 52 | 53 | // Compute the ideal (floating-point) step size, which represents the average number of steps between previews 54 | let idealStep = Double(numInferenceSteps) / Double(numPreviews) 55 | 56 | // Compute the actual steps at which previews should be made. For each preview, we multiply the ideal step size by the preview number, and round to the nearest integer. 57 | // The result is converted to a `Set` for fast membership tests. 58 | let previewIndices: Set = Set((0.. Double { 70 | let multiplier = pow(10, Double(places)) 71 | let newDecimal = multiplier * self // move the decimal right 72 | let truncated = Double(Int(newDecimal)) // drop the fraction 73 | let originalDecimal = truncated / multiplier // move the decimal back 74 | return originalDecimal 75 | } 76 | } 77 | 78 | func formatLargeNumber(_ n: UInt32) -> String { 79 | let num = abs(Double(n)) 80 | 81 | switch num { 82 | case 1_000_000_000...: 83 | var formatted = num / 1_000_000_000 84 | formatted = formatted.reduceScale(to: 3) 85 | return "\(formatted)B" 86 | 87 | case 1_000_000...: 88 | var formatted = num / 1_000_000 89 | formatted = formatted.reduceScale(to: 3) 90 | return "\(formatted)M" 91 | 92 | case 1_000...: 93 | return "\(n)" 94 | 95 | case 0...: 96 | return "\(n)" 97 | 98 | default: 99 | return "\(n)" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Diffusion/Common/Views/PromptTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PromptTextField.swift 3 | // Diffusion-macOS 4 | // 5 | // Created by Dolmere on 22/06/2023. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import SwiftUI 10 | import Combine 11 | import StableDiffusion 12 | 13 | struct PromptTextField: View { 14 | @State private var output: String = "" 15 | @State private var input: String = "" 16 | @State private var typing = false 17 | @State private var tokenCount: Int = 0 18 | @State var isPositivePrompt: Bool = true 19 | @State private var tokenizer: BPETokenizer? 20 | @State private var currentModelVersion: String = "" 21 | 22 | @Binding var textBinding: String 23 | @Binding var model: String // the model version as it's stored in Settings 24 | 25 | private let maxTokenCount = 77 26 | 27 | private var modelInfo: ModelInfo? { 28 | ModelInfo.from(modelVersion: $model.wrappedValue) 29 | } 30 | 31 | private var pipelineLoader: PipelineLoader? { 32 | guard let modelInfo = modelInfo else { return nil } 33 | return PipelineLoader(model: modelInfo) 34 | } 35 | 36 | private var compiledURL: URL? { 37 | return pipelineLoader?.compiledURL 38 | } 39 | 40 | private var textColor: Color { 41 | switch tokenCount { 42 | case 0...65: 43 | return .green 44 | case 66...75: 45 | return .orange 46 | default: 47 | return .red 48 | } 49 | } 50 | 51 | // macOS initializer 52 | init(text: Binding, isPositivePrompt: Bool, model: Binding) { 53 | _textBinding = text 54 | self.isPositivePrompt = isPositivePrompt 55 | _model = model 56 | } 57 | 58 | // iOS initializer 59 | init(text: Binding, isPositivePrompt: Bool, model: String) { 60 | _textBinding = text 61 | self.isPositivePrompt = isPositivePrompt 62 | _model = .constant(model) 63 | } 64 | 65 | var body: some View { 66 | VStack { 67 | #if os(macOS) 68 | TextField(isPositivePrompt ? "Positive prompt" : "Negative Prompt", text: $textBinding, 69 | axis: .vertical) 70 | .lineLimit(20) 71 | .textFieldStyle(.squareBorder) 72 | .listRowInsets(EdgeInsets(top: 0, leading: -20, bottom: 0, trailing: 20)) 73 | .foregroundColor(textColor == .green ? .primary : textColor) 74 | .frame(minHeight: 30) 75 | if modelInfo != nil && tokenizer != nil { 76 | HStack { 77 | Spacer() 78 | if !textBinding.isEmpty { 79 | Text("\(tokenCount)") 80 | .foregroundColor(textColor) 81 | Text(" / \(maxTokenCount)") 82 | } 83 | } 84 | .onReceive(Just(textBinding)) { text in 85 | updateTokenCount(newText: text) 86 | } 87 | .font(.caption) 88 | } 89 | #else 90 | TextField("Prompt", text: $textBinding, axis: .vertical) 91 | .lineLimit(20) 92 | .listRowInsets(EdgeInsets(top: 0, leading: -20, bottom: 0, trailing: 20)) 93 | .foregroundColor(textColor == .green ? .primary : textColor) 94 | .frame(minHeight: 30) 95 | HStack { 96 | if !textBinding.isEmpty { 97 | Text("\(tokenCount)") 98 | .foregroundColor(textColor) 99 | Text(" / \(maxTokenCount)") 100 | } 101 | Spacer() 102 | } 103 | .onReceive(Just(textBinding)) { text in 104 | updateTokenCount(newText: text) 105 | } 106 | .font(.caption) 107 | #endif 108 | } 109 | .onChange(of: model) { model in 110 | updateTokenCount(newText: textBinding) 111 | } 112 | .onAppear { 113 | updateTokenCount(newText: textBinding) 114 | } 115 | } 116 | 117 | private func updateTokenCount(newText: String) { 118 | // ensure that the compiled URL exists 119 | guard let compiledURL = compiledURL else { return } 120 | // Initialize the tokenizer only when it's not created yet or the model changes 121 | // Check if the model version has changed 122 | let modelVersion = $model.wrappedValue 123 | if modelVersion != currentModelVersion { 124 | do { 125 | tokenizer = try BPETokenizer( 126 | mergesAt: compiledURL.appendingPathComponent("merges.txt"), 127 | vocabularyAt: compiledURL.appendingPathComponent("vocab.json") 128 | ) 129 | currentModelVersion = modelVersion 130 | } catch { 131 | print("Failed to create tokenizer: \(error)") 132 | return 133 | } 134 | } 135 | let (tokens, _) = tokenizer?.tokenize(input: newText) ?? ([], []) 136 | 137 | DispatchQueue.main.async { 138 | self.tokenCount = tokens.count 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Diffusion/Diffusion.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.kernel.extended-virtual-addressing 6 | 7 | com.apple.developer.kernel.increased-memory-limit 8 | 9 | com.apple.security.app-sandbox 10 | 11 | com.apple.security.files.user-selected.read-write 12 | 13 | com.apple.security.network.client 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Diffusion/DiffusionApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiffusionApp.swift 3 | // Diffusion 4 | // 5 | // Created by Pedro Cuenca on December 2022. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import SwiftUI 10 | 11 | @main 12 | struct DiffusionApp: App { 13 | var body: some Scene { 14 | WindowGroup { 15 | LoadingView() 16 | } 17 | } 18 | } 19 | 20 | let runningOnMac = ProcessInfo.processInfo.isMacCatalystApp 21 | let deviceHas6GBOrMore = ProcessInfo.processInfo.physicalMemory > 5910000000 // Reported by iOS 17 beta (21A5319a) on iPhone 13 Pro: 5917753344 22 | let deviceHas8GBOrMore = ProcessInfo.processInfo.physicalMemory > 7900000000 // Reported by iOS 17.0.2 on iPhone 15 Pro Max: 8021032960 23 | 24 | let deviceSupportsQuantization = { 25 | if #available(iOS 17, *) { 26 | true 27 | } else { 28 | false 29 | } 30 | }() 31 | -------------------------------------------------------------------------------- /Diffusion/DiffusionImage+iOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiffusionImage+iOS.swift 3 | // Diffusion 4 | // 5 | // Created by Dolmere and Pedro Cuenca on 30/07/2023. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | import UniformTypeIdentifiers 11 | 12 | extension DiffusionImage { 13 | 14 | /// Instance func to place the generated image on the file system and return the `fileURL` where it is stored. 15 | func save(cgImage: CGImage, filename: String?) -> URL? { 16 | 17 | let image = UIImage(cgImage: cgImage) 18 | let fn = filename ?? "diffusion_generated_image" 19 | let appSupportURL = Settings.shared.tempStorageURL() 20 | 21 | let fileURL = appSupportURL 22 | .appendingPathComponent(fn) 23 | .appendingPathExtension("png") 24 | 25 | if let imageData = image.pngData() { 26 | do { 27 | try imageData.write(to: fileURL) 28 | return fileURL 29 | } catch { 30 | print("Error saving image to temporary file: \(error)") 31 | } 32 | } 33 | return nil 34 | } 35 | 36 | /// Returns a `Data` representation of this generated image in PNG format or nil if there is an error converting the image data. 37 | func pngRepresentation() -> Data? { 38 | let bitmapRep = UIImage(cgImage: cgImage).pngData() 39 | return bitmapRep 40 | } 41 | } 42 | 43 | extension DiffusionImage { 44 | 45 | // MARK: - UIPasteboardWriting 46 | 47 | func writableTypeIdentifiers(for pasteboard: UIPasteboard) -> [String] { 48 | return [UTType.png.identifier] 49 | } 50 | 51 | func itemProviders(forActivityType activityType: UIActivity.ActivityType?) -> [NSItemProvider] { 52 | let itemProvider = NSItemProvider() 53 | itemProvider.registerDataRepresentation(forTypeIdentifier: UTType.png.identifier, visibility: .all) { completion in 54 | guard let pngData = self.pngRepresentation() else { 55 | completion(nil, NSError(domain: "DiffusionImageErrorDomain", code: 0, userInfo: nil)) 56 | return nil 57 | } 58 | completion(pngData, nil) 59 | return nil 60 | } 61 | return [itemProvider] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Diffusion/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPhotoLibraryAddUsageDescription 6 | To be able to save generated images to your Photo Library, you’ll need to allow this. 7 | CFBundleDisplayName 8 | Diffusers 9 | 10 | 11 | -------------------------------------------------------------------------------- /Diffusion/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Diffusion/Utils_iOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils_iOS.swift 3 | // Diffusion 4 | // 5 | // Created by Dolmere on 31/07/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension CGImage { 11 | static func fromData(_ imageData: Data) -> CGImage? { 12 | if let image = UIImage(data: imageData)?.cgImage { 13 | return image 14 | } 15 | return nil 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Diffusion/Views/Loading.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Loading.swift 3 | // Diffusion 4 | // 5 | // Created by Pedro Cuenca on December 2022. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import SwiftUI 10 | import Combine 11 | 12 | func iosModel() -> ModelInfo { 13 | guard deviceSupportsQuantization else { return ModelInfo.v21Base } 14 | if deviceHas6GBOrMore { return ModelInfo.xlmbpChunked } 15 | return ModelInfo.v21Palettized 16 | } 17 | 18 | struct LoadingView: View { 19 | 20 | @StateObject var generation = GenerationContext() 21 | 22 | @State private var preparationPhase = "Downloading…" 23 | @State private var downloadProgress: Double = 0 24 | 25 | enum CurrentView { 26 | case loading 27 | case textToImage 28 | case error(String) 29 | } 30 | @State private var currentView: CurrentView = .loading 31 | 32 | @State private var stateSubscriber: Cancellable? 33 | 34 | var body: some View { 35 | VStack { 36 | switch currentView { 37 | case .textToImage: TextToImage().transition(.opacity) 38 | case .error(let message): ErrorPopover(errorMessage: message).transition(.move(edge: .top)) 39 | case .loading: 40 | // TODO: Don't present progress view if the pipeline is cached 41 | ProgressView(preparationPhase, value: downloadProgress, total: 1).padding() 42 | } 43 | } 44 | .animation(.easeIn, value: currentView) 45 | .environmentObject(generation) 46 | .onAppear { 47 | Task.init { 48 | let loader = PipelineLoader(model: iosModel()) 49 | stateSubscriber = loader.statePublisher.sink { state in 50 | DispatchQueue.main.async { 51 | switch state { 52 | case .downloading(let progress): 53 | preparationPhase = "Downloading" 54 | downloadProgress = progress 55 | case .uncompressing: 56 | preparationPhase = "Uncompressing" 57 | downloadProgress = 1 58 | case .readyOnDisk: 59 | preparationPhase = "Loading" 60 | downloadProgress = 1 61 | default: 62 | break 63 | } 64 | } 65 | } 66 | do { 67 | generation.pipeline = try await loader.prepare() 68 | self.currentView = .textToImage 69 | } catch { 70 | self.currentView = .error("Could not load model, error: \(error)") 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | // Required by .animation 78 | extension LoadingView.CurrentView: Equatable {} 79 | 80 | struct ErrorPopover: View { 81 | var errorMessage: String 82 | 83 | var body: some View { 84 | Text(errorMessage) 85 | .font(.headline) 86 | .padding() 87 | .foregroundColor(.red) 88 | .background(Color.white) 89 | .cornerRadius(8) 90 | .shadow(color: Color.black.opacity(0.2), radius: 8, x: 0, y: 4) 91 | } 92 | } 93 | 94 | struct LoadingView_Previews: PreviewProvider { 95 | static var previews: some View { 96 | LoadingView() 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Diffusion/Views/TextToImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextToImage.swift 3 | // Diffusion 4 | // 5 | // Created by Pedro Cuenca on December 2022. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import SwiftUI 10 | import Combine 11 | import StableDiffusion 12 | 13 | 14 | /// Presents "Share" + "Save" buttons on Mac; just "Share" on iOS/iPadOS. 15 | /// This is because I didn't find a way for "Share" to show a Save option when running on macOS. 16 | struct ShareButtons: View { 17 | var image: CGImage 18 | var name: String 19 | 20 | var filename: String { 21 | name.replacingOccurrences(of: " ", with: "_") 22 | } 23 | 24 | var body: some View { 25 | let imageView = Image(image, scale: 1, label: Text(name)) 26 | 27 | if runningOnMac { 28 | HStack { 29 | ShareLink(item: imageView, preview: SharePreview(name, image: imageView)) 30 | Button() { 31 | guard let imageData = UIImage(cgImage: image).pngData() else { 32 | return 33 | } 34 | do { 35 | let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent("\(filename).png") 36 | try imageData.write(to: fileURL) 37 | let controller = UIDocumentPickerViewController(forExporting: [fileURL]) 38 | 39 | let scene = UIApplication.shared.connectedScenes.first as! UIWindowScene 40 | scene.windows.first!.rootViewController!.present(controller, animated: true) 41 | } catch { 42 | print("Error creating file") 43 | } 44 | } label: { 45 | Label("Save…", systemImage: "square.and.arrow.down") 46 | } 47 | } 48 | } else { 49 | ShareLink(item: imageView, preview: SharePreview(name, image: imageView)) 50 | } 51 | } 52 | } 53 | 54 | struct ImageWithPlaceholder: View { 55 | @EnvironmentObject var generation: GenerationContext 56 | var state: Binding 57 | 58 | var body: some View { 59 | switch state.wrappedValue { 60 | case .startup: return AnyView(Image("placeholder").resizable()) 61 | case .running(let progress): 62 | guard let progress = progress, progress.stepCount > 0 else { 63 | // The first time it takes a little bit before generation starts 64 | return AnyView(ProgressView()) 65 | } 66 | 67 | let step = Int(progress.step) + 1 68 | let fraction = Double(step) / Double(progress.stepCount) 69 | let label = "Step \(step) of \(progress.stepCount)" 70 | return AnyView(VStack { 71 | Group { 72 | if let safeImage = generation.previewImage { 73 | Image(safeImage, scale: 1, label: Text("generated")) 74 | .resizable() 75 | .clipShape(RoundedRectangle(cornerRadius: 20)) 76 | } 77 | } 78 | ProgressView(label, value: fraction, total: 1).padding() 79 | }) 80 | case .complete(let lastPrompt, let image, _, let interval): 81 | guard let theImage = image else { 82 | return AnyView(Image(systemName: "exclamationmark.triangle").resizable()) 83 | } 84 | 85 | let imageView = Image(theImage, scale: 1, label: Text("generated")) 86 | return AnyView( 87 | VStack { 88 | imageView.resizable().clipShape(RoundedRectangle(cornerRadius: 20)) 89 | HStack { 90 | let intervalString = String(format: "Time: %.1fs", interval ?? 0) 91 | Rectangle().fill(.clear).overlay(Text(intervalString).frame(maxWidth: .infinity, alignment: .leading).padding(.leading)) 92 | Rectangle().fill(.clear).overlay( 93 | HStack { 94 | Spacer() 95 | ShareButtons(image: theImage, name: lastPrompt).padding(.trailing) 96 | } 97 | ) 98 | }.frame(maxHeight: 25) 99 | }) 100 | case .failed(_): 101 | return AnyView(Image(systemName: "exclamationmark.triangle").resizable()) 102 | case .userCanceled: 103 | return AnyView(Text("Generation canceled")) 104 | } 105 | } 106 | } 107 | 108 | struct TextToImage: View { 109 | @EnvironmentObject var generation: GenerationContext 110 | 111 | func submit() { 112 | if case .running = generation.state { return } 113 | Task { 114 | generation.state = .running(nil) 115 | do { 116 | let result = try await generation.generate() 117 | generation.state = .complete(generation.positivePrompt, result.image, result.lastSeed, result.interval) 118 | } catch { 119 | generation.state = .failed(error) 120 | } 121 | } 122 | } 123 | 124 | var body: some View { 125 | VStack { 126 | HStack { 127 | PromptTextField(text: $generation.positivePrompt, isPositivePrompt: true, model: iosModel().modelVersion) 128 | Button("Generate") { 129 | submit() 130 | } 131 | .padding() 132 | .buttonStyle(.borderedProminent) 133 | } 134 | ImageWithPlaceholder(state: $generation.state) 135 | .scaledToFit() 136 | Spacer() 137 | } 138 | .padding() 139 | .environmentObject(generation) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /DiffusionTests/DiffusionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiffusionTests.swift 3 | // DiffusionTests 4 | // 5 | // Created by Pedro Cuenca on December 2022. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import XCTest 10 | 11 | final class DiffusionTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | // Any test you write for XCTest can be annotated as throws and async. 25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 27 | } 28 | 29 | func testPerformanceExample() throws { 30 | // This is an example of a performance test case. 31 | measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /DiffusionUITests/DiffusionUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiffusionUITests.swift 3 | // DiffusionUITests 4 | // 5 | // Created by Pedro Cuenca on December 2022. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import XCTest 10 | 11 | final class DiffusionUITests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 20 | } 21 | 22 | override func tearDownWithError() throws { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | } 25 | 26 | func testExample() throws { 27 | // UI tests must launch the application that they test. 28 | let app = XCUIApplication() 29 | app.launch() 30 | 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | func testLaunchPerformance() throws { 35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 36 | // This measures how long it takes to launch your application. 37 | measure(metrics: [XCTApplicationLaunchMetric()]) { 38 | XCUIApplication().launch() 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DiffusionUITests/DiffusionUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiffusionUITestsLaunchTests.swift 3 | // DiffusionUITests 4 | // 5 | // Created by Pedro Cuenca on December 2022. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | import XCTest 10 | 11 | final class DiffusionUITestsLaunchTests: XCTestCase { 12 | 13 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 14 | true 15 | } 16 | 17 | override func setUpWithError() throws { 18 | continueAfterFailure = false 19 | } 20 | 21 | func testLaunch() throws { 22 | let app = XCUIApplication() 23 | app.launch() 24 | 25 | // Insert steps here to perform after app launch but before taking a screenshot, 26 | // such as logging into a test account or navigating somewhere in the app 27 | 28 | let attachment = XCTAttachment(screenshot: app.screenshot()) 29 | attachment.name = "Launch Screen" 30 | attachment.lifetime = .keepAlways 31 | add(attachment) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 Hugging Face SAS. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift Core ML Diffusers 🧨 2 | 3 | This is a native app that shows how to integrate Apple's [Core ML Stable Diffusion implementation](https://github.com/apple/ml-stable-diffusion) in a native Swift UI application. The Core ML port is a simplification of the Stable Diffusion implementation from the [diffusers library](https://github.com/huggingface/diffusers). This application can be used for faster iteration, or as sample code for any use cases. 4 | 5 | This is what the app looks like on macOS: 6 | ![App Screenshot](screenshot.jpg) 7 | 8 | On first launch, the application downloads a zipped archive with a Core ML version of Stability AI's Stable Diffusion v2 base, from [this location in the Hugging Face Hub](https://huggingface.co/pcuenq/coreml-stable-diffusion-2-base/tree/main). This process takes a while, as several GB of data have to be downloaded and unarchived. 9 | 10 | For faster inference, we use a very fast scheduler: [DPM-Solver++](https://github.com/LuChengTHU/dpm-solver), that we ported to Swift from our [diffusers DPMSolverMultistepScheduler implementation](https://github.com/huggingface/diffusers/blob/main/src/diffusers/schedulers/scheduling_dpmsolver_multistep.py). 11 | 12 | The app supports models quantized with `coremltools` version 7 or better. This requires macOS 14 or iOS/iPadOS 17. 13 | 14 | ## Compatibility and Performance 15 | 16 | - macOS Ventura 13.1, iOS/iPadOS 16.2, Xcode 14.2. 17 | - Performance (after the initial generation, which is slower) 18 | * ~8s in macOS on MacBook Pro M1 Max (64 GB). Model: Stable Diffusion v2-base, ORIGINAL attention implementation, running on CPU + GPU. 19 | * 23 ~ 30s on iPhone 13 Pro. Model: Stable Diffusion v2-base, SPLIT_EINSUM attention, CPU + Neural Engine, memory reduction enabled. 20 | 21 | See [this post](https://huggingface.co/blog/fast-mac-diffusers) and [this issue](https://github.com/huggingface/swift-coreml-diffusers/issues/31) for additional performance figures. 22 | 23 | Quantized models run faster, but they require macOS Ventura 14, or iOS/iPadOS 17. 24 | 25 | The application will try to guess the best hardware to run models on. You can override this setting using the `Advanced` section in the controls sidebar. 26 | 27 | ## How to Run 28 | 29 | The easiest way to test the app on macOS is by [downloading it from the Mac App Store](https://apps.apple.com/app/diffusers/id1666309574). 30 | 31 | ## How to Build 32 | 33 | You need [Xcode](https://developer.apple.com/xcode/) to build the app. When you clone the repo, please update `common.xcconfig` with your development team identifier. Code signing is required to run on iOS, but it's currently disabled for macOS. 34 | 35 | ## Known Issues 36 | 37 | Performance on iPhone is somewhat erratic, sometimes it's ~20x slower and the phone heats up. This happens because the model could not be scheduled to run on the Neural Engine and everything happens in the CPU. We have not been able to determine the reasons for this problem. If you observe the same, here are some recommendations: 38 | - Detach from Xcode 39 | - Kill apps you are not using. 40 | - Let the iPhone cool down before repeating the test. 41 | - Reboot your device. 42 | 43 | ## Next Steps 44 | 45 | - Allow additional models to be downloaded from the Hub. 46 | -------------------------------------------------------------------------------- /config/common.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // common.xcconfig 3 | // Diffusion 4 | // 5 | // Created by Pedro Cuenca on 202212. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | // Configuration settings file format documentation can be found at: 10 | // https://help.apple.com/xcode/#/dev745c5c974 11 | 12 | PRODUCT_NAME = Diffusers 13 | CURRENT_PROJECT_VERSION = 20240607.135124 14 | MARKETING_VERSION = 1.5 15 | 16 | // Update if you fork this repo 17 | DEVELOPMENT_TEAM = 2EADP68M95 18 | PRODUCT_BUNDLE_IDENTIFIER = com.huggingface.Diffusers 19 | -------------------------------------------------------------------------------- /config/debug.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // debug.xcconfig 3 | // Diffusion 4 | // 5 | // Created by Pedro Cuenca on 17/1/23. 6 | // See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE 7 | // 8 | 9 | #include "common.xcconfig" 10 | 11 | // Disable code-signing for macOS 12 | CODE_SIGN_IDENTITY[sdk=macos*] = 13 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huggingface/swift-coreml-diffusers/a561fae8b47707efa68f70e2fa1fea5ab4462ab9/screenshot.jpg --------------------------------------------------------------------------------