diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml deleted file mode 100644 index 26d18c1a..00000000 --- a/.github/workflows/Release.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Release - -on: - push: - tags: - - "*" - -jobs: - pod-trunk-push: - runs-on: macOS-12 - steps: - - uses: maxim-lobanov/setup-xcode@v1.1 - with: - xcode-version: "14.2" - - uses: actions/checkout@v2 - - name: Deploy - env: - COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} - run: pod trunk push Brightroom.podspec --allow-warnings diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 71e28fea..eaadc944 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -12,10 +12,8 @@ jobs: with: xcode-version: "14.2" - uses: actions/checkout@v2 - - name: Pod install - run: pod install - name: Build demo app - run: xcodebuild -scheme SwiftUIDemo -destination 'platform=iOS Simulator,name=iPhone 8,OS=16.2' -derivedDataPath ./DerivedData | xcpretty + run: xcodebuild -project ./Dev/Brightroom.xcodeproj -scheme SwiftUIDemo -destination 'platform=iOS Simulator,name=iPhone 8,OS=16.2' | xcpretty demo: runs-on: macos-12 @@ -25,21 +23,8 @@ jobs: with: xcode-version: "14.2" - uses: actions/checkout@v2 - - name: Pod install - run: pod install - name: Build demo app - run: xcodebuild -scheme Demo -destination 'platform=iOS Simulator,name=iPhone 8,OS=16.2' -derivedDataPath ./DerivedData | xcpretty - - pod-lint: - runs-on: macos-12 - - steps: - - uses: maxim-lobanov/setup-xcode@v1.1 - with: - xcode-version: "14.2" - - uses: actions/checkout@v2 - - name: CocoaPods Lint - run: pod lib lint --allow-warnings + run: xcodebuild -project ./Dev/Brightroom.xcodeproj -scheme Demo -destination 'platform=iOS Simulator,name=iPhone 8,OS=16.2' | xcpretty test: runs-on: macos-12 @@ -52,13 +37,7 @@ jobs: with: submodules: true - name: Test - run: xcodebuild -scheme BrightroomEngineTests -resultBundlePath results/BrightroomEngineTests.xcresult test -destination 'platform=iOS Simulator,name=iPhone 8,OS=16.2' -derivedDataPath ./DerivedData | xcpretty - - - uses: kishikawakatsumi/xcresulttool@v1 - with: - path: | - results/BrightroomEngineTests.xcresult - if: success() || failure() + run: xcodebuild -project ./Dev/Brightroom.xcodeproj -scheme BrightroomEngineTests test -destination 'platform=iOS Simulator,name=iPhone 8,OS=16.2' | xcpretty swiftpm: runs-on: macos-12 @@ -69,4 +48,4 @@ jobs: xcode-version: "14.2" - uses: actions/checkout@v2 - name: Build - run: xcodebuild -scheme BrightroomUI -sdk iphoneos | xcpretty + run: xcodebuild -scheme BrightroomUI -destination 'platform=iOS Simulator,name=iPhone 8,OS=16.2' | xcpretty diff --git a/Brightroom.podspec b/Brightroom.podspec deleted file mode 100644 index 571ae0f7..00000000 --- a/Brightroom.podspec +++ /dev/null @@ -1,38 +0,0 @@ -Pod::Spec.new do |s| - s.name = "Brightroom" - s.version = "2.8.0" - s.summary = "A component-oriented image editor on top of CoreImage." - - s.homepage = "https://github.com/muukii/Brightroom" - s.license = "MIT" - s.author = "muukii" - s.source = { :git => "https://github.com/muukii/Brightroom.git", :tag => s.version } - - s.swift_version = "5.6" - s.module_name = s.name - s.requires_arc = true - s.ios.deployment_target = "13.0" - s.ios.frameworks = ["UIKit", "CoreImage"] - s.ios.dependency "Verge/Store", ">= 8.19.0" - - s.subspec "Engine" do |ss| - ss.source_files = "Sources/BrightroomEngine/**/*.swift" - end - - s.subspec "UI-Classic" do |ss| - ss.source_files = "Sources/BrightroomUI/Shared/**/*.swift", "Sources/BrightroomUI/Built-in UI/ClassicImageEdit/**/*.swift" - ss.dependency "Brightroom/Engine" - ss.dependency "TransitionPatch" - ss.resource_bundles = { - "BrightroomUI" => ["Sources/BrightroomUI/Media.xcassets"] - } - end - - s.subspec "UI-Crop" do |ss| - ss.source_files = "Sources/BrightroomUI/Shared/**/*.swift", "Sources/BrightroomUI/Built-in UI/PhotosCrop/**/*.swift" - ss.dependency "Brightroom/Engine" - ss.resource_bundles = { - "BrightroomUI" => ["Sources/BrightroomUI/Media.xcassets"] - } - end -end diff --git a/Brightroom.xcodeproj/project.pbxproj b/Dev/Brightroom.xcodeproj/project.pbxproj similarity index 99% rename from Brightroom.xcodeproj/project.pbxproj rename to Dev/Brightroom.xcodeproj/project.pbxproj index 8fa79696..44df11fb 100644 --- a/Brightroom.xcodeproj/project.pbxproj +++ b/Dev/Brightroom.xcodeproj/project.pbxproj @@ -587,7 +587,7 @@ 4B36195526107ADB00877B21 /* path-data */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "path-data"; sourceTree = ""; }; 4B4103572611EAA80061A218 /* ImitationTinderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImitationTinderViewController.swift; sourceTree = ""; }; 4B41035F2611EAB20061A218 /* DemoImitationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoImitationsViewController.swift; sourceTree = ""; }; - 4B487E7528EECDCF0026A8CF /* Package */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Package; sourceTree = ""; }; + 4B524FE229B8F6A600C1A416 /* Brightroom */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Brightroom; path = ..; sourceTree = ""; }; 4B58E888260F0DEA004A834F /* DemoPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoPreviewViewController.swift; sourceTree = ""; }; 4B600B20216B7C9C001E1456 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4B600B22216B7C9C001E1456 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -1126,6 +1126,14 @@ path = Imitations; sourceTree = ""; }; + 4B524FE129B8F6A600C1A416 /* Packages */ = { + isa = PBXGroup; + children = ( + 4B524FE229B8F6A600C1A416 /* Brightroom */, + ); + name = Packages; + sourceTree = ""; + }; 4B58DF5E2606047400D85F97 /* Contents */ = { isa = PBXGroup; children = ( @@ -1149,7 +1157,7 @@ 4B600AF3216B7A94001E1456 = { isa = PBXGroup; children = ( - 4B487E7528EECDCF0026A8CF /* Package */, + 4B524FE129B8F6A600C1A416 /* Packages */, 4BE9B3D1260BA72D000A3D09 /* Bundle */, 4B6A5F5B2179D43A004D68DC /* Sources */, 4BDEE0A625F8CF6800FA22CB /* Tests */, diff --git a/Brightroom.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Dev/Brightroom.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Brightroom.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to Dev/Brightroom.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/Brightroom.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Dev/Brightroom.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from Brightroom.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to Dev/Brightroom.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/Brightroom.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Dev/Brightroom.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved similarity index 100% rename from Brightroom.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved rename to Dev/Brightroom.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/Brightroom.xcodeproj/xcshareddata/xcschemes/BrightroomEngine.xcscheme b/Dev/Brightroom.xcodeproj/xcshareddata/xcschemes/BrightroomEngine.xcscheme similarity index 100% rename from Brightroom.xcodeproj/xcshareddata/xcschemes/BrightroomEngine.xcscheme rename to Dev/Brightroom.xcodeproj/xcshareddata/xcschemes/BrightroomEngine.xcscheme diff --git a/Brightroom.xcodeproj/xcshareddata/xcschemes/BrightroomEngineTests.xcscheme b/Dev/Brightroom.xcodeproj/xcshareddata/xcschemes/BrightroomEngineTests.xcscheme similarity index 100% rename from Brightroom.xcodeproj/xcshareddata/xcschemes/BrightroomEngineTests.xcscheme rename to Dev/Brightroom.xcodeproj/xcshareddata/xcschemes/BrightroomEngineTests.xcscheme diff --git a/Brightroom.xcodeproj/xcshareddata/xcschemes/BrightroomUI.xcscheme b/Dev/Brightroom.xcodeproj/xcshareddata/xcschemes/BrightroomUI.xcscheme similarity index 100% rename from Brightroom.xcodeproj/xcshareddata/xcschemes/BrightroomUI.xcscheme rename to Dev/Brightroom.xcodeproj/xcshareddata/xcschemes/BrightroomUI.xcscheme diff --git a/Brightroom.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme b/Dev/Brightroom.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme similarity index 100% rename from Brightroom.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme rename to Dev/Brightroom.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme diff --git a/Brightroom.xcodeproj/xcshareddata/xcschemes/SwiftUIDemo.xcscheme b/Dev/Brightroom.xcodeproj/xcshareddata/xcschemes/SwiftUIDemo.xcscheme similarity index 100% rename from Brightroom.xcodeproj/xcshareddata/xcschemes/SwiftUIDemo.xcscheme rename to Dev/Brightroom.xcodeproj/xcshareddata/xcschemes/SwiftUIDemo.xcscheme diff --git a/Bundle/Images/008b8bb75b8a487dc5aac86c9abb06fb.png b/Dev/Bundle/Images/008b8bb75b8a487dc5aac86c9abb06fb.png similarity index 100% rename from Bundle/Images/008b8bb75b8a487dc5aac86c9abb06fb.png rename to Dev/Bundle/Images/008b8bb75b8a487dc5aac86c9abb06fb.png diff --git a/Bundle/Images/0132cfdbd8ca323574a2072e7ed5014c.png b/Dev/Bundle/Images/0132cfdbd8ca323574a2072e7ed5014c.png similarity index 100% rename from Bundle/Images/0132cfdbd8ca323574a2072e7ed5014c.png rename to Dev/Bundle/Images/0132cfdbd8ca323574a2072e7ed5014c.png diff --git a/Bundle/Images/0301fde58080883e938b604cab9768ea.png b/Dev/Bundle/Images/0301fde58080883e938b604cab9768ea.png similarity index 100% rename from Bundle/Images/0301fde58080883e938b604cab9768ea.png rename to Dev/Bundle/Images/0301fde58080883e938b604cab9768ea.png diff --git a/Bundle/Images/0646caeb9b9161c777f117007921a687.gif b/Dev/Bundle/Images/0646caeb9b9161c777f117007921a687.gif similarity index 100% rename from Bundle/Images/0646caeb9b9161c777f117007921a687.gif rename to Dev/Bundle/Images/0646caeb9b9161c777f117007921a687.gif diff --git a/Bundle/Images/073c98872b81d1004d750f18a4b5f732.png b/Dev/Bundle/Images/073c98872b81d1004d750f18a4b5f732.png similarity index 100% rename from Bundle/Images/073c98872b81d1004d750f18a4b5f732.png rename to Dev/Bundle/Images/073c98872b81d1004d750f18a4b5f732.png diff --git a/Bundle/Images/0839d93f8e77e21acd0ac40a80b14b7b.png b/Dev/Bundle/Images/0839d93f8e77e21acd0ac40a80b14b7b.png similarity index 100% rename from Bundle/Images/0839d93f8e77e21acd0ac40a80b14b7b.png rename to Dev/Bundle/Images/0839d93f8e77e21acd0ac40a80b14b7b.png diff --git a/Bundle/Images/0b7d50ac449fd59eb3de00647636d0c9.png b/Dev/Bundle/Images/0b7d50ac449fd59eb3de00647636d0c9.png similarity index 100% rename from Bundle/Images/0b7d50ac449fd59eb3de00647636d0c9.png rename to Dev/Bundle/Images/0b7d50ac449fd59eb3de00647636d0c9.png diff --git a/Bundle/Images/0d466db9067b719df0b06ef441bf1ee7.png b/Dev/Bundle/Images/0d466db9067b719df0b06ef441bf1ee7.png similarity index 100% rename from Bundle/Images/0d466db9067b719df0b06ef441bf1ee7.png rename to Dev/Bundle/Images/0d466db9067b719df0b06ef441bf1ee7.png diff --git a/Bundle/Images/138331052d7c6e4acebfaa92af314e12.png b/Dev/Bundle/Images/138331052d7c6e4acebfaa92af314e12.png similarity index 100% rename from Bundle/Images/138331052d7c6e4acebfaa92af314e12.png rename to Dev/Bundle/Images/138331052d7c6e4acebfaa92af314e12.png diff --git a/Bundle/Images/138d3b9e0d9fbf641b8135981e597c3a.jpg b/Dev/Bundle/Images/138d3b9e0d9fbf641b8135981e597c3a.jpg similarity index 100% rename from Bundle/Images/138d3b9e0d9fbf641b8135981e597c3a.jpg rename to Dev/Bundle/Images/138d3b9e0d9fbf641b8135981e597c3a.jpg diff --git a/Bundle/Images/13f665c09e4b03cdbe2fff3015ec8aa7.png b/Dev/Bundle/Images/13f665c09e4b03cdbe2fff3015ec8aa7.png similarity index 100% rename from Bundle/Images/13f665c09e4b03cdbe2fff3015ec8aa7.png rename to Dev/Bundle/Images/13f665c09e4b03cdbe2fff3015ec8aa7.png diff --git a/Bundle/Images/18288f761922f9b9b00e927eaeb9e707.png b/Dev/Bundle/Images/18288f761922f9b9b00e927eaeb9e707.png similarity index 100% rename from Bundle/Images/18288f761922f9b9b00e927eaeb9e707.png rename to Dev/Bundle/Images/18288f761922f9b9b00e927eaeb9e707.png diff --git a/Bundle/Images/18bd8bf75e7a9b40b961dd501654ce0e.png b/Dev/Bundle/Images/18bd8bf75e7a9b40b961dd501654ce0e.png similarity index 100% rename from Bundle/Images/18bd8bf75e7a9b40b961dd501654ce0e.png rename to Dev/Bundle/Images/18bd8bf75e7a9b40b961dd501654ce0e.png diff --git a/Bundle/Images/18f9baf3834980f4b80a3e82ad45be48.png b/Dev/Bundle/Images/18f9baf3834980f4b80a3e82ad45be48.png similarity index 100% rename from Bundle/Images/18f9baf3834980f4b80a3e82ad45be48.png rename to Dev/Bundle/Images/18f9baf3834980f4b80a3e82ad45be48.png diff --git a/Bundle/Images/194531363df5b73f59c4c0517422f917.jpg b/Dev/Bundle/Images/194531363df5b73f59c4c0517422f917.jpg similarity index 100% rename from Bundle/Images/194531363df5b73f59c4c0517422f917.jpg rename to Dev/Bundle/Images/194531363df5b73f59c4c0517422f917.jpg diff --git a/Bundle/Images/1ae14e57b7062597279134ff2eeb39c0.png b/Dev/Bundle/Images/1ae14e57b7062597279134ff2eeb39c0.png similarity index 100% rename from Bundle/Images/1ae14e57b7062597279134ff2eeb39c0.png rename to Dev/Bundle/Images/1ae14e57b7062597279134ff2eeb39c0.png diff --git a/Bundle/Images/1b9a48cf04466108f6f2d225d100edbf.png b/Dev/Bundle/Images/1b9a48cf04466108f6f2d225d100edbf.png similarity index 100% rename from Bundle/Images/1b9a48cf04466108f6f2d225d100edbf.png rename to Dev/Bundle/Images/1b9a48cf04466108f6f2d225d100edbf.png diff --git a/Bundle/Images/1bcc34d49e56a2fba38490db206328b8.png b/Dev/Bundle/Images/1bcc34d49e56a2fba38490db206328b8.png similarity index 100% rename from Bundle/Images/1bcc34d49e56a2fba38490db206328b8.png rename to Dev/Bundle/Images/1bcc34d49e56a2fba38490db206328b8.png diff --git a/Bundle/Images/1cbb1bb37d62c44f67374cd451643dc4.jpg b/Dev/Bundle/Images/1cbb1bb37d62c44f67374cd451643dc4.jpg similarity index 100% rename from Bundle/Images/1cbb1bb37d62c44f67374cd451643dc4.jpg rename to Dev/Bundle/Images/1cbb1bb37d62c44f67374cd451643dc4.jpg diff --git a/Bundle/Images/1ebd73c1d3fbc89782f29507364128fc.png b/Dev/Bundle/Images/1ebd73c1d3fbc89782f29507364128fc.png similarity index 100% rename from Bundle/Images/1ebd73c1d3fbc89782f29507364128fc.png rename to Dev/Bundle/Images/1ebd73c1d3fbc89782f29507364128fc.png diff --git a/Bundle/Images/2183d39878e734cf79b62428b02fafb5.jpg b/Dev/Bundle/Images/2183d39878e734cf79b62428b02fafb5.jpg similarity index 100% rename from Bundle/Images/2183d39878e734cf79b62428b02fafb5.jpg rename to Dev/Bundle/Images/2183d39878e734cf79b62428b02fafb5.jpg diff --git a/Bundle/Images/21a84b8472f6d18f5bb5c0026e97cfaa.jpg b/Dev/Bundle/Images/21a84b8472f6d18f5bb5c0026e97cfaa.jpg similarity index 100% rename from Bundle/Images/21a84b8472f6d18f5bb5c0026e97cfaa.jpg rename to Dev/Bundle/Images/21a84b8472f6d18f5bb5c0026e97cfaa.jpg diff --git a/Bundle/Images/21ad703b38e2c350215bb92a849486f3.jpg b/Dev/Bundle/Images/21ad703b38e2c350215bb92a849486f3.jpg similarity index 100% rename from Bundle/Images/21ad703b38e2c350215bb92a849486f3.jpg rename to Dev/Bundle/Images/21ad703b38e2c350215bb92a849486f3.jpg diff --git a/Bundle/Images/243d9798466d64aba0acaa41f980bea6.gif b/Dev/Bundle/Images/243d9798466d64aba0acaa41f980bea6.gif similarity index 100% rename from Bundle/Images/243d9798466d64aba0acaa41f980bea6.gif rename to Dev/Bundle/Images/243d9798466d64aba0acaa41f980bea6.gif diff --git a/Bundle/Images/255015e07b6f9137b53b0f97d67a8aef.jpg b/Dev/Bundle/Images/255015e07b6f9137b53b0f97d67a8aef.jpg similarity index 100% rename from Bundle/Images/255015e07b6f9137b53b0f97d67a8aef.jpg rename to Dev/Bundle/Images/255015e07b6f9137b53b0f97d67a8aef.jpg diff --git a/Bundle/Images/28968137f4fc75fbf56f16d7a7a8551a.jpg b/Dev/Bundle/Images/28968137f4fc75fbf56f16d7a7a8551a.jpg similarity index 100% rename from Bundle/Images/28968137f4fc75fbf56f16d7a7a8551a.jpg rename to Dev/Bundle/Images/28968137f4fc75fbf56f16d7a7a8551a.jpg diff --git a/Bundle/Images/28c74d9284d9836017fd519f6932efd8.jpg b/Dev/Bundle/Images/28c74d9284d9836017fd519f6932efd8.jpg similarity index 100% rename from Bundle/Images/28c74d9284d9836017fd519f6932efd8.jpg rename to Dev/Bundle/Images/28c74d9284d9836017fd519f6932efd8.jpg diff --git a/Bundle/Images/2a6ff5f8106894b22dad3ce99673481a.png b/Dev/Bundle/Images/2a6ff5f8106894b22dad3ce99673481a.png similarity index 100% rename from Bundle/Images/2a6ff5f8106894b22dad3ce99673481a.png rename to Dev/Bundle/Images/2a6ff5f8106894b22dad3ce99673481a.png diff --git a/Bundle/Images/2b5bc31d84703bfb9f371925f0e3e57d.gif b/Dev/Bundle/Images/2b5bc31d84703bfb9f371925f0e3e57d.gif similarity index 100% rename from Bundle/Images/2b5bc31d84703bfb9f371925f0e3e57d.gif rename to Dev/Bundle/Images/2b5bc31d84703bfb9f371925f0e3e57d.gif diff --git a/Bundle/Images/2c9e7a1805f8b47630bbb83d21bf8222.jpg b/Dev/Bundle/Images/2c9e7a1805f8b47630bbb83d21bf8222.jpg similarity index 100% rename from Bundle/Images/2c9e7a1805f8b47630bbb83d21bf8222.jpg rename to Dev/Bundle/Images/2c9e7a1805f8b47630bbb83d21bf8222.jpg diff --git a/Bundle/Images/2d641a11233385bb37a524ff010a8531.png b/Dev/Bundle/Images/2d641a11233385bb37a524ff010a8531.png similarity index 100% rename from Bundle/Images/2d641a11233385bb37a524ff010a8531.png rename to Dev/Bundle/Images/2d641a11233385bb37a524ff010a8531.png diff --git a/Bundle/Images/316be81dfdeeb942e904feb3a77f4f83.jpg b/Dev/Bundle/Images/316be81dfdeeb942e904feb3a77f4f83.jpg similarity index 100% rename from Bundle/Images/316be81dfdeeb942e904feb3a77f4f83.jpg rename to Dev/Bundle/Images/316be81dfdeeb942e904feb3a77f4f83.jpg diff --git a/Bundle/Images/31e3bc3eb811cff582b5feee2494fed8.png b/Dev/Bundle/Images/31e3bc3eb811cff582b5feee2494fed8.png similarity index 100% rename from Bundle/Images/31e3bc3eb811cff582b5feee2494fed8.png rename to Dev/Bundle/Images/31e3bc3eb811cff582b5feee2494fed8.png diff --git a/Bundle/Images/32d08f4a5eb10332506ebedbb9bc7257.jpg b/Dev/Bundle/Images/32d08f4a5eb10332506ebedbb9bc7257.jpg similarity index 100% rename from Bundle/Images/32d08f4a5eb10332506ebedbb9bc7257.jpg rename to Dev/Bundle/Images/32d08f4a5eb10332506ebedbb9bc7257.jpg diff --git a/Bundle/Images/3625f98e00148cdc136c53bdcd2d2e1e.png b/Dev/Bundle/Images/3625f98e00148cdc136c53bdcd2d2e1e.png similarity index 100% rename from Bundle/Images/3625f98e00148cdc136c53bdcd2d2e1e.png rename to Dev/Bundle/Images/3625f98e00148cdc136c53bdcd2d2e1e.png diff --git a/Bundle/Images/3976a754ef0aca80e84e2c403d714579.jpg b/Dev/Bundle/Images/3976a754ef0aca80e84e2c403d714579.jpg similarity index 100% rename from Bundle/Images/3976a754ef0aca80e84e2c403d714579.jpg rename to Dev/Bundle/Images/3976a754ef0aca80e84e2c403d714579.jpg diff --git a/Bundle/Images/39f43f280b31152f1d27df3f9d189317.jpg b/Dev/Bundle/Images/39f43f280b31152f1d27df3f9d189317.jpg similarity index 100% rename from Bundle/Images/39f43f280b31152f1d27df3f9d189317.jpg rename to Dev/Bundle/Images/39f43f280b31152f1d27df3f9d189317.jpg diff --git a/Bundle/Images/3ba6af611cc5467cfdbd5566561b8478.jpg b/Dev/Bundle/Images/3ba6af611cc5467cfdbd5566561b8478.jpg similarity index 100% rename from Bundle/Images/3ba6af611cc5467cfdbd5566561b8478.jpg rename to Dev/Bundle/Images/3ba6af611cc5467cfdbd5566561b8478.jpg diff --git a/Bundle/Images/3cc4a7fc6481ea3681138da4643f3d16.jpg b/Dev/Bundle/Images/3cc4a7fc6481ea3681138da4643f3d16.jpg similarity index 100% rename from Bundle/Images/3cc4a7fc6481ea3681138da4643f3d16.jpg rename to Dev/Bundle/Images/3cc4a7fc6481ea3681138da4643f3d16.jpg diff --git a/Bundle/Images/3ea649db8e81a46ca4f92fb3238f78ff.jpg b/Dev/Bundle/Images/3ea649db8e81a46ca4f92fb3238f78ff.jpg similarity index 100% rename from Bundle/Images/3ea649db8e81a46ca4f92fb3238f78ff.jpg rename to Dev/Bundle/Images/3ea649db8e81a46ca4f92fb3238f78ff.jpg diff --git a/Bundle/Images/3ef05501315073d9d4e1c6b654d99ac0.jpg b/Dev/Bundle/Images/3ef05501315073d9d4e1c6b654d99ac0.jpg similarity index 100% rename from Bundle/Images/3ef05501315073d9d4e1c6b654d99ac0.jpg rename to Dev/Bundle/Images/3ef05501315073d9d4e1c6b654d99ac0.jpg diff --git a/Bundle/Images/4085c929e00c446d3fee18b5b20a27f9.jpg b/Dev/Bundle/Images/4085c929e00c446d3fee18b5b20a27f9.jpg similarity index 100% rename from Bundle/Images/4085c929e00c446d3fee18b5b20a27f9.jpg rename to Dev/Bundle/Images/4085c929e00c446d3fee18b5b20a27f9.jpg diff --git a/Bundle/Images/40bb78b1ac031125a6d8466b374962a8.jpg b/Dev/Bundle/Images/40bb78b1ac031125a6d8466b374962a8.jpg similarity index 100% rename from Bundle/Images/40bb78b1ac031125a6d8466b374962a8.jpg rename to Dev/Bundle/Images/40bb78b1ac031125a6d8466b374962a8.jpg diff --git a/Bundle/Images/429104334d1fb6a58e17307883c17608.png b/Dev/Bundle/Images/429104334d1fb6a58e17307883c17608.png similarity index 100% rename from Bundle/Images/429104334d1fb6a58e17307883c17608.png rename to Dev/Bundle/Images/429104334d1fb6a58e17307883c17608.png diff --git a/Bundle/Images/42ec8668adb5dbc6581393f463976510.png b/Dev/Bundle/Images/42ec8668adb5dbc6581393f463976510.png similarity index 100% rename from Bundle/Images/42ec8668adb5dbc6581393f463976510.png rename to Dev/Bundle/Images/42ec8668adb5dbc6581393f463976510.png diff --git a/Bundle/Images/4389427591c18bf36e748172640862c3.png b/Dev/Bundle/Images/4389427591c18bf36e748172640862c3.png similarity index 100% rename from Bundle/Images/4389427591c18bf36e748172640862c3.png rename to Dev/Bundle/Images/4389427591c18bf36e748172640862c3.png diff --git a/Bundle/Images/463d3570f92a6b6ecba0cc4fd9a7a384.png b/Dev/Bundle/Images/463d3570f92a6b6ecba0cc4fd9a7a384.png similarity index 100% rename from Bundle/Images/463d3570f92a6b6ecba0cc4fd9a7a384.png rename to Dev/Bundle/Images/463d3570f92a6b6ecba0cc4fd9a7a384.png diff --git a/Bundle/Images/46e5ac4a62d7a445a7c1fb704fafe05c.jpg b/Dev/Bundle/Images/46e5ac4a62d7a445a7c1fb704fafe05c.jpg similarity index 100% rename from Bundle/Images/46e5ac4a62d7a445a7c1fb704fafe05c.jpg rename to Dev/Bundle/Images/46e5ac4a62d7a445a7c1fb704fafe05c.jpg diff --git a/Bundle/Images/46f5d9c1b0fe352353688f736e5617b6.jpg b/Dev/Bundle/Images/46f5d9c1b0fe352353688f736e5617b6.jpg similarity index 100% rename from Bundle/Images/46f5d9c1b0fe352353688f736e5617b6.jpg rename to Dev/Bundle/Images/46f5d9c1b0fe352353688f736e5617b6.jpg diff --git a/Bundle/Images/4838ece0d3900220d33528ee027289bc.jpg b/Dev/Bundle/Images/4838ece0d3900220d33528ee027289bc.jpg similarity index 100% rename from Bundle/Images/4838ece0d3900220d33528ee027289bc.jpg rename to Dev/Bundle/Images/4838ece0d3900220d33528ee027289bc.jpg diff --git a/Bundle/Images/4aae896ba900c48c63cffc0cc9f8c4dc.png b/Dev/Bundle/Images/4aae896ba900c48c63cffc0cc9f8c4dc.png similarity index 100% rename from Bundle/Images/4aae896ba900c48c63cffc0cc9f8c4dc.png rename to Dev/Bundle/Images/4aae896ba900c48c63cffc0cc9f8c4dc.png diff --git a/Bundle/Images/4c5b82ba0a9c12356007bd71e52185b2.png b/Dev/Bundle/Images/4c5b82ba0a9c12356007bd71e52185b2.png similarity index 100% rename from Bundle/Images/4c5b82ba0a9c12356007bd71e52185b2.png rename to Dev/Bundle/Images/4c5b82ba0a9c12356007bd71e52185b2.png diff --git a/Bundle/Images/4f14b7aab3a41855378c5517342598b9.png b/Dev/Bundle/Images/4f14b7aab3a41855378c5517342598b9.png similarity index 100% rename from Bundle/Images/4f14b7aab3a41855378c5517342598b9.png rename to Dev/Bundle/Images/4f14b7aab3a41855378c5517342598b9.png diff --git a/Bundle/Images/51a4d21670dc8dfa8ffc9e54afd62f5f.png b/Dev/Bundle/Images/51a4d21670dc8dfa8ffc9e54afd62f5f.png similarity index 100% rename from Bundle/Images/51a4d21670dc8dfa8ffc9e54afd62f5f.png rename to Dev/Bundle/Images/51a4d21670dc8dfa8ffc9e54afd62f5f.png diff --git a/Bundle/Images/5315c35bbcc28d8eee419028ac9f38e0.jpg b/Dev/Bundle/Images/5315c35bbcc28d8eee419028ac9f38e0.jpg similarity index 100% rename from Bundle/Images/5315c35bbcc28d8eee419028ac9f38e0.jpg rename to Dev/Bundle/Images/5315c35bbcc28d8eee419028ac9f38e0.jpg diff --git a/Bundle/Images/5482a54657765056f1a94116a8dbffe7.jpg b/Dev/Bundle/Images/5482a54657765056f1a94116a8dbffe7.jpg similarity index 100% rename from Bundle/Images/5482a54657765056f1a94116a8dbffe7.jpg rename to Dev/Bundle/Images/5482a54657765056f1a94116a8dbffe7.jpg diff --git a/Bundle/Images/551c2656a4f6f9f5ea7e9945b9081202.jpg b/Dev/Bundle/Images/551c2656a4f6f9f5ea7e9945b9081202.jpg similarity index 100% rename from Bundle/Images/551c2656a4f6f9f5ea7e9945b9081202.jpg rename to Dev/Bundle/Images/551c2656a4f6f9f5ea7e9945b9081202.jpg diff --git a/Bundle/Images/55abb3cc464305dd554171c3d44cb61f.gif b/Dev/Bundle/Images/55abb3cc464305dd554171c3d44cb61f.gif similarity index 100% rename from Bundle/Images/55abb3cc464305dd554171c3d44cb61f.gif rename to Dev/Bundle/Images/55abb3cc464305dd554171c3d44cb61f.gif diff --git a/Bundle/Images/5633ed9d0eb700d0093bf85d86a95ebf.jpg b/Dev/Bundle/Images/5633ed9d0eb700d0093bf85d86a95ebf.jpg similarity index 100% rename from Bundle/Images/5633ed9d0eb700d0093bf85d86a95ebf.jpg rename to Dev/Bundle/Images/5633ed9d0eb700d0093bf85d86a95ebf.jpg diff --git a/Bundle/Images/56d4a1bb53241f7c5ed6ab531320a542.jpg b/Dev/Bundle/Images/56d4a1bb53241f7c5ed6ab531320a542.jpg similarity index 100% rename from Bundle/Images/56d4a1bb53241f7c5ed6ab531320a542.jpg rename to Dev/Bundle/Images/56d4a1bb53241f7c5ed6ab531320a542.jpg diff --git a/Bundle/Images/579294d4d8110fc64980dd72a5066780.png b/Dev/Bundle/Images/579294d4d8110fc64980dd72a5066780.png similarity index 100% rename from Bundle/Images/579294d4d8110fc64980dd72a5066780.png rename to Dev/Bundle/Images/579294d4d8110fc64980dd72a5066780.png diff --git a/Bundle/Images/586914b5d01d3889fb7bb5c44fe29771.png b/Dev/Bundle/Images/586914b5d01d3889fb7bb5c44fe29771.png similarity index 100% rename from Bundle/Images/586914b5d01d3889fb7bb5c44fe29771.png rename to Dev/Bundle/Images/586914b5d01d3889fb7bb5c44fe29771.png diff --git a/Bundle/Images/59d3b529c78ac722127c41ba75b3355b.jpg b/Dev/Bundle/Images/59d3b529c78ac722127c41ba75b3355b.jpg similarity index 100% rename from Bundle/Images/59d3b529c78ac722127c41ba75b3355b.jpg rename to Dev/Bundle/Images/59d3b529c78ac722127c41ba75b3355b.jpg diff --git a/Bundle/Images/5a43fa2cf9c1e47f0331ef71b928ee55.jpg b/Dev/Bundle/Images/5a43fa2cf9c1e47f0331ef71b928ee55.jpg similarity index 100% rename from Bundle/Images/5a43fa2cf9c1e47f0331ef71b928ee55.jpg rename to Dev/Bundle/Images/5a43fa2cf9c1e47f0331ef71b928ee55.jpg diff --git a/Bundle/Images/5b689479bd7e527c2385a40437272607.png b/Dev/Bundle/Images/5b689479bd7e527c2385a40437272607.png similarity index 100% rename from Bundle/Images/5b689479bd7e527c2385a40437272607.png rename to Dev/Bundle/Images/5b689479bd7e527c2385a40437272607.png diff --git a/Bundle/Images/5baad44ca4702949724234e35c5bb341.jpg b/Dev/Bundle/Images/5baad44ca4702949724234e35c5bb341.jpg similarity index 100% rename from Bundle/Images/5baad44ca4702949724234e35c5bb341.jpg rename to Dev/Bundle/Images/5baad44ca4702949724234e35c5bb341.jpg diff --git a/Bundle/Images/5bc61724b33e34a6188a817f9f2f8138.jpg b/Dev/Bundle/Images/5bc61724b33e34a6188a817f9f2f8138.jpg similarity index 100% rename from Bundle/Images/5bc61724b33e34a6188a817f9f2f8138.jpg rename to Dev/Bundle/Images/5bc61724b33e34a6188a817f9f2f8138.jpg diff --git a/Bundle/Images/5beaadc10dfdbf61124e98fdf8a5c191.png b/Dev/Bundle/Images/5beaadc10dfdbf61124e98fdf8a5c191.png similarity index 100% rename from Bundle/Images/5beaadc10dfdbf61124e98fdf8a5c191.png rename to Dev/Bundle/Images/5beaadc10dfdbf61124e98fdf8a5c191.png diff --git a/Bundle/Images/5c67195f6993c9f8d0d32d4ffe0d8e62.jpg b/Dev/Bundle/Images/5c67195f6993c9f8d0d32d4ffe0d8e62.jpg similarity index 100% rename from Bundle/Images/5c67195f6993c9f8d0d32d4ffe0d8e62.jpg rename to Dev/Bundle/Images/5c67195f6993c9f8d0d32d4ffe0d8e62.jpg diff --git a/Bundle/Images/5dc71b1d868ef137394d3cc23abea65a.jpg b/Dev/Bundle/Images/5dc71b1d868ef137394d3cc23abea65a.jpg similarity index 100% rename from Bundle/Images/5dc71b1d868ef137394d3cc23abea65a.jpg rename to Dev/Bundle/Images/5dc71b1d868ef137394d3cc23abea65a.jpg diff --git a/Bundle/Images/5e2b64196b9e014e0ed0a27873cafdb3.png b/Dev/Bundle/Images/5e2b64196b9e014e0ed0a27873cafdb3.png similarity index 100% rename from Bundle/Images/5e2b64196b9e014e0ed0a27873cafdb3.png rename to Dev/Bundle/Images/5e2b64196b9e014e0ed0a27873cafdb3.png diff --git a/Bundle/Images/5f09a896c191db3fa7ea6bdd5ebe9485.gif b/Dev/Bundle/Images/5f09a896c191db3fa7ea6bdd5ebe9485.gif similarity index 100% rename from Bundle/Images/5f09a896c191db3fa7ea6bdd5ebe9485.gif rename to Dev/Bundle/Images/5f09a896c191db3fa7ea6bdd5ebe9485.gif diff --git a/Bundle/Images/611b294df9cf794eeaa1ffcc620bf6a4.png b/Dev/Bundle/Images/611b294df9cf794eeaa1ffcc620bf6a4.png similarity index 100% rename from Bundle/Images/611b294df9cf794eeaa1ffcc620bf6a4.png rename to Dev/Bundle/Images/611b294df9cf794eeaa1ffcc620bf6a4.png diff --git a/Bundle/Images/627c0779eb46b98f751187c5c9f43aa3.jpg b/Dev/Bundle/Images/627c0779eb46b98f751187c5c9f43aa3.jpg similarity index 100% rename from Bundle/Images/627c0779eb46b98f751187c5c9f43aa3.jpg rename to Dev/Bundle/Images/627c0779eb46b98f751187c5c9f43aa3.jpg diff --git a/Bundle/Images/6399623892b45aa4901aa6e702c7a62d.png b/Dev/Bundle/Images/6399623892b45aa4901aa6e702c7a62d.png similarity index 100% rename from Bundle/Images/6399623892b45aa4901aa6e702c7a62d.png rename to Dev/Bundle/Images/6399623892b45aa4901aa6e702c7a62d.png diff --git a/Bundle/Images/64221ffc9050c92b8980326acc0e4194.png b/Dev/Bundle/Images/64221ffc9050c92b8980326acc0e4194.png similarity index 100% rename from Bundle/Images/64221ffc9050c92b8980326acc0e4194.png rename to Dev/Bundle/Images/64221ffc9050c92b8980326acc0e4194.png diff --git a/Bundle/Images/66ac49ef3f48ac9482049e1ab57a53e9.png b/Dev/Bundle/Images/66ac49ef3f48ac9482049e1ab57a53e9.png similarity index 100% rename from Bundle/Images/66ac49ef3f48ac9482049e1ab57a53e9.png rename to Dev/Bundle/Images/66ac49ef3f48ac9482049e1ab57a53e9.png diff --git a/Bundle/Images/6903d4538fd33c8fd0ded32cb30d618e.jpg b/Dev/Bundle/Images/6903d4538fd33c8fd0ded32cb30d618e.jpg similarity index 100% rename from Bundle/Images/6903d4538fd33c8fd0ded32cb30d618e.jpg rename to Dev/Bundle/Images/6903d4538fd33c8fd0ded32cb30d618e.jpg diff --git a/Bundle/Images/6c853ed9dacd5716bc54eb59cec30889.png b/Dev/Bundle/Images/6c853ed9dacd5716bc54eb59cec30889.png similarity index 100% rename from Bundle/Images/6c853ed9dacd5716bc54eb59cec30889.png rename to Dev/Bundle/Images/6c853ed9dacd5716bc54eb59cec30889.png diff --git a/Bundle/Images/6d939393058de0579fca1bbf10ecff25.gif b/Dev/Bundle/Images/6d939393058de0579fca1bbf10ecff25.gif similarity index 100% rename from Bundle/Images/6d939393058de0579fca1bbf10ecff25.gif rename to Dev/Bundle/Images/6d939393058de0579fca1bbf10ecff25.gif diff --git a/Bundle/Images/6de166ee2a3a60df9017650e2a808408.jpg b/Dev/Bundle/Images/6de166ee2a3a60df9017650e2a808408.jpg similarity index 100% rename from Bundle/Images/6de166ee2a3a60df9017650e2a808408.jpg rename to Dev/Bundle/Images/6de166ee2a3a60df9017650e2a808408.jpg diff --git a/Bundle/Images/7092f253998c1b6b869707ad7ae92854.gif b/Dev/Bundle/Images/7092f253998c1b6b869707ad7ae92854.gif similarity index 100% rename from Bundle/Images/7092f253998c1b6b869707ad7ae92854.gif rename to Dev/Bundle/Images/7092f253998c1b6b869707ad7ae92854.gif diff --git a/Bundle/Images/71714b783e01aec455b5a4a760326ccc.png b/Dev/Bundle/Images/71714b783e01aec455b5a4a760326ccc.png similarity index 100% rename from Bundle/Images/71714b783e01aec455b5a4a760326ccc.png rename to Dev/Bundle/Images/71714b783e01aec455b5a4a760326ccc.png diff --git a/Bundle/Images/71dd006377602359ebd2cbe7b9eaab09.png b/Dev/Bundle/Images/71dd006377602359ebd2cbe7b9eaab09.png similarity index 100% rename from Bundle/Images/71dd006377602359ebd2cbe7b9eaab09.png rename to Dev/Bundle/Images/71dd006377602359ebd2cbe7b9eaab09.png diff --git a/Bundle/Images/72d091e08c93c9e590360130fa35221b.jpg b/Dev/Bundle/Images/72d091e08c93c9e590360130fa35221b.jpg similarity index 100% rename from Bundle/Images/72d091e08c93c9e590360130fa35221b.jpg rename to Dev/Bundle/Images/72d091e08c93c9e590360130fa35221b.jpg diff --git a/Bundle/Images/743b8442c69efbc457af7376af71b44c.png b/Dev/Bundle/Images/743b8442c69efbc457af7376af71b44c.png similarity index 100% rename from Bundle/Images/743b8442c69efbc457af7376af71b44c.png rename to Dev/Bundle/Images/743b8442c69efbc457af7376af71b44c.png diff --git a/Bundle/Images/754664a12e36abff7950e796c906ae39.jpg b/Dev/Bundle/Images/754664a12e36abff7950e796c906ae39.jpg similarity index 100% rename from Bundle/Images/754664a12e36abff7950e796c906ae39.jpg rename to Dev/Bundle/Images/754664a12e36abff7950e796c906ae39.jpg diff --git a/Bundle/Images/75e4bd7544a85af6438497980b62fba5.jpg b/Dev/Bundle/Images/75e4bd7544a85af6438497980b62fba5.jpg similarity index 100% rename from Bundle/Images/75e4bd7544a85af6438497980b62fba5.jpg rename to Dev/Bundle/Images/75e4bd7544a85af6438497980b62fba5.jpg diff --git a/Bundle/Images/786b67badc535fc95a4a76c29a0e0146.jpg b/Dev/Bundle/Images/786b67badc535fc95a4a76c29a0e0146.jpg similarity index 100% rename from Bundle/Images/786b67badc535fc95a4a76c29a0e0146.jpg rename to Dev/Bundle/Images/786b67badc535fc95a4a76c29a0e0146.jpg diff --git a/Bundle/Images/7997b6b229f25315d33f5c7085e37500.jpg b/Dev/Bundle/Images/7997b6b229f25315d33f5c7085e37500.jpg similarity index 100% rename from Bundle/Images/7997b6b229f25315d33f5c7085e37500.jpg rename to Dev/Bundle/Images/7997b6b229f25315d33f5c7085e37500.jpg diff --git a/Bundle/Images/79f5fc6bca756e1f067c6fc83e18b32e.jpg b/Dev/Bundle/Images/79f5fc6bca756e1f067c6fc83e18b32e.jpg similarity index 100% rename from Bundle/Images/79f5fc6bca756e1f067c6fc83e18b32e.jpg rename to Dev/Bundle/Images/79f5fc6bca756e1f067c6fc83e18b32e.jpg diff --git a/Bundle/Images/7acc832f70b2ca62e58a953f3b90fd82.jpg b/Dev/Bundle/Images/7acc832f70b2ca62e58a953f3b90fd82.jpg similarity index 100% rename from Bundle/Images/7acc832f70b2ca62e58a953f3b90fd82.jpg rename to Dev/Bundle/Images/7acc832f70b2ca62e58a953f3b90fd82.jpg diff --git a/Bundle/Images/7b9abb94ace0278f943a6df29d0ca652.png b/Dev/Bundle/Images/7b9abb94ace0278f943a6df29d0ca652.png similarity index 100% rename from Bundle/Images/7b9abb94ace0278f943a6df29d0ca652.png rename to Dev/Bundle/Images/7b9abb94ace0278f943a6df29d0ca652.png diff --git a/Bundle/Images/7ce702ec69b7af26b3218d1278520bce.png b/Dev/Bundle/Images/7ce702ec69b7af26b3218d1278520bce.png similarity index 100% rename from Bundle/Images/7ce702ec69b7af26b3218d1278520bce.png rename to Dev/Bundle/Images/7ce702ec69b7af26b3218d1278520bce.png diff --git a/Bundle/Images/7dbf474f80e466e9e25ee46b84166420.jpg b/Dev/Bundle/Images/7dbf474f80e466e9e25ee46b84166420.jpg similarity index 100% rename from Bundle/Images/7dbf474f80e466e9e25ee46b84166420.jpg rename to Dev/Bundle/Images/7dbf474f80e466e9e25ee46b84166420.jpg diff --git a/Bundle/Images/7e7cdf7f4ee50b308531313bbf43e0c3.jpg b/Dev/Bundle/Images/7e7cdf7f4ee50b308531313bbf43e0c3.jpg similarity index 100% rename from Bundle/Images/7e7cdf7f4ee50b308531313bbf43e0c3.jpg rename to Dev/Bundle/Images/7e7cdf7f4ee50b308531313bbf43e0c3.jpg diff --git a/Bundle/Images/817f96555e2d683e7b12f778c4e38022.png b/Dev/Bundle/Images/817f96555e2d683e7b12f778c4e38022.png similarity index 100% rename from Bundle/Images/817f96555e2d683e7b12f778c4e38022.png rename to Dev/Bundle/Images/817f96555e2d683e7b12f778c4e38022.png diff --git a/Bundle/Images/829b05b759b2977bc3eb970ab256d867.png b/Dev/Bundle/Images/829b05b759b2977bc3eb970ab256d867.png similarity index 100% rename from Bundle/Images/829b05b759b2977bc3eb970ab256d867.png rename to Dev/Bundle/Images/829b05b759b2977bc3eb970ab256d867.png diff --git a/Bundle/Images/8417a305e3b43d5b1bda4ff06a660c54.jpg b/Dev/Bundle/Images/8417a305e3b43d5b1bda4ff06a660c54.jpg similarity index 100% rename from Bundle/Images/8417a305e3b43d5b1bda4ff06a660c54.jpg rename to Dev/Bundle/Images/8417a305e3b43d5b1bda4ff06a660c54.jpg diff --git a/Bundle/Images/8546907dbe574744d7fea6ca9de1de6b.jpg b/Dev/Bundle/Images/8546907dbe574744d7fea6ca9de1de6b.jpg similarity index 100% rename from Bundle/Images/8546907dbe574744d7fea6ca9de1de6b.jpg rename to Dev/Bundle/Images/8546907dbe574744d7fea6ca9de1de6b.jpg diff --git a/Bundle/Images/865db3dd2d380626f16b6f9dc6d62dba.jpg b/Dev/Bundle/Images/865db3dd2d380626f16b6f9dc6d62dba.jpg similarity index 100% rename from Bundle/Images/865db3dd2d380626f16b6f9dc6d62dba.jpg rename to Dev/Bundle/Images/865db3dd2d380626f16b6f9dc6d62dba.jpg diff --git a/Bundle/Images/8711007ea5e351755a80cba913d16a32.png b/Dev/Bundle/Images/8711007ea5e351755a80cba913d16a32.png similarity index 100% rename from Bundle/Images/8711007ea5e351755a80cba913d16a32.png rename to Dev/Bundle/Images/8711007ea5e351755a80cba913d16a32.png diff --git a/Bundle/Images/8905ba870cd5d3327a8310fa437aa076.png b/Dev/Bundle/Images/8905ba870cd5d3327a8310fa437aa076.png similarity index 100% rename from Bundle/Images/8905ba870cd5d3327a8310fa437aa076.png rename to Dev/Bundle/Images/8905ba870cd5d3327a8310fa437aa076.png diff --git a/Bundle/Images/897b8b6d8feb466aa6cad5f512c3fce2.jpg b/Dev/Bundle/Images/897b8b6d8feb466aa6cad5f512c3fce2.jpg similarity index 100% rename from Bundle/Images/897b8b6d8feb466aa6cad5f512c3fce2.jpg rename to Dev/Bundle/Images/897b8b6d8feb466aa6cad5f512c3fce2.jpg diff --git a/Bundle/Images/8a9cc8eeed66aeb423a91c44111d9450.jpg b/Dev/Bundle/Images/8a9cc8eeed66aeb423a91c44111d9450.jpg similarity index 100% rename from Bundle/Images/8a9cc8eeed66aeb423a91c44111d9450.jpg rename to Dev/Bundle/Images/8a9cc8eeed66aeb423a91c44111d9450.jpg diff --git a/Bundle/Images/8e330afbd99ba01b66570ed62fcdc6ab.jpg b/Dev/Bundle/Images/8e330afbd99ba01b66570ed62fcdc6ab.jpg similarity index 100% rename from Bundle/Images/8e330afbd99ba01b66570ed62fcdc6ab.jpg rename to Dev/Bundle/Images/8e330afbd99ba01b66570ed62fcdc6ab.jpg diff --git a/Bundle/Images/8e5e74dbf9b68a322fbb9512db837329.jpg b/Dev/Bundle/Images/8e5e74dbf9b68a322fbb9512db837329.jpg similarity index 100% rename from Bundle/Images/8e5e74dbf9b68a322fbb9512db837329.jpg rename to Dev/Bundle/Images/8e5e74dbf9b68a322fbb9512db837329.jpg diff --git a/Bundle/Images/9032e447e32e09aef5b7de2fab42494d.png b/Dev/Bundle/Images/9032e447e32e09aef5b7de2fab42494d.png similarity index 100% rename from Bundle/Images/9032e447e32e09aef5b7de2fab42494d.png rename to Dev/Bundle/Images/9032e447e32e09aef5b7de2fab42494d.png diff --git a/Bundle/Images/90e46387f562ca8fa106b51dfcda1dc6.jpg b/Dev/Bundle/Images/90e46387f562ca8fa106b51dfcda1dc6.jpg similarity index 100% rename from Bundle/Images/90e46387f562ca8fa106b51dfcda1dc6.jpg rename to Dev/Bundle/Images/90e46387f562ca8fa106b51dfcda1dc6.jpg diff --git a/Bundle/Images/93e6127b9c4e7a99459c558b81d31bc5.png b/Dev/Bundle/Images/93e6127b9c4e7a99459c558b81d31bc5.png similarity index 100% rename from Bundle/Images/93e6127b9c4e7a99459c558b81d31bc5.png rename to Dev/Bundle/Images/93e6127b9c4e7a99459c558b81d31bc5.png diff --git a/Bundle/Images/94e1bdbb03c42581d8407602634636ea.png b/Dev/Bundle/Images/94e1bdbb03c42581d8407602634636ea.png similarity index 100% rename from Bundle/Images/94e1bdbb03c42581d8407602634636ea.png rename to Dev/Bundle/Images/94e1bdbb03c42581d8407602634636ea.png diff --git a/Bundle/Images/9540743374e1fdb273b6a6ca625eb7a3.png b/Dev/Bundle/Images/9540743374e1fdb273b6a6ca625eb7a3.png similarity index 100% rename from Bundle/Images/9540743374e1fdb273b6a6ca625eb7a3.png rename to Dev/Bundle/Images/9540743374e1fdb273b6a6ca625eb7a3.png diff --git a/Bundle/Images/96b3e939852157613fa2e48d58fe35fe.jpg b/Dev/Bundle/Images/96b3e939852157613fa2e48d58fe35fe.jpg similarity index 100% rename from Bundle/Images/96b3e939852157613fa2e48d58fe35fe.jpg rename to Dev/Bundle/Images/96b3e939852157613fa2e48d58fe35fe.jpg diff --git a/Bundle/Images/9a3e0c7b687b526987e2270541002d47.png b/Dev/Bundle/Images/9a3e0c7b687b526987e2270541002d47.png similarity index 100% rename from Bundle/Images/9a3e0c7b687b526987e2270541002d47.png rename to Dev/Bundle/Images/9a3e0c7b687b526987e2270541002d47.png diff --git a/Bundle/Images/9bd8a9ed81c5a9190f74496197da7249.png b/Dev/Bundle/Images/9bd8a9ed81c5a9190f74496197da7249.png similarity index 100% rename from Bundle/Images/9bd8a9ed81c5a9190f74496197da7249.png rename to Dev/Bundle/Images/9bd8a9ed81c5a9190f74496197da7249.png diff --git a/Bundle/Images/9efd60f04cd971daa83d3131e6d6f389.jpg b/Dev/Bundle/Images/9efd60f04cd971daa83d3131e6d6f389.jpg similarity index 100% rename from Bundle/Images/9efd60f04cd971daa83d3131e6d6f389.jpg rename to Dev/Bundle/Images/9efd60f04cd971daa83d3131e6d6f389.jpg diff --git a/Bundle/Images/9f8f6046eaf9ffa2d9c5d6db05c5f881.gif b/Dev/Bundle/Images/9f8f6046eaf9ffa2d9c5d6db05c5f881.gif similarity index 100% rename from Bundle/Images/9f8f6046eaf9ffa2d9c5d6db05c5f881.gif rename to Dev/Bundle/Images/9f8f6046eaf9ffa2d9c5d6db05c5f881.gif diff --git a/Bundle/Images/AppleRAW_1.DNG b/Dev/Bundle/Images/AppleRAW_1.DNG similarity index 100% rename from Bundle/Images/AppleRAW_1.DNG rename to Dev/Bundle/Images/AppleRAW_1.DNG diff --git a/Bundle/Images/AppleRAW_2.DNG b/Dev/Bundle/Images/AppleRAW_2.DNG similarity index 100% rename from Bundle/Images/AppleRAW_2.DNG rename to Dev/Bundle/Images/AppleRAW_2.DNG diff --git a/Bundle/Images/a17806f32b45d63eea5230e7893e1f15.jpg b/Dev/Bundle/Images/a17806f32b45d63eea5230e7893e1f15.jpg similarity index 100% rename from Bundle/Images/a17806f32b45d63eea5230e7893e1f15.jpg rename to Dev/Bundle/Images/a17806f32b45d63eea5230e7893e1f15.jpg diff --git a/Bundle/Images/a1d1aafb5bca660229f8e9fc65291eab.png b/Dev/Bundle/Images/a1d1aafb5bca660229f8e9fc65291eab.png similarity index 100% rename from Bundle/Images/a1d1aafb5bca660229f8e9fc65291eab.png rename to Dev/Bundle/Images/a1d1aafb5bca660229f8e9fc65291eab.png diff --git a/Bundle/Images/a1d54c960686558901e320a52a967158.png b/Dev/Bundle/Images/a1d54c960686558901e320a52a967158.png similarity index 100% rename from Bundle/Images/a1d54c960686558901e320a52a967158.png rename to Dev/Bundle/Images/a1d54c960686558901e320a52a967158.png diff --git a/Bundle/Images/a24a39e69554a701412b3ed0c009e7f6.png b/Dev/Bundle/Images/a24a39e69554a701412b3ed0c009e7f6.png similarity index 100% rename from Bundle/Images/a24a39e69554a701412b3ed0c009e7f6.png rename to Dev/Bundle/Images/a24a39e69554a701412b3ed0c009e7f6.png diff --git a/Bundle/Images/a54f8c866cbef6e6cda858c85d72dfc8.jpg b/Dev/Bundle/Images/a54f8c866cbef6e6cda858c85d72dfc8.jpg similarity index 100% rename from Bundle/Images/a54f8c866cbef6e6cda858c85d72dfc8.jpg rename to Dev/Bundle/Images/a54f8c866cbef6e6cda858c85d72dfc8.jpg diff --git a/Bundle/Images/a7326ba8f3f4559991126474dd30083d.jpg b/Dev/Bundle/Images/a7326ba8f3f4559991126474dd30083d.jpg similarity index 100% rename from Bundle/Images/a7326ba8f3f4559991126474dd30083d.jpg rename to Dev/Bundle/Images/a7326ba8f3f4559991126474dd30083d.jpg diff --git a/Bundle/Images/ac6343a98f8edabfcc6e536dd75aacb0.png b/Dev/Bundle/Images/ac6343a98f8edabfcc6e536dd75aacb0.png similarity index 100% rename from Bundle/Images/ac6343a98f8edabfcc6e536dd75aacb0.png rename to Dev/Bundle/Images/ac6343a98f8edabfcc6e536dd75aacb0.png diff --git a/Bundle/Images/acb1fac4e618f636d415f62496e8b70e.jpg b/Dev/Bundle/Images/acb1fac4e618f636d415f62496e8b70e.jpg similarity index 100% rename from Bundle/Images/acb1fac4e618f636d415f62496e8b70e.jpg rename to Dev/Bundle/Images/acb1fac4e618f636d415f62496e8b70e.jpg diff --git a/Bundle/Images/acce3629083f0e348e94fb58f952d3de.jpg b/Dev/Bundle/Images/acce3629083f0e348e94fb58f952d3de.jpg similarity index 100% rename from Bundle/Images/acce3629083f0e348e94fb58f952d3de.jpg rename to Dev/Bundle/Images/acce3629083f0e348e94fb58f952d3de.jpg diff --git a/Bundle/Images/adaf0da1764aafb7039440dbe098569b.gif b/Dev/Bundle/Images/adaf0da1764aafb7039440dbe098569b.gif similarity index 100% rename from Bundle/Images/adaf0da1764aafb7039440dbe098569b.gif rename to Dev/Bundle/Images/adaf0da1764aafb7039440dbe098569b.gif diff --git a/Bundle/Images/adcb34b94f4c839bdd29037419a0ee53.jpg b/Dev/Bundle/Images/adcb34b94f4c839bdd29037419a0ee53.jpg similarity index 100% rename from Bundle/Images/adcb34b94f4c839bdd29037419a0ee53.jpg rename to Dev/Bundle/Images/adcb34b94f4c839bdd29037419a0ee53.jpg diff --git a/Bundle/Images/adf6f850b13dff73ebb22862c6ab028b.gif b/Dev/Bundle/Images/adf6f850b13dff73ebb22862c6ab028b.gif similarity index 100% rename from Bundle/Images/adf6f850b13dff73ebb22862c6ab028b.gif rename to Dev/Bundle/Images/adf6f850b13dff73ebb22862c6ab028b.gif diff --git a/Bundle/Images/affc57dfffa5ec448a0795738d456018.png b/Dev/Bundle/Images/affc57dfffa5ec448a0795738d456018.png similarity index 100% rename from Bundle/Images/affc57dfffa5ec448a0795738d456018.png rename to Dev/Bundle/Images/affc57dfffa5ec448a0795738d456018.png diff --git a/Bundle/Images/b0b8914cc5f7a6eff409f16d8cc236c5.jpg b/Dev/Bundle/Images/b0b8914cc5f7a6eff409f16d8cc236c5.jpg similarity index 100% rename from Bundle/Images/b0b8914cc5f7a6eff409f16d8cc236c5.jpg rename to Dev/Bundle/Images/b0b8914cc5f7a6eff409f16d8cc236c5.jpg diff --git a/Bundle/Images/b3ac9fdb7239f42c734921dfe790291b.png b/Dev/Bundle/Images/b3ac9fdb7239f42c734921dfe790291b.png similarity index 100% rename from Bundle/Images/b3ac9fdb7239f42c734921dfe790291b.png rename to Dev/Bundle/Images/b3ac9fdb7239f42c734921dfe790291b.png diff --git a/Bundle/Images/b4103df93880fc5677c2a081e4bfc712.jpg b/Dev/Bundle/Images/b4103df93880fc5677c2a081e4bfc712.jpg similarity index 100% rename from Bundle/Images/b4103df93880fc5677c2a081e4bfc712.jpg rename to Dev/Bundle/Images/b4103df93880fc5677c2a081e4bfc712.jpg diff --git a/Bundle/Images/b5369bcbddca7135a5708c5237ad64e4.jpg b/Dev/Bundle/Images/b5369bcbddca7135a5708c5237ad64e4.jpg similarity index 100% rename from Bundle/Images/b5369bcbddca7135a5708c5237ad64e4.jpg rename to Dev/Bundle/Images/b5369bcbddca7135a5708c5237ad64e4.jpg diff --git a/Bundle/Images/b55977028a3a574336966b6536640fc9.jpg b/Dev/Bundle/Images/b55977028a3a574336966b6536640fc9.jpg similarity index 100% rename from Bundle/Images/b55977028a3a574336966b6536640fc9.jpg rename to Dev/Bundle/Images/b55977028a3a574336966b6536640fc9.jpg diff --git a/Bundle/Images/b583e48e218193e4c287f033931a6314.png b/Dev/Bundle/Images/b583e48e218193e4c287f033931a6314.png similarity index 100% rename from Bundle/Images/b583e48e218193e4c287f033931a6314.png rename to Dev/Bundle/Images/b583e48e218193e4c287f033931a6314.png diff --git a/Bundle/Images/b59d7a023a8dcd112da2eb859004199a.png b/Dev/Bundle/Images/b59d7a023a8dcd112da2eb859004199a.png similarity index 100% rename from Bundle/Images/b59d7a023a8dcd112da2eb859004199a.png rename to Dev/Bundle/Images/b59d7a023a8dcd112da2eb859004199a.png diff --git a/Bundle/Images/ba2b2b6e72ca0e4683bb640e2d5572f8.png b/Dev/Bundle/Images/ba2b2b6e72ca0e4683bb640e2d5572f8.png similarity index 100% rename from Bundle/Images/ba2b2b6e72ca0e4683bb640e2d5572f8.png rename to Dev/Bundle/Images/ba2b2b6e72ca0e4683bb640e2d5572f8.png diff --git a/Bundle/Images/ba60305ac83fe3d8ef01da1d9a0ecc79.jpg b/Dev/Bundle/Images/ba60305ac83fe3d8ef01da1d9a0ecc79.jpg similarity index 100% rename from Bundle/Images/ba60305ac83fe3d8ef01da1d9a0ecc79.jpg rename to Dev/Bundle/Images/ba60305ac83fe3d8ef01da1d9a0ecc79.jpg diff --git a/Bundle/Images/bc7af0616c4ae99144c8600e7b39beea.gif b/Dev/Bundle/Images/bc7af0616c4ae99144c8600e7b39beea.gif similarity index 100% rename from Bundle/Images/bc7af0616c4ae99144c8600e7b39beea.gif rename to Dev/Bundle/Images/bc7af0616c4ae99144c8600e7b39beea.gif diff --git a/Bundle/Images/bd8cf05698aee36b82b4caf58edea442.jpg b/Dev/Bundle/Images/bd8cf05698aee36b82b4caf58edea442.jpg similarity index 100% rename from Bundle/Images/bd8cf05698aee36b82b4caf58edea442.jpg rename to Dev/Bundle/Images/bd8cf05698aee36b82b4caf58edea442.jpg diff --git a/Bundle/Images/bd927c8547634cdbdd22af0afe818a9b.png b/Dev/Bundle/Images/bd927c8547634cdbdd22af0afe818a9b.png similarity index 100% rename from Bundle/Images/bd927c8547634cdbdd22af0afe818a9b.png rename to Dev/Bundle/Images/bd927c8547634cdbdd22af0afe818a9b.png diff --git a/Bundle/Images/bf203e765c98b12f6c2b2c33577c730d.png b/Dev/Bundle/Images/bf203e765c98b12f6c2b2c33577c730d.png similarity index 100% rename from Bundle/Images/bf203e765c98b12f6c2b2c33577c730d.png rename to Dev/Bundle/Images/bf203e765c98b12f6c2b2c33577c730d.png diff --git a/Bundle/Images/c-3625f98e00148cdc136c53bdcd2d2e1e.png b/Dev/Bundle/Images/c-3625f98e00148cdc136c53bdcd2d2e1e.png similarity index 100% rename from Bundle/Images/c-3625f98e00148cdc136c53bdcd2d2e1e.png rename to Dev/Bundle/Images/c-3625f98e00148cdc136c53bdcd2d2e1e.png diff --git a/Bundle/Images/c-586914b5d01d3889fb7bb5c44fe29771.png b/Dev/Bundle/Images/c-586914b5d01d3889fb7bb5c44fe29771.png similarity index 100% rename from Bundle/Images/c-586914b5d01d3889fb7bb5c44fe29771.png rename to Dev/Bundle/Images/c-586914b5d01d3889fb7bb5c44fe29771.png diff --git a/Bundle/Images/c-5e2b64196b9e014e0ed0a27873cafdb3.png b/Dev/Bundle/Images/c-5e2b64196b9e014e0ed0a27873cafdb3.png similarity index 100% rename from Bundle/Images/c-5e2b64196b9e014e0ed0a27873cafdb3.png rename to Dev/Bundle/Images/c-5e2b64196b9e014e0ed0a27873cafdb3.png diff --git a/Bundle/Images/c-71dd006377602359ebd2cbe7b9eaab09.png b/Dev/Bundle/Images/c-71dd006377602359ebd2cbe7b9eaab09.png similarity index 100% rename from Bundle/Images/c-71dd006377602359ebd2cbe7b9eaab09.png rename to Dev/Bundle/Images/c-71dd006377602359ebd2cbe7b9eaab09.png diff --git a/Bundle/Images/c-817f96555e2d683e7b12f778c4e38022.png b/Dev/Bundle/Images/c-817f96555e2d683e7b12f778c4e38022.png similarity index 100% rename from Bundle/Images/c-817f96555e2d683e7b12f778c4e38022.png rename to Dev/Bundle/Images/c-817f96555e2d683e7b12f778c4e38022.png diff --git a/Bundle/Images/c-9032e447e32e09aef5b7de2fab42494d.png b/Dev/Bundle/Images/c-9032e447e32e09aef5b7de2fab42494d.png similarity index 100% rename from Bundle/Images/c-9032e447e32e09aef5b7de2fab42494d.png rename to Dev/Bundle/Images/c-9032e447e32e09aef5b7de2fab42494d.png diff --git a/Bundle/Images/c-94e1bdbb03c42581d8407602634636ea.png b/Dev/Bundle/Images/c-94e1bdbb03c42581d8407602634636ea.png similarity index 100% rename from Bundle/Images/c-94e1bdbb03c42581d8407602634636ea.png rename to Dev/Bundle/Images/c-94e1bdbb03c42581d8407602634636ea.png diff --git a/Bundle/Images/c-9a3e0c7b687b526987e2270541002d47.png b/Dev/Bundle/Images/c-9a3e0c7b687b526987e2270541002d47.png similarity index 100% rename from Bundle/Images/c-9a3e0c7b687b526987e2270541002d47.png rename to Dev/Bundle/Images/c-9a3e0c7b687b526987e2270541002d47.png diff --git a/Bundle/Images/c-c53911b0385c34a8204c30fdc14ea5cc.png b/Dev/Bundle/Images/c-c53911b0385c34a8204c30fdc14ea5cc.png similarity index 100% rename from Bundle/Images/c-c53911b0385c34a8204c30fdc14ea5cc.png rename to Dev/Bundle/Images/c-c53911b0385c34a8204c30fdc14ea5cc.png diff --git a/Bundle/Images/c-d3ffec5786387c590721e674d705f16e.png b/Dev/Bundle/Images/c-d3ffec5786387c590721e674d705f16e.png similarity index 100% rename from Bundle/Images/c-d3ffec5786387c590721e674d705f16e.png rename to Dev/Bundle/Images/c-d3ffec5786387c590721e674d705f16e.png diff --git a/Bundle/Images/c-e585afb2ecf50c04eaf0dedb71602cb8.png b/Dev/Bundle/Images/c-e585afb2ecf50c04eaf0dedb71602cb8.png similarity index 100% rename from Bundle/Images/c-e585afb2ecf50c04eaf0dedb71602cb8.png rename to Dev/Bundle/Images/c-e585afb2ecf50c04eaf0dedb71602cb8.png diff --git a/Bundle/Images/c-ea01d6c175bb25dc75757cf8a5793822.png b/Dev/Bundle/Images/c-ea01d6c175bb25dc75757cf8a5793822.png similarity index 100% rename from Bundle/Images/c-ea01d6c175bb25dc75757cf8a5793822.png rename to Dev/Bundle/Images/c-ea01d6c175bb25dc75757cf8a5793822.png diff --git a/Bundle/Images/c-f23a99688fa66359f6186678e6b2f14a.png b/Dev/Bundle/Images/c-f23a99688fa66359f6186678e6b2f14a.png similarity index 100% rename from Bundle/Images/c-f23a99688fa66359f6186678e6b2f14a.png rename to Dev/Bundle/Images/c-f23a99688fa66359f6186678e6b2f14a.png diff --git a/Bundle/Images/c-m1-125cdc39e13ce7c237b7b4a9e1d8f21c.png b/Dev/Bundle/Images/c-m1-125cdc39e13ce7c237b7b4a9e1d8f21c.png similarity index 100% rename from Bundle/Images/c-m1-125cdc39e13ce7c237b7b4a9e1d8f21c.png rename to Dev/Bundle/Images/c-m1-125cdc39e13ce7c237b7b4a9e1d8f21c.png diff --git a/Bundle/Images/c-m1-19e0d1d0dfe97dca39e9d449c6b8b3d2.png b/Dev/Bundle/Images/c-m1-19e0d1d0dfe97dca39e9d449c6b8b3d2.png similarity index 100% rename from Bundle/Images/c-m1-19e0d1d0dfe97dca39e9d449c6b8b3d2.png rename to Dev/Bundle/Images/c-m1-19e0d1d0dfe97dca39e9d449c6b8b3d2.png diff --git a/Bundle/Images/c-m1-1f97f040d0b6b26faeb0a1a7f1499590.png b/Dev/Bundle/Images/c-m1-1f97f040d0b6b26faeb0a1a7f1499590.png similarity index 100% rename from Bundle/Images/c-m1-1f97f040d0b6b26faeb0a1a7f1499590.png rename to Dev/Bundle/Images/c-m1-1f97f040d0b6b26faeb0a1a7f1499590.png diff --git a/Bundle/Images/c-m1-1fc0c0de88608a9445d6f98a544b5abc.png b/Dev/Bundle/Images/c-m1-1fc0c0de88608a9445d6f98a544b5abc.png similarity index 100% rename from Bundle/Images/c-m1-1fc0c0de88608a9445d6f98a544b5abc.png rename to Dev/Bundle/Images/c-m1-1fc0c0de88608a9445d6f98a544b5abc.png diff --git a/Bundle/Images/c-m1-2dc3bdd9274b121b851fa536b0e35b6a.png b/Dev/Bundle/Images/c-m1-2dc3bdd9274b121b851fa536b0e35b6a.png similarity index 100% rename from Bundle/Images/c-m1-2dc3bdd9274b121b851fa536b0e35b6a.png rename to Dev/Bundle/Images/c-m1-2dc3bdd9274b121b851fa536b0e35b6a.png diff --git a/Bundle/Images/c-m1-3625f98e00148cdc136c53bdcd2d2e1e.png b/Dev/Bundle/Images/c-m1-3625f98e00148cdc136c53bdcd2d2e1e.png similarity index 100% rename from Bundle/Images/c-m1-3625f98e00148cdc136c53bdcd2d2e1e.png rename to Dev/Bundle/Images/c-m1-3625f98e00148cdc136c53bdcd2d2e1e.png diff --git a/Bundle/Images/c-m1-49e39033e275de9786d8c41f834c710b.png b/Dev/Bundle/Images/c-m1-49e39033e275de9786d8c41f834c710b.png similarity index 100% rename from Bundle/Images/c-m1-49e39033e275de9786d8c41f834c710b.png rename to Dev/Bundle/Images/c-m1-49e39033e275de9786d8c41f834c710b.png diff --git a/Bundle/Images/c-m1-4bdd87fd0324f0a3d84d6905d17e1731.png b/Dev/Bundle/Images/c-m1-4bdd87fd0324f0a3d84d6905d17e1731.png similarity index 100% rename from Bundle/Images/c-m1-4bdd87fd0324f0a3d84d6905d17e1731.png rename to Dev/Bundle/Images/c-m1-4bdd87fd0324f0a3d84d6905d17e1731.png diff --git a/Bundle/Images/c-m1-559dcf17d281e285b7f09f943b9706de.png b/Dev/Bundle/Images/c-m1-559dcf17d281e285b7f09f943b9706de.png similarity index 100% rename from Bundle/Images/c-m1-559dcf17d281e285b7f09f943b9706de.png rename to Dev/Bundle/Images/c-m1-559dcf17d281e285b7f09f943b9706de.png diff --git a/Bundle/Images/c-m1-585dd0ac594e8226c49ae7986b8f47d3.png b/Dev/Bundle/Images/c-m1-585dd0ac594e8226c49ae7986b8f47d3.png similarity index 100% rename from Bundle/Images/c-m1-585dd0ac594e8226c49ae7986b8f47d3.png rename to Dev/Bundle/Images/c-m1-585dd0ac594e8226c49ae7986b8f47d3.png diff --git a/Bundle/Images/c-m1-58d30745083f25952342caafb6ee5f37.png b/Dev/Bundle/Images/c-m1-58d30745083f25952342caafb6ee5f37.png similarity index 100% rename from Bundle/Images/c-m1-58d30745083f25952342caafb6ee5f37.png rename to Dev/Bundle/Images/c-m1-58d30745083f25952342caafb6ee5f37.png diff --git a/Bundle/Images/c-m1-5e149c14dc7b7c16ff6bcedd1625ca31.png b/Dev/Bundle/Images/c-m1-5e149c14dc7b7c16ff6bcedd1625ca31.png similarity index 100% rename from Bundle/Images/c-m1-5e149c14dc7b7c16ff6bcedd1625ca31.png rename to Dev/Bundle/Images/c-m1-5e149c14dc7b7c16ff6bcedd1625ca31.png diff --git a/Bundle/Images/c-m1-6593e140dba21ccb8c8724f8fe88fdb6.png b/Dev/Bundle/Images/c-m1-6593e140dba21ccb8c8724f8fe88fdb6.png similarity index 100% rename from Bundle/Images/c-m1-6593e140dba21ccb8c8724f8fe88fdb6.png rename to Dev/Bundle/Images/c-m1-6593e140dba21ccb8c8724f8fe88fdb6.png diff --git a/Bundle/Images/c-m1-66ac49ef3f48ac9482049e1ab57a53e9.png b/Dev/Bundle/Images/c-m1-66ac49ef3f48ac9482049e1ab57a53e9.png similarity index 100% rename from Bundle/Images/c-m1-66ac49ef3f48ac9482049e1ab57a53e9.png rename to Dev/Bundle/Images/c-m1-66ac49ef3f48ac9482049e1ab57a53e9.png diff --git a/Bundle/Images/c-m1-6bfb149151f58d124d6fa76eaad75520.png b/Dev/Bundle/Images/c-m1-6bfb149151f58d124d6fa76eaad75520.png similarity index 100% rename from Bundle/Images/c-m1-6bfb149151f58d124d6fa76eaad75520.png rename to Dev/Bundle/Images/c-m1-6bfb149151f58d124d6fa76eaad75520.png diff --git a/Bundle/Images/c-m1-6e3914f26bcc8f9d004ffeac71656c01.png b/Dev/Bundle/Images/c-m1-6e3914f26bcc8f9d004ffeac71656c01.png similarity index 100% rename from Bundle/Images/c-m1-6e3914f26bcc8f9d004ffeac71656c01.png rename to Dev/Bundle/Images/c-m1-6e3914f26bcc8f9d004ffeac71656c01.png diff --git a/Bundle/Images/c-m1-80e163ebface8b0d2fbf9823bca02936.png b/Dev/Bundle/Images/c-m1-80e163ebface8b0d2fbf9823bca02936.png similarity index 100% rename from Bundle/Images/c-m1-80e163ebface8b0d2fbf9823bca02936.png rename to Dev/Bundle/Images/c-m1-80e163ebface8b0d2fbf9823bca02936.png diff --git a/Bundle/Images/c-m1-8f2b481b7fd9bd745e620b7c01a18df2.png b/Dev/Bundle/Images/c-m1-8f2b481b7fd9bd745e620b7c01a18df2.png similarity index 100% rename from Bundle/Images/c-m1-8f2b481b7fd9bd745e620b7c01a18df2.png rename to Dev/Bundle/Images/c-m1-8f2b481b7fd9bd745e620b7c01a18df2.png diff --git a/Bundle/Images/c-m1-9a3e0c7b687b526987e2270541002d47.png b/Dev/Bundle/Images/c-m1-9a3e0c7b687b526987e2270541002d47.png similarity index 100% rename from Bundle/Images/c-m1-9a3e0c7b687b526987e2270541002d47.png rename to Dev/Bundle/Images/c-m1-9a3e0c7b687b526987e2270541002d47.png diff --git a/Bundle/Images/c-m1-a4842373fc20cc42b8e023a329761915.png b/Dev/Bundle/Images/c-m1-a4842373fc20cc42b8e023a329761915.png similarity index 100% rename from Bundle/Images/c-m1-a4842373fc20cc42b8e023a329761915.png rename to Dev/Bundle/Images/c-m1-a4842373fc20cc42b8e023a329761915.png diff --git a/Bundle/Images/c-m1-aded9dc1dc9361363ad0b426c2ff1846.png b/Dev/Bundle/Images/c-m1-aded9dc1dc9361363ad0b426c2ff1846.png similarity index 100% rename from Bundle/Images/c-m1-aded9dc1dc9361363ad0b426c2ff1846.png rename to Dev/Bundle/Images/c-m1-aded9dc1dc9361363ad0b426c2ff1846.png diff --git a/Bundle/Images/c-m1-b977e74d9de1f6689fdd84c4e38830f5.png b/Dev/Bundle/Images/c-m1-b977e74d9de1f6689fdd84c4e38830f5.png similarity index 100% rename from Bundle/Images/c-m1-b977e74d9de1f6689fdd84c4e38830f5.png rename to Dev/Bundle/Images/c-m1-b977e74d9de1f6689fdd84c4e38830f5.png diff --git a/Bundle/Images/c-m1-bfce28c0e44bc8d1824d48fbec5075e2.png b/Dev/Bundle/Images/c-m1-bfce28c0e44bc8d1824d48fbec5075e2.png similarity index 100% rename from Bundle/Images/c-m1-bfce28c0e44bc8d1824d48fbec5075e2.png rename to Dev/Bundle/Images/c-m1-bfce28c0e44bc8d1824d48fbec5075e2.png diff --git a/Bundle/Images/c-m1-c5a372c145ce25ce712959cd3b6df35e.png b/Dev/Bundle/Images/c-m1-c5a372c145ce25ce712959cd3b6df35e.png similarity index 100% rename from Bundle/Images/c-m1-c5a372c145ce25ce712959cd3b6df35e.png rename to Dev/Bundle/Images/c-m1-c5a372c145ce25ce712959cd3b6df35e.png diff --git a/Bundle/Images/c-m1-d87a07bdc461bf81e43447ca0620d71d.png b/Dev/Bundle/Images/c-m1-d87a07bdc461bf81e43447ca0620d71d.png similarity index 100% rename from Bundle/Images/c-m1-d87a07bdc461bf81e43447ca0620d71d.png rename to Dev/Bundle/Images/c-m1-d87a07bdc461bf81e43447ca0620d71d.png diff --git a/Bundle/Images/c-m1-e0f25ec3373dfdca79ba7bcc3ad366f3.png b/Dev/Bundle/Images/c-m1-e0f25ec3373dfdca79ba7bcc3ad366f3.png similarity index 100% rename from Bundle/Images/c-m1-e0f25ec3373dfdca79ba7bcc3ad366f3.png rename to Dev/Bundle/Images/c-m1-e0f25ec3373dfdca79ba7bcc3ad366f3.png diff --git a/Bundle/Images/c-m1-fcac2d6a6a739e8ceb946ac99200d9f1.png b/Dev/Bundle/Images/c-m1-fcac2d6a6a739e8ceb946ac99200d9f1.png similarity index 100% rename from Bundle/Images/c-m1-fcac2d6a6a739e8ceb946ac99200d9f1.png rename to Dev/Bundle/Images/c-m1-fcac2d6a6a739e8ceb946ac99200d9f1.png diff --git a/Bundle/Images/c-m2-0699098e769a2d80e60f33dbb3332b61.png b/Dev/Bundle/Images/c-m2-0699098e769a2d80e60f33dbb3332b61.png similarity index 100% rename from Bundle/Images/c-m2-0699098e769a2d80e60f33dbb3332b61.png rename to Dev/Bundle/Images/c-m2-0699098e769a2d80e60f33dbb3332b61.png diff --git a/Bundle/Images/c-m2-1f97f040d0b6b26faeb0a1a7f1499590.png b/Dev/Bundle/Images/c-m2-1f97f040d0b6b26faeb0a1a7f1499590.png similarity index 100% rename from Bundle/Images/c-m2-1f97f040d0b6b26faeb0a1a7f1499590.png rename to Dev/Bundle/Images/c-m2-1f97f040d0b6b26faeb0a1a7f1499590.png diff --git a/Bundle/Images/c-m2-272ae9468b7883e5cf61873a17271fb4.png b/Dev/Bundle/Images/c-m2-272ae9468b7883e5cf61873a17271fb4.png similarity index 100% rename from Bundle/Images/c-m2-272ae9468b7883e5cf61873a17271fb4.png rename to Dev/Bundle/Images/c-m2-272ae9468b7883e5cf61873a17271fb4.png diff --git a/Bundle/Images/c-m2-2dc3bdd9274b121b851fa536b0e35b6a.png b/Dev/Bundle/Images/c-m2-2dc3bdd9274b121b851fa536b0e35b6a.png similarity index 100% rename from Bundle/Images/c-m2-2dc3bdd9274b121b851fa536b0e35b6a.png rename to Dev/Bundle/Images/c-m2-2dc3bdd9274b121b851fa536b0e35b6a.png diff --git a/Bundle/Images/c-m2-559dcf17d281e285b7f09f943b9706de.png b/Dev/Bundle/Images/c-m2-559dcf17d281e285b7f09f943b9706de.png similarity index 100% rename from Bundle/Images/c-m2-559dcf17d281e285b7f09f943b9706de.png rename to Dev/Bundle/Images/c-m2-559dcf17d281e285b7f09f943b9706de.png diff --git a/Bundle/Images/c-m2-593d4b1a0b5d13b539c6c098dc5797ca.png b/Dev/Bundle/Images/c-m2-593d4b1a0b5d13b539c6c098dc5797ca.png similarity index 100% rename from Bundle/Images/c-m2-593d4b1a0b5d13b539c6c098dc5797ca.png rename to Dev/Bundle/Images/c-m2-593d4b1a0b5d13b539c6c098dc5797ca.png diff --git a/Bundle/Images/c-m2-66ac49ef3f48ac9482049e1ab57a53e9.png b/Dev/Bundle/Images/c-m2-66ac49ef3f48ac9482049e1ab57a53e9.png similarity index 100% rename from Bundle/Images/c-m2-66ac49ef3f48ac9482049e1ab57a53e9.png rename to Dev/Bundle/Images/c-m2-66ac49ef3f48ac9482049e1ab57a53e9.png diff --git a/Bundle/Images/c-m2-71915ab0b1cc7350091ef7073a312d16.png b/Dev/Bundle/Images/c-m2-71915ab0b1cc7350091ef7073a312d16.png similarity index 100% rename from Bundle/Images/c-m2-71915ab0b1cc7350091ef7073a312d16.png rename to Dev/Bundle/Images/c-m2-71915ab0b1cc7350091ef7073a312d16.png diff --git a/Bundle/Images/c-m2-80e163ebface8b0d2fbf9823bca02936.png b/Dev/Bundle/Images/c-m2-80e163ebface8b0d2fbf9823bca02936.png similarity index 100% rename from Bundle/Images/c-m2-80e163ebface8b0d2fbf9823bca02936.png rename to Dev/Bundle/Images/c-m2-80e163ebface8b0d2fbf9823bca02936.png diff --git a/Bundle/Images/c-m2-8f2b481b7fd9bd745e620b7c01a18df2.png b/Dev/Bundle/Images/c-m2-8f2b481b7fd9bd745e620b7c01a18df2.png similarity index 100% rename from Bundle/Images/c-m2-8f2b481b7fd9bd745e620b7c01a18df2.png rename to Dev/Bundle/Images/c-m2-8f2b481b7fd9bd745e620b7c01a18df2.png diff --git a/Bundle/Images/c-m2-96b70653ba3f8a83b7cfd48749bed8b1.png b/Dev/Bundle/Images/c-m2-96b70653ba3f8a83b7cfd48749bed8b1.png similarity index 100% rename from Bundle/Images/c-m2-96b70653ba3f8a83b7cfd48749bed8b1.png rename to Dev/Bundle/Images/c-m2-96b70653ba3f8a83b7cfd48749bed8b1.png diff --git a/Bundle/Images/c-m2-9a3e0c7b687b526987e2270541002d47.png b/Dev/Bundle/Images/c-m2-9a3e0c7b687b526987e2270541002d47.png similarity index 100% rename from Bundle/Images/c-m2-9a3e0c7b687b526987e2270541002d47.png rename to Dev/Bundle/Images/c-m2-9a3e0c7b687b526987e2270541002d47.png diff --git a/Bundle/Images/c-m2-a1f9d85a8243b884d40e74f656c55e75.png b/Dev/Bundle/Images/c-m2-a1f9d85a8243b884d40e74f656c55e75.png similarity index 100% rename from Bundle/Images/c-m2-a1f9d85a8243b884d40e74f656c55e75.png rename to Dev/Bundle/Images/c-m2-a1f9d85a8243b884d40e74f656c55e75.png diff --git a/Bundle/Images/c-m2-a46ce91d8975a017917156b8824f936e.png b/Dev/Bundle/Images/c-m2-a46ce91d8975a017917156b8824f936e.png similarity index 100% rename from Bundle/Images/c-m2-a46ce91d8975a017917156b8824f936e.png rename to Dev/Bundle/Images/c-m2-a46ce91d8975a017917156b8824f936e.png diff --git a/Bundle/Images/c-m2-a4842373fc20cc42b8e023a329761915.png b/Dev/Bundle/Images/c-m2-a4842373fc20cc42b8e023a329761915.png similarity index 100% rename from Bundle/Images/c-m2-a4842373fc20cc42b8e023a329761915.png rename to Dev/Bundle/Images/c-m2-a4842373fc20cc42b8e023a329761915.png diff --git a/Bundle/Images/c-m2-b977e74d9de1f6689fdd84c4e38830f5.png b/Dev/Bundle/Images/c-m2-b977e74d9de1f6689fdd84c4e38830f5.png similarity index 100% rename from Bundle/Images/c-m2-b977e74d9de1f6689fdd84c4e38830f5.png rename to Dev/Bundle/Images/c-m2-b977e74d9de1f6689fdd84c4e38830f5.png diff --git a/Bundle/Images/c-m2-fcac2d6a6a739e8ceb946ac99200d9f1.png b/Dev/Bundle/Images/c-m2-fcac2d6a6a739e8ceb946ac99200d9f1.png similarity index 100% rename from Bundle/Images/c-m2-fcac2d6a6a739e8ceb946ac99200d9f1.png rename to Dev/Bundle/Images/c-m2-fcac2d6a6a739e8ceb946ac99200d9f1.png diff --git a/Bundle/Images/c-m3-1f97f040d0b6b26faeb0a1a7f1499590.png b/Dev/Bundle/Images/c-m3-1f97f040d0b6b26faeb0a1a7f1499590.png similarity index 100% rename from Bundle/Images/c-m3-1f97f040d0b6b26faeb0a1a7f1499590.png rename to Dev/Bundle/Images/c-m3-1f97f040d0b6b26faeb0a1a7f1499590.png diff --git a/Bundle/Images/c-m3-593d4b1a0b5d13b539c6c098dc5797ca.png b/Dev/Bundle/Images/c-m3-593d4b1a0b5d13b539c6c098dc5797ca.png similarity index 100% rename from Bundle/Images/c-m3-593d4b1a0b5d13b539c6c098dc5797ca.png rename to Dev/Bundle/Images/c-m3-593d4b1a0b5d13b539c6c098dc5797ca.png diff --git a/Bundle/Images/c-m3-66ac49ef3f48ac9482049e1ab57a53e9.png b/Dev/Bundle/Images/c-m3-66ac49ef3f48ac9482049e1ab57a53e9.png similarity index 100% rename from Bundle/Images/c-m3-66ac49ef3f48ac9482049e1ab57a53e9.png rename to Dev/Bundle/Images/c-m3-66ac49ef3f48ac9482049e1ab57a53e9.png diff --git a/Bundle/Images/c-m3-a46ce91d8975a017917156b8824f936e.png b/Dev/Bundle/Images/c-m3-a46ce91d8975a017917156b8824f936e.png similarity index 100% rename from Bundle/Images/c-m3-a46ce91d8975a017917156b8824f936e.png rename to Dev/Bundle/Images/c-m3-a46ce91d8975a017917156b8824f936e.png diff --git a/Bundle/Images/c-m3-b68163a6a8e2ccf3c8ad2c70a26c1150.png b/Dev/Bundle/Images/c-m3-b68163a6a8e2ccf3c8ad2c70a26c1150.png similarity index 100% rename from Bundle/Images/c-m3-b68163a6a8e2ccf3c8ad2c70a26c1150.png rename to Dev/Bundle/Images/c-m3-b68163a6a8e2ccf3c8ad2c70a26c1150.png diff --git a/Bundle/Images/c-m4-6bfb149151f58d124d6fa76eaad75520.png b/Dev/Bundle/Images/c-m4-6bfb149151f58d124d6fa76eaad75520.png similarity index 100% rename from Bundle/Images/c-m4-6bfb149151f58d124d6fa76eaad75520.png rename to Dev/Bundle/Images/c-m4-6bfb149151f58d124d6fa76eaad75520.png diff --git a/Bundle/Images/c-m4-96b70653ba3f8a83b7cfd48749bed8b1.png b/Dev/Bundle/Images/c-m4-96b70653ba3f8a83b7cfd48749bed8b1.png similarity index 100% rename from Bundle/Images/c-m4-96b70653ba3f8a83b7cfd48749bed8b1.png rename to Dev/Bundle/Images/c-m4-96b70653ba3f8a83b7cfd48749bed8b1.png diff --git a/Bundle/Images/c-m4-a46ce91d8975a017917156b8824f936e.png b/Dev/Bundle/Images/c-m4-a46ce91d8975a017917156b8824f936e.png similarity index 100% rename from Bundle/Images/c-m4-a46ce91d8975a017917156b8824f936e.png rename to Dev/Bundle/Images/c-m4-a46ce91d8975a017917156b8824f936e.png diff --git a/Bundle/Images/c-m5-1f97f040d0b6b26faeb0a1a7f1499590.png b/Dev/Bundle/Images/c-m5-1f97f040d0b6b26faeb0a1a7f1499590.png similarity index 100% rename from Bundle/Images/c-m5-1f97f040d0b6b26faeb0a1a7f1499590.png rename to Dev/Bundle/Images/c-m5-1f97f040d0b6b26faeb0a1a7f1499590.png diff --git a/Bundle/Images/c-m5-a46ce91d8975a017917156b8824f936e.png b/Dev/Bundle/Images/c-m5-a46ce91d8975a017917156b8824f936e.png similarity index 100% rename from Bundle/Images/c-m5-a46ce91d8975a017917156b8824f936e.png rename to Dev/Bundle/Images/c-m5-a46ce91d8975a017917156b8824f936e.png diff --git a/Bundle/Images/c-m6-1f97f040d0b6b26faeb0a1a7f1499590.png b/Dev/Bundle/Images/c-m6-1f97f040d0b6b26faeb0a1a7f1499590.png similarity index 100% rename from Bundle/Images/c-m6-1f97f040d0b6b26faeb0a1a7f1499590.png rename to Dev/Bundle/Images/c-m6-1f97f040d0b6b26faeb0a1a7f1499590.png diff --git a/Bundle/Images/c-m6-a46ce91d8975a017917156b8824f936e.png b/Dev/Bundle/Images/c-m6-a46ce91d8975a017917156b8824f936e.png similarity index 100% rename from Bundle/Images/c-m6-a46ce91d8975a017917156b8824f936e.png rename to Dev/Bundle/Images/c-m6-a46ce91d8975a017917156b8824f936e.png diff --git a/Bundle/Images/c-m7-1f97f040d0b6b26faeb0a1a7f1499590.png b/Dev/Bundle/Images/c-m7-1f97f040d0b6b26faeb0a1a7f1499590.png similarity index 100% rename from Bundle/Images/c-m7-1f97f040d0b6b26faeb0a1a7f1499590.png rename to Dev/Bundle/Images/c-m7-1f97f040d0b6b26faeb0a1a7f1499590.png diff --git a/Bundle/Images/c-m7-a46ce91d8975a017917156b8824f936e.png b/Dev/Bundle/Images/c-m7-a46ce91d8975a017917156b8824f936e.png similarity index 100% rename from Bundle/Images/c-m7-a46ce91d8975a017917156b8824f936e.png rename to Dev/Bundle/Images/c-m7-a46ce91d8975a017917156b8824f936e.png diff --git a/Bundle/Images/c-m8-1f97f040d0b6b26faeb0a1a7f1499590.png b/Dev/Bundle/Images/c-m8-1f97f040d0b6b26faeb0a1a7f1499590.png similarity index 100% rename from Bundle/Images/c-m8-1f97f040d0b6b26faeb0a1a7f1499590.png rename to Dev/Bundle/Images/c-m8-1f97f040d0b6b26faeb0a1a7f1499590.png diff --git a/Bundle/Images/c-m8-a46ce91d8975a017917156b8824f936e.png b/Dev/Bundle/Images/c-m8-a46ce91d8975a017917156b8824f936e.png similarity index 100% rename from Bundle/Images/c-m8-a46ce91d8975a017917156b8824f936e.png rename to Dev/Bundle/Images/c-m8-a46ce91d8975a017917156b8824f936e.png diff --git a/Bundle/Images/c0a76d267196727887d45de4889bec33.png b/Dev/Bundle/Images/c0a76d267196727887d45de4889bec33.png similarity index 100% rename from Bundle/Images/c0a76d267196727887d45de4889bec33.png rename to Dev/Bundle/Images/c0a76d267196727887d45de4889bec33.png diff --git a/Bundle/Images/c1a4baf5d7c68d366d4d4f948f7295be.png b/Dev/Bundle/Images/c1a4baf5d7c68d366d4d4f948f7295be.png similarity index 100% rename from Bundle/Images/c1a4baf5d7c68d366d4d4f948f7295be.png rename to Dev/Bundle/Images/c1a4baf5d7c68d366d4d4f948f7295be.png diff --git a/Bundle/Images/c1ca5583e4bfadc73e7fe9418b6e6bf4.jpg b/Dev/Bundle/Images/c1ca5583e4bfadc73e7fe9418b6e6bf4.jpg similarity index 100% rename from Bundle/Images/c1ca5583e4bfadc73e7fe9418b6e6bf4.jpg rename to Dev/Bundle/Images/c1ca5583e4bfadc73e7fe9418b6e6bf4.jpg diff --git a/Bundle/Images/c3018ebe53d0046eecb58858ca869a99.jpg b/Dev/Bundle/Images/c3018ebe53d0046eecb58858ca869a99.jpg similarity index 100% rename from Bundle/Images/c3018ebe53d0046eecb58858ca869a99.jpg rename to Dev/Bundle/Images/c3018ebe53d0046eecb58858ca869a99.jpg diff --git a/Bundle/Images/c4ced510f44a9bfe85c696c05a7f791d.jpg b/Dev/Bundle/Images/c4ced510f44a9bfe85c696c05a7f791d.jpg similarity index 100% rename from Bundle/Images/c4ced510f44a9bfe85c696c05a7f791d.jpg rename to Dev/Bundle/Images/c4ced510f44a9bfe85c696c05a7f791d.jpg diff --git a/Bundle/Images/c52ffdd6a0346c4d09271f8ccbdfd5a3.jpg b/Dev/Bundle/Images/c52ffdd6a0346c4d09271f8ccbdfd5a3.jpg similarity index 100% rename from Bundle/Images/c52ffdd6a0346c4d09271f8ccbdfd5a3.jpg rename to Dev/Bundle/Images/c52ffdd6a0346c4d09271f8ccbdfd5a3.jpg diff --git a/Bundle/Images/c53911b0385c34a8204c30fdc14ea5cc.png b/Dev/Bundle/Images/c53911b0385c34a8204c30fdc14ea5cc.png similarity index 100% rename from Bundle/Images/c53911b0385c34a8204c30fdc14ea5cc.png rename to Dev/Bundle/Images/c53911b0385c34a8204c30fdc14ea5cc.png diff --git a/Bundle/Images/c5c030bf52b9b2d8c45c88988fafff4f.png b/Dev/Bundle/Images/c5c030bf52b9b2d8c45c88988fafff4f.png similarity index 100% rename from Bundle/Images/c5c030bf52b9b2d8c45c88988fafff4f.png rename to Dev/Bundle/Images/c5c030bf52b9b2d8c45c88988fafff4f.png diff --git a/Bundle/Images/c636287a4d7cb1a36362f7f236564cef.png b/Dev/Bundle/Images/c636287a4d7cb1a36362f7f236564cef.png similarity index 100% rename from Bundle/Images/c636287a4d7cb1a36362f7f236564cef.png rename to Dev/Bundle/Images/c636287a4d7cb1a36362f7f236564cef.png diff --git a/Bundle/Images/c8bc97335529d069a753c67475b8c82c.jpg b/Dev/Bundle/Images/c8bc97335529d069a753c67475b8c82c.jpg similarity index 100% rename from Bundle/Images/c8bc97335529d069a753c67475b8c82c.jpg rename to Dev/Bundle/Images/c8bc97335529d069a753c67475b8c82c.jpg diff --git a/Bundle/Images/c8c1a5675f82021d92b928a10c597bad.jpg b/Dev/Bundle/Images/c8c1a5675f82021d92b928a10c597bad.jpg similarity index 100% rename from Bundle/Images/c8c1a5675f82021d92b928a10c597bad.jpg rename to Dev/Bundle/Images/c8c1a5675f82021d92b928a10c597bad.jpg diff --git a/Bundle/Images/cc23dd79637b606cf5ba234a037e17ba.jpg b/Dev/Bundle/Images/cc23dd79637b606cf5ba234a037e17ba.jpg similarity index 100% rename from Bundle/Images/cc23dd79637b606cf5ba234a037e17ba.jpg rename to Dev/Bundle/Images/cc23dd79637b606cf5ba234a037e17ba.jpg diff --git a/Bundle/Images/cc4ee796d16c9fe68978166c7cd1ae1b.jpg b/Dev/Bundle/Images/cc4ee796d16c9fe68978166c7cd1ae1b.jpg similarity index 100% rename from Bundle/Images/cc4ee796d16c9fe68978166c7cd1ae1b.jpg rename to Dev/Bundle/Images/cc4ee796d16c9fe68978166c7cd1ae1b.jpg diff --git a/Bundle/Images/ce380515a534e8226209daae00e7b4e8.jpg b/Dev/Bundle/Images/ce380515a534e8226209daae00e7b4e8.jpg similarity index 100% rename from Bundle/Images/ce380515a534e8226209daae00e7b4e8.jpg rename to Dev/Bundle/Images/ce380515a534e8226209daae00e7b4e8.jpg diff --git a/Bundle/Images/ce774930ac70449f38a18789c70095b8.gif b/Dev/Bundle/Images/ce774930ac70449f38a18789c70095b8.gif similarity index 100% rename from Bundle/Images/ce774930ac70449f38a18789c70095b8.gif rename to Dev/Bundle/Images/ce774930ac70449f38a18789c70095b8.gif diff --git a/Bundle/Images/d085a42245996e5750a30ccb48791bcf.jpg b/Dev/Bundle/Images/d085a42245996e5750a30ccb48791bcf.jpg similarity index 100% rename from Bundle/Images/d085a42245996e5750a30ccb48791bcf.jpg rename to Dev/Bundle/Images/d085a42245996e5750a30ccb48791bcf.jpg diff --git a/Bundle/Images/d15b71b8cebe35a57cc6e996cc09218b.jpg b/Dev/Bundle/Images/d15b71b8cebe35a57cc6e996cc09218b.jpg similarity index 100% rename from Bundle/Images/d15b71b8cebe35a57cc6e996cc09218b.jpg rename to Dev/Bundle/Images/d15b71b8cebe35a57cc6e996cc09218b.jpg diff --git a/Bundle/Images/d22db5be7594c17a18a047ca9264ea0a.jpg b/Dev/Bundle/Images/d22db5be7594c17a18a047ca9264ea0a.jpg similarity index 100% rename from Bundle/Images/d22db5be7594c17a18a047ca9264ea0a.jpg rename to Dev/Bundle/Images/d22db5be7594c17a18a047ca9264ea0a.jpg diff --git a/Bundle/Images/d2e515cfdabae699301dcf290382474d.png b/Dev/Bundle/Images/d2e515cfdabae699301dcf290382474d.png similarity index 100% rename from Bundle/Images/d2e515cfdabae699301dcf290382474d.png rename to Dev/Bundle/Images/d2e515cfdabae699301dcf290382474d.png diff --git a/Bundle/Images/d3b044a94486cae0224c002800ddd642.jpg b/Dev/Bundle/Images/d3b044a94486cae0224c002800ddd642.jpg similarity index 100% rename from Bundle/Images/d3b044a94486cae0224c002800ddd642.jpg rename to Dev/Bundle/Images/d3b044a94486cae0224c002800ddd642.jpg diff --git a/Bundle/Images/d3ffec5786387c590721e674d705f16e.png b/Dev/Bundle/Images/d3ffec5786387c590721e674d705f16e.png similarity index 100% rename from Bundle/Images/d3ffec5786387c590721e674d705f16e.png rename to Dev/Bundle/Images/d3ffec5786387c590721e674d705f16e.png diff --git a/Bundle/Images/d45b0dbbb808df6486f8a13ea44ea174.png b/Dev/Bundle/Images/d45b0dbbb808df6486f8a13ea44ea174.png similarity index 100% rename from Bundle/Images/d45b0dbbb808df6486f8a13ea44ea174.png rename to Dev/Bundle/Images/d45b0dbbb808df6486f8a13ea44ea174.png diff --git a/Bundle/Images/d5a0175c07418852152ef33a886a5029.gif b/Dev/Bundle/Images/d5a0175c07418852152ef33a886a5029.gif similarity index 100% rename from Bundle/Images/d5a0175c07418852152ef33a886a5029.gif rename to Dev/Bundle/Images/d5a0175c07418852152ef33a886a5029.gif diff --git a/Bundle/Images/d92428f3fc9c806b0a4373b54e06785e.png b/Dev/Bundle/Images/d92428f3fc9c806b0a4373b54e06785e.png similarity index 100% rename from Bundle/Images/d92428f3fc9c806b0a4373b54e06785e.png rename to Dev/Bundle/Images/d92428f3fc9c806b0a4373b54e06785e.png diff --git a/Bundle/Images/dd18aac055d531e0e4ff8979458dbaa3.png b/Dev/Bundle/Images/dd18aac055d531e0e4ff8979458dbaa3.png similarity index 100% rename from Bundle/Images/dd18aac055d531e0e4ff8979458dbaa3.png rename to Dev/Bundle/Images/dd18aac055d531e0e4ff8979458dbaa3.png diff --git a/Bundle/Images/de4ae285a275bcfe2ac87c0126742552.jpg b/Dev/Bundle/Images/de4ae285a275bcfe2ac87c0126742552.jpg similarity index 100% rename from Bundle/Images/de4ae285a275bcfe2ac87c0126742552.jpg rename to Dev/Bundle/Images/de4ae285a275bcfe2ac87c0126742552.jpg diff --git a/Bundle/Images/de5884cec093257d239f3b8be3e2f2e5.jpg b/Dev/Bundle/Images/de5884cec093257d239f3b8be3e2f2e5.jpg similarity index 100% rename from Bundle/Images/de5884cec093257d239f3b8be3e2f2e5.jpg rename to Dev/Bundle/Images/de5884cec093257d239f3b8be3e2f2e5.jpg diff --git a/Bundle/Images/e18bb52107598f65b81b02be2c6c5124.jpg b/Dev/Bundle/Images/e18bb52107598f65b81b02be2c6c5124.jpg similarity index 100% rename from Bundle/Images/e18bb52107598f65b81b02be2c6c5124.jpg rename to Dev/Bundle/Images/e18bb52107598f65b81b02be2c6c5124.jpg diff --git a/Bundle/Images/e34116d68f49c7852b362ec72a636df5.gif b/Dev/Bundle/Images/e34116d68f49c7852b362ec72a636df5.gif similarity index 100% rename from Bundle/Images/e34116d68f49c7852b362ec72a636df5.gif rename to Dev/Bundle/Images/e34116d68f49c7852b362ec72a636df5.gif diff --git a/Bundle/Images/e585afb2ecf50c04eaf0dedb71602cb8.png b/Dev/Bundle/Images/e585afb2ecf50c04eaf0dedb71602cb8.png similarity index 100% rename from Bundle/Images/e585afb2ecf50c04eaf0dedb71602cb8.png rename to Dev/Bundle/Images/e585afb2ecf50c04eaf0dedb71602cb8.png diff --git a/Bundle/Images/e59ec0cfb8ab64558099543dc19f8378.png b/Dev/Bundle/Images/e59ec0cfb8ab64558099543dc19f8378.png similarity index 100% rename from Bundle/Images/e59ec0cfb8ab64558099543dc19f8378.png rename to Dev/Bundle/Images/e59ec0cfb8ab64558099543dc19f8378.png diff --git a/Bundle/Images/e6aa0c45a13dd7fc94f7b5451bd89bf4.gif b/Dev/Bundle/Images/e6aa0c45a13dd7fc94f7b5451bd89bf4.gif similarity index 100% rename from Bundle/Images/e6aa0c45a13dd7fc94f7b5451bd89bf4.gif rename to Dev/Bundle/Images/e6aa0c45a13dd7fc94f7b5451bd89bf4.gif diff --git a/Bundle/Images/e6d9eca2c7405e13cfb850b7d0ef7476.jpg b/Dev/Bundle/Images/e6d9eca2c7405e13cfb850b7d0ef7476.jpg similarity index 100% rename from Bundle/Images/e6d9eca2c7405e13cfb850b7d0ef7476.jpg rename to Dev/Bundle/Images/e6d9eca2c7405e13cfb850b7d0ef7476.jpg diff --git a/Bundle/Images/e76546768d4a8f2f4c39339345c7614c.png b/Dev/Bundle/Images/e76546768d4a8f2f4c39339345c7614c.png similarity index 100% rename from Bundle/Images/e76546768d4a8f2f4c39339345c7614c.png rename to Dev/Bundle/Images/e76546768d4a8f2f4c39339345c7614c.png diff --git a/Bundle/Images/ea01d6c175bb25dc75757cf8a5793822.png b/Dev/Bundle/Images/ea01d6c175bb25dc75757cf8a5793822.png similarity index 100% rename from Bundle/Images/ea01d6c175bb25dc75757cf8a5793822.png rename to Dev/Bundle/Images/ea01d6c175bb25dc75757cf8a5793822.png diff --git a/Bundle/Images/ea754e040929b7f9c157efc88c4d0eaf.gif b/Dev/Bundle/Images/ea754e040929b7f9c157efc88c4d0eaf.gif similarity index 100% rename from Bundle/Images/ea754e040929b7f9c157efc88c4d0eaf.gif rename to Dev/Bundle/Images/ea754e040929b7f9c157efc88c4d0eaf.gif diff --git a/Bundle/Images/ebfb1cd42314a557e72d4da75c21fc1c.png b/Dev/Bundle/Images/ebfb1cd42314a557e72d4da75c21fc1c.png similarity index 100% rename from Bundle/Images/ebfb1cd42314a557e72d4da75c21fc1c.png rename to Dev/Bundle/Images/ebfb1cd42314a557e72d4da75c21fc1c.png diff --git a/Bundle/Images/ed5f2464fcaadd4e0a5e905e3ac41ad5.png b/Dev/Bundle/Images/ed5f2464fcaadd4e0a5e905e3ac41ad5.png similarity index 100% rename from Bundle/Images/ed5f2464fcaadd4e0a5e905e3ac41ad5.png rename to Dev/Bundle/Images/ed5f2464fcaadd4e0a5e905e3ac41ad5.png diff --git a/Bundle/Images/eddea4ef9629be031f750a8ff0b7497c.jpg b/Dev/Bundle/Images/eddea4ef9629be031f750a8ff0b7497c.jpg similarity index 100% rename from Bundle/Images/eddea4ef9629be031f750a8ff0b7497c.jpg rename to Dev/Bundle/Images/eddea4ef9629be031f750a8ff0b7497c.jpg diff --git a/Bundle/Images/edf5c1b0aa5b01eea5017290a286a173.png b/Dev/Bundle/Images/edf5c1b0aa5b01eea5017290a286a173.png similarity index 100% rename from Bundle/Images/edf5c1b0aa5b01eea5017290a286a173.png rename to Dev/Bundle/Images/edf5c1b0aa5b01eea5017290a286a173.png diff --git a/Bundle/Images/ee6d1133f9264dc6467990e53d0bf104.gif b/Dev/Bundle/Images/ee6d1133f9264dc6467990e53d0bf104.gif similarity index 100% rename from Bundle/Images/ee6d1133f9264dc6467990e53d0bf104.gif rename to Dev/Bundle/Images/ee6d1133f9264dc6467990e53d0bf104.gif diff --git a/Bundle/Images/eecb78b937a7c5f04aae2f5b0f5b5acc.jpg b/Dev/Bundle/Images/eecb78b937a7c5f04aae2f5b0f5b5acc.jpg similarity index 100% rename from Bundle/Images/eecb78b937a7c5f04aae2f5b0f5b5acc.jpg rename to Dev/Bundle/Images/eecb78b937a7c5f04aae2f5b0f5b5acc.jpg diff --git a/Bundle/Images/ef1f8a057bb6056674fad92f6b8c0acd.jpg b/Dev/Bundle/Images/ef1f8a057bb6056674fad92f6b8c0acd.jpg similarity index 100% rename from Bundle/Images/ef1f8a057bb6056674fad92f6b8c0acd.jpg rename to Dev/Bundle/Images/ef1f8a057bb6056674fad92f6b8c0acd.jpg diff --git a/Bundle/Images/ef724193653930f52acffa90e6426fd2.jpg b/Dev/Bundle/Images/ef724193653930f52acffa90e6426fd2.jpg similarity index 100% rename from Bundle/Images/ef724193653930f52acffa90e6426fd2.jpg rename to Dev/Bundle/Images/ef724193653930f52acffa90e6426fd2.jpg diff --git a/Bundle/Images/f006e96f3b27fdfaa075322d759ea2e8.jpg b/Dev/Bundle/Images/f006e96f3b27fdfaa075322d759ea2e8.jpg similarity index 100% rename from Bundle/Images/f006e96f3b27fdfaa075322d759ea2e8.jpg rename to Dev/Bundle/Images/f006e96f3b27fdfaa075322d759ea2e8.jpg diff --git a/Bundle/Images/f012a4321f00f12af6b1eee7580ffb9c.jpg b/Dev/Bundle/Images/f012a4321f00f12af6b1eee7580ffb9c.jpg similarity index 100% rename from Bundle/Images/f012a4321f00f12af6b1eee7580ffb9c.jpg rename to Dev/Bundle/Images/f012a4321f00f12af6b1eee7580ffb9c.jpg diff --git a/Bundle/Images/f1fad47f213bb64c99f714652f30e49e.jpg b/Dev/Bundle/Images/f1fad47f213bb64c99f714652f30e49e.jpg similarity index 100% rename from Bundle/Images/f1fad47f213bb64c99f714652f30e49e.jpg rename to Dev/Bundle/Images/f1fad47f213bb64c99f714652f30e49e.jpg diff --git a/Bundle/Images/f23a99688fa66359f6186678e6b2f14a.png b/Dev/Bundle/Images/f23a99688fa66359f6186678e6b2f14a.png similarity index 100% rename from Bundle/Images/f23a99688fa66359f6186678e6b2f14a.png rename to Dev/Bundle/Images/f23a99688fa66359f6186678e6b2f14a.png diff --git a/Bundle/Images/f427b6bee1acd1fea3ec953bc556a18a.png b/Dev/Bundle/Images/f427b6bee1acd1fea3ec953bc556a18a.png similarity index 100% rename from Bundle/Images/f427b6bee1acd1fea3ec953bc556a18a.png rename to Dev/Bundle/Images/f427b6bee1acd1fea3ec953bc556a18a.png diff --git a/Bundle/Images/f5e7b9db8e8d002a26304f5c81889ee1.png b/Dev/Bundle/Images/f5e7b9db8e8d002a26304f5c81889ee1.png similarity index 100% rename from Bundle/Images/f5e7b9db8e8d002a26304f5c81889ee1.png rename to Dev/Bundle/Images/f5e7b9db8e8d002a26304f5c81889ee1.png diff --git a/Bundle/Images/f617c7af7f36296a37ddb419b828099c.gif b/Dev/Bundle/Images/f617c7af7f36296a37ddb419b828099c.gif similarity index 100% rename from Bundle/Images/f617c7af7f36296a37ddb419b828099c.gif rename to Dev/Bundle/Images/f617c7af7f36296a37ddb419b828099c.gif diff --git a/Bundle/Images/f6266c0e9c2f7db9fab0f84562f63b6c.png b/Dev/Bundle/Images/f6266c0e9c2f7db9fab0f84562f63b6c.png similarity index 100% rename from Bundle/Images/f6266c0e9c2f7db9fab0f84562f63b6c.png rename to Dev/Bundle/Images/f6266c0e9c2f7db9fab0f84562f63b6c.png diff --git a/Bundle/Images/f6419b06a39ff09604343848658b1a41.jpg b/Dev/Bundle/Images/f6419b06a39ff09604343848658b1a41.jpg similarity index 100% rename from Bundle/Images/f6419b06a39ff09604343848658b1a41.jpg rename to Dev/Bundle/Images/f6419b06a39ff09604343848658b1a41.jpg diff --git a/Bundle/Images/f6b4389c3cf0f5997b2e5a4b905aea8d.jpg b/Dev/Bundle/Images/f6b4389c3cf0f5997b2e5a4b905aea8d.jpg similarity index 100% rename from Bundle/Images/f6b4389c3cf0f5997b2e5a4b905aea8d.jpg rename to Dev/Bundle/Images/f6b4389c3cf0f5997b2e5a4b905aea8d.jpg diff --git a/Bundle/Images/f6d3f522dcb693d9e731d5a0fb4e1393.jpg b/Dev/Bundle/Images/f6d3f522dcb693d9e731d5a0fb4e1393.jpg similarity index 100% rename from Bundle/Images/f6d3f522dcb693d9e731d5a0fb4e1393.jpg rename to Dev/Bundle/Images/f6d3f522dcb693d9e731d5a0fb4e1393.jpg diff --git a/Bundle/Images/f757de9794666c3d14985210679bc98c.png b/Dev/Bundle/Images/f757de9794666c3d14985210679bc98c.png similarity index 100% rename from Bundle/Images/f757de9794666c3d14985210679bc98c.png rename to Dev/Bundle/Images/f757de9794666c3d14985210679bc98c.png diff --git a/Bundle/Images/f85c09bb72db5a572d24b8d3a0d56542.png b/Dev/Bundle/Images/f85c09bb72db5a572d24b8d3a0d56542.png similarity index 100% rename from Bundle/Images/f85c09bb72db5a572d24b8d3a0d56542.png rename to Dev/Bundle/Images/f85c09bb72db5a572d24b8d3a0d56542.png diff --git a/Bundle/Images/f88b6907ee086c4c8ac4b8c395748c49.gif b/Dev/Bundle/Images/f88b6907ee086c4c8ac4b8c395748c49.gif similarity index 100% rename from Bundle/Images/f88b6907ee086c4c8ac4b8c395748c49.gif rename to Dev/Bundle/Images/f88b6907ee086c4c8ac4b8c395748c49.gif diff --git a/Bundle/Images/f8e19feecd246156b5d7e79efc455e99.jpg b/Dev/Bundle/Images/f8e19feecd246156b5d7e79efc455e99.jpg similarity index 100% rename from Bundle/Images/f8e19feecd246156b5d7e79efc455e99.jpg rename to Dev/Bundle/Images/f8e19feecd246156b5d7e79efc455e99.jpg diff --git a/Bundle/Images/fa9f6aa9bcc679d20e171dbf07a628fd.png b/Dev/Bundle/Images/fa9f6aa9bcc679d20e171dbf07a628fd.png similarity index 100% rename from Bundle/Images/fa9f6aa9bcc679d20e171dbf07a628fd.png rename to Dev/Bundle/Images/fa9f6aa9bcc679d20e171dbf07a628fd.png diff --git a/Bundle/Images/fc3e2b992c559055267e26dc23e484c0.gif b/Dev/Bundle/Images/fc3e2b992c559055267e26dc23e484c0.gif similarity index 100% rename from Bundle/Images/fc3e2b992c559055267e26dc23e484c0.gif rename to Dev/Bundle/Images/fc3e2b992c559055267e26dc23e484c0.gif diff --git a/Bundle/Images/fd44dc63fa7bdd12ee34fc602231ef02.jpg b/Dev/Bundle/Images/fd44dc63fa7bdd12ee34fc602231ef02.jpg similarity index 100% rename from Bundle/Images/fd44dc63fa7bdd12ee34fc602231ef02.jpg rename to Dev/Bundle/Images/fd44dc63fa7bdd12ee34fc602231ef02.jpg diff --git a/Bundle/Images/fddcfc778ada60229380c2493fc4c243.jpg b/Dev/Bundle/Images/fddcfc778ada60229380c2493fc4c243.jpg similarity index 100% rename from Bundle/Images/fddcfc778ada60229380c2493fc4c243.jpg rename to Dev/Bundle/Images/fddcfc778ada60229380c2493fc4c243.jpg diff --git a/Bundle/Images/fde6410fe7fb87f095bc855279d5beab.png b/Dev/Bundle/Images/fde6410fe7fb87f095bc855279d5beab.png similarity index 100% rename from Bundle/Images/fde6410fe7fb87f095bc855279d5beab.png rename to Dev/Bundle/Images/fde6410fe7fb87f095bc855279d5beab.png diff --git a/Bundle/Images/gaku.jpeg b/Dev/Bundle/Images/gaku.jpeg similarity index 100% rename from Bundle/Images/gaku.jpeg rename to Dev/Bundle/Images/gaku.jpeg diff --git a/Bundle/Images/m1-04c2707d63235dd5ab2c66ee98a36521.png b/Dev/Bundle/Images/m1-04c2707d63235dd5ab2c66ee98a36521.png similarity index 100% rename from Bundle/Images/m1-04c2707d63235dd5ab2c66ee98a36521.png rename to Dev/Bundle/Images/m1-04c2707d63235dd5ab2c66ee98a36521.png diff --git a/Bundle/Images/m1-0699098e769a2d80e60f33dbb3332b61.png b/Dev/Bundle/Images/m1-0699098e769a2d80e60f33dbb3332b61.png similarity index 100% rename from Bundle/Images/m1-0699098e769a2d80e60f33dbb3332b61.png rename to Dev/Bundle/Images/m1-0699098e769a2d80e60f33dbb3332b61.png diff --git a/Bundle/Images/m1-125cdc39e13ce7c237b7b4a9e1d8f21c.png b/Dev/Bundle/Images/m1-125cdc39e13ce7c237b7b4a9e1d8f21c.png similarity index 100% rename from Bundle/Images/m1-125cdc39e13ce7c237b7b4a9e1d8f21c.png rename to Dev/Bundle/Images/m1-125cdc39e13ce7c237b7b4a9e1d8f21c.png diff --git a/Bundle/Images/m1-19e0d1d0dfe97dca39e9d449c6b8b3d2.png b/Dev/Bundle/Images/m1-19e0d1d0dfe97dca39e9d449c6b8b3d2.png similarity index 100% rename from Bundle/Images/m1-19e0d1d0dfe97dca39e9d449c6b8b3d2.png rename to Dev/Bundle/Images/m1-19e0d1d0dfe97dca39e9d449c6b8b3d2.png diff --git a/Bundle/Images/m1-1b5df699719c4a7cc8314ab9af139405.png b/Dev/Bundle/Images/m1-1b5df699719c4a7cc8314ab9af139405.png similarity index 100% rename from Bundle/Images/m1-1b5df699719c4a7cc8314ab9af139405.png rename to Dev/Bundle/Images/m1-1b5df699719c4a7cc8314ab9af139405.png diff --git a/Bundle/Images/m1-1f97f040d0b6b26faeb0a1a7f1499590.png b/Dev/Bundle/Images/m1-1f97f040d0b6b26faeb0a1a7f1499590.png similarity index 100% rename from Bundle/Images/m1-1f97f040d0b6b26faeb0a1a7f1499590.png rename to Dev/Bundle/Images/m1-1f97f040d0b6b26faeb0a1a7f1499590.png diff --git a/Bundle/Images/m1-1fc0c0de88608a9445d6f98a544b5abc.png b/Dev/Bundle/Images/m1-1fc0c0de88608a9445d6f98a544b5abc.png similarity index 100% rename from Bundle/Images/m1-1fc0c0de88608a9445d6f98a544b5abc.png rename to Dev/Bundle/Images/m1-1fc0c0de88608a9445d6f98a544b5abc.png diff --git a/Bundle/Images/m1-272ae9468b7883e5cf61873a17271fb4.png b/Dev/Bundle/Images/m1-272ae9468b7883e5cf61873a17271fb4.png similarity index 100% rename from Bundle/Images/m1-272ae9468b7883e5cf61873a17271fb4.png rename to Dev/Bundle/Images/m1-272ae9468b7883e5cf61873a17271fb4.png diff --git a/Bundle/Images/m1-2dc3bdd9274b121b851fa536b0e35b6a.png b/Dev/Bundle/Images/m1-2dc3bdd9274b121b851fa536b0e35b6a.png similarity index 100% rename from Bundle/Images/m1-2dc3bdd9274b121b851fa536b0e35b6a.png rename to Dev/Bundle/Images/m1-2dc3bdd9274b121b851fa536b0e35b6a.png diff --git a/Bundle/Images/m1-3625f98e00148cdc136c53bdcd2d2e1e.png b/Dev/Bundle/Images/m1-3625f98e00148cdc136c53bdcd2d2e1e.png similarity index 100% rename from Bundle/Images/m1-3625f98e00148cdc136c53bdcd2d2e1e.png rename to Dev/Bundle/Images/m1-3625f98e00148cdc136c53bdcd2d2e1e.png diff --git a/Bundle/Images/m1-49e39033e275de9786d8c41f834c710b.png b/Dev/Bundle/Images/m1-49e39033e275de9786d8c41f834c710b.png similarity index 100% rename from Bundle/Images/m1-49e39033e275de9786d8c41f834c710b.png rename to Dev/Bundle/Images/m1-49e39033e275de9786d8c41f834c710b.png diff --git a/Bundle/Images/m1-4bdd87fd0324f0a3d84d6905d17e1731.png b/Dev/Bundle/Images/m1-4bdd87fd0324f0a3d84d6905d17e1731.png similarity index 100% rename from Bundle/Images/m1-4bdd87fd0324f0a3d84d6905d17e1731.png rename to Dev/Bundle/Images/m1-4bdd87fd0324f0a3d84d6905d17e1731.png diff --git a/Bundle/Images/m1-559dcf17d281e285b7f09f943b9706de.png b/Dev/Bundle/Images/m1-559dcf17d281e285b7f09f943b9706de.png similarity index 100% rename from Bundle/Images/m1-559dcf17d281e285b7f09f943b9706de.png rename to Dev/Bundle/Images/m1-559dcf17d281e285b7f09f943b9706de.png diff --git a/Bundle/Images/m1-585dd0ac594e8226c49ae7986b8f47d3.png b/Dev/Bundle/Images/m1-585dd0ac594e8226c49ae7986b8f47d3.png similarity index 100% rename from Bundle/Images/m1-585dd0ac594e8226c49ae7986b8f47d3.png rename to Dev/Bundle/Images/m1-585dd0ac594e8226c49ae7986b8f47d3.png diff --git a/Bundle/Images/m1-58d30745083f25952342caafb6ee5f37.png b/Dev/Bundle/Images/m1-58d30745083f25952342caafb6ee5f37.png similarity index 100% rename from Bundle/Images/m1-58d30745083f25952342caafb6ee5f37.png rename to Dev/Bundle/Images/m1-58d30745083f25952342caafb6ee5f37.png diff --git a/Bundle/Images/m1-593d4b1a0b5d13b539c6c098dc5797ca.png b/Dev/Bundle/Images/m1-593d4b1a0b5d13b539c6c098dc5797ca.png similarity index 100% rename from Bundle/Images/m1-593d4b1a0b5d13b539c6c098dc5797ca.png rename to Dev/Bundle/Images/m1-593d4b1a0b5d13b539c6c098dc5797ca.png diff --git a/Bundle/Images/m1-5ae377bebf643e2e53ba7038103e48c4.png b/Dev/Bundle/Images/m1-5ae377bebf643e2e53ba7038103e48c4.png similarity index 100% rename from Bundle/Images/m1-5ae377bebf643e2e53ba7038103e48c4.png rename to Dev/Bundle/Images/m1-5ae377bebf643e2e53ba7038103e48c4.png diff --git a/Bundle/Images/m1-5e149c14dc7b7c16ff6bcedd1625ca31.png b/Dev/Bundle/Images/m1-5e149c14dc7b7c16ff6bcedd1625ca31.png similarity index 100% rename from Bundle/Images/m1-5e149c14dc7b7c16ff6bcedd1625ca31.png rename to Dev/Bundle/Images/m1-5e149c14dc7b7c16ff6bcedd1625ca31.png diff --git a/Bundle/Images/m1-5efba06832cc674ae5d290ba7ebc2533.png b/Dev/Bundle/Images/m1-5efba06832cc674ae5d290ba7ebc2533.png similarity index 100% rename from Bundle/Images/m1-5efba06832cc674ae5d290ba7ebc2533.png rename to Dev/Bundle/Images/m1-5efba06832cc674ae5d290ba7ebc2533.png diff --git a/Bundle/Images/m1-6593e140dba21ccb8c8724f8fe88fdb6.png b/Dev/Bundle/Images/m1-6593e140dba21ccb8c8724f8fe88fdb6.png similarity index 100% rename from Bundle/Images/m1-6593e140dba21ccb8c8724f8fe88fdb6.png rename to Dev/Bundle/Images/m1-6593e140dba21ccb8c8724f8fe88fdb6.png diff --git a/Bundle/Images/m1-66ac49ef3f48ac9482049e1ab57a53e9.png b/Dev/Bundle/Images/m1-66ac49ef3f48ac9482049e1ab57a53e9.png similarity index 100% rename from Bundle/Images/m1-66ac49ef3f48ac9482049e1ab57a53e9.png rename to Dev/Bundle/Images/m1-66ac49ef3f48ac9482049e1ab57a53e9.png diff --git a/Bundle/Images/m1-6bfb149151f58d124d6fa76eaad75520.png b/Dev/Bundle/Images/m1-6bfb149151f58d124d6fa76eaad75520.png similarity index 100% rename from Bundle/Images/m1-6bfb149151f58d124d6fa76eaad75520.png rename to Dev/Bundle/Images/m1-6bfb149151f58d124d6fa76eaad75520.png diff --git a/Bundle/Images/m1-6e3914f26bcc8f9d004ffeac71656c01.png b/Dev/Bundle/Images/m1-6e3914f26bcc8f9d004ffeac71656c01.png similarity index 100% rename from Bundle/Images/m1-6e3914f26bcc8f9d004ffeac71656c01.png rename to Dev/Bundle/Images/m1-6e3914f26bcc8f9d004ffeac71656c01.png diff --git a/Bundle/Images/m1-71915ab0b1cc7350091ef7073a312d16.png b/Dev/Bundle/Images/m1-71915ab0b1cc7350091ef7073a312d16.png similarity index 100% rename from Bundle/Images/m1-71915ab0b1cc7350091ef7073a312d16.png rename to Dev/Bundle/Images/m1-71915ab0b1cc7350091ef7073a312d16.png diff --git a/Bundle/Images/m1-7dc9db3d3e510156c619273f8f913cbe.png b/Dev/Bundle/Images/m1-7dc9db3d3e510156c619273f8f913cbe.png similarity index 100% rename from Bundle/Images/m1-7dc9db3d3e510156c619273f8f913cbe.png rename to Dev/Bundle/Images/m1-7dc9db3d3e510156c619273f8f913cbe.png diff --git a/Bundle/Images/m1-80e163ebface8b0d2fbf9823bca02936.png b/Dev/Bundle/Images/m1-80e163ebface8b0d2fbf9823bca02936.png similarity index 100% rename from Bundle/Images/m1-80e163ebface8b0d2fbf9823bca02936.png rename to Dev/Bundle/Images/m1-80e163ebface8b0d2fbf9823bca02936.png diff --git a/Bundle/Images/m1-814fcedc62fe4e43923c042ff1d6747f.png b/Dev/Bundle/Images/m1-814fcedc62fe4e43923c042ff1d6747f.png similarity index 100% rename from Bundle/Images/m1-814fcedc62fe4e43923c042ff1d6747f.png rename to Dev/Bundle/Images/m1-814fcedc62fe4e43923c042ff1d6747f.png diff --git a/Bundle/Images/m1-8f2b481b7fd9bd745e620b7c01a18df2.png b/Dev/Bundle/Images/m1-8f2b481b7fd9bd745e620b7c01a18df2.png similarity index 100% rename from Bundle/Images/m1-8f2b481b7fd9bd745e620b7c01a18df2.png rename to Dev/Bundle/Images/m1-8f2b481b7fd9bd745e620b7c01a18df2.png diff --git a/Bundle/Images/m1-94f94e608d647b1b433f4d0ecc21e023.png b/Dev/Bundle/Images/m1-94f94e608d647b1b433f4d0ecc21e023.png similarity index 100% rename from Bundle/Images/m1-94f94e608d647b1b433f4d0ecc21e023.png rename to Dev/Bundle/Images/m1-94f94e608d647b1b433f4d0ecc21e023.png diff --git a/Bundle/Images/m1-96b70653ba3f8a83b7cfd48749bed8b1.png b/Dev/Bundle/Images/m1-96b70653ba3f8a83b7cfd48749bed8b1.png similarity index 100% rename from Bundle/Images/m1-96b70653ba3f8a83b7cfd48749bed8b1.png rename to Dev/Bundle/Images/m1-96b70653ba3f8a83b7cfd48749bed8b1.png diff --git a/Bundle/Images/m1-9a3e0c7b687b526987e2270541002d47.png b/Dev/Bundle/Images/m1-9a3e0c7b687b526987e2270541002d47.png similarity index 100% rename from Bundle/Images/m1-9a3e0c7b687b526987e2270541002d47.png rename to Dev/Bundle/Images/m1-9a3e0c7b687b526987e2270541002d47.png diff --git a/Bundle/Images/m1-9bec9d0461c0ef0f5faf16d0d4bdcc13.png b/Dev/Bundle/Images/m1-9bec9d0461c0ef0f5faf16d0d4bdcc13.png similarity index 100% rename from Bundle/Images/m1-9bec9d0461c0ef0f5faf16d0d4bdcc13.png rename to Dev/Bundle/Images/m1-9bec9d0461c0ef0f5faf16d0d4bdcc13.png diff --git a/Bundle/Images/m1-9eb5b67f01da30f0e16062004c343e4a.png b/Dev/Bundle/Images/m1-9eb5b67f01da30f0e16062004c343e4a.png similarity index 100% rename from Bundle/Images/m1-9eb5b67f01da30f0e16062004c343e4a.png rename to Dev/Bundle/Images/m1-9eb5b67f01da30f0e16062004c343e4a.png diff --git a/Bundle/Images/m1-a1f9d85a8243b884d40e74f656c55e75.png b/Dev/Bundle/Images/m1-a1f9d85a8243b884d40e74f656c55e75.png similarity index 100% rename from Bundle/Images/m1-a1f9d85a8243b884d40e74f656c55e75.png rename to Dev/Bundle/Images/m1-a1f9d85a8243b884d40e74f656c55e75.png diff --git a/Bundle/Images/m1-a46ce91d8975a017917156b8824f936e.png b/Dev/Bundle/Images/m1-a46ce91d8975a017917156b8824f936e.png similarity index 100% rename from Bundle/Images/m1-a46ce91d8975a017917156b8824f936e.png rename to Dev/Bundle/Images/m1-a46ce91d8975a017917156b8824f936e.png diff --git a/Bundle/Images/m1-a4842373fc20cc42b8e023a329761915.png b/Dev/Bundle/Images/m1-a4842373fc20cc42b8e023a329761915.png similarity index 100% rename from Bundle/Images/m1-a4842373fc20cc42b8e023a329761915.png rename to Dev/Bundle/Images/m1-a4842373fc20cc42b8e023a329761915.png diff --git a/Bundle/Images/m1-aded9dc1dc9361363ad0b426c2ff1846.png b/Dev/Bundle/Images/m1-aded9dc1dc9361363ad0b426c2ff1846.png similarity index 100% rename from Bundle/Images/m1-aded9dc1dc9361363ad0b426c2ff1846.png rename to Dev/Bundle/Images/m1-aded9dc1dc9361363ad0b426c2ff1846.png diff --git a/Bundle/Images/m1-b68163a6a8e2ccf3c8ad2c70a26c1150.png b/Dev/Bundle/Images/m1-b68163a6a8e2ccf3c8ad2c70a26c1150.png similarity index 100% rename from Bundle/Images/m1-b68163a6a8e2ccf3c8ad2c70a26c1150.png rename to Dev/Bundle/Images/m1-b68163a6a8e2ccf3c8ad2c70a26c1150.png diff --git a/Bundle/Images/m1-b977e74d9de1f6689fdd84c4e38830f5.png b/Dev/Bundle/Images/m1-b977e74d9de1f6689fdd84c4e38830f5.png similarity index 100% rename from Bundle/Images/m1-b977e74d9de1f6689fdd84c4e38830f5.png rename to Dev/Bundle/Images/m1-b977e74d9de1f6689fdd84c4e38830f5.png diff --git a/Bundle/Images/m1-bfce28c0e44bc8d1824d48fbec5075e2.png b/Dev/Bundle/Images/m1-bfce28c0e44bc8d1824d48fbec5075e2.png similarity index 100% rename from Bundle/Images/m1-bfce28c0e44bc8d1824d48fbec5075e2.png rename to Dev/Bundle/Images/m1-bfce28c0e44bc8d1824d48fbec5075e2.png diff --git a/Bundle/Images/m1-c5a372c145ce25ce712959cd3b6df35e.png b/Dev/Bundle/Images/m1-c5a372c145ce25ce712959cd3b6df35e.png similarity index 100% rename from Bundle/Images/m1-c5a372c145ce25ce712959cd3b6df35e.png rename to Dev/Bundle/Images/m1-c5a372c145ce25ce712959cd3b6df35e.png diff --git a/Bundle/Images/m1-cb265e4ae8967567fca5b0ecd58b90cb.png b/Dev/Bundle/Images/m1-cb265e4ae8967567fca5b0ecd58b90cb.png similarity index 100% rename from Bundle/Images/m1-cb265e4ae8967567fca5b0ecd58b90cb.png rename to Dev/Bundle/Images/m1-cb265e4ae8967567fca5b0ecd58b90cb.png diff --git a/Bundle/Images/m1-d4b25a2b8b5fcec0a3e284579d539f35.png b/Dev/Bundle/Images/m1-d4b25a2b8b5fcec0a3e284579d539f35.png similarity index 100% rename from Bundle/Images/m1-d4b25a2b8b5fcec0a3e284579d539f35.png rename to Dev/Bundle/Images/m1-d4b25a2b8b5fcec0a3e284579d539f35.png diff --git a/Bundle/Images/m1-d87a07bdc461bf81e43447ca0620d71d.png b/Dev/Bundle/Images/m1-d87a07bdc461bf81e43447ca0620d71d.png similarity index 100% rename from Bundle/Images/m1-d87a07bdc461bf81e43447ca0620d71d.png rename to Dev/Bundle/Images/m1-d87a07bdc461bf81e43447ca0620d71d.png diff --git a/Bundle/Images/m1-e0f25ec3373dfdca79ba7bcc3ad366f3.png b/Dev/Bundle/Images/m1-e0f25ec3373dfdca79ba7bcc3ad366f3.png similarity index 100% rename from Bundle/Images/m1-e0f25ec3373dfdca79ba7bcc3ad366f3.png rename to Dev/Bundle/Images/m1-e0f25ec3373dfdca79ba7bcc3ad366f3.png diff --git a/Bundle/Images/m1-e275d32a37943b0d5eeb86ffb04b7cf2.png b/Dev/Bundle/Images/m1-e275d32a37943b0d5eeb86ffb04b7cf2.png similarity index 100% rename from Bundle/Images/m1-e275d32a37943b0d5eeb86ffb04b7cf2.png rename to Dev/Bundle/Images/m1-e275d32a37943b0d5eeb86ffb04b7cf2.png diff --git a/Bundle/Images/m1-fcac2d6a6a739e8ceb946ac99200d9f1.png b/Dev/Bundle/Images/m1-fcac2d6a6a739e8ceb946ac99200d9f1.png similarity index 100% rename from Bundle/Images/m1-fcac2d6a6a739e8ceb946ac99200d9f1.png rename to Dev/Bundle/Images/m1-fcac2d6a6a739e8ceb946ac99200d9f1.png diff --git a/Bundle/Images/m2-0699098e769a2d80e60f33dbb3332b61.png b/Dev/Bundle/Images/m2-0699098e769a2d80e60f33dbb3332b61.png similarity index 100% rename from Bundle/Images/m2-0699098e769a2d80e60f33dbb3332b61.png rename to Dev/Bundle/Images/m2-0699098e769a2d80e60f33dbb3332b61.png diff --git a/Bundle/Images/m2-1f97f040d0b6b26faeb0a1a7f1499590.png b/Dev/Bundle/Images/m2-1f97f040d0b6b26faeb0a1a7f1499590.png similarity index 100% rename from Bundle/Images/m2-1f97f040d0b6b26faeb0a1a7f1499590.png rename to Dev/Bundle/Images/m2-1f97f040d0b6b26faeb0a1a7f1499590.png diff --git a/Bundle/Images/m2-272ae9468b7883e5cf61873a17271fb4.png b/Dev/Bundle/Images/m2-272ae9468b7883e5cf61873a17271fb4.png similarity index 100% rename from Bundle/Images/m2-272ae9468b7883e5cf61873a17271fb4.png rename to Dev/Bundle/Images/m2-272ae9468b7883e5cf61873a17271fb4.png diff --git a/Bundle/Images/m2-2dc3bdd9274b121b851fa536b0e35b6a.png b/Dev/Bundle/Images/m2-2dc3bdd9274b121b851fa536b0e35b6a.png similarity index 100% rename from Bundle/Images/m2-2dc3bdd9274b121b851fa536b0e35b6a.png rename to Dev/Bundle/Images/m2-2dc3bdd9274b121b851fa536b0e35b6a.png diff --git a/Bundle/Images/m2-3625f98e00148cdc136c53bdcd2d2e1e.png b/Dev/Bundle/Images/m2-3625f98e00148cdc136c53bdcd2d2e1e.png similarity index 100% rename from Bundle/Images/m2-3625f98e00148cdc136c53bdcd2d2e1e.png rename to Dev/Bundle/Images/m2-3625f98e00148cdc136c53bdcd2d2e1e.png diff --git a/Bundle/Images/m2-559dcf17d281e285b7f09f943b9706de.png b/Dev/Bundle/Images/m2-559dcf17d281e285b7f09f943b9706de.png similarity index 100% rename from Bundle/Images/m2-559dcf17d281e285b7f09f943b9706de.png rename to Dev/Bundle/Images/m2-559dcf17d281e285b7f09f943b9706de.png diff --git a/Bundle/Images/m2-593d4b1a0b5d13b539c6c098dc5797ca.png b/Dev/Bundle/Images/m2-593d4b1a0b5d13b539c6c098dc5797ca.png similarity index 100% rename from Bundle/Images/m2-593d4b1a0b5d13b539c6c098dc5797ca.png rename to Dev/Bundle/Images/m2-593d4b1a0b5d13b539c6c098dc5797ca.png diff --git a/Bundle/Images/m2-66ac49ef3f48ac9482049e1ab57a53e9.png b/Dev/Bundle/Images/m2-66ac49ef3f48ac9482049e1ab57a53e9.png similarity index 100% rename from Bundle/Images/m2-66ac49ef3f48ac9482049e1ab57a53e9.png rename to Dev/Bundle/Images/m2-66ac49ef3f48ac9482049e1ab57a53e9.png diff --git a/Bundle/Images/m2-6bfb149151f58d124d6fa76eaad75520.png b/Dev/Bundle/Images/m2-6bfb149151f58d124d6fa76eaad75520.png similarity index 100% rename from Bundle/Images/m2-6bfb149151f58d124d6fa76eaad75520.png rename to Dev/Bundle/Images/m2-6bfb149151f58d124d6fa76eaad75520.png diff --git a/Bundle/Images/m2-71915ab0b1cc7350091ef7073a312d16.png b/Dev/Bundle/Images/m2-71915ab0b1cc7350091ef7073a312d16.png similarity index 100% rename from Bundle/Images/m2-71915ab0b1cc7350091ef7073a312d16.png rename to Dev/Bundle/Images/m2-71915ab0b1cc7350091ef7073a312d16.png diff --git a/Bundle/Images/m2-80e163ebface8b0d2fbf9823bca02936.png b/Dev/Bundle/Images/m2-80e163ebface8b0d2fbf9823bca02936.png similarity index 100% rename from Bundle/Images/m2-80e163ebface8b0d2fbf9823bca02936.png rename to Dev/Bundle/Images/m2-80e163ebface8b0d2fbf9823bca02936.png diff --git a/Bundle/Images/m2-814fcedc62fe4e43923c042ff1d6747f.png b/Dev/Bundle/Images/m2-814fcedc62fe4e43923c042ff1d6747f.png similarity index 100% rename from Bundle/Images/m2-814fcedc62fe4e43923c042ff1d6747f.png rename to Dev/Bundle/Images/m2-814fcedc62fe4e43923c042ff1d6747f.png diff --git a/Bundle/Images/m2-8f2b481b7fd9bd745e620b7c01a18df2.png b/Dev/Bundle/Images/m2-8f2b481b7fd9bd745e620b7c01a18df2.png similarity index 100% rename from Bundle/Images/m2-8f2b481b7fd9bd745e620b7c01a18df2.png rename to Dev/Bundle/Images/m2-8f2b481b7fd9bd745e620b7c01a18df2.png diff --git a/Bundle/Images/m2-94f94e608d647b1b433f4d0ecc21e023.png b/Dev/Bundle/Images/m2-94f94e608d647b1b433f4d0ecc21e023.png similarity index 100% rename from Bundle/Images/m2-94f94e608d647b1b433f4d0ecc21e023.png rename to Dev/Bundle/Images/m2-94f94e608d647b1b433f4d0ecc21e023.png diff --git a/Bundle/Images/m2-96b70653ba3f8a83b7cfd48749bed8b1.png b/Dev/Bundle/Images/m2-96b70653ba3f8a83b7cfd48749bed8b1.png similarity index 100% rename from Bundle/Images/m2-96b70653ba3f8a83b7cfd48749bed8b1.png rename to Dev/Bundle/Images/m2-96b70653ba3f8a83b7cfd48749bed8b1.png diff --git a/Bundle/Images/m2-9a3e0c7b687b526987e2270541002d47.png b/Dev/Bundle/Images/m2-9a3e0c7b687b526987e2270541002d47.png similarity index 100% rename from Bundle/Images/m2-9a3e0c7b687b526987e2270541002d47.png rename to Dev/Bundle/Images/m2-9a3e0c7b687b526987e2270541002d47.png diff --git a/Bundle/Images/m2-a1f9d85a8243b884d40e74f656c55e75.png b/Dev/Bundle/Images/m2-a1f9d85a8243b884d40e74f656c55e75.png similarity index 100% rename from Bundle/Images/m2-a1f9d85a8243b884d40e74f656c55e75.png rename to Dev/Bundle/Images/m2-a1f9d85a8243b884d40e74f656c55e75.png diff --git a/Bundle/Images/m2-a46ce91d8975a017917156b8824f936e.png b/Dev/Bundle/Images/m2-a46ce91d8975a017917156b8824f936e.png similarity index 100% rename from Bundle/Images/m2-a46ce91d8975a017917156b8824f936e.png rename to Dev/Bundle/Images/m2-a46ce91d8975a017917156b8824f936e.png diff --git a/Bundle/Images/m2-a4842373fc20cc42b8e023a329761915.png b/Dev/Bundle/Images/m2-a4842373fc20cc42b8e023a329761915.png similarity index 100% rename from Bundle/Images/m2-a4842373fc20cc42b8e023a329761915.png rename to Dev/Bundle/Images/m2-a4842373fc20cc42b8e023a329761915.png diff --git a/Bundle/Images/m2-b68163a6a8e2ccf3c8ad2c70a26c1150.png b/Dev/Bundle/Images/m2-b68163a6a8e2ccf3c8ad2c70a26c1150.png similarity index 100% rename from Bundle/Images/m2-b68163a6a8e2ccf3c8ad2c70a26c1150.png rename to Dev/Bundle/Images/m2-b68163a6a8e2ccf3c8ad2c70a26c1150.png diff --git a/Bundle/Images/m2-b977e74d9de1f6689fdd84c4e38830f5.png b/Dev/Bundle/Images/m2-b977e74d9de1f6689fdd84c4e38830f5.png similarity index 100% rename from Bundle/Images/m2-b977e74d9de1f6689fdd84c4e38830f5.png rename to Dev/Bundle/Images/m2-b977e74d9de1f6689fdd84c4e38830f5.png diff --git a/Bundle/Images/m2-d87a07bdc461bf81e43447ca0620d71d.png b/Dev/Bundle/Images/m2-d87a07bdc461bf81e43447ca0620d71d.png similarity index 100% rename from Bundle/Images/m2-d87a07bdc461bf81e43447ca0620d71d.png rename to Dev/Bundle/Images/m2-d87a07bdc461bf81e43447ca0620d71d.png diff --git a/Bundle/Images/m2-e275d32a37943b0d5eeb86ffb04b7cf2.png b/Dev/Bundle/Images/m2-e275d32a37943b0d5eeb86ffb04b7cf2.png similarity index 100% rename from Bundle/Images/m2-e275d32a37943b0d5eeb86ffb04b7cf2.png rename to Dev/Bundle/Images/m2-e275d32a37943b0d5eeb86ffb04b7cf2.png diff --git a/Bundle/Images/m2-fcac2d6a6a739e8ceb946ac99200d9f1.png b/Dev/Bundle/Images/m2-fcac2d6a6a739e8ceb946ac99200d9f1.png similarity index 100% rename from Bundle/Images/m2-fcac2d6a6a739e8ceb946ac99200d9f1.png rename to Dev/Bundle/Images/m2-fcac2d6a6a739e8ceb946ac99200d9f1.png diff --git a/Bundle/Images/m3-1f97f040d0b6b26faeb0a1a7f1499590.png b/Dev/Bundle/Images/m3-1f97f040d0b6b26faeb0a1a7f1499590.png similarity index 100% rename from Bundle/Images/m3-1f97f040d0b6b26faeb0a1a7f1499590.png rename to Dev/Bundle/Images/m3-1f97f040d0b6b26faeb0a1a7f1499590.png diff --git a/Bundle/Images/m3-593d4b1a0b5d13b539c6c098dc5797ca.png b/Dev/Bundle/Images/m3-593d4b1a0b5d13b539c6c098dc5797ca.png similarity index 100% rename from Bundle/Images/m3-593d4b1a0b5d13b539c6c098dc5797ca.png rename to Dev/Bundle/Images/m3-593d4b1a0b5d13b539c6c098dc5797ca.png diff --git a/Bundle/Images/m3-66ac49ef3f48ac9482049e1ab57a53e9.png b/Dev/Bundle/Images/m3-66ac49ef3f48ac9482049e1ab57a53e9.png similarity index 100% rename from Bundle/Images/m3-66ac49ef3f48ac9482049e1ab57a53e9.png rename to Dev/Bundle/Images/m3-66ac49ef3f48ac9482049e1ab57a53e9.png diff --git a/Bundle/Images/m3-6bfb149151f58d124d6fa76eaad75520.png b/Dev/Bundle/Images/m3-6bfb149151f58d124d6fa76eaad75520.png similarity index 100% rename from Bundle/Images/m3-6bfb149151f58d124d6fa76eaad75520.png rename to Dev/Bundle/Images/m3-6bfb149151f58d124d6fa76eaad75520.png diff --git a/Bundle/Images/m3-71915ab0b1cc7350091ef7073a312d16.png b/Dev/Bundle/Images/m3-71915ab0b1cc7350091ef7073a312d16.png similarity index 100% rename from Bundle/Images/m3-71915ab0b1cc7350091ef7073a312d16.png rename to Dev/Bundle/Images/m3-71915ab0b1cc7350091ef7073a312d16.png diff --git a/Bundle/Images/m3-96b70653ba3f8a83b7cfd48749bed8b1.png b/Dev/Bundle/Images/m3-96b70653ba3f8a83b7cfd48749bed8b1.png similarity index 100% rename from Bundle/Images/m3-96b70653ba3f8a83b7cfd48749bed8b1.png rename to Dev/Bundle/Images/m3-96b70653ba3f8a83b7cfd48749bed8b1.png diff --git a/Bundle/Images/m3-9a3e0c7b687b526987e2270541002d47.png b/Dev/Bundle/Images/m3-9a3e0c7b687b526987e2270541002d47.png similarity index 100% rename from Bundle/Images/m3-9a3e0c7b687b526987e2270541002d47.png rename to Dev/Bundle/Images/m3-9a3e0c7b687b526987e2270541002d47.png diff --git a/Bundle/Images/m3-a46ce91d8975a017917156b8824f936e.png b/Dev/Bundle/Images/m3-a46ce91d8975a017917156b8824f936e.png similarity index 100% rename from Bundle/Images/m3-a46ce91d8975a017917156b8824f936e.png rename to Dev/Bundle/Images/m3-a46ce91d8975a017917156b8824f936e.png diff --git a/Bundle/Images/m3-b68163a6a8e2ccf3c8ad2c70a26c1150.png b/Dev/Bundle/Images/m3-b68163a6a8e2ccf3c8ad2c70a26c1150.png similarity index 100% rename from Bundle/Images/m3-b68163a6a8e2ccf3c8ad2c70a26c1150.png rename to Dev/Bundle/Images/m3-b68163a6a8e2ccf3c8ad2c70a26c1150.png diff --git a/Bundle/Images/m4-1f97f040d0b6b26faeb0a1a7f1499590.png b/Dev/Bundle/Images/m4-1f97f040d0b6b26faeb0a1a7f1499590.png similarity index 100% rename from Bundle/Images/m4-1f97f040d0b6b26faeb0a1a7f1499590.png rename to Dev/Bundle/Images/m4-1f97f040d0b6b26faeb0a1a7f1499590.png diff --git a/Bundle/Images/m4-6bfb149151f58d124d6fa76eaad75520.png b/Dev/Bundle/Images/m4-6bfb149151f58d124d6fa76eaad75520.png similarity index 100% rename from Bundle/Images/m4-6bfb149151f58d124d6fa76eaad75520.png rename to Dev/Bundle/Images/m4-6bfb149151f58d124d6fa76eaad75520.png diff --git a/Bundle/Images/m4-96b70653ba3f8a83b7cfd48749bed8b1.png b/Dev/Bundle/Images/m4-96b70653ba3f8a83b7cfd48749bed8b1.png similarity index 100% rename from Bundle/Images/m4-96b70653ba3f8a83b7cfd48749bed8b1.png rename to Dev/Bundle/Images/m4-96b70653ba3f8a83b7cfd48749bed8b1.png diff --git a/Bundle/Images/m4-a46ce91d8975a017917156b8824f936e.png b/Dev/Bundle/Images/m4-a46ce91d8975a017917156b8824f936e.png similarity index 100% rename from Bundle/Images/m4-a46ce91d8975a017917156b8824f936e.png rename to Dev/Bundle/Images/m4-a46ce91d8975a017917156b8824f936e.png diff --git a/Bundle/Images/m5-1f97f040d0b6b26faeb0a1a7f1499590.png b/Dev/Bundle/Images/m5-1f97f040d0b6b26faeb0a1a7f1499590.png similarity index 100% rename from Bundle/Images/m5-1f97f040d0b6b26faeb0a1a7f1499590.png rename to Dev/Bundle/Images/m5-1f97f040d0b6b26faeb0a1a7f1499590.png diff --git a/Bundle/Images/m5-a46ce91d8975a017917156b8824f936e.png b/Dev/Bundle/Images/m5-a46ce91d8975a017917156b8824f936e.png similarity index 100% rename from Bundle/Images/m5-a46ce91d8975a017917156b8824f936e.png rename to Dev/Bundle/Images/m5-a46ce91d8975a017917156b8824f936e.png diff --git a/Bundle/Images/m6-1f97f040d0b6b26faeb0a1a7f1499590.png b/Dev/Bundle/Images/m6-1f97f040d0b6b26faeb0a1a7f1499590.png similarity index 100% rename from Bundle/Images/m6-1f97f040d0b6b26faeb0a1a7f1499590.png rename to Dev/Bundle/Images/m6-1f97f040d0b6b26faeb0a1a7f1499590.png diff --git a/Bundle/Images/m6-a46ce91d8975a017917156b8824f936e.png b/Dev/Bundle/Images/m6-a46ce91d8975a017917156b8824f936e.png similarity index 100% rename from Bundle/Images/m6-a46ce91d8975a017917156b8824f936e.png rename to Dev/Bundle/Images/m6-a46ce91d8975a017917156b8824f936e.png diff --git a/Bundle/Images/m7-1f97f040d0b6b26faeb0a1a7f1499590.png b/Dev/Bundle/Images/m7-1f97f040d0b6b26faeb0a1a7f1499590.png similarity index 100% rename from Bundle/Images/m7-1f97f040d0b6b26faeb0a1a7f1499590.png rename to Dev/Bundle/Images/m7-1f97f040d0b6b26faeb0a1a7f1499590.png diff --git a/Bundle/Images/m7-a46ce91d8975a017917156b8824f936e.png b/Dev/Bundle/Images/m7-a46ce91d8975a017917156b8824f936e.png similarity index 100% rename from Bundle/Images/m7-a46ce91d8975a017917156b8824f936e.png rename to Dev/Bundle/Images/m7-a46ce91d8975a017917156b8824f936e.png diff --git a/Bundle/Images/m8-1f97f040d0b6b26faeb0a1a7f1499590.png b/Dev/Bundle/Images/m8-1f97f040d0b6b26faeb0a1a7f1499590.png similarity index 100% rename from Bundle/Images/m8-1f97f040d0b6b26faeb0a1a7f1499590.png rename to Dev/Bundle/Images/m8-1f97f040d0b6b26faeb0a1a7f1499590.png diff --git a/Bundle/Images/m8-a46ce91d8975a017917156b8824f936e.png b/Dev/Bundle/Images/m8-a46ce91d8975a017917156b8824f936e.png similarity index 100% rename from Bundle/Images/m8-a46ce91d8975a017917156b8824f936e.png rename to Dev/Bundle/Images/m8-a46ce91d8975a017917156b8824f936e.png diff --git a/Bundle/Images/nasa.jpg b/Dev/Bundle/Images/nasa.jpg similarity index 100% rename from Bundle/Images/nasa.jpg rename to Dev/Bundle/Images/nasa.jpg diff --git a/Bundle/Images/orientation_down.HEIC b/Dev/Bundle/Images/orientation_down.HEIC similarity index 100% rename from Bundle/Images/orientation_down.HEIC rename to Dev/Bundle/Images/orientation_down.HEIC diff --git a/Bundle/Images/orientation_down_mirrored.HEIC b/Dev/Bundle/Images/orientation_down_mirrored.HEIC similarity index 100% rename from Bundle/Images/orientation_down_mirrored.HEIC rename to Dev/Bundle/Images/orientation_down_mirrored.HEIC diff --git a/Bundle/Images/orientation_left.HEIC b/Dev/Bundle/Images/orientation_left.HEIC similarity index 100% rename from Bundle/Images/orientation_left.HEIC rename to Dev/Bundle/Images/orientation_left.HEIC diff --git a/Bundle/Images/orientation_left_mirrored.HEIC b/Dev/Bundle/Images/orientation_left_mirrored.HEIC similarity index 100% rename from Bundle/Images/orientation_left_mirrored.HEIC rename to Dev/Bundle/Images/orientation_left_mirrored.HEIC diff --git a/Bundle/Images/orientation_right.HEIC b/Dev/Bundle/Images/orientation_right.HEIC similarity index 100% rename from Bundle/Images/orientation_right.HEIC rename to Dev/Bundle/Images/orientation_right.HEIC diff --git a/Bundle/Images/orientation_right_mirrored.HEIC b/Dev/Bundle/Images/orientation_right_mirrored.HEIC similarity index 100% rename from Bundle/Images/orientation_right_mirrored.HEIC rename to Dev/Bundle/Images/orientation_right_mirrored.HEIC diff --git a/Bundle/Images/orientation_up.HEIC b/Dev/Bundle/Images/orientation_up.HEIC similarity index 100% rename from Bundle/Images/orientation_up.HEIC rename to Dev/Bundle/Images/orientation_up.HEIC diff --git a/Bundle/Images/orientation_up_mirrored.HEIC b/Dev/Bundle/Images/orientation_up_mirrored.HEIC similarity index 100% rename from Bundle/Images/orientation_up_mirrored.HEIC rename to Dev/Bundle/Images/orientation_up_mirrored.HEIC diff --git a/Bundle/Images/screenshot-16bit-p3-alpha.png b/Dev/Bundle/Images/screenshot-16bit-p3-alpha.png similarity index 100% rename from Bundle/Images/screenshot-16bit-p3-alpha.png rename to Dev/Bundle/Images/screenshot-16bit-p3-alpha.png diff --git a/Bundle/Images/screenshot-8bit-lcd.png b/Dev/Bundle/Images/screenshot-8bit-lcd.png similarity index 100% rename from Bundle/Images/screenshot-8bit-lcd.png rename to Dev/Bundle/Images/screenshot-8bit-lcd.png diff --git a/Bundle/Images/screenshot-8bit-p3-alpha.png b/Dev/Bundle/Images/screenshot-8bit-p3-alpha.png similarity index 100% rename from Bundle/Images/screenshot-8bit-p3-alpha.png rename to Dev/Bundle/Images/screenshot-8bit-p3-alpha.png diff --git a/Bundle/LUTs/Import LUT/Action.js b/Dev/Bundle/LUTs/Import LUT/Action.js similarity index 100% rename from Bundle/LUTs/Import LUT/Action.js rename to Dev/Bundle/LUTs/Import LUT/Action.js diff --git a/Bundle/LUTs/Import LUT/ActionRequestHandler.swift b/Dev/Bundle/LUTs/Import LUT/ActionRequestHandler.swift similarity index 100% rename from Bundle/LUTs/Import LUT/ActionRequestHandler.swift rename to Dev/Bundle/LUTs/Import LUT/ActionRequestHandler.swift diff --git a/Bundle/LUTs/Import LUT/Info.plist b/Dev/Bundle/LUTs/Import LUT/Info.plist similarity index 100% rename from Bundle/LUTs/Import LUT/Info.plist rename to Dev/Bundle/LUTs/Import LUT/Info.plist diff --git a/Bundle/LUTs/Import LUT/Media.xcassets/Contents.json b/Dev/Bundle/LUTs/Import LUT/Media.xcassets/Contents.json similarity index 100% rename from Bundle/LUTs/Import LUT/Media.xcassets/Contents.json rename to Dev/Bundle/LUTs/Import LUT/Media.xcassets/Contents.json diff --git a/Bundle/LUTs/Import LUT/Media.xcassets/TouchBarBezel.colorset/Contents.json b/Dev/Bundle/LUTs/Import LUT/Media.xcassets/TouchBarBezel.colorset/Contents.json similarity index 100% rename from Bundle/LUTs/Import LUT/Media.xcassets/TouchBarBezel.colorset/Contents.json rename to Dev/Bundle/LUTs/Import LUT/Media.xcassets/TouchBarBezel.colorset/Contents.json diff --git a/Bundle/LUTs/LUT_64_1.jpg b/Dev/Bundle/LUTs/LUT_64_1.jpg similarity index 100% rename from Bundle/LUTs/LUT_64_1.jpg rename to Dev/Bundle/LUTs/LUT_64_1.jpg diff --git a/Bundle/LUTs/LUT_64_2.jpg b/Dev/Bundle/LUTs/LUT_64_2.jpg similarity index 100% rename from Bundle/LUTs/LUT_64_2.jpg rename to Dev/Bundle/LUTs/LUT_64_2.jpg diff --git a/Bundle/LUTs/LUT_64_3.jpg b/Dev/Bundle/LUTs/LUT_64_3.jpg similarity index 100% rename from Bundle/LUTs/LUT_64_3.jpg rename to Dev/Bundle/LUTs/LUT_64_3.jpg diff --git a/Bundle/LUTs/LUT_64_Dark_HighContrast_v3.png b/Dev/Bundle/LUTs/LUT_64_Dark_HighContrast_v3.png similarity index 100% rename from Bundle/LUTs/LUT_64_Dark_HighContrast_v3.png rename to Dev/Bundle/LUTs/LUT_64_Dark_HighContrast_v3.png diff --git a/Bundle/LUTs/LUT_64_Gloss.png b/Dev/Bundle/LUTs/LUT_64_Gloss.png similarity index 100% rename from Bundle/LUTs/LUT_64_Gloss.png rename to Dev/Bundle/LUTs/LUT_64_Gloss.png diff --git a/Bundle/LUTs/ShareLUT/Base.lproj/MainInterface.storyboard b/Dev/Bundle/LUTs/ShareLUT/Base.lproj/MainInterface.storyboard similarity index 100% rename from Bundle/LUTs/ShareLUT/Base.lproj/MainInterface.storyboard rename to Dev/Bundle/LUTs/ShareLUT/Base.lproj/MainInterface.storyboard diff --git a/Bundle/LUTs/ShareLUT/Info.plist b/Dev/Bundle/LUTs/ShareLUT/Info.plist similarity index 100% rename from Bundle/LUTs/ShareLUT/Info.plist rename to Dev/Bundle/LUTs/ShareLUT/Info.plist diff --git a/Bundle/LUTs/ShareLUT/ShareViewController.swift b/Dev/Bundle/LUTs/ShareLUT/ShareViewController.swift similarity index 100% rename from Bundle/LUTs/ShareLUT/ShareViewController.swift rename to Dev/Bundle/LUTs/ShareLUT/ShareViewController.swift diff --git a/Bundle/image-test-suite/test-image-1 b/Dev/Bundle/image-test-suite/test-image-1 similarity index 100% rename from Bundle/image-test-suite/test-image-1 rename to Dev/Bundle/image-test-suite/test-image-1 diff --git a/Bundle/image-test-suite/test-image-10 b/Dev/Bundle/image-test-suite/test-image-10 similarity index 100% rename from Bundle/image-test-suite/test-image-10 rename to Dev/Bundle/image-test-suite/test-image-10 diff --git a/Bundle/image-test-suite/test-image-11 b/Dev/Bundle/image-test-suite/test-image-11 similarity index 100% rename from Bundle/image-test-suite/test-image-11 rename to Dev/Bundle/image-test-suite/test-image-11 diff --git a/Bundle/image-test-suite/test-image-12 b/Dev/Bundle/image-test-suite/test-image-12 similarity index 100% rename from Bundle/image-test-suite/test-image-12 rename to Dev/Bundle/image-test-suite/test-image-12 diff --git a/Bundle/image-test-suite/test-image-13 b/Dev/Bundle/image-test-suite/test-image-13 similarity index 100% rename from Bundle/image-test-suite/test-image-13 rename to Dev/Bundle/image-test-suite/test-image-13 diff --git a/Bundle/image-test-suite/test-image-2 b/Dev/Bundle/image-test-suite/test-image-2 similarity index 100% rename from Bundle/image-test-suite/test-image-2 rename to Dev/Bundle/image-test-suite/test-image-2 diff --git a/Bundle/image-test-suite/test-image-3 b/Dev/Bundle/image-test-suite/test-image-3 similarity index 100% rename from Bundle/image-test-suite/test-image-3 rename to Dev/Bundle/image-test-suite/test-image-3 diff --git a/Bundle/image-test-suite/test-image-4 b/Dev/Bundle/image-test-suite/test-image-4 similarity index 100% rename from Bundle/image-test-suite/test-image-4 rename to Dev/Bundle/image-test-suite/test-image-4 diff --git a/Bundle/image-test-suite/test-image-5 b/Dev/Bundle/image-test-suite/test-image-5 similarity index 100% rename from Bundle/image-test-suite/test-image-5 rename to Dev/Bundle/image-test-suite/test-image-5 diff --git a/Bundle/image-test-suite/test-image-6 b/Dev/Bundle/image-test-suite/test-image-6 similarity index 100% rename from Bundle/image-test-suite/test-image-6 rename to Dev/Bundle/image-test-suite/test-image-6 diff --git a/Bundle/image-test-suite/test-image-7 b/Dev/Bundle/image-test-suite/test-image-7 similarity index 100% rename from Bundle/image-test-suite/test-image-7 rename to Dev/Bundle/image-test-suite/test-image-7 diff --git a/Bundle/image-test-suite/test-image-8 b/Dev/Bundle/image-test-suite/test-image-8 similarity index 100% rename from Bundle/image-test-suite/test-image-8 rename to Dev/Bundle/image-test-suite/test-image-8 diff --git a/Bundle/image-test-suite/test-image-9 b/Dev/Bundle/image-test-suite/test-image-9 similarity index 100% rename from Bundle/image-test-suite/test-image-9 rename to Dev/Bundle/image-test-suite/test-image-9 diff --git a/Dev/Resources/HALD_256.png b/Dev/Resources/HALD_256.png new file mode 100644 index 00000000..a89c9c3d Binary files /dev/null and b/Dev/Resources/HALD_256.png differ diff --git a/Dev/Resources/LUT_64_Neutral.png b/Dev/Resources/LUT_64_Neutral.png new file mode 100644 index 00000000..cbea6c0f Binary files /dev/null and b/Dev/Resources/LUT_64_Neutral.png differ diff --git a/Sources/Demo/AppDelegate.swift b/Dev/Sources/Demo/AppDelegate.swift similarity index 100% rename from Sources/Demo/AppDelegate.swift rename to Dev/Sources/Demo/AppDelegate.swift diff --git a/Sources/Demo/Base.lproj/LaunchScreen.storyboard b/Dev/Sources/Demo/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from Sources/Demo/Base.lproj/LaunchScreen.storyboard rename to Dev/Sources/Demo/Base.lproj/LaunchScreen.storyboard diff --git a/Sources/Demo/Contents/Components.swift b/Dev/Sources/Demo/Contents/Components.swift similarity index 100% rename from Sources/Demo/Contents/Components.swift rename to Dev/Sources/Demo/Contents/Components.swift diff --git a/Sources/Demo/Contents/DemoBuiltInEditorViewController.swift b/Dev/Sources/Demo/Contents/DemoBuiltInEditorViewController.swift similarity index 100% rename from Sources/Demo/Contents/DemoBuiltInEditorViewController.swift rename to Dev/Sources/Demo/Contents/DemoBuiltInEditorViewController.swift diff --git a/Sources/Demo/Contents/DemoCIKernelViewController.swift b/Dev/Sources/Demo/Contents/DemoCIKernelViewController.swift similarity index 100% rename from Sources/Demo/Contents/DemoCIKernelViewController.swift rename to Dev/Sources/Demo/Contents/DemoCIKernelViewController.swift diff --git a/Sources/Demo/Contents/DemoCropMenuViewController.swift b/Dev/Sources/Demo/Contents/DemoCropMenuViewController.swift similarity index 100% rename from Sources/Demo/Contents/DemoCropMenuViewController.swift rename to Dev/Sources/Demo/Contents/DemoCropMenuViewController.swift diff --git a/Sources/Demo/Contents/DemoImitationsViewController.swift b/Dev/Sources/Demo/Contents/DemoImitationsViewController.swift similarity index 100% rename from Sources/Demo/Contents/DemoImitationsViewController.swift rename to Dev/Sources/Demo/Contents/DemoImitationsViewController.swift diff --git a/Sources/Demo/Contents/DemoLUTViewController.swift b/Dev/Sources/Demo/Contents/DemoLUTViewController.swift similarity index 100% rename from Sources/Demo/Contents/DemoLUTViewController.swift rename to Dev/Sources/Demo/Contents/DemoLUTViewController.swift diff --git a/Sources/Demo/Contents/DemoMTLTextureViewController.swift b/Dev/Sources/Demo/Contents/DemoMTLTextureViewController.swift similarity index 100% rename from Sources/Demo/Contents/DemoMTLTextureViewController.swift rename to Dev/Sources/Demo/Contents/DemoMTLTextureViewController.swift diff --git a/Sources/Demo/Contents/DemoMaskingViewController.swift b/Dev/Sources/Demo/Contents/DemoMaskingViewController.swift similarity index 100% rename from Sources/Demo/Contents/DemoMaskingViewController.swift rename to Dev/Sources/Demo/Contents/DemoMaskingViewController.swift diff --git a/Sources/Demo/Contents/DemoPreviewViewController.swift b/Dev/Sources/Demo/Contents/DemoPreviewViewController.swift similarity index 100% rename from Sources/Demo/Contents/DemoPreviewViewController.swift rename to Dev/Sources/Demo/Contents/DemoPreviewViewController.swift diff --git a/Sources/Demo/Contents/DemoRAWProcessingViewController.swift b/Dev/Sources/Demo/Contents/DemoRAWProcessingViewController.swift similarity index 100% rename from Sources/Demo/Contents/DemoRAWProcessingViewController.swift rename to Dev/Sources/Demo/Contents/DemoRAWProcessingViewController.swift diff --git a/Sources/Demo/Contents/Imitations/ImitationTinderViewController.swift b/Dev/Sources/Demo/Contents/Imitations/ImitationTinderViewController.swift similarity index 100% rename from Sources/Demo/Contents/Imitations/ImitationTinderViewController.swift rename to Dev/Sources/Demo/Contents/Imitations/ImitationTinderViewController.swift diff --git a/Sources/Demo/Contents/StackScrollNodeViewController.swift b/Dev/Sources/Demo/Contents/StackScrollNodeViewController.swift similarity index 100% rename from Sources/Demo/Contents/StackScrollNodeViewController.swift rename to Dev/Sources/Demo/Contents/StackScrollNodeViewController.swift diff --git a/Sources/Demo/Contents/TopMenuViewController.swift b/Dev/Sources/Demo/Contents/TopMenuViewController.swift similarity index 100% rename from Sources/Demo/Contents/TopMenuViewController.swift rename to Dev/Sources/Demo/Contents/TopMenuViewController.swift diff --git a/Sources/Demo/Info.plist b/Dev/Sources/Demo/Info.plist similarity index 100% rename from Sources/Demo/Info.plist rename to Dev/Sources/Demo/Info.plist diff --git a/Sources/Demo/Library/StackScrollNode.swift b/Dev/Sources/Demo/Library/StackScrollNode.swift similarity index 100% rename from Sources/Demo/Library/StackScrollNode.swift rename to Dev/Sources/Demo/Library/StackScrollNode.swift diff --git a/Sources/Demo/Library/UIViewController+Picker.swift b/Dev/Sources/Demo/Library/UIViewController+Picker.swift similarity index 100% rename from Sources/Demo/Library/UIViewController+Picker.swift rename to Dev/Sources/Demo/Library/UIViewController+Picker.swift diff --git a/Sources/Demo/Library/Utilities.swift b/Dev/Sources/Demo/Library/Utilities.swift similarity index 100% rename from Sources/Demo/Library/Utilities.swift rename to Dev/Sources/Demo/Library/Utilities.swift diff --git a/Sources/Demo/nasa.jpg b/Dev/Sources/Demo/nasa.jpg similarity index 100% rename from Sources/Demo/nasa.jpg rename to Dev/Sources/Demo/nasa.jpg diff --git a/Sources/SharedForDemo/Assets.xcassets/AppIcon.appiconset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/AppIcon.appiconset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/Package/Sources/BrightroomUI/Media.xcassets/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/Contents.json similarity index 100% rename from Package/Sources/BrightroomUI/Media.xcassets/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/L1000069.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/L1000069.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/L1000069.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/L1000069.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/L1000069.imageset/L1000069.jpeg b/Dev/Sources/SharedForDemo/Assets.xcassets/L1000069.imageset/L1000069.jpeg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/L1000069.imageset/L1000069.jpeg rename to Dev/Sources/SharedForDemo/Assets.xcassets/L1000069.imageset/L1000069.jpeg diff --git a/Sources/SharedForDemo/Assets.xcassets/L1000316.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/L1000316.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/L1000316.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/L1000316.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/L1000316.imageset/L1000316.jpeg b/Dev/Sources/SharedForDemo/Assets.xcassets/L1000316.imageset/L1000316.jpeg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/L1000316.imageset/L1000316.jpeg rename to Dev/Sources/SharedForDemo/Assets.xcassets/L1000316.imageset/L1000316.jpeg diff --git a/Sources/SharedForDemo/Assets.xcassets/L1002725.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/L1002725.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/L1002725.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/L1002725.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/L1002725.imageset/L1002725.jpeg b/Dev/Sources/SharedForDemo/Assets.xcassets/L1002725.imageset/L1002725.jpeg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/L1002725.imageset/L1002725.jpeg rename to Dev/Sources/SharedForDemo/Assets.xcassets/L1002725.imageset/L1002725.jpeg diff --git a/Sources/SharedForDemo/Assets.xcassets/horizontal-rect.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/horizontal-rect.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/horizontal-rect.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/horizontal-rect.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/horizontal-rect.imageset/L1009650.jpeg b/Dev/Sources/SharedForDemo/Assets.xcassets/horizontal-rect.imageset/L1009650.jpeg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/horizontal-rect.imageset/L1009650.jpeg rename to Dev/Sources/SharedForDemo/Assets.xcassets/horizontal-rect.imageset/L1009650.jpeg diff --git a/Sources/SharedForDemo/Assets.xcassets/insta-logo.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/insta-logo.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/insta-logo.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/insta-logo.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/insta-logo.imageset/insta-logo.jpg b/Dev/Sources/SharedForDemo/Assets.xcassets/insta-logo.imageset/insta-logo.jpg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/insta-logo.imageset/insta-logo.jpg rename to Dev/Sources/SharedForDemo/Assets.xcassets/insta-logo.imageset/insta-logo.jpg diff --git a/Sources/SharedForDemo/Assets.xcassets/large.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/large.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/large.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/large.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/large.imageset/photo-1604456930969-37f67bcd6e1e.jpeg b/Dev/Sources/SharedForDemo/Assets.xcassets/large.imageset/photo-1604456930969-37f67bcd6e1e.jpeg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/large.imageset/photo-1604456930969-37f67bcd6e1e.jpeg rename to Dev/Sources/SharedForDemo/Assets.xcassets/large.imageset/photo-1604456930969-37f67bcd6e1e.jpeg diff --git a/Sources/SharedForDemo/Assets.xcassets/leica.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/leica.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/leica.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/leica.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/leica.imageset/leica.jpg b/Dev/Sources/SharedForDemo/Assets.xcassets/leica.imageset/leica.jpg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/leica.imageset/leica.jpg rename to Dev/Sources/SharedForDemo/Assets.xcassets/leica.imageset/leica.jpg diff --git a/Sources/SharedForDemo/Assets.xcassets/profile.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/profile.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/profile.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/profile.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/profile.imageset/toa-heftiba-O3ymvT7Wf9U-unsplash.jpg b/Dev/Sources/SharedForDemo/Assets.xcassets/profile.imageset/toa-heftiba-O3ymvT7Wf9U-unsplash.jpg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/profile.imageset/toa-heftiba-O3ymvT7Wf9U-unsplash.jpg rename to Dev/Sources/SharedForDemo/Assets.xcassets/profile.imageset/toa-heftiba-O3ymvT7Wf9U-unsplash.jpg diff --git a/Sources/SharedForDemo/Assets.xcassets/small.imageset/36912154_677107939288433_2252120879670493184_n.jpg b/Dev/Sources/SharedForDemo/Assets.xcassets/small.imageset/36912154_677107939288433_2252120879670493184_n.jpg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/small.imageset/36912154_677107939288433_2252120879670493184_n.jpg rename to Dev/Sources/SharedForDemo/Assets.xcassets/small.imageset/36912154_677107939288433_2252120879670493184_n.jpg diff --git a/Sources/SharedForDemo/Assets.xcassets/small.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/small.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/small.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/small.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/square-rect.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/square-rect.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/square-rect.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/square-rect.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/square-rect.imageset/L1003275.jpeg b/Dev/Sources/SharedForDemo/Assets.xcassets/square-rect.imageset/L1003275.jpeg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/square-rect.imageset/L1003275.jpeg rename to Dev/Sources/SharedForDemo/Assets.xcassets/square-rect.imageset/L1003275.jpeg diff --git a/Sources/SharedForDemo/Assets.xcassets/super-small.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/super-small.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/super-small.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/super-small.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/super-small.imageset/super-small.jpg b/Dev/Sources/SharedForDemo/Assets.xcassets/super-small.imageset/super-small.jpg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/super-small.imageset/super-small.jpg rename to Dev/Sources/SharedForDemo/Assets.xcassets/super-small.imageset/super-small.jpg diff --git a/Sources/SharedForDemo/Assets.xcassets/unsplash1.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/unsplash1.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/unsplash1.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/unsplash1.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/unsplash1.imageset/jakob-owens-422375-unsplash.jpg b/Dev/Sources/SharedForDemo/Assets.xcassets/unsplash1.imageset/jakob-owens-422375-unsplash.jpg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/unsplash1.imageset/jakob-owens-422375-unsplash.jpg rename to Dev/Sources/SharedForDemo/Assets.xcassets/unsplash1.imageset/jakob-owens-422375-unsplash.jpg diff --git a/Sources/SharedForDemo/Assets.xcassets/unsplash2.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/unsplash2.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/unsplash2.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/unsplash2.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/unsplash2.imageset/erol-ahmed-aIYFR0vbADk-unsplash.jpg b/Dev/Sources/SharedForDemo/Assets.xcassets/unsplash2.imageset/erol-ahmed-aIYFR0vbADk-unsplash.jpg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/unsplash2.imageset/erol-ahmed-aIYFR0vbADk-unsplash.jpg rename to Dev/Sources/SharedForDemo/Assets.xcassets/unsplash2.imageset/erol-ahmed-aIYFR0vbADk-unsplash.jpg diff --git a/Sources/SharedForDemo/Assets.xcassets/unsplash3.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/unsplash3.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/unsplash3.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/unsplash3.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/unsplash3.imageset/amy-shamblen-pJ_DCj9KswI-unsplash.jpg b/Dev/Sources/SharedForDemo/Assets.xcassets/unsplash3.imageset/amy-shamblen-pJ_DCj9KswI-unsplash.jpg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/unsplash3.imageset/amy-shamblen-pJ_DCj9KswI-unsplash.jpg rename to Dev/Sources/SharedForDemo/Assets.xcassets/unsplash3.imageset/amy-shamblen-pJ_DCj9KswI-unsplash.jpg diff --git a/Sources/SharedForDemo/Assets.xcassets/vertical-rect.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/vertical-rect.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/vertical-rect.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/vertical-rect.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/vertical-rect.imageset/L1000343.jpeg b/Dev/Sources/SharedForDemo/Assets.xcassets/vertical-rect.imageset/L1000343.jpeg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/vertical-rect.imageset/L1000343.jpeg rename to Dev/Sources/SharedForDemo/Assets.xcassets/vertical-rect.imageset/L1000343.jpeg diff --git a/Sources/SharedForDemo/Assets.xcassets/vertical.imageset/Contents.json b/Dev/Sources/SharedForDemo/Assets.xcassets/vertical.imageset/Contents.json similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/vertical.imageset/Contents.json rename to Dev/Sources/SharedForDemo/Assets.xcassets/vertical.imageset/Contents.json diff --git a/Sources/SharedForDemo/Assets.xcassets/vertical.imageset/nathan-dumlao-1105821-unsplash.jpg b/Dev/Sources/SharedForDemo/Assets.xcassets/vertical.imageset/nathan-dumlao-1105821-unsplash.jpg similarity index 100% rename from Sources/SharedForDemo/Assets.xcassets/vertical.imageset/nathan-dumlao-1105821-unsplash.jpg rename to Dev/Sources/SharedForDemo/Assets.xcassets/vertical.imageset/nathan-dumlao-1105821-unsplash.jpg diff --git a/Sources/SharedForDemo/Mocks.swift b/Dev/Sources/SharedForDemo/Mocks.swift similarity index 100% rename from Sources/SharedForDemo/Mocks.swift rename to Dev/Sources/SharedForDemo/Mocks.swift diff --git a/Sources/SharedForDemo/XCAssets+Generated.swift b/Dev/Sources/SharedForDemo/XCAssets+Generated.swift similarity index 100% rename from Sources/SharedForDemo/XCAssets+Generated.swift rename to Dev/Sources/SharedForDemo/XCAssets+Generated.swift diff --git a/Sources/SwiftUIDemo/BlurryMaskingViewWrapper.swift b/Dev/Sources/SwiftUIDemo/BlurryMaskingViewWrapper.swift similarity index 100% rename from Sources/SwiftUIDemo/BlurryMaskingViewWrapper.swift rename to Dev/Sources/SwiftUIDemo/BlurryMaskingViewWrapper.swift diff --git a/Sources/SwiftUIDemo/ContentView.swift b/Dev/Sources/SwiftUIDemo/ContentView.swift similarity index 100% rename from Sources/SwiftUIDemo/ContentView.swift rename to Dev/Sources/SwiftUIDemo/ContentView.swift diff --git a/Sources/SwiftUIDemo/DemoCropView.swift b/Dev/Sources/SwiftUIDemo/DemoCropView.swift similarity index 100% rename from Sources/SwiftUIDemo/DemoCropView.swift rename to Dev/Sources/SwiftUIDemo/DemoCropView.swift diff --git a/Sources/SwiftUIDemo/FullscreenIdentifiableView.swift b/Dev/Sources/SwiftUIDemo/FullscreenIdentifiableView.swift similarity index 100% rename from Sources/SwiftUIDemo/FullscreenIdentifiableView.swift rename to Dev/Sources/SwiftUIDemo/FullscreenIdentifiableView.swift diff --git a/Sources/SwiftUIDemo/Info.plist b/Dev/Sources/SwiftUIDemo/Info.plist similarity index 100% rename from Sources/SwiftUIDemo/Info.plist rename to Dev/Sources/SwiftUIDemo/Info.plist diff --git a/Sources/SwiftUIDemo/IsolatedEditingView.swift b/Dev/Sources/SwiftUIDemo/IsolatedEditingView.swift similarity index 100% rename from Sources/SwiftUIDemo/IsolatedEditingView.swift rename to Dev/Sources/SwiftUIDemo/IsolatedEditingView.swift diff --git a/Sources/SwiftUIDemo/Launch Screen.storyboard b/Dev/Sources/SwiftUIDemo/Launch Screen.storyboard similarity index 100% rename from Sources/SwiftUIDemo/Launch Screen.storyboard rename to Dev/Sources/SwiftUIDemo/Launch Screen.storyboard diff --git a/Package/Sources/BrightroomUI/Media.xcassets/crop/Contents.json b/Dev/Sources/SwiftUIDemo/Preview Content/Preview Assets.xcassets/Contents.json similarity index 100% rename from Package/Sources/BrightroomUI/Media.xcassets/crop/Contents.json rename to Dev/Sources/SwiftUIDemo/Preview Content/Preview Assets.xcassets/Contents.json diff --git a/Sources/SwiftUIDemo/SwiftUIDemoApp.swift b/Dev/Sources/SwiftUIDemo/SwiftUIDemoApp.swift similarity index 100% rename from Sources/SwiftUIDemo/SwiftUIDemoApp.swift rename to Dev/Sources/SwiftUIDemo/SwiftUIDemoApp.swift diff --git a/Tests/BrightroomEngineTests/Bundle.swift b/Dev/Tests/BrightroomEngineTests/Bundle.swift similarity index 100% rename from Tests/BrightroomEngineTests/Bundle.swift rename to Dev/Tests/BrightroomEngineTests/Bundle.swift diff --git a/Tests/BrightroomEngineTests/CGContextCreationTests.swift b/Dev/Tests/BrightroomEngineTests/CGContextCreationTests.swift similarity index 100% rename from Tests/BrightroomEngineTests/CGContextCreationTests.swift rename to Dev/Tests/BrightroomEngineTests/CGContextCreationTests.swift diff --git a/Tests/BrightroomEngineTests/CGContextTests.swift b/Dev/Tests/BrightroomEngineTests/CGContextTests.swift similarity index 100% rename from Tests/BrightroomEngineTests/CGContextTests.swift rename to Dev/Tests/BrightroomEngineTests/CGContextTests.swift diff --git a/Tests/BrightroomEngineTests/ImageSourceInitTest.swift b/Dev/Tests/BrightroomEngineTests/ImageSourceInitTest.swift similarity index 100% rename from Tests/BrightroomEngineTests/ImageSourceInitTest.swift rename to Dev/Tests/BrightroomEngineTests/ImageSourceInitTest.swift diff --git a/Tests/BrightroomEngineTests/Info.plist b/Dev/Tests/BrightroomEngineTests/Info.plist similarity index 100% rename from Tests/BrightroomEngineTests/Info.plist rename to Dev/Tests/BrightroomEngineTests/Info.plist diff --git a/Tests/BrightroomEngineTests/LoadingTests.swift b/Dev/Tests/BrightroomEngineTests/LoadingTests.swift similarity index 100% rename from Tests/BrightroomEngineTests/LoadingTests.swift rename to Dev/Tests/BrightroomEngineTests/LoadingTests.swift diff --git a/Tests/BrightroomEngineTests/PixelEngineTests.swift b/Dev/Tests/BrightroomEngineTests/PixelEngineTests.swift similarity index 100% rename from Tests/BrightroomEngineTests/PixelEngineTests.swift rename to Dev/Tests/BrightroomEngineTests/PixelEngineTests.swift diff --git a/Tests/BrightroomEngineTests/RAWImportTests.swift b/Dev/Tests/BrightroomEngineTests/RAWImportTests.swift similarity index 100% rename from Tests/BrightroomEngineTests/RAWImportTests.swift rename to Dev/Tests/BrightroomEngineTests/RAWImportTests.swift diff --git a/Tests/BrightroomEngineTests/RendererOrientationTests.swift b/Dev/Tests/BrightroomEngineTests/RendererOrientationTests.swift similarity index 100% rename from Tests/BrightroomEngineTests/RendererOrientationTests.swift rename to Dev/Tests/BrightroomEngineTests/RendererOrientationTests.swift diff --git a/Tests/BrightroomEngineTests/RendererTests.swift b/Dev/Tests/BrightroomEngineTests/RendererTests.swift similarity index 100% rename from Tests/BrightroomEngineTests/RendererTests.swift rename to Dev/Tests/BrightroomEngineTests/RendererTests.swift diff --git a/Tests/BrightroomEngineTests/path-data b/Dev/Tests/BrightroomEngineTests/path-data similarity index 100% rename from Tests/BrightroomEngineTests/path-data rename to Dev/Tests/BrightroomEngineTests/path-data diff --git a/Package/Package.resolved b/Package.resolved similarity index 67% rename from Package/Package.resolved rename to Package.resolved index 7f3ab1df..9e70ce4a 100644 --- a/Package/Package.resolved +++ b/Package.resolved @@ -10,6 +10,15 @@ "version": "6.1.0" } }, + { + "package": "swift-atomics", + "repositoryURL": "https://github.com/apple/swift-atomics.git", + "state": { + "branch": null, + "revision": "ff3d2212b6b093db7f177d0855adbc4ef9c5f036", + "version": "1.0.3" + } + }, { "package": "TransitionPatch", "repositoryURL": "https://github.com/FluidGroup/TransitionPatch.git", @@ -24,8 +33,8 @@ "repositoryURL": "https://github.com/VergeGroup/Verge.git", "state": { "branch": null, - "revision": "e066eb7dbaa9b55d2285af27dad2000cd905938d", - "version": "8.19.0" + "revision": "4e5938313601f8522c1146b5fbaaa0aada6820b6", + "version": "10.1.1" } } ] diff --git a/Package/Package.swift b/Package.swift similarity index 100% rename from Package/Package.swift rename to Package.swift diff --git a/Package/Sources/BrightroomEngine/BrightroomEngine.h b/Package/Sources/BrightroomEngine/BrightroomEngine.h deleted file mode 100644 index 95a99f9c..00000000 --- a/Package/Sources/BrightroomEngine/BrightroomEngine.h +++ /dev/null @@ -1,9 +0,0 @@ - -#import - -//! Project version number for PixelEngine. -FOUNDATION_EXPORT double PixelEngineVersionNumber; - -//! Project version string for PixelEngine. -FOUNDATION_EXPORT const unsigned char PixelEngineVersionString[]; - diff --git a/Package/Sources/BrightroomEngine/BrightroomEngine.swift b/Package/Sources/BrightroomEngine/BrightroomEngine.swift deleted file mode 100644 index dc1fce8f..00000000 --- a/Package/Sources/BrightroomEngine/BrightroomEngine.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -import UIKit - -@inline(__always) -func _pixelengine_ensureMainThread() { - assert(Thread.isMainThread) -} - - -#if os(macOS) -import AppKit -public typealias PlatformImage = NSImage -#elseif os(iOS) -import UIKit -public typealias PlatformImage = UIImage -#endif diff --git a/Package/Sources/BrightroomEngine/ColorCube/ColorCubeHelper.swift b/Package/Sources/BrightroomEngine/ColorCube/ColorCubeHelper.swift deleted file mode 100644 index 5e27ea87..00000000 --- a/Package/Sources/BrightroomEngine/ColorCube/ColorCubeHelper.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// ColorCubeHelper.swift -// -// Created by Joshua Sullivan on 10/10/16. -// Copyright © 2016 Joshua Sullivan. All rights reserved. -// - -import UIKit -import Accelerate - -public enum ColorCubeHelperError: Error { - case incorrectImageSize - case unableToCreateDataProvider - case unableToGetBitmpaDataBuffer -} - -/** - arranged code based on https://chibicode.org/?p=57 - */ -public class ColorCubeHelper { - - public static func createColorCubeData(inputImage cgImage: CGImage, cubeDimension: Int) throws -> Data { - - let pixels = cgImage.width * cgImage.height - let channels = 4 - - // If the number of pixels doesn't match what's needed for the supplied cube dimension, abort. - guard pixels == cubeDimension * cubeDimension * cubeDimension else { - throw ColorCubeHelperError.incorrectImageSize - } - - // We don't need a sizeof() because uint_8t is explicitly 1 byte. - let memSize = pixels * channels - - let inBitmapData = cgImage.dataProvider!.data - guard let inBuffer = CFDataGetBytePtr(inBitmapData) else { - throw ColorCubeHelperError.unableToGetBitmpaDataBuffer - } - - // Calculate the size of the float buffer and allocate it. - let floatSize = memSize * MemoryLayout.size - let finalBuffer = unsafeBitCast(malloc(floatSize), to:UnsafeMutablePointer.self) - - // Convert the uint_8t to float. Note: a uint of 255 will convert to 255.0f. - vDSP_vfltu8(inBuffer, 1, finalBuffer, 1, UInt(memSize)) - - // Divide each float by 255.0 to get the 0-1 range we are looking for. - var divisor = Float(255.0) - vDSP_vsdiv(finalBuffer, 1, &divisor, finalBuffer, 1, UInt(memSize)) - - // Don't copy the bytes, just have the NSData take ownership of the buffer. - let cubeData = NSData(bytesNoCopy: finalBuffer, length: floatSize, freeWhenDone: true) - - return cubeData as Data - - } -} - -extension ColorCubeHelper { - - public static func makeColorCubeFilter( - lutImage: ImageSource, - dimension: Int, - cacheKey: String? - ) -> CIFilter { - - - if let cacheKey = cacheKey, let cached = cache.object(forKey: cacheKey as NSString) { - return cached.copy() as! CIFilter - } else { - - let cgImage = lutImage.loadOriginalCGImage() - let colorSpace = cgImage.colorSpace ?? CGColorSpaceCreateDeviceRGB() - - let data = try! ColorCubeHelper.createColorCubeData(inputImage: cgImage, cubeDimension: dimension) - - let filter = CIFilter( - name: "CIColorCubeWithColorSpace", - parameters: [ - "inputCubeDimension" : dimension, - "inputCubeData" : data, - "inputColorSpace" : colorSpace, - ] - )! - - if let cacheKey = cacheKey { - cache.setObject(filter, forKey: cacheKey as NSString) - } - - return filter - } - - } -} - -let cache = NSCache() diff --git a/Package/Sources/BrightroomEngine/ColorCube/ColorCubeLoader.swift b/Package/Sources/BrightroomEngine/ColorCube/ColorCubeLoader.swift deleted file mode 100644 index 41221482..00000000 --- a/Package/Sources/BrightroomEngine/ColorCube/ColorCubeLoader.swift +++ /dev/null @@ -1,82 +0,0 @@ -import CoreGraphics -import Foundation -import ImageIO - -public enum ColorCubeLoaderError: Error { - case failedToGetDimensionFromFilename(String) - case failedToCreageCGDataProvider(String) - case failedToCraeteCGImageSource(String) -} - -/// An object for loading color-cube image from bundle. -/// It finds based on specified naming-rule. -/// -/// `LUT__.` -public final class ColorCubeLoader { - public let bundle: Bundle - - public init(bundle: Bundle) { - self.bundle = bundle - } - - public func load() throws -> [FilterColorCube] { - let rootPath = bundle.bundlePath as NSString - let fileList = try FileManager.default.contentsOfDirectory(atPath: rootPath as String) - - func takeDimension(from string: String) -> Int? { - enum Static { - static let regex: NSRegularExpression = { - let pattern = "LUT_([0-9]+)_.*" - let regex = try! NSRegularExpression(pattern: pattern, options: []) - return regex - }() - } - - guard - let matched = Static.regex.firstMatch( - in: string, - options: [], - range: NSRange(location: 0, length: string.count) - ) - else { - return nil - } - - let numberString = (string as NSString).substring(with: matched.range(at: 1)) - - return Int(numberString) - } - - let filters = - try fileList - .filter { $0.hasPrefix("LUT_") } - .sorted() - .map { path -> FilterColorCube in - - let url = URL(fileURLWithPath: rootPath.appendingPathComponent(path)) - - guard let dimension = takeDimension(from: path) else { - throw ColorCubeLoaderError.failedToGetDimensionFromFilename(path) - } - - guard let dataProvider = CGDataProvider(url: url as CFURL) else { - throw ColorCubeLoaderError.failedToCreageCGDataProvider(path) - } - - guard let imageSource = CGImageSourceCreateWithDataProvider(dataProvider, nil) else { - throw ColorCubeLoaderError.failedToCraeteCGImageSource(path) - } - - let name = (path as NSString).deletingPathExtension - .replacingOccurrences(of: "LUT_\(dimension)_", with: "") - - return FilterColorCube( - name: name, - identifier: path, - lutImage: .init(cgImageSource: imageSource), - dimension: dimension - ) - } - return filters - } -} diff --git a/Package/Sources/BrightroomEngine/ColorCube/ColorCubeStorage.swift b/Package/Sources/BrightroomEngine/ColorCube/ColorCubeStorage.swift deleted file mode 100644 index 2e34417f..00000000 --- a/Package/Sources/BrightroomEngine/ColorCube/ColorCubeStorage.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -@available(*, deprecated, message: "Use PresetStorage instead") -open class ColorCubeStorage { - - public static let `default` = ColorCubeStorage(filters: []) - - public var filters: [FilterColorCube] = [] - - public init(filters: [FilterColorCube]) { - self.filters = filters - } -} - diff --git a/Package/Sources/BrightroomEngine/ColorCube/CoreImageInspector.swift b/Package/Sources/BrightroomEngine/ColorCube/CoreImageInspector.swift deleted file mode 100644 index 77c2c3d0..00000000 --- a/Package/Sources/BrightroomEngine/ColorCube/CoreImageInspector.swift +++ /dev/null @@ -1,15 +0,0 @@ -import CoreImage - -public enum CoreImageInspector { - - public static func hasIOSurface(image: CIImage) -> Bool { - let debugDescription = image.debugDescription - let result = debugDescription.contains("IOSurface") - return result - } - - public static func hasCGImage(image: CIImage) -> Bool { - return image.cgImage != nil - } - -} diff --git a/Package/Sources/BrightroomEngine/Core/BlurredMask.swift b/Package/Sources/BrightroomEngine/Core/BlurredMask.swift deleted file mode 100644 index 40792fab..00000000 --- a/Package/Sources/BrightroomEngine/Core/BlurredMask.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreImage -import UIKit - -public struct BlurredMask: GraphicsDrawing { - public var paths: [DrawnPath] - - public init(paths: [DrawnPath]) { - self.paths = paths - } - - public func draw(in context: CGContext) { - guard !paths.isEmpty else { - return - } - - let mainContext = context - - guard - let cglayer = CGLayer( - mainContext, - size: mainContext.boundingBoxOfClipPath.size, - auxiliaryInfo: nil - ), - let layerCGContext = cglayer.context - else { - assert(false, "Failed to create CGLayer") - return - } - - renderDrawings: do { - let ciContext = CIContext( - cgContext: layerCGContext, - options: [ - .workingFormat: CIFormat.RGBA8, - .highQualityDownsample: true, - .cacheIntermediates: false, - ] - ) - let ciBlurredImage = BlurredMask.blur(image: CIImage(cgImage: context.makeImage()!))! - - /** - To keep wide-color(DisplayP3), use createCGImage instead drawing with CIContext - */ - let cgImage = ciContext.createCGImage( - ciBlurredImage, - from: ciBlurredImage.extent, - format: CIFormat.RGBA8, - colorSpace: CGColorSpace.init(name: CGColorSpace.displayP3), - deferred: true - )! - - UIGraphicsPushContext(layerCGContext) - - paths.forEach { path in - layerCGContext.saveGState() - layerCGContext.translateBy( - x: -mainContext.boundingBoxOfClipPath.minX, - y: -mainContext.boundingBoxOfClipPath.minY - ) - path.draw(in: layerCGContext) - - layerCGContext.restoreGState() - } - - layerCGContext.saveGState() - - layerCGContext.setBlendMode(.sourceIn) - - layerCGContext.draw(cgImage, in: ciBlurredImage.extent) - - layerCGContext.restoreGState() - - UIGraphicsPopContext() - } - - renderLayer: do { - UIGraphicsPushContext(mainContext) - - mainContext.draw( - cglayer, - at: .init( - x: mainContext.boundingBoxOfClipPath.minX, - y: mainContext.boundingBoxOfClipPath.minY - ) - ) - -// #if DEBUG -// paths.forEach { path in -// path.draw(in: mainContext, canvasSize: canvasSize) -// } -// #endif - - UIGraphicsPopContext() - } - } - - public static func blur(image: CIImage) -> CIImage? { - func radius(_ imageExtent: CGRect) -> Double { - let v = Double(sqrt(pow(imageExtent.width, 2) + pow(imageExtent.height, 2))) - return v / 20 // ? - } - - // let min: Double = 0 - let max: Double = 100 - let value: Double = 40 - - let _radius = radius(image.extent) * value / max - - let outputImage = image - .clamped(to: image.extent) - .applyingFilter( - "CIGaussianBlur", - parameters: [ - "inputRadius": _radius, - ] - ) - .cropped(to: image.extent) - - return outputImage - } -} diff --git a/Package/Sources/BrightroomEngine/Core/DrawnPath.swift b/Package/Sources/BrightroomEngine/Core/DrawnPath.swift deleted file mode 100644 index 7d0c246d..00000000 --- a/Package/Sources/BrightroomEngine/Core/DrawnPath.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreImage -import UIKit - -public struct DrawnPath : GraphicsDrawing, Equatable { - - // MARK: - Properties - - public let brush: OvalBrush - public let bezierPath: UIBezierPath - - // MARK: - Initializers - - public init( - brush: OvalBrush, - path: UIBezierPath - ) { - self.brush = brush - self.bezierPath = path - } - - // MARK: - Functions - - func brushedPath() -> UIBezierPath { - - let _bezierPath = bezierPath.copy() as! UIBezierPath - _bezierPath.lineJoinStyle = .round - _bezierPath.lineCapStyle = .round - _bezierPath.lineWidth = brush.pixelSize - - return _bezierPath - } - - public func draw(in context: CGContext) { - UIGraphicsPushContext(context) - context.saveGState() - defer { - context.restoreGState() - UIGraphicsPopContext() - } - - draw() - } - - private func draw() { - - guard let context = UIGraphicsGetCurrentContext() else { - return - } - - context.saveGState() - defer { - context.restoreGState() - } - - let boundingBox = context.boundingBoxOfClipPath - context.scaleBy(x: 1, y: -1) - context.translateBy(x: 0, y: -(boundingBox.maxY + boundingBox.minY)) - assert(context.boundingBoxOfClipPath == boundingBox) - - brush.color.setStroke() - let bezierPath = brushedPath() - bezierPath.stroke(with: .normal, alpha: brush.alpha) - } - -} diff --git a/Package/Sources/BrightroomEngine/Core/EditingCrop.swift b/Package/Sources/BrightroomEngine/Core/EditingCrop.swift deleted file mode 100644 index ebc89141..00000000 --- a/Package/Sources/BrightroomEngine/Core/EditingCrop.swift +++ /dev/null @@ -1,383 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit -import Vision - -/// A representation of cropping extent in Image. -public struct EditingCrop: Equatable { - public enum Rotation: Equatable, CaseIterable { - /// 0 degree - default - case angle_0 - - /// 90 degree - case angle_90 - - /// 180 degree - case angle_180 - - /// 270 degree - case angle_270 - - public var angle: CGFloat { - switch self { - case .angle_0: - return 0 - case .angle_90: - return -CGFloat.pi / 2 - case .angle_180: - return -CGFloat.pi - case .angle_270: - return CGFloat.pi / 2 - } - } - - public var transform: CGAffineTransform { - .init(rotationAngle: angle) - } - - public func next() -> Self { - switch self { - case .angle_0: return .angle_90 - case .angle_90: return .angle_180 - case .angle_180: return .angle_270 - case .angle_270: return .angle_0 - } - } - } - - /// The dimensions in pixel for the image. - /// Applied image-orientation. - public var imageSize: CGSize - - /// The rectangle that specifies the extent of the cropping. - public private(set) var cropExtent: CGRect - - /// The angle that specifies rotation for the image. - public var rotation: Rotation = .angle_0 - - public private(set) var scaleToRestore: CGFloat - - public init(from ciImage: CIImage) { - self.init( - imageSize: .init(image: ciImage), - cropRect: .init(origin: .zero, size: ciImage.extent.size) - ) - } - - public init(imageSize: CGSize) { - self.init( - imageSize: imageSize, - cropRect: .init(origin: .zero, size: imageSize), - rotation: .angle_0 - ) - } - - public init( - imageSize: CGSize, - cropRect: CGRect, - rotation: Rotation = .angle_0, - scaleToRestore: CGFloat = 1 - ) { - self.imageSize = imageSize - self.cropExtent = Self.fittingRect(rect: cropRect, in: imageSize, respectingAspectRatio: nil) - self.rotation = rotation - self.scaleToRestore = scaleToRestore - } - - public func makeInitial() -> Self { - .init( - imageSize: imageSize, - cropRect: .init(origin: .zero, size: imageSize), - scaleToRestore: scaleToRestore - ) - } - - public func scaledWithPixelPerfect(maxPixelSize: CGFloat) -> Self { - - let scaledImageSize = imageSize.scaled(maxPixelSize: maxPixelSize) - - let scale = scaledImageSize.width / imageSize.width - - var new = scaled(scale) - new.imageSize = scaledImageSize - new.scaleToRestore = imageSize.width / scaledImageSize.width - - return new - } - - private func scaled(_ scale: CGFloat) -> Self { - - var modified = self - - var cropExtent = modified.cropExtent - var imageSize = modified.imageSize - - cropExtent.origin.x *= scale - cropExtent.origin.y *= scale - cropExtent.size.width *= scale - cropExtent.size.height *= scale - - imageSize.width *= scale - imageSize.height *= scale - imageSize.width.round(.down) - imageSize.height.round(.down) - - modified.cropExtent = Self.fittingRect( - rect: cropExtent, - in: imageSize, - respectingAspectRatio: nil - ) - modified.imageSize = imageSize - - return modified - } - - /** - Set new aspect ratio with updating cropping extent. - Currently, the cropping extent changes to maximum size in the size of image. - - - TODO: Resizing cropping extent with keeping area by new aspect ratio. - */ - public mutating func updateCropExtent(toFitAspectRatio newAspectRatio: PixelAspectRatio) { - - let maxSize = newAspectRatio.sizeThatFitsWithRounding(in: imageSize) - - let proposed = CGRect( - origin: .init( - x: (imageSize.width - maxSize.width) / 2, - y: (imageSize.height - maxSize.height) / 2 - ), - size: maxSize - ) - - self.cropExtent = Self.fittingRect( - rect: proposed, - in: imageSize, - respectingAspectRatio: newAspectRatio - ) - } - - /** - (Won't do mutating, If current aspect ratio is the same with specified aspect ratio.) - Set new aspect ratio with updating cropping extent. - Currently, the cropping extent changes to maximum size in the size of image. - - */ - public mutating func updateCropExtentIfNeeded(toFitAspectRatio newAspectRatio: PixelAspectRatio) { - // FIXME: it won't perform correctly. won't match values as using floating point value - /* - Depends on the size of image, it can not always express the exact value of given aspect-ratio. - - image-size: (7864.0, 5248.0) - - aspect ratio: 1.0:1.2 (0.8333333333) - - calculated fitting image size with ratio: (1745.0, 0.0, 4373.0, 5247.0) - - (4373.0 : 5247.0) -> 0.8334286259 - */ - guard PixelAspectRatio(cropExtent.size) != newAspectRatio else { - return - } - updateCropExtent(toFitAspectRatio: newAspectRatio) - } - - /** - Updates the crop extent to fit bounding box that comes from Vision.framework. - */ - public mutating func updateCropExtent( - toFitBoundingBox boundingBox: CGRect, - respectingApectRatio: PixelAspectRatio? - ) { - - var proposed = cropExtent - - let transform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -proposed.height) - - let scale = CGAffineTransform.identity.scaledBy(x: proposed.width, y: proposed.height) - - proposed = - boundingBox - .applying(scale) - .applying(transform) - - self.cropExtent = Self.fittingRect( - rect: proposed, - in: imageSize, - respectingAspectRatio: respectingApectRatio - ) - } - - /// Updates cropExtent with new specified rect and normalizing value using aspectRatio(optional). - /// cropExtent would be rounded in order to drop floating point value for fitting pixel. - /// With specifing `respectingAspectRatio`, it fixes cropExtent's size. - /// - /// - Parameters: - /// - cropExtent: - /// - respectingAspectRatio: - public mutating func updateCropExtentNormalizing( - _ cropExtent: CGRect, - respectingAspectRatio: PixelAspectRatio? - ) { - self.cropExtent = Self.fittingRect( - rect: cropExtent, - in: imageSize, - respectingAspectRatio: respectingAspectRatio - ) - } - - private static func fittingRect( - rect: CGRect, - in imageSize: CGSize, - respectingAspectRatio: PixelAspectRatio? - ) -> CGRect { - - var fixed = rect - - func containsFractionInCGFloat(_ value: CGFloat) -> Bool { - Int(exactly: value) == nil - } - - func rectIsPixelPerfect(_ rect: CGRect) -> Bool { - guard containsFractionInCGFloat(rect.origin.x) == false else { return false } - guard containsFractionInCGFloat(rect.origin.y) == false else { return false } - guard containsFractionInCGFloat(rect.size.width) == false else { return false } - guard containsFractionInCGFloat(rect.size.height) == false else { return false } - return true - } - - func clamp(value: T, lower: T, upper: T) -> T { - return min(max(value, lower), upper) - } - - /* - Drops decimal fraction - */ - - fixed.origin.x.round(.down) - fixed.origin.y.round(.down) - fixed.size.width.round(.down) - fixed.size.height.round(.down) - - /* - Cuts the area off that out of maximum bounds - - image-size - ┌────────────┐ - │ │ - │ │ crop extent - │ ┌───────┼───┐ - │ │xxxxxxx│ │ - │ │xxxxxxx│ │ - │ │xxxxxxx│ │ - │ │xxxxxxx│ │ - └────┼───────┘ │ - │ │ - └───────────┘ - */ - - fixed = CGRect(origin: .zero, size: imageSize).intersection(fixed) - - respectAspectRatio: do { - - /* - Fits the fixed rect to aspect ratio if present. - */ - - if let aspectRatio = respectingAspectRatio { - - /* - Find maximum bounds to create a new rect inside. - */ - - let maxSizeFromPoint = CGSize( - width: imageSize.width - fixed.minX, - height: imageSize.height - fixed.minY - ) - - let maxRect = CGRect( - origin: fixed.origin, - size: .init( - width: clamp(value: fixed.width, lower: 0, upper: maxSizeFromPoint.width), - height: clamp(value: fixed.height, lower: 0, upper: maxSizeFromPoint.height) - ) - ) - - let newRect = aspectRatio.rectThatFitsWithRounding(in: maxRect) - - fixed = newRect - - } - - } - - validation: do { - - assert(fixed.maxX <= imageSize.width) - assert(fixed.maxY <= imageSize.height) - - assert(fixed.origin.x >= 0) - assert(fixed.origin.y >= 0) - assert(fixed.width <= imageSize.width) - assert(fixed.height <= imageSize.height) - - assert(rectIsPixelPerfect(fixed)) - } - - #if DEBUG - EngineLog.debug( - """ - [Normalizing CropExtent] - Output: \(fixed) - - resultAspectRatio: \(PixelAspectRatio(fixed.size)._minimized().localizedText) - - source: \(rect) - - imageSize: \(imageSize) - - respectingApectRatio: \(respectingAspectRatio.map { "\($0.width):\($0.height)" } ?? "null") - """ - ) - #endif - - return fixed - } - - /* - @objc - public func debugQuickLookObject() -> AnyObject? { - - let path = UIBezierPath(rect: CGRect(origin: .zero, size: imageSize)) - - return path - } - */ -} - -extension CIImage { - func cropped(to _cropRect: EditingCrop) -> CIImage { - - let targetImage = self - var cropRect = _cropRect.cropExtent - - cropRect.origin.y = targetImage.extent.height - cropRect.minY - cropRect.height - - let croppedImage = - targetImage - .cropped(to: cropRect) - - return croppedImage - } -} diff --git a/Package/Sources/BrightroomEngine/Core/EditingStack.CropModifier.swift b/Package/Sources/BrightroomEngine/Core/EditingStack.CropModifier.swift deleted file mode 100644 index af2f8f29..00000000 --- a/Package/Sources/BrightroomEngine/Core/EditingStack.CropModifier.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// Copyright (c) 2021 Hiroshi Kimura(Muukii) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreImage -import Vision - -extension EditingStack { - public struct CropModifier { - public typealias Closure = (CIImage, EditingCrop, @escaping (EditingCrop) -> Void) -> Void - - private let modifier: Closure - - public init(modify: @escaping Closure) { - modifier = modify - } - - func run(_ image: CIImage, editingCrop: EditingCrop, completion: @escaping (EditingCrop) -> Void) { - modifier(image, editingCrop) { result in - completion(result) - } - } - - public static func faceDetection(paddingBias: CGFloat = 1.3, aspectRatio: PixelAspectRatio? = nil) -> Self { - return .init { image, crop, completion in - - let request = VNDetectFaceRectanglesRequest { request, error in - - if let error = error { - EngineLog.debug(error) - completion(crop) - return - } - - guard let results = request.results as? [VNFaceObservation] else { - completion(crop) - return - } - - guard let first = results.first else { - completion(crop) - return - } - - var new = crop - let box = first.boundingBox - - let denormalizedRect = VNImageRectForNormalizedRect(box, Int(crop.imageSize.width), Int(crop.imageSize.height)) - - let paddingRect = denormalizedRect.insetBy(dx: -denormalizedRect.width * paddingBias, dy: -denormalizedRect.height * paddingBias) - - let normalizedRect = VNNormalizedRectForImageRect(paddingRect, Int(crop.imageSize.width), Int(crop.imageSize.height)) - - new.updateCropExtent(toFitBoundingBox: normalizedRect, respectingApectRatio: aspectRatio ?? .init(crop.imageSize)) - completion(new) - } - - request.revision = VNDetectFaceRectanglesRequestRevision2 - let handler = VNImageRequestHandler(ciImage: image, orientation: .up, options: [:]) - do { - try handler.perform([request]) - } catch { - EngineLog.error(.stack, "Face detection start failed : \(error)") - } - } - } - } -} diff --git a/Package/Sources/BrightroomEngine/Core/EditingStack.Edit.swift b/Package/Sources/BrightroomEngine/Core/EditingStack.Edit.swift deleted file mode 100644 index 700e3f18..00000000 --- a/Package/Sources/BrightroomEngine/Core/EditingStack.Edit.swift +++ /dev/null @@ -1,119 +0,0 @@ -// -// Copyright (c) 2021 Hiroshi Kimura(Muukii) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreImage - -extension EditingStack { - // TODO: Consider more effective shape - public struct Edit: Equatable { - func makeFilters() -> [AnyFilter] { - return filters.makeFilters() - } - - public var imageSize: CGSize { - crop.imageSize - } - - public var crop: EditingCrop - public var filters: Filters = .init() - public var drawings: Drawings = .init() - - init(crop: EditingCrop) { - self.crop = crop - } - - public struct Drawings: Equatable { - // TODO: Remove Rect from DrawnPath - public var blurredMaskPaths: [DrawnPath] = [] - } - - // - // public struct Light { - // - // } - // - // public struct Color { - // - // } - // - // public struct Effects { - // - // } - // - // public struct Detail { - // - // } - - public struct Filters: Equatable { - public var preset: FilterPreset? - - public var brightness: FilterBrightness? - public var contrast: FilterContrast? - public var saturation: FilterSaturation? - public var exposure: FilterExposure? - - public var highlights: FilterHighlights? - public var shadows: FilterShadows? - - public var temperature: FilterTemperature? - - public var sharpen: FilterSharpen? - public var gaussianBlur: FilterGaussianBlur? - public var unsharpMask: FilterUnsharpMask? - - public var vignette: FilterVignette? - public var fade: FilterFade? - - func makeFilters() -> [AnyFilter] { - return ([ - - /** - Must be first filter since color-cube does not support wide range color. - */ - preset?.asAny(), - - // Before - exposure?.asAny(), - brightness?.asAny(), - temperature?.asAny(), - highlights?.asAny(), - shadows?.asAny(), - saturation?.asAny(), - contrast?.asAny(), - - // After - sharpen?.asAny(), - unsharpMask?.asAny(), - gaussianBlur?.asAny(), - fade?.asAny(), - vignette?.asAny(), - ] as [AnyFilter?]) - .compactMap { $0 } - } - - public func apply(to ciImage: CIImage) -> CIImage { - makeFilters().reduce(ciImage) { (image, filter) -> CIImage in - filter.apply(to: image, sourceImage: image) - } - } - } - } -} diff --git a/Package/Sources/BrightroomEngine/Core/EditingStack.swift b/Package/Sources/BrightroomEngine/Core/EditingStack.swift deleted file mode 100644 index 9fd784c3..00000000 --- a/Package/Sources/BrightroomEngine/Core/EditingStack.swift +++ /dev/null @@ -1,772 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreImage -import MetalKit -import SwiftUI -import UIKit -import Verge - -@available(iOS 13, *) -extension EditingStack: ObservableObject {} - -public enum EditingStackError: Error { - case unableToCreateRendererInLoading -} - -fileprivate enum MTLImageCreationError: Error { - case imageTooBig -} - -fileprivate extension MTLDevice { - func supportsImage(size: CGSize) -> Bool { -#if DEBUG - switch MTLGPUFamily.apple1 { - case .apple1, .apple2, .apple3, .apple4, .apple5, .apple6, .apple7, .apple8, .common1, .common2, .common3, .mac1, .mac2, .macCatalyst1, .macCatalyst2, .metal3: - break; - @unknown default: //If a warning is triggered here, please check https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf for a possibly new value in the Maximum 2D texture width and height table. - break - } -#endif - let maxSideSize: CGFloat = self.supportsFamily(.apple3) ? 16384 : 8192 - return size.width <= maxSideSize && size.height <= maxSideSize - } -} - -/// A stateful object that manages current editing status from original image. -/// And supports rendering a result image. -/// -/// - Attension: Source text -/// Please make sure of EditingStack is started state before editing in UI with calling `start()`. -open class EditingStack: Hashable, StoreComponentType { - - private static let centralQueue = DispatchQueue.init(label: "app.muukii.Brightroom.EditingStack.central", qos: .default, attributes: .concurrent) - - private let backgroundQueue = DispatchQueue.init(label: "app.muukii.Brightroom.EditingStack", qos: .default, target: centralQueue) - - public struct Options { - - public var usesMTLTextureForEditingImage: Bool = true - - public init() {} - } - - public static func == (lhs: EditingStack, rhs: EditingStack) -> Bool { - lhs === rhs - } - - public func hash(into hasher: inout Hasher) { - ObjectIdentifier(self).hash(into: &hasher) - } - - /** - A representation of state in EditingStack - */ - public struct State: Equatable { - public struct Loading: Equatable {} - - public struct Loaded: Equatable { - - // MARK: - Properties - - fileprivate let imageSource: ImageSource - - public let metadata: ImageProvider.State.ImageMetadata - - private let initialEditing: Edit - - /** - - - TODO: Should be marked as `fileprivate(set)`, but compile fails in CocoaPods installed. - */ - public var currentEdit: Edit { - didSet { - editingPreviewImage = currentEdit.filters.apply(to: editingSourceImage) - } - } - - /// Won't change from initial state - public var imageSize: CGSize { - initialEditing.imageSize - } - - /** - A stack of editing history - */ - public fileprivate(set) var history: [Edit] = [] - - public fileprivate(set) var thumbnailImage: CIImage - - public let editingSourceCGImage: CGImage - /** - An original image - Can be used in cropping - */ - public let editingSourceImage: CIImage - - public fileprivate(set) var editingPreviewImage: CIImage - - public fileprivate(set) var imageForCrop: CGImage - - public fileprivate(set) var previewFilterPresets: [PreviewFilterPreset] = [] - - public var canUndo: Bool { - return history.count > 0 - } - - /** - A boolean value that indicates if EditingStack has updates against the original image. - */ - public var isDirty: Bool { - return currentEdit != initialEditing - } - - public var hasUncommitedChanges: Bool { - guard currentEdit == initialEditing else { - return true - } - - guard let latestHistory = history.last else { - return false - } - - guard latestHistory == currentEdit else { - return true - } - - return false - } - - // MARK: - Initializers - - init( - imageSource: ImageSource, - metadata: ImageProvider.State.ImageMetadata, - initialEditing: EditingStack.Edit, - currentEdit: EditingStack.Edit, - history: [EditingStack.Edit] = [], - thumbnailCIImage: CIImage, - editingSourceCGImage: CGImage, - editingSourceCIImage: CIImage, - editingPreviewCIImage: CIImage, - imageForCrop: CGImage, - previewFilterPresets: [PreviewFilterPreset] = [] - ) { - self.imageSource = imageSource - self.metadata = metadata - self.initialEditing = initialEditing - self.currentEdit = currentEdit - self.history = history - self.thumbnailImage = thumbnailCIImage - self.editingSourceCGImage = editingSourceCGImage - self.editingSourceImage = editingSourceCIImage - self.editingPreviewImage = editingPreviewCIImage - self.previewFilterPresets = previewFilterPresets - self.imageForCrop = imageForCrop - } - - // MARK: - Functions - - mutating func makeVersion() { - history.append(currentEdit) - } - - mutating func revertCurrentEditing() { - currentEdit = history.last ?? initialEditing - } - - mutating func revert(to revision: Revision) { - history.removeSubrange(revision.. CIImage { - editingSourceImage.cropped( - to: currentEdit.crop.scaledWithPixelPerfect( - maxPixelSize: max(editingSourceImage.extent.width, editingSourceImage.extent.height) - ) - ) - } - - } - - public fileprivate(set) var hasStartedEditing = false - /** - A Boolean value that indicates whether the image is currently loading for editing. - */ - public var isLoading: Bool { - loadedState == nil - } - - public fileprivate(set) var loadingState: Loading = .init() - public fileprivate(set) var loadedState: Loaded? - - init() {} - } - - // MARK: - Stored Properties - - public let store: DefaultStore - - public let options: Options - - public let imageProvider: ImageProvider - - private let filterPresets: [FilterPreset] - - private var subscriptions = Set() - private var imageProviderSubscription: VergeAnyCancellable? - - public var cropModifier: CropModifier - - private let editingImageMaxPixelSize: CGFloat = 2560 - - private let debounceForCreatingCGImage = _BrightroomDebounce(interval: 0.1, queue: DispatchQueue.init(label: "Brightroom.cgImage")) - - // MARK: - Initializers - - /// Creates an instance - /// - Parameters: - /// - source: - /// - previewSize: - /// - colorCubeStorage: - /// - modifyCrop: A chance to modify cropping. It runs in background-thread. CIImage is not original image. - public init( - imageProvider: ImageProvider, - colorCubeStorage: ColorCubeStorage = .default, - presetStorage: PresetStorage = .default, - options: Options = .init(), - cropModifier: CropModifier = .init(modify: { _, c, completion in completion(c) }) - ) { - - self.options = options - self.cropModifier = cropModifier - store = .init( - initialState: .init() - ) - - filterPresets = colorCubeStorage.filters.map { - FilterPreset( - name: $0.name, - identifier: $0.identifier, - filters: [$0.asAny()], - userInfo: [:] - ) - } + presetStorage.presets - - self.imageProvider = imageProvider - } - - /** - EditingStack awakes from cold state. - - - Calling from background-thread supported. - */ - public func start(onPreparationCompleted: @escaping () -> Void = {}) { - - let previousHasCompleted = commit { s -> Bool in - /** - Mutual exclusion - */ - if s.hasStartedEditing { - return true - } else { - s.hasStartedEditing = true - return false - } - } - - guard previousHasCompleted == false else { - DispatchQueue.main.async { - onPreparationCompleted() - } - return - } - - store.sinkState(queue: .specific(backgroundQueue)) { [weak self] (state: Changes) in - guard let self = self else { return } - self.receiveInBackground(newState: state) - } - .store(in: &subscriptions) - - /** - Start downloading image - */ - - backgroundQueue.async { - self.imageProvider.start() - } - - imageProviderSubscription = - imageProvider - .sinkState(queue: .specific(backgroundQueue)) { - [weak self] (state: Changes) in - - /* - In Background thread - */ - - guard let self = self else { return } - - state.ifChanged(\.loadedImage) { image in - - guard let image = image else { - return - } - - switch image { - case let .editable(image, metadata): - - let thumbnailCGImage = image.loadThumbnailCGImage(maxPixelSize: 180) - - /** - An image resised from original image - */ - let editingSourceCGImage = image.loadThumbnailCGImage( - maxPixelSize: self.editingImageMaxPixelSize - ) - - assert(editingSourceCGImage.colorSpace != nil) - - let device = MTLCreateSystemDefaultDevice() - - /// resized - let _editingSourceCIImage: CIImage = _makeCIImage( - source: editingSourceCGImage, - orientation: metadata.orientation, - device: device, - usesMTLTexture: self.options.usesMTLTextureForEditingImage - ) - - let _thumbnailImage: CIImage = _makeCIImage( - source: thumbnailCGImage, - orientation: metadata.orientation, - device: device, - usesMTLTexture: self.options.usesMTLTextureForEditingImage - ) - - let cgImageForCrop: CGImage = { - do { - return try Self.renderCGImageForCrop( - filters: [], - source: .init(cgImage: editingSourceCGImage), - orientation: metadata.orientation - ) - } catch { - EngineSanitizer.global.onDidFindRuntimeError(.failedToRenderCGImageForCrop(sourceImage: editingSourceCGImage)) - assertionFailure() - return editingSourceCGImage - } - }() - - self.adjustCropExtent( - image: _editingSourceCIImage, - imageSize: metadata.imageSize, - completion: { [weak self] crop in - - guard let self = self else { return } - - self.commit { (s: inout InoutRef) in - assert( - (_editingSourceCIImage.extent.width > _editingSourceCIImage.extent.height) - == (metadata.imageSize.width > metadata.imageSize.height) - ) - - let initialEdit = Edit(crop: crop) - - s.loadedState = .init( - imageSource: image, - metadata: metadata, - initialEditing: initialEdit, - currentEdit: initialEdit, - thumbnailCIImage: _thumbnailImage, - editingSourceCGImage: editingSourceCGImage, - editingSourceCIImage: _editingSourceCIImage, - editingPreviewCIImage: initialEdit.filters.apply(to: _editingSourceCIImage), - imageForCrop: cgImageForCrop - ) - - self.imageProviderSubscription?.cancel() - - DispatchQueue.main.async { - onPreparationCompleted() - } - } - } - ) - - } - } - } - } - - deinit { - EngineLog.debug("[EditingStack] deinit") - } - - private func receiveInBackground(newState state: Changes) { - - assert(Thread.isMainThread == false) - - commit { (modifyingState: inout InoutRef) in - - if let loadedState = state.mapIfPresent(\.loadedState) { - modifyingState.map(keyPath: \.loadedState!) { (nextState) -> Void in - - loadedState.ifChanged(\.thumbnailImage) { image in - - nextState.previewFilterPresets = self.filterPresets.map { - PreviewFilterPreset(sourceImage: image, filter: $0) - } - } - - loadedState.ifChanged(\.currentEdit.filters) { currentEdit in - - self.debounceForCreatingCGImage.on { [weak self] in - - guard let self = self else { return } - - let cgImageForCrop: CGImage = { - do { - return try Self.renderCGImageForCrop( - filters: currentEdit.makeFilters(), - source: .init(cgImage: loadedState.editingSourceCGImage), - orientation: loadedState.metadata.orientation - ) - } catch { - assertionFailure() - return loadedState.editingSourceCGImage - } - }() - - self.commit { - $0.loadedState?.imageForCrop = cgImageForCrop - } - - } - - } - - } - - } - - } - } - - // MARK: - Functions - - /** - Adds a new snapshot as a history. - */ - public func takeSnapshot() { - commit { - $0.loadedState?.makeVersion() - } - } - - public typealias Revision = Int - - public var currentRevision: Revision? { - self.primitiveState.loadedState?.history.count - } - - public func revert(to revision: Revision) { - commit { - $0.loadedState?.revert(to: revision) - } - } - - /** - Reverts the current editing. - */ - public func revertEdit() { - _pixelengine_ensureMainThread() - - commit { - $0.loadedState?.revertCurrentEditing() - } - } - - /** - Undo editing, pulling the latest history back into the current edit. - */ - public func undoEdit() { - _pixelengine_ensureMainThread() - - commit { - $0.loadedState?.undoEditing() - } - } - - /** - Purges the all of the history - */ - public func removeAllEditsHistory() { - _pixelengine_ensureMainThread() - - commit { - $0.loadedState?.history = [] - } - } - - public func set(filters: (inout Edit.Filters) -> Void) { - _pixelengine_ensureMainThread() - - applyIfChanged { - filters(&$0.filters) - } - } - - public func crop(_ value: EditingCrop) { - applyIfChanged { - $0.crop = value - } - } - - public func set(blurringMaskPaths: [DrawnPath]) { - _pixelengine_ensureMainThread() - - applyIfChanged { - $0.drawings.blurredMaskPaths = blurringMaskPaths - } - } - - public func append(blurringMaskPaths: C) where C.Element == DrawnPath { - _pixelengine_ensureMainThread() - - applyIfChanged { - $0.drawings.blurredMaskPaths += blurringMaskPaths - } - } - - public func makeRenderer() throws -> BrightRoomImageRenderer { - let stateSnapshot = state - - guard let loaded = stateSnapshot.loadedState else { - throw EditingStackError.unableToCreateRendererInLoading - } - - let imageSource = loaded.imageSource - - let renderer = BrightRoomImageRenderer(source: imageSource, orientation: loaded.metadata.orientation) - - // TODO: Clean up ImageRenderer.Edit - - let edit = loaded.currentEdit - - renderer.edit.croppingRect = edit.crop - - if edit.drawings.blurredMaskPaths.isEmpty == false { - renderer.edit.drawer = [ - BlurredMask(paths: edit.drawings.blurredMaskPaths) - ] - } - - renderer.edit.modifiers = edit.makeFilters() - - return renderer - } - - private func applyIfChanged(_ perform: (inout InoutRef) -> Void) { - commit { - guard $0.loadedState != nil else { - return - } - $0.map(keyPath: \.loadedState!.currentEdit, perform: perform) - } - } - - private func adjustCropExtent( - image: CIImage, - imageSize: CGSize, - completion: @escaping (EditingCrop) -> Void - ) { - let crop = EditingCrop(imageSize: imageSize) - - let scaled = image.transformed( - by: .init( - scaleX: image.extent.width < imageSize.width ? imageSize.width / image.extent.width : 1, - y: image.extent.height < imageSize.width ? imageSize.height / image.extent.height : 1 - ) - ) - - let translated = scaled.transformed( - by: .init( - translationX: scaled.extent.origin.x, - y: scaled.extent.origin.y - ) - ) - - let actualSizeFromDownsampledImage = translated - - cropModifier.run(actualSizeFromDownsampledImage, editingCrop: crop, completion: completion) - } - - private static func renderCGImageForCrop( - filters: [AnyFilter], - source: ImageSource, - orientation: CGImagePropertyOrientation - ) throws -> CGImage { - - let renderer = BrightRoomImageRenderer(source: source, orientation: orientation) - renderer.edit.modifiers = filters - - let result = try renderer.render().cgImage - - return result - } - -} - - -/// TODO: As possible, creates CIImage from MTLTexture -/// 16bits image can't be MTLTexture with MTKTextureLoader. -/// https://stackoverflow.com/questions/54710592/cant-load-large-jpeg-into-a-mtltexture-with-mtktextureloader -private func makeMTLTexture(from cgImage: CGImage, device: MTLDevice) throws -> MTLTexture { - guard device.supportsImage(size: cgImage.size) else { - throw MTLImageCreationError.imageTooBig - } - - #if true - let loader = MTKTextureLoader(device: device) - let texture = try loader.newTexture(cgImage: cgImage, options: [:]) - return texture - #else - - // Here does not work well. - - let textureDescriptor = MTLTextureDescriptor() - - textureDescriptor.pixelFormat = .rgba16Uint - textureDescriptor.width = cgImage.width - textureDescriptor.height = cgImage.height - - let texture = try device.makeTexture(descriptor: textureDescriptor).unwrap( - orThrow: "Failed to create MTLTexture" - ) - - let context = try CGContext.makeContext(for: cgImage) - .perform { context in - let flip = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: CGFloat(cgImage.height)) - context.concatenate(flip) - context.draw( - cgImage, - in: CGRect(x: 0, y: 0, width: CGFloat(cgImage.width), height: CGFloat(cgImage.height)) - ) - } - - let data = try context.data.unwrap() - - texture.replace( - region: MTLRegionMake2D(0, 0, cgImage.width, cgImage.height), - mipmapLevel: 0, - withBytes: data, - bytesPerRow: 8 * cgImage.width - ) - - return texture - #endif - -} - -private func _makeCIImage( - source cgImage: CGImage, - orientation: CGImagePropertyOrientation, - device: MTLDevice?, - usesMTLTexture: Bool -) -> CIImage { - - let colorSpace = cgImage.colorSpace ?? CGColorSpaceCreateDeviceRGB() - - func createFromCGImage() -> CIImage { - return CIImage( - cgImage: cgImage - ) - .oriented(orientation) - } - - func createFromMTLTexture(device: MTLDevice) throws -> CIImage { - let thumbnailTexture = try makeMTLTexture( - from: cgImage, - device: device - ) - - let ciImage = try CIImage( - mtlTexture: thumbnailTexture, - options: [.colorSpace: colorSpace] - ) - .map { - $0.transformed(by: .init(scaleX: 1, y: -1)) - }.map { - $0.transformed(by: .init(translationX: 0, y: $0.extent.height)) - } - .map { - $0.oriented(orientation) - } - .unwrap() - - EngineLog.debug(.stack, "Load MTLTexture") - - return ciImage - } - - if usesMTLTexture { - assert(device != nil) - } - - if usesMTLTexture, let device = device { - - do { - // TODO: As possible, creates CIImage from MTLTexture - // 16bits image can't be MTLTexture with MTKTextureLoader. - // https://stackoverflow.com/questions/54710592/cant-load-large-jpeg-into-a-mtltexture-with-mtktextureloader - return try createFromMTLTexture(device: device) - } catch { - EngineLog.debug( - .stack, - "Unable to create MTLTexutre, fallback to CIImage from CGImage.\n\(cgImage)" - ) - - return createFromCGImage() - } - } else { - - if usesMTLTexture, device == nil { - EngineLog.error( - .stack, - "MTLDevice not found, fallback to using CGImage to create CIImage." - ) - } - - return createFromCGImage() - } - -} diff --git a/Package/Sources/BrightroomEngine/Core/Filtering.swift b/Package/Sources/BrightroomEngine/Core/Filtering.swift deleted file mode 100644 index 8184bb6f..00000000 --- a/Package/Sources/BrightroomEngine/Core/Filtering.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreImage -import UIKit - -enum RadiusCalculator { - - static func radius(value: Double, max: Double, imageExtent: CGRect) -> Double { - - let base = Double(sqrt(pow(imageExtent.width, 2) + pow(imageExtent.height, 2))) - let c = base / 20 - return c * value / max - } -} - - -public protocol Filtering: Hashable { - - func apply(to image: CIImage, sourceImage: CIImage) -> CIImage -} - -extension Filtering { - public func asAny() -> AnyFilter { - .init(filter: self) - } -} - -public struct AnyFilter: Filtering { - - public static func == (lhs: AnyFilter, rhs: AnyFilter) -> Bool { - lhs.base == rhs.base - } - - public func hash(into hasher: inout Hasher) { - base.hash(into: &hasher) - } - - private let applier: (CIImage, CIImage) -> CIImage - public let base: AnyHashable - - public init(filter: Filter) { - self.base = filter - self.applier = filter.apply - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - applier(image, sourceImage) - } -} diff --git a/Package/Sources/BrightroomEngine/Core/GraphicDrawing.swift b/Package/Sources/BrightroomEngine/Core/GraphicDrawing.swift deleted file mode 100644 index 0633da37..00000000 --- a/Package/Sources/BrightroomEngine/Core/GraphicDrawing.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreGraphics - -public protocol GraphicsDrawing { - - func draw(in context: CGContext) -} diff --git a/Package/Sources/BrightroomEngine/Core/OvalBrush.swift b/Package/Sources/BrightroomEngine/Core/OvalBrush.swift deleted file mode 100644 index f56f9edd..00000000 --- a/Package/Sources/BrightroomEngine/Core/OvalBrush.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -public struct OvalBrush : Equatable { - - // MARK: - Properties - - public var color: UIColor - public var pixelSize: CGFloat - public var alpha: CGFloat - - // MARK: - Initializers - - public init( - color: UIColor, - pixelSize: CGFloat, - alpha: CGFloat = 1 - ) { - - self.color = color - self.pixelSize = pixelSize - self.alpha = alpha - } -} diff --git a/Package/Sources/BrightroomEngine/Core/ParameterRange.swift b/Package/Sources/BrightroomEngine/Core/ParameterRange.swift deleted file mode 100644 index 63ef0c07..00000000 --- a/Package/Sources/BrightroomEngine/Core/ParameterRange.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -public struct ParameterRange { - - public let min: T - public let max: T - - public init(min: T, max: T) { - self.min = min - self.max = max - } - -} diff --git a/Package/Sources/BrightroomEngine/DataSource/ImageProvider.swift b/Package/Sources/BrightroomEngine/DataSource/ImageProvider.swift deleted file mode 100644 index d7c9ac7a..00000000 --- a/Package/Sources/BrightroomEngine/DataSource/ImageProvider.swift +++ /dev/null @@ -1,448 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -import CoreImage -import Verge - -#if canImport(UIKit) -import UIKit -#endif - -#if canImport(Photos) -import Photos -#endif - -public enum ImageProviderError: Error { - case failedToDownloadPreviewImage(underlyingError: Error) - case failedToDownloadEditableImage(underlyingError: Error) - - case urlIsNotFileURL(URL) - - case failedToCreateCGDataProvider - case failedToCreateCGImageSource - case failedToCreateImageSource(underlyingError: Error) - - case failedToGetImageSize - - case failedToGetImageMetadata - - case failedToCreateCIFilterToLoadRAW - case failedToGetRenderedImageFromRAW - case failedToCreateCGImageFromRAW -} - -/** - A stateful object that provides multiple image for EditingStack. - */ -public final class ImageProvider: Equatable, StoreComponentType { - public static func == (lhs: ImageProvider, rhs: ImageProvider) -> Bool { - lhs === rhs - } - - /** - To access, `ImageProvider.store.state` - To modify, `ImageProvider.store.commit()` - */ - public struct State: Equatable { - - public struct ImageMetadata: Equatable { - public var orientation: CGImagePropertyOrientation - - /// A size that applied orientation - public var imageSize: CGSize - - public init(orientation: CGImagePropertyOrientation, imageSize: CGSize) { - self.orientation = orientation - self.imageSize = imageSize - } - } - - public enum Image: Equatable { - case editable(imageSource: ImageSource, metadata: ImageMetadata) - } - - /** - Editable image's size - */ - public var imageSize: CGSize? - - public var orientation: CGImagePropertyOrientation? - - public var editableImage: ImageSource? - - @Edge public var loadingNonFatalErrors: [ImageProviderError] = [] - - @Edge public var loadingFatalErrors: [ImageProviderError] = [] - - public var loadedImage: Image? { - - if let editable = editableImage, let imageSize = imageSize, let orientation = orientation { - return .editable(imageSource: editable, metadata: .init(orientation: orientation, imageSize: imageSize)) - } - - return nil - } - - public init( - imageSize: CGSize? = nil, - orientation: CGImagePropertyOrientation? = nil, - editableImage: ImageSource? = nil - ) { - self.imageSize = imageSize - self.orientation = orientation - self.editableImage = editableImage - } - - public mutating func resolve(with metadata: ImageMetadata) { - imageSize = metadata.imageSize - orientation = metadata.orientation - } - - } - - public let store: DefaultStore - - private var pendingAction: (ImageProvider) -> VergeAnyCancellable - - #if os(iOS) - - private var cancellable: VergeAnyCancellable? - - /// Creates an instance for your own external data provider. - public init( - initialState: State, - pendingAction: @escaping (ImageProvider) -> VergeAnyCancellable - ) { - - self.store = .init(initialState: initialState) - self.pendingAction = pendingAction - - } - - public init(rawData: Data) { - - store = .init( - initialState: .init() - ) - - pendingAction = { `self` in - - guard let filter = CIFilter(imageData: rawData, options: [:]) else { - self.commit { - $0.loadingFatalErrors.append(.failedToCreateCIFilterToLoadRAW) - } - return .init {} - } - - guard let outputImage = filter.outputImage else { - self.commit { - $0.loadingFatalErrors.append(.failedToGetRenderedImageFromRAW) - } - return .init {} - } - - let ciContext = CIContext() - guard let cgImage = ciContext.createCGImage(outputImage, from: outputImage.extent) else { - self.commit { - $0.loadingFatalErrors.append(.failedToCreateCGImageFromRAW) - } - return .init {} - } - - self.commit { - $0.editableImage = .init(cgImage: cgImage) - $0.wrapped.resolve(with: .init(orientation: .up, imageSize: outputImage.extent.size)) - } - - return .init {} - } - - } - - public init(rawDataURL: URL) { - - store = .init( - initialState: .init() - ) - - pendingAction = { `self` in - - guard let filter = CIFilter(imageURL: rawDataURL, options: [:]) else { - self.commit { - $0.loadingFatalErrors.append(.failedToCreateCIFilterToLoadRAW) - } - return .init {} - } - - guard let outputImage = filter.outputImage else { - self.commit { - $0.loadingFatalErrors.append(.failedToGetRenderedImageFromRAW) - } - return .init {} - } - - let ciContext = CIContext() - guard let cgImage = ciContext.createCGImage(outputImage, from: outputImage.extent) else { - self.commit { - $0.loadingFatalErrors.append(.failedToCreateCGImageFromRAW) - } - return .init {} - } - self.commit { - $0.editableImage = .init(cgImage: cgImage) - $0.wrapped.resolve(with: .init(orientation: .up, imageSize: outputImage.extent.size)) - } - return .init {} - } - } - - /// Creates an instance from data - public init(data: Data) throws { - - guard let provider = CGDataProvider(data: data as CFData) else { - throw ImageProviderError.failedToCreateCGDataProvider - } - - guard let imageSource = CGImageSourceCreateWithDataProvider(provider, nil) else { - throw ImageProviderError.failedToCreateCGImageSource - } - - guard let metadata = ImageTool.makeImageMetadata(from: imageSource) else { - throw ImageProviderError.failedToGetImageMetadata - } - - store = .init( - initialState: .init( - editableImage: .init(cgImageSource: imageSource) - ) - ) - - store.commit { - $0.wrapped.resolve(with: metadata) - } - - pendingAction = { _ in - return .init {} - } - } - - /// Creates an instance from UIImage - /// - /// - Attention: To reduce memory footprint, as possible creating an instance from url instead. - public init(image uiImage: UIImage) { - precondition(uiImage.cgImage != nil) - - store = .init( - initialState: .init( - editableImage: .init(image: uiImage) - ) - ) - - store.commit { - $0.wrapped.resolve(with: .init(orientation: .init(uiImage.imageOrientation), imageSize: .init(image: uiImage))) - } - - pendingAction = { _ in return .init {} } - - } - - #endif - - /** - Creates an instance from fileURL. - This is most efficient way to edit image without large memory footprint. - */ - public init( - fileURL: URL - ) throws { - guard fileURL.isFileURL else { - throw ImageProviderError.urlIsNotFileURL(fileURL) - } - - guard let provider = CGDataProvider(url: fileURL as CFURL) else { - throw ImageProviderError.failedToCreateCGDataProvider - } - - guard let imageSource = CGImageSourceCreateWithDataProvider(provider, nil) else { - throw ImageProviderError.failedToCreateCGImageSource - } - - guard let metadata = ImageTool.makeImageMetadata(from: imageSource) else { - throw ImageProviderError.failedToGetImageSize - } - - store = .init( - initialState: .init( - editableImage: .init(cgImageSource: imageSource) - ) - ) - - store.commit { - $0.wrapped.resolve(with: metadata) - } - - pendingAction = { _ in return .init {} } - } - - #if canImport(Photos) - public convenience init?( - contentEditingInput: PHContentEditingInput - ) { - guard let url = contentEditingInput.fullSizeImageURL else { - return nil - } - self.init(editableRemoteURL: url) - } - #endif - - /** - Creates an instance - */ - public convenience init( - editableRemoteURL: URL - ) { - self.init( - editableRemoteURLRequest: URLRequest(url: editableRemoteURL) - ) - } - - public init( - editableRemoteURLRequest: URLRequest - ) { - - store = .init( - initialState: .init( - imageSize: nil, - orientation: nil, - editableImage: nil - ) - ) - - pendingAction = { `self` in - - let editableTask = URLSession.shared.downloadTask(with: editableRemoteURLRequest) { [weak self] url, response, error in - - guard let self = self else { return } - - if let error = error { - self.store.commit { - $0.loadingFatalErrors.append(.failedToDownloadEditableImage(underlyingError: error)) - } - } - - self.commit { state in - if let url = url { - - guard let provider = CGDataProvider(url: url as CFURL) else { - state.loadingFatalErrors.append(ImageProviderError.failedToCreateCGDataProvider) - return - } - - guard let imageSource = CGImageSourceCreateWithDataProvider(provider, nil) else { - state.loadingFatalErrors.append(ImageProviderError.failedToCreateCGImageSource) - return - } - - guard let metadata = ImageTool.makeImageMetadata(from: imageSource) else { - state.loadingNonFatalErrors.append(ImageProviderError.failedToGetImageMetadata) - return - } - - state.wrapped.resolve(with: metadata) - state.editableImage = .init(cgImageSource: imageSource) - } - } - } - - editableTask.resume() - - return .init { - editableTask.cancel() - } - } - } - - #if canImport(Photos) - - public init(asset: PHAsset) { - // TODO: cancellation, Error handeling - - store = .init( - initialState: .init( - editableImage: nil - ) - ) - - pendingAction = { `self` in - - let finalImageRequestOptions = PHImageRequestOptions() - finalImageRequestOptions.deliveryMode = .highQualityFormat - finalImageRequestOptions.isNetworkAccessAllowed = true - finalImageRequestOptions.version = .current - finalImageRequestOptions.resizeMode = .none - - let request = PHImageManager.default().requestImage( - for: asset, - targetSize: PHImageManagerMaximumSize, - contentMode: .aspectFit, - options: finalImageRequestOptions - ) { [weak self] image, info in - - // FIXME: Avoid loading image, get a url instead. - - guard let self = self else { return } - - self.commit { state in - - if let error = info?[PHImageErrorKey] as? Error { - state.loadingFatalErrors.append(.failedToDownloadEditableImage(underlyingError: error)) - return - } - - guard let image = image else { return } - - state.wrapped.resolve(with: .init( - orientation: .init(image.imageOrientation), - imageSize: .init(width: asset.pixelWidth, height: asset.pixelHeight) - )) - state.editableImage = .init(image: image) - } - } - - return .init { - PHImageManager.default().cancelImageRequest(request) - } - } - } - - #endif - - func start() { - guard cancellable == nil else { return } - cancellable = pendingAction(self) - } - - deinit { - EngineLog.debug("[ImageProvider] deinit") - } -} diff --git a/Package/Sources/BrightroomEngine/DataSource/ImageSource.swift b/Package/Sources/BrightroomEngine/DataSource/ImageSource.swift deleted file mode 100644 index 1bf77735..00000000 --- a/Package/Sources/BrightroomEngine/DataSource/ImageSource.swift +++ /dev/null @@ -1,134 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreImage -import UIKit -import Verge - -#if canImport(UIKit) - import UIKit -#endif - -#if canImport(Photos) - import Photos -#endif - - -/// An object that provides an image-data from multiple backing storage. -public final class ImageSource: Equatable { - - private struct Closures { - let readImageSize: () -> CGSize - let loadOriginalCGImage: () -> CGImage - let loadThumbnailCGImage: (CGFloat) -> CGImage - let makeCIImage: () -> CIImage - } - - public static func == (lhs: ImageSource, rhs: ImageSource) -> Bool { - lhs === rhs - } - - private let closures: Closures - - public init(image: UIImage) { - precondition(image.cgImage != nil) - self.closures = .init( - readImageSize: { - image.size.applying(.init(scaleX: image.scale, y: image.scale)) - }, - loadOriginalCGImage: { - image.cgImage! - }, - loadThumbnailCGImage: { (maxPixelSize) -> CGImage in - return ImageTool.makeResizedCGImage( - from: image.cgImage!, - maxPixelSizeHint: maxPixelSize - )! - }, - makeCIImage: { - CIImage(image: image)! - } - ) - } - - public convenience init(cgImage: CGImage) { - self.init(image: UIImage(cgImage: cgImage)) - } - - public init(cgImageSource: CGImageSource) { - self.closures = .init( - readImageSize: { - ImageTool.readImageSize(from: cgImageSource)! - }, - loadOriginalCGImage: { - ImageTool.loadOriginalCGImage(from: cgImageSource, fixesOrientation: false)! - }, - loadThumbnailCGImage: { (maxPixelSize) -> CGImage in - ImageTool.makeResizedCGImage( - from: cgImageSource, - maxPixelSizeHint: maxPixelSize, - fixesOrientation: false - )! - }, - makeCIImage: { - if #available(iOS 13.0, *) { - return CIImage(cgImageSource: cgImageSource, index: 0, options: [:]) - } else { - return CIImage( - cgImage: ImageTool.loadOriginalCGImage(from: cgImageSource, fixesOrientation: false)! - ) - } - } - ) - } - - public func readImageSize() -> CGSize { - closures.readImageSize() - } - - /** - Creates an instance of CGImage full-resolution. - - - Attention: The image is not orientated. - */ - public func loadOriginalCGImage() -> CGImage { - closures.loadOriginalCGImage() - } - - /** - Creates an instance of CGImage resized to maximum pixel size. - - - Attention: The image is not orientated. - */ - public func loadThumbnailCGImage(maxPixelSize: CGFloat) -> CGImage { - closures.loadThumbnailCGImage(maxPixelSize) - } - - /** - Creates an instance of CIImage that backed full-resolution image. - - - Attention: The image is not orientated. - */ - public func makeOriginalCIImage() -> CIImage { - closures.makeCIImage() - } - -} diff --git a/Package/Sources/BrightroomEngine/Engine/BrightRoomImageRenderer.swift b/Package/Sources/BrightroomEngine/Engine/BrightRoomImageRenderer.swift deleted file mode 100644 index ca8e73b7..00000000 --- a/Package/Sources/BrightroomEngine/Engine/BrightRoomImageRenderer.swift +++ /dev/null @@ -1,405 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreImage -import SwiftUI -import UIKit - -@available(*, deprecated, renamed: "BrightRoomImageRenderer", message: "Renamed in favor of SwiftUI.ImageRenderer") -public typealias ImageRenderer = BrightRoomImageRenderer - -/// It renders an image with options -public final class BrightRoomImageRenderer { - - public struct Options { - - public var resolution: Resolution - public var workingFormat: CIFormat - - /// An colorspace that uses on rendering. - /// Result image would use this colorspace. - /// Nil means letting the renderer use the intrinsic colorspace of the working image. - public var workingColorSpace: CGColorSpace? - - /// - /// - Parameters: - /// - resolution: - /// - workingFormat: - /// - workingColorSpace: - public init( - resolution: BrightRoomImageRenderer.Resolution = .full, - workingFormat: CIFormat = .ARGB8, - workingColorSpace: CGColorSpace? = nil - ) { - self.resolution = resolution - self.workingFormat = workingFormat - self.workingColorSpace = workingColorSpace - } - - } - - /** - A result of rendering. - */ - public struct Rendered { - - public enum Engine { - case coreGraphics - case combined - } - - public enum DataType { - case jpeg(quality: CGFloat) - case png - } - - /// A type of engine how rendered by - public let engine: Engine - - /// An Options instance that used in redering. - public let options: Options - - /// A rendered image that working on specified color-space. - /// Orientation fixed. - public let cgImage: CGImage - - public var uiImage: UIImage { - UIImage(cgImage: cgImage, scale: 1, orientation: .up) - .withRenderingMode(.alwaysOriginal) - } - - @available(iOS 13.0, *) - public var swiftUIImage: SwiftUI.Image { - .init(decorative: cgImage, scale: 1, orientation: .up) - } - - init(cgImage: CGImage, options: Options, engine: Engine) { - self.cgImage = cgImage - self.options = options - self.engine = engine - } - - /** - Makes a data of the image that optimized for sharing. - - Since the rendered image is working on DisplayP3 profile, that data might display wrong color on other platform devices. - To avoid those issues, use this method to create data to send instead of creating data from `cgImageDisplayP3`. - */ - public func makeOptimizedForSharingData(dataType: DataType) -> Data { - switch dataType { - case .jpeg(let quality): - return ImageTool.makeImageForJPEGOptimizedSharing(image: cgImage, quality: quality) - case .png: - return ImageTool.makeImageForPNGOptimizedSharing(image: cgImage) - } - } - - } - - private static let queue = DispatchQueue.init(label: "app.muukii.Pixel.renderer") - - public enum Resolution { - case full - case resize(maxPixelSize: CGFloat) - } - - public struct Edit { - public var croppingRect: EditingCrop? - public var modifiers: [AnyFilter] = [] - public var drawer: [GraphicsDrawing] = [] - } - - public let source: ImageSource - public let orientation: CGImagePropertyOrientation - - public var edit: Edit - - public init(source: ImageSource, orientation: CGImagePropertyOrientation) { - self.source = source - self.orientation = orientation - edit = .init() - } - - /// Renders the image according edits asynchronously - /// - /// - Parameters: - /// - options: - /// - callbackQueue: A queue that completion closure runs on. - /// - completion: A closure that runs on rendering completed. - public func render( - options: Options = .init(), - callbackQueue: DispatchQueue = .main, - completion: @escaping ( - Result - ) -> Void - ) { - type(of: self).queue.async { - do { - let rendered = try self.render(options: options) - callbackQueue.async { - completion(.success(rendered)) - } - } catch { - callbackQueue.async { - completion(.failure(error)) - } - } - } - } - - /** - Renders an image according to the editing. - - - Attension: This operation can be run background-thread. - */ - public func render(options: Options = .init()) throws -> Rendered { - if edit.drawer.isEmpty, edit.modifiers.isEmpty, options.workingColorSpace == nil { - return try renderOnlyCropping(options: options) - } else { - return try renderRevison2(options: options) - } - } - - /** - Render for only cropping using CoreGraphics - */ - private func renderOnlyCropping(options: Options = .init()) throws -> Rendered { - - assert(options.workingColorSpace == nil, "This rendering operation no supports working with specifying colorspace.") - - EngineLog.debug(.renderer, "Start render in using CoreGraphics") - - /* - === - === - === - */ - EngineLog.debug(.renderer, "Load full resolution CGImage from ImageSource.") - - let sourceCGImage: CGImage = source.loadOriginalCGImage() - - /* - === - === - === - */ - EngineLog.debug(.renderer, "Fix orientation") - - let orientedImage = try sourceCGImage.oriented(orientation) - - /* - === - === - === - */ - // TODO: Better management of orientation - let crop = edit.croppingRect ?? .init(imageSize: source.readImageSize().applying(cgOrientation: orientation)) - EngineLog.debug(.renderer, "Crop CGImage with extent \(crop)") - - let croppedImage = try orientedImage.croppedWithColorspace(to: crop.cropExtent) - - /* - === - === - === - */ - EngineLog.debug(.renderer, "Resize if needed") - - - let resizedImage: CGImage - - switch options.resolution { - case .full: - resizedImage = croppedImage - case .resize(let maxPixelSize): - resizedImage = try croppedImage.resized(maxPixelSize: maxPixelSize) - } - - /* - === - === - === - */ - EngineLog.debug(.renderer, "Rotation") - - let rotatedImage = try resizedImage.rotated(rotation: crop.rotation) - - return .init(cgImage: rotatedImage, options: options, engine: .coreGraphics) - } - - /** - Render for full features using CoreImage and CoreGraphics - */ - private func renderRevison2( - options: Options = .init(), - debug: @escaping (CIImage) -> Void = { _ in } - ) throws -> Rendered { - - let ciContext = CIContext( - options: [ - .workingFormat: options.workingFormat, - .highQualityDownsample: true, - .useSoftwareRenderer: true, - .cacheIntermediates: false - ] - ) - - let startTime = CACurrentMediaTime() - - EngineLog.debug(.renderer, "Start render in v2 using CIContext => \(ciContext)") - - /* - === - === - === - */ - EngineLog.debug(.renderer, "Take full resolution CIImage from ImageSource.") - - let sourceCIImage: CIImage = source.makeOriginalCIImage().oriented(orientation) - - EngineLog.debug(.renderer, "Input oriented CIImage => \(sourceCIImage)") - - assert( - { - guard let crop = edit.croppingRect else { return true } - return crop.imageSize == CGSize(image: sourceCIImage) - }() - ) - - /* - === - === - === - */ - EngineLog.debug(.renderer, "Applies Effect") - - let effected_CIImage = edit.modifiers.reduce(sourceCIImage) { image, modifier in - modifier.apply(to: image, sourceImage: sourceCIImage) - } - - /* - === - === - === - */ - EngineLog.debug(.renderer, "Applies Crop to effected image") - - // TODO: Better management of orientation - let crop = edit.croppingRect ?? .init(imageSize: source.readImageSize().applying(cgOrientation: orientation)) - - let cropped_effected_CIImage = effected_CIImage.cropped(to: crop) - - debug(cropped_effected_CIImage) - - /* - === - === - === - */ - EngineLog.debug(.renderer, "Creates CGImage from crop applied CIImage.") - - /** - To keep wide-color(DisplayP3), use createCGImage instead drawing with CIContext - */ - let cropped_effected_CGImage = ciContext.createCGImage( - cropped_effected_CIImage, - from: cropped_effected_CIImage.extent, - format: options.workingFormat, - colorSpace: options.workingColorSpace ?? sourceCIImage.colorSpace, - deferred: false - )! - - EngineLog.debug(.renderer, "Created effected CGImage => \(cropped_effected_CGImage)") - - /* - === - === - === - */ - - let drawings_CGImage: CGImage - - if edit.drawer.isEmpty { - EngineLog.debug(.renderer, "No drawings") - - drawings_CGImage = cropped_effected_CGImage - } else { - EngineLog.debug(.renderer, "Found drawings") - /** - Render drawings - */ - drawings_CGImage = try CGContext.makeContext(for: cropped_effected_CGImage) - .perform { c in - - c.draw( - cropped_effected_CGImage, - in: .init(origin: .zero, size: cropped_effected_CGImage.size) - ) - c.translateBy(x: -crop.cropExtent.origin.x, y: -crop.cropExtent.origin.y) - - self.edit.drawer.forEach { drawer in - drawer.draw(in: c) - } - } - .makeImage() - .unwrap() - } - - /* - === - === - === - */ - - let resizedImage: CGImage - - switch options.resolution { - case .full: - - EngineLog.debug(.renderer, "No resizing") - - resizedImage = drawings_CGImage - - case let .resize(maxPixelSize): - - EngineLog.debug(.renderer, "Resizing with maxPixelSize: \(maxPixelSize)") - - resizedImage = try drawings_CGImage.resized(maxPixelSize: maxPixelSize) - - } - - /* - === - === - === - */ - - EngineLog.debug(.renderer, "Rotates image if needed") - - let rotatedImage = try resizedImage.rotated(rotation: crop.rotation) - - let duration = CACurrentMediaTime() - startTime - EngineLog.debug(.renderer, "Rendering has completed - took \(duration * 1000)ms") - - return .init(cgImage: rotatedImage, options: options, engine: .combined) - - } -} diff --git a/Package/Sources/BrightroomEngine/Engine/CoreGraphics+.swift b/Package/Sources/BrightroomEngine/Engine/CoreGraphics+.swift deleted file mode 100644 index e686dc21..00000000 --- a/Package/Sources/BrightroomEngine/Engine/CoreGraphics+.swift +++ /dev/null @@ -1,239 +0,0 @@ -// -// Copyright (c) 2021 Hiroshi Kimura(Muukii) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreGraphics -import ImageIO - -extension CGContext { - - @discardableResult - func perform(_ drawing: (CGContext) -> Void) -> CGContext { - drawing(self) - return self - } - - static func makeContext(for image: CGImage, size: CGSize? = nil) throws -> CGContext { - - var bitmapInfo = image.bitmapInfo - - /** - Modifies alpha info in order to solve following issues: - - [For creating CGContext] - - A screenshot image taken on iPhone might be DisplayP3 16bpc. This is not supported in CoreGraphics. - https://stackoverflow.com/a/42684334/2753383 - - [For MTLTexture] - - An image loaded from ImageIO seems to contains something different bitmap-info compared with UIImage(named:) - That causes creating broken MTLTexture, technically texture contains alpha and wrong color format. - I don't know why it happens. - */ - bitmapInfo.remove(.alphaInfoMask) - bitmapInfo.formUnion(.init(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)) - /** - The image from PHImageManager uses `.byteOrder32Little`. - This is not compatible with MTLTexture. - */ - bitmapInfo.remove(.byteOrder32Little) - - /** - - Ref: https://github.com/guoyingtao/Mantis/issues/12 - */ - let outputColorSpace: CGColorSpace - - if let colorSpace = image.colorSpace, colorSpace.supportsOutput { - outputColorSpace = colorSpace - } else { - EngineLog.error(.default, "CGImage's color-space does not support output. \(image.colorSpace as Any)") - outputColorSpace = CGColorSpaceCreateDeviceRGB() - } - - let width = size.map { Int($0.width) } ?? image.width - let height = size.map { Int($0.height) } ?? image.height - - if let context = CGContext( - data: nil, - width: width, - height: height, - bitsPerComponent: image.bitsPerComponent, - bytesPerRow: 0, - space: outputColorSpace, - bitmapInfo: bitmapInfo.rawValue - ) { - return context - } - return - try CGContext( - data: nil, - width: width, - height: height, - bitsPerComponent: 8, - bytesPerRow: 8 * 4 * image.width, - space: CGColorSpaceCreateDeviceRGB(), - bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue - ).unwrap() - } - - fileprivate func detached(_ perform: () -> Void) { - saveGState() - perform() - restoreGState() - } -} - -extension CGImage { - - var size: CGSize { - return .init(width: width, height: height) - } - - func croppedWithColorspace(to cropRect: CGRect) throws -> CGImage { - - let cgImage = try autoreleasepool { () -> CGImage? in - - let context = try CGContext.makeContext(for: self, size: cropRect.size) - .perform { c in - - c.draw( - self, - in: CGRect( - origin: .init( - x: -cropRect.origin.x, - y: -(size.height - cropRect.maxY) - ), - size: size - ) - ) - - } - return context.makeImage() - } - - return try cgImage.unwrap() - - } - - func resized(maxPixelSize: CGFloat) throws -> CGImage { - - let cgImage = try autoreleasepool { () -> CGImage? in - - let targetSize = Geometry.sizeThatAspectFit( - size: size, - maxPixelSize: maxPixelSize - ) - - let context = try CGContext.makeContext(for: self, size: targetSize) - .perform { c in - c.interpolationQuality = .high - c.draw(self, in: c.boundingBoxOfClipPath) - } - return context.makeImage() - } - - return try cgImage.unwrap() - } - - enum Flipping { - case vertically - case horizontally - } - - func rotated(angle: CGFloat, flipping: Flipping? = nil) throws -> CGImage { - guard angle != 0 else { - return self - } - - var rotatedSize: CGSize = - size - .applying(.init(rotationAngle: angle)) - - rotatedSize.width = abs(rotatedSize.width) - rotatedSize.height = abs(rotatedSize.height) - - let cgImage = try autoreleasepool { () -> CGImage? in - let rotatingContext = try CGContext.makeContext(for: self, size: rotatedSize) - .perform { c in - - if let flipping = flipping { - switch flipping { - case .vertically: - c.translateBy(x: 0, y: rotatedSize.height) - c.scaleBy(x: 1, y: -1) - case .horizontally: - c.translateBy(x: rotatedSize.width, y: 0) - c.scaleBy(x: -1, y: 1) - } - } - - c.translateBy(x: rotatedSize.width / 2, y: rotatedSize.height / 2) - c.rotate(by: angle) - c.translateBy( - x: -size.width / 2, - y: -size.height / 2 - ) - - c.draw(self, in: .init(origin: .zero, size: self.size)) - } - - return rotatingContext.makeImage() - } - - return try cgImage.unwrap() - } - - func oriented(_ orientation: CGImagePropertyOrientation) throws -> CGImage { - - let angle: CGFloat - - switch orientation { - case .down, .downMirrored: - angle = CGFloat.pi - case .left, .leftMirrored: - angle = CGFloat.pi / 2.0 - case .right, .rightMirrored: - angle = CGFloat.pi / -2.0 - case .up, .upMirrored: - angle = 0 - } - - let flipping: Flipping? - switch orientation { - case .upMirrored, .downMirrored: - flipping = .horizontally - case .leftMirrored, .rightMirrored: - flipping = .vertically - case .up, .down, .left, .right: - flipping = nil - } - - let result = try rotated(angle: angle, flipping: flipping) - - return result - } - - func rotated(rotation: EditingCrop.Rotation, flipping: Flipping? = nil) - throws -> CGImage - { - try rotated(angle: -rotation.angle, flipping: flipping) - } - -} diff --git a/Package/Sources/BrightroomEngine/Engine/ImageTool.swift b/Package/Sources/BrightroomEngine/Engine/ImageTool.swift deleted file mode 100644 index ae3fadee..00000000 --- a/Package/Sources/BrightroomEngine/Engine/ImageTool.swift +++ /dev/null @@ -1,295 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import AVFoundation -import CoreImage -import MobileCoreServices -import UIKit - -/// A set of functions that handle image and manipulations. -public enum ImageTool { - - public static func makeImageMetadata( - from imageSource: CGImageSource - ) -> ImageProvider.State.ImageMetadata? { - let propertiesOptions = [kCGImageSourceShouldCache: false] as CFDictionary - guard - let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, propertiesOptions) - as? [CFString: Any] - else { - return nil - } - - EngineLog.debug("\(properties)") - - guard - let width = properties[kCGImagePropertyPixelWidth] as? CGFloat, - let height = properties[kCGImagePropertyPixelHeight] as? CGFloat - else { - return nil - } - - let orientation: CGImagePropertyOrientation = - (properties[kCGImagePropertyTIFFOrientation] as? UInt32).flatMap { - CGImagePropertyOrientation(rawValue: $0) - } ?? .up - - let size = CGSize(width: width, height: height) - - return .init(orientation: orientation, imageSize: size.applying(cgOrientation: orientation)) - } - - /** - Returns a pixel size of image. - - https://oleb.net/blog/2011/09/accessing-image-properties-without-loading-the-image-into-memory/ - */ - public static func readImageSize(from imageSource: CGImageSource) -> CGSize? { - let propertiesOptions = [kCGImageSourceShouldCache: false] as CFDictionary - guard - let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, propertiesOptions) - as? [CFString: Any] - else { - return nil - } - - guard - let width = properties[kCGImagePropertyPixelWidth] as? CGFloat, - let height = properties[kCGImagePropertyPixelHeight] as? CGFloat - else { - return nil - } - return CGSize(width: width, height: height) - } - - public static func readOrientation( - from imageSource: CGImageSource - ) -> CGImagePropertyOrientation? { - let propertiesOptions = [kCGImageSourceShouldCache: false] as CFDictionary - - guard - let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, propertiesOptions) - as? [CFString: Any] - else { - return nil - } - - let _orientation: CGImagePropertyOrientation? = - (properties[kCGImagePropertyTIFFOrientation] as? UInt32).flatMap { - CGImagePropertyOrientation(rawValue: $0) - } - - guard - let orientation = _orientation - else { - return nil - } - - return orientation - } - - public static func loadOriginalCGImage( - from imageSource: CGImageSource, - fixesOrientation: Bool - ) -> CGImage? { - CGImageSourceCreateImageAtIndex( - imageSource, - 0, - [ - kCGImageSourceCreateThumbnailWithTransform: fixesOrientation - ] as CFDictionary - ) - } - - public static func writeImageToTmpDirectory(image: UIImage) -> URL? { - let directory = NSTemporaryDirectory() - let fileName = UUID().uuidString - let path = directory + "/" + fileName - let destination = URL(fileURLWithPath: path) - - guard let data = image.pngData() else { - return nil - } - - do { - try data.write(to: destination, options: []) - } catch { - return nil - } - - return destination - } - - public static func makeResizedCGImage( - from imageSource: CGImageSource, - maxPixelSizeHint: CGFloat, - fixesOrientation: Bool - ) -> CGImage? { - let imageSize: CGSize = { - guard - let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [AnyHashable: Any] - else { - return .zero - } - let pixelWidth = imageProperties[kCGImagePropertyPixelWidth as String] as! CFNumber - let pixelHeight = imageProperties[kCGImagePropertyPixelHeight as String] as! CFNumber - var width: CGFloat = 0 - var height: CGFloat = 0 - CFNumberGetValue(pixelWidth, .cgFloatType, &width) - CFNumberGetValue(pixelHeight, .cgFloatType, &height) - return CGSize(width: width, height: height) - }() - let maxPixelSize: CGFloat = { - let largestSide = max(imageSize.width, imageSize.height) - let smallestSide = min(imageSize.width, imageSize.height) - guard smallestSide >= maxPixelSizeHint else { - return largestSide - } - return largestSide * maxPixelSizeHint / smallestSide - }() - return makeResizedCGImage(from: imageSource, maxPixelSize: maxPixelSize, fixesOrientation: fixesOrientation) - } - - public static func makeResizedCGImage( - from imageSource: CGImageSource, - maxPixelSize: CGFloat, - fixesOrientation: Bool - ) - -> CGImage? - { - - let scaledImage = try? CGImageSourceCreateThumbnailAtIndex( - imageSource, - 0, - [ - kCGImageSourceThumbnailMaxPixelSize: maxPixelSize, - kCGImageSourceCreateThumbnailFromImageAlways: true, - kCGImageSourceCreateThumbnailWithTransform: false, - ] as CFDictionary - ) - .flatMap { image in - try CGContext.makeContext(for: image).perform { (c) in - c.draw(image, in: c.boundingBoxOfClipPath) - } - .makeImage() - } - - return scaledImage - } - - public static func makeResizedCGImage( - from sourceImage: CGImage, - maxPixelSizeHint: CGFloat - ) -> CGImage? { - let maxPixelSize: CGFloat = { - let largestSide = max(sourceImage.size.width, sourceImage.size.height) - let smallestSide = min(sourceImage.size.width, sourceImage.size.height) - guard smallestSide >= maxPixelSizeHint else { - return largestSide - } - return largestSide * maxPixelSizeHint / smallestSide - }() - return makeResizedCGImage(from: sourceImage, maxPixelSize: maxPixelSize) - } - - public static func makeResizedCGImage( - from sourceImage: CGImage, - maxPixelSize: CGFloat - ) -> CGImage? { - - let imageSize = CGSize( - width: sourceImage.width, - height: sourceImage.height - ) - - let targetSize: CGSize = imageSize.scaled(maxPixelSize: maxPixelSize) - - guard let context = try? CGContext.makeContext(for: sourceImage, size: targetSize) else { - EngineSanitizer.global.onDidFindRuntimeError( - .failedToCreateCGContext(sourceImage: sourceImage) - ) - return nil - } - guard let image = context.perform({ c in - c.draw(sourceImage, in: .init(origin: .zero, size: targetSize)) - }).makeImage() else { - EngineSanitizer.global.onDidFindRuntimeError( - .failedToCreateResizedCGImage(sourceImage: sourceImage, maxPixelSize: maxPixelSize) - ) - return nil - } - return image - } - - /// Makes an image that optimized for sharing. - /// It contains fixing color space to sRGB - public static func makeImageForJPEGOptimizedSharing( - image: CGImage, - quality: CGFloat = 1 - ) -> Data { - let data = NSMutableData() - - let destination = CGImageDestinationCreateWithData( - data, - kUTTypeJPEG, - 1, - [:] as CFDictionary - )! - - CGImageDestinationAddImage( - destination, - image, - [ - kCGImageDestinationLossyCompressionQuality: quality, - kCGImageDestinationOptimizeColorForSharing: true, - ] as CFDictionary - ) - - CGImageDestinationFinalize(destination) - - return data as Data - } - - /// Makes an image that optimized for sharing. - /// It contains fixing color space to sRGB - public static func makeImageForPNGOptimizedSharing(image: CGImage) -> Data { - let data = NSMutableData() - - let destination = CGImageDestinationCreateWithData( - data, - kUTTypePNG, - 1, - [:] as CFDictionary - )! - - CGImageDestinationAddImage( - destination, - image, - [ - kCGImageDestinationOptimizeColorForSharing: true - ] as CFDictionary - ) - - CGImageDestinationFinalize(destination) - - return data as Data - } -} diff --git a/Package/Sources/BrightroomEngine/Filter/FilterBrightness.swift b/Package/Sources/BrightroomEngine/Filter/FilterBrightness.swift deleted file mode 100644 index 5891461b..00000000 --- a/Package/Sources/BrightroomEngine/Filter/FilterBrightness.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit -import CoreImage - -public struct FilterBrightness: Filtering, Equatable, Codable { - - public static let range: ParameterRange = .init(min: -0.2, max: 0.2) - - public var value: Double = 0 - - public init() { - - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - return - image - .applyingFilter( - "CIColorControls", - parameters: [ - "inputBrightness": value, - ] - ) - } - -} diff --git a/Package/Sources/BrightroomEngine/Filter/FilterColorCube.swift b/Package/Sources/BrightroomEngine/Filter/FilterColorCube.swift deleted file mode 100644 index 50bf5e29..00000000 --- a/Package/Sources/BrightroomEngine/Filter/FilterColorCube.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit -import CoreImage - -public struct PreviewFilterColorCube : Equatable { - - public let image: CIImage - public let filter: FilterColorCube - - init(sourceImage: CIImage, filter: FilterColorCube) { - self.filter = filter - self.image = filter.apply(to: sourceImage, sourceImage: sourceImage) - } - -} - -/// A Filter using LUT Image (backed by CIColorCubeWithColorSpace) -/// About LUT Image -> https://en.wikipedia.org/wiki/Lookup_table -public struct FilterColorCube : Filtering { - - public static let range: ParameterRange = .init(min: 0, max: 1) - - public let name: String - public let identifier: String - public var amount: Double = 1 - public let lutImage: ImageSource - public let dimension: Int - - public init( - name: String, - identifier: String, - lutImage: ImageSource, - dimension: Int - ) { - - self.dimension = dimension - self.lutImage = lutImage - self.name = name - self.identifier = identifier - } - - public func hash(into hasher: inout Hasher) { - name.hash(into: &hasher) - identifier.hash(into: &hasher) - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - - #if false - - let f = ColorLookup() - f.inputColorLookupTable = lutImage.ciImage ?? CIImage(image: lutImage)! - f.inputImage = image - f.inputIntensity = CGFloat(0.8) - - return f.outputImage! - - #else - - let f: CIFilter = ColorCubeHelper.makeColorCubeFilter( - lutImage: lutImage, - dimension: dimension, - cacheKey: identifier - ) - - f.setValue(image, forKeyPath: kCIInputImageKey) - - let background = image - let foreground = f.outputImage!.applyingFilter( - "CIColorMatrix", parameters: [ - "inputRVector": CIVector(x: 1, y: 0, z: 0, w: 0), - "inputGVector": CIVector(x: 0, y: 1, z: 0, w: 0), - "inputBVector": CIVector(x: 0, y: 0, z: 1, w: 0), - "inputAVector": CIVector(x: 0, y: 0, z: 0, w: CGFloat(amount)), - "inputBiasVector": CIVector(x: 0, y: 0, z: 0, w: 0), - ]) - - let composition = CIFilter( - name: "CISourceOverCompositing", - parameters: [ - kCIInputImageKey : foreground, - kCIInputBackgroundImageKey : background - ])! - - return composition.outputImage! - - #endif - } -} diff --git a/Package/Sources/BrightroomEngine/Filter/FilterContrast.swift b/Package/Sources/BrightroomEngine/Filter/FilterContrast.swift deleted file mode 100644 index eb4470e0..00000000 --- a/Package/Sources/BrightroomEngine/Filter/FilterContrast.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -import UIKit -import CoreImage - -public struct FilterContrast: Filtering, Equatable, Codable { - - public static let range: ParameterRange = .init(min: -0.18, max: 0.18) - - public var value: Double = 0 - - public init() { - - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - return - image - .applyingFilter( - "CIColorControls", - parameters: [ - kCIInputContrastKey: 1 + value, - ] - ) - } - -} diff --git a/Package/Sources/BrightroomEngine/Filter/FilterExposure.swift b/Package/Sources/BrightroomEngine/Filter/FilterExposure.swift deleted file mode 100644 index 211d61c3..00000000 --- a/Package/Sources/BrightroomEngine/Filter/FilterExposure.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit -import CoreImage - -public struct FilterExposure : Filtering, Equatable, Codable { - - public static let range: ParameterRange = .init(min: -1.8, max: 1.8) - - public var value: Double = 0 - - public init() { - - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - return - image - .applyingFilter( - "CIExposureAdjust", - parameters: [ - kCIInputEVKey: value as AnyObject - ] - ) - } - -} diff --git a/Package/Sources/BrightroomEngine/Filter/FilterFade.swift b/Package/Sources/BrightroomEngine/Filter/FilterFade.swift deleted file mode 100644 index e03ae2d9..00000000 --- a/Package/Sources/BrightroomEngine/Filter/FilterFade.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreImage -import UIKit - -public struct FilterFade : Filtering, Equatable, Codable { - - public enum Params { - public static let intensity: ParameterRange = .init(min: 0, max: 0.5) - } - - public var intensity: Double = 0 - - public init() { - - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - - let background = image - let foreground = CIFilter( - name: "CIConstantColorGenerator", - parameters: [kCIInputColorKey : CIColor(red: 1, green: 1, blue: 1, alpha: CGFloat(intensity))] - )! - .outputImage! - .cropped(to: image.extent) - - let composition = CIFilter( - name: "CISourceOverCompositing", - parameters: [ - kCIInputImageKey : foreground, - kCIInputBackgroundImageKey : background - ])! - - return composition.outputImage! - } - -} diff --git a/Package/Sources/BrightroomEngine/Filter/FilterGaussianBlur.swift b/Package/Sources/BrightroomEngine/Filter/FilterGaussianBlur.swift deleted file mode 100644 index 0ab93963..00000000 --- a/Package/Sources/BrightroomEngine/Filter/FilterGaussianBlur.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -import UIKit -import CoreImage - -public struct FilterGaussianBlur : Filtering, Equatable, Codable { - - public static let range: ParameterRange = .init(min: 0, max: 100) - - public var value: Double = 0 - - public init() { - - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - - let radius = RadiusCalculator.radius(value: value, max: FilterGaussianBlur.range.max, imageExtent: image.extent) - - return - image - .clamped(to: image.extent) - .applyingFilter( - "CIGaussianBlur", - parameters: [ - "inputRadius" : radius - ]) - .cropped(to: image.extent) - } - -} diff --git a/Package/Sources/BrightroomEngine/Filter/FilterHighlightShadowTint.swift b/Package/Sources/BrightroomEngine/Filter/FilterHighlightShadowTint.swift deleted file mode 100644 index 39382e4f..00000000 --- a/Package/Sources/BrightroomEngine/Filter/FilterHighlightShadowTint.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreImage -import UIKit - -public struct FilterHighlightShadowTint : Filtering, Equatable { - - public var highlightColor: CIColor = CIColor(red: 0, green: 0, blue: 0, alpha: 0) - public var shadowColor: CIColor = CIColor(red: 0, green: 0, blue: 0, alpha: 0) - - public init() { - - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - - let highlight = CIFilter( - name: "CIConstantColorGenerator", - parameters: [kCIInputColorKey : highlightColor] - )! - .outputImage! - .cropped(to: image.extent) - - let shadow = CIFilter( - name: "CIConstantColorGenerator", - parameters: [kCIInputColorKey : shadowColor] - )! - .outputImage! - .cropped(to: image.extent) - - let darken = CIFilter( - name: "CISourceOverCompositing", - parameters: [ - kCIInputImageKey : shadow, - kCIInputBackgroundImageKey : image - ])! - - let lighten = CIFilter( - name: "CISourceOverCompositing", - parameters: [ - kCIInputImageKey : highlight, - kCIInputBackgroundImageKey : darken.outputImage! - ])! - - return lighten.outputImage! - } -} diff --git a/Package/Sources/BrightroomEngine/Filter/FilterHighlights.swift b/Package/Sources/BrightroomEngine/Filter/FilterHighlights.swift deleted file mode 100644 index e327eac7..00000000 --- a/Package/Sources/BrightroomEngine/Filter/FilterHighlights.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -import UIKit -import CoreImage - -public struct FilterHighlights: Filtering, Equatable, Codable { - - public static let range: ParameterRange = .init(min: 0, max: 1) - - public var value: Double = 0 - - public init() { - - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - - return - image - .applyingFilter("CIHighlightShadowAdjust", parameters: ["inputHighlightAmount": 1 - value]) - } -} diff --git a/Package/Sources/BrightroomEngine/Filter/FilterPreset.swift b/Package/Sources/BrightroomEngine/Filter/FilterPreset.swift deleted file mode 100644 index 65fda10c..00000000 --- a/Package/Sources/BrightroomEngine/Filter/FilterPreset.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreImage -import UIKit - -public struct FilterPreset: Filtering { - - public static let range: ParameterRange = .init(min: 0, max: 1) - - public let name: String - public let identifier: String - public let filters: [AnyFilter] - public let userInfo: [String : AnyHashable] - - public init( - name: String, - identifier: String, - filters: [AnyFilter], - userInfo: [String : AnyHashable] - ) { - - self.name = name - self.identifier = identifier - self.filters = filters - self.userInfo = userInfo - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - - filters.reduce(image) { (image, filter) -> CIImage in - filter.apply(to: image, sourceImage: sourceImage) - } - - } -} - -public struct PreviewFilterPreset: Hashable { - - /** - An CIImage applied preset. - Using ``MetalImageView`` may get better performance to display instead of ``UIImageView``. - */ - public let image: CIImage - - public let filter: FilterPreset - - init(sourceImage: CIImage, filter: FilterPreset) { - self.filter = filter - self.image = filter.apply(to: sourceImage, sourceImage: sourceImage) - } - -} diff --git a/Package/Sources/BrightroomEngine/Filter/FilterSaturation.swift b/Package/Sources/BrightroomEngine/Filter/FilterSaturation.swift deleted file mode 100644 index d1541198..00000000 --- a/Package/Sources/BrightroomEngine/Filter/FilterSaturation.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit -import CoreImage - -public struct FilterSaturation: Filtering, Equatable, Codable { - - public static let range: ParameterRange = .init(min: -1, max: 1) - - public var value: Double = 0 - - public init() { - - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - return - image - .applyingFilter( - "CIColorControls", - parameters: [ - kCIInputSaturationKey: 1 + value, - ] - ) - } -} diff --git a/Package/Sources/BrightroomEngine/Filter/FilterShadows.swift b/Package/Sources/BrightroomEngine/Filter/FilterShadows.swift deleted file mode 100644 index 4d0005a3..00000000 --- a/Package/Sources/BrightroomEngine/Filter/FilterShadows.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit -import CoreImage - -public struct FilterShadows: Filtering, Equatable, Codable { - - public static let range: ParameterRange = .init(min: -1, max: 1) - - public var value: Double = 0 - - public init() { - - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - - return - image - .applyingFilter("CIHighlightShadowAdjust", parameters: ["inputShadowAmount" : value]) - } -} diff --git a/Package/Sources/BrightroomEngine/Filter/FilterSharpen.swift b/Package/Sources/BrightroomEngine/Filter/FilterSharpen.swift deleted file mode 100644 index ee057f0f..00000000 --- a/Package/Sources/BrightroomEngine/Filter/FilterSharpen.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit -import CoreImage - -public struct FilterSharpen: Filtering, Equatable, Codable { - - public enum Params { - public static let radius: ParameterRange = .init(min: 0, max: 20) - public static let sharpness: ParameterRange = .init(min: 0, max: 1) - } - - public var sharpness: Double = 0 - public var radius: Double = 0 - - public init() { - - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - - let _radius = RadiusCalculator.radius(value: radius, max: FilterGaussianBlur.range.max, imageExtent: image.extent) - - return - image - .applyingFilter( - "CISharpenLuminance", parameters: [ - "inputRadius" : _radius, - "inputSharpness": sharpness, - ]) - } -} diff --git a/Package/Sources/BrightroomEngine/Filter/FilterTemperature.swift b/Package/Sources/BrightroomEngine/Filter/FilterTemperature.swift deleted file mode 100644 index 91e862f2..00000000 --- a/Package/Sources/BrightroomEngine/Filter/FilterTemperature.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit -import CoreImage - -public struct FilterTemperature: Filtering, Equatable, Codable { - - public static let range: ParameterRange = .init(min: -3000, max: 3000) - - public var value: Double = 0 - - public init() { - - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - return - image - .applyingFilter( - "CITemperatureAndTint", - parameters: [ - "inputNeutral": CIVector.init(x: CGFloat(value) + 6500, y: 0), - "inputTargetNeutral": CIVector.init(x: 6500, y: 0), - ] - ) - } - - -} diff --git a/Package/Sources/BrightroomEngine/Filter/FilterUnsharpMask.swift b/Package/Sources/BrightroomEngine/Filter/FilterUnsharpMask.swift deleted file mode 100644 index a637a255..00000000 --- a/Package/Sources/BrightroomEngine/Filter/FilterUnsharpMask.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreImage - -public struct FilterUnsharpMask: Filtering, Equatable, Codable { - - public enum Params { - public static let intensity: ParameterRange = .init(min: 0, max: 0.3) - public static let radius: ParameterRange = .init(min: 0, max: 1) - } - - public var intensity: Double = 0 - public var radius: Double = 0 - - public init() { - - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - - let _radius = RadiusCalculator.radius(value: radius, max: FilterUnsharpMask.Params.radius.max, imageExtent: image.extent) - - return - image - .applyingFilter( - "CIUnsharpMask", - parameters: [ - "inputIntensity" : intensity, - "inputRadius" : _radius, - ]) - } -} diff --git a/Package/Sources/BrightroomEngine/Filter/FilterVignette.swift b/Package/Sources/BrightroomEngine/Filter/FilterVignette.swift deleted file mode 100644 index 268e07d3..00000000 --- a/Package/Sources/BrightroomEngine/Filter/FilterVignette.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -import UIKit -import CoreImage - -public struct FilterVignette: Filtering, Equatable, Codable { - - public static let range: ParameterRange = .init(min: 0, max: 2) - - public var value: Double = 0 - - public init() { - - } - - public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { - - let radius = RadiusCalculator.radius(value: value, max: FilterVignette.range.max, imageExtent: image.extent) - - return - image.applyingFilter( - "CIVignette", - parameters: [ - kCIInputRadiusKey: radius as AnyObject, - kCIInputIntensityKey: value as AnyObject, - ]) - } -} diff --git a/Package/Sources/BrightroomEngine/Info.plist b/Package/Sources/BrightroomEngine/Info.plist deleted file mode 100644 index e1fe4cfb..00000000 --- a/Package/Sources/BrightroomEngine/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/Package/Sources/BrightroomEngine/Library/CIImage+.swift b/Package/Sources/BrightroomEngine/Library/CIImage+.swift deleted file mode 100644 index 7da1092a..00000000 --- a/Package/Sources/BrightroomEngine/Library/CIImage+.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) 2021 Hiroshi Kimura(Muukii) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreImage - -extension CIImage { - - public func removingExtentOffset() -> CIImage { - transformed( - by: .init( - translationX: -extent.origin.x, - y: -extent.origin.y - )) - } -} diff --git a/Package/Sources/BrightroomEngine/Library/EngineLog.swift b/Package/Sources/BrightroomEngine/Library/EngineLog.swift deleted file mode 100644 index 785c1eea..00000000 --- a/Package/Sources/BrightroomEngine/Library/EngineLog.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -import os.log - -enum EngineLog { - - private static let osLog = OSLog.init(subsystem: "PixelEngine", category: "Engine") - - static func debug(_ object: Any...) { - #if DEBUG - if #available(iOS 12.0, *) { - os_log(.debug, log: osLog, "%@", object.map { "\($0)" }.joined(separator: " ")) - } else { - os_log("%@", log: osLog, type: .debug, object.map { "\($0)" }.joined(separator: " ")) - } - #endif - } - - static func debug(_ log: OSLog, _ object: Any...) { - os_log(.debug, log: log, "%@", object.map { "\($0)" }.joined(separator: " ")) - } - - static func error(_ log: OSLog, _ object: Any...) { - os_log(.error, log: log, "%@", object.map { "\($0)" }.joined(separator: " ")) - } - -} - -extension OSLog { - - static let renderer = OSLog.init(subsystem: "BrightroomEngine", category: "🎨 Renderer") - static let stack = OSLog.init(subsystem: "BrightroomEngine", category: "🥞 Stack") - -} diff --git a/Package/Sources/BrightroomEngine/Library/EngineSanitizer.swift b/Package/Sources/BrightroomEngine/Library/EngineSanitizer.swift deleted file mode 100644 index e8195209..00000000 --- a/Package/Sources/BrightroomEngine/Library/EngineSanitizer.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import Foundation -import CoreGraphics - -public enum EngineRuntimeError: Swift.Error { - case failedToCreateResizedCGImage(sourceImage: CGImage, maxPixelSize: CGFloat) - case failedToCreateCGContext(sourceImage: CGImage) - case failedToRenderCGImageForCrop(sourceImage: CGImage) -} - -public final class EngineSanitizer { - - public static let global = EngineSanitizer() - - public var onDidFindRuntimeError: (EngineRuntimeError) -> Void = { _ in } - - public init() { - - } -} diff --git a/Package/Sources/BrightroomEngine/Library/Geometry.swift b/Package/Sources/BrightroomEngine/Library/Geometry.swift deleted file mode 100644 index 532c5e2b..00000000 --- a/Package/Sources/BrightroomEngine/Library/Geometry.swift +++ /dev/null @@ -1,265 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreImage -import UIKit - -public enum Geometry { - - public static func sizeThatAspectFit(size: CGSize, maxPixelSize: CGFloat) -> CGSize { - - guard size.width >= maxPixelSize || size.height >= maxPixelSize else { - return size - } - - var s = size - - if size.width > size.height { - s.width = maxPixelSize - s.height *= maxPixelSize / size.width - } else { - s.height = maxPixelSize - s.width *= maxPixelSize / size.height - } - - s.width.round() - s.height.round() - - return s - } - - public static func sizeThatAspectFit(aspectRatio: CGSize, boundingSize: CGSize) -> CGSize { - let widthRatio = boundingSize.width / aspectRatio.width - let heightRatio = boundingSize.height / aspectRatio.height - var size = boundingSize - - if widthRatio < heightRatio { - size.height = boundingSize.width / aspectRatio.width * aspectRatio.height - } else if heightRatio < widthRatio { - size.width = boundingSize.height / aspectRatio.height * aspectRatio.width - } - - return CGSize( - width: ceil(size.width), - height: ceil(size.height) - ) - } - - public static func sizeThatAspectFill(aspectRatio: CGSize, minimumSize: CGSize) -> CGSize { - let widthRatio = minimumSize.width / aspectRatio.width - let heightRatio = minimumSize.height / aspectRatio.height - - var size = minimumSize - - if widthRatio > heightRatio { - size.height = minimumSize.width / aspectRatio.width * aspectRatio.height - } else if heightRatio > widthRatio { - size.width = minimumSize.height / aspectRatio.height * aspectRatio.width - } - - return CGSize( - width: ceil(size.width), - height: ceil(size.height) - ) - } - - public static func rectThatAspectFit(aspectRatio: CGSize, boundingRect: CGRect) -> CGRect { - let size = sizeThatAspectFit(aspectRatio: aspectRatio, boundingSize: boundingRect.size) - var origin = boundingRect.origin - origin.x += (boundingRect.size.width - size.width) / 2.0 - origin.y += (boundingRect.size.height - size.height) / 2.0 - return CGRect(origin: origin, size: size) - } - - public static func rectThatAspectFill(aspectRatio: CGSize, minimumRect: CGRect) -> CGRect { - let size = sizeThatAspectFill(aspectRatio: aspectRatio, minimumSize: minimumRect.size) - var origin = CGPoint.zero - origin.x = (minimumRect.size.width - size.width) / 2.0 - origin.y = (minimumRect.size.height - size.height) / 2.0 - return CGRect(origin: origin, size: size) - } - - public static func diagonalRatio(to: CGSize, from: CGSize) -> CGFloat { - let _from = sqrt(pow(from.height, 2) + pow(from.width, 2)) - let _to = sqrt(pow(to.height, 2) + pow(to.width, 2)) - - return _to / _from - } -} - -extension CGSize { - - /** - Creates an instance from CGPoint. - The values would be rounded. - */ - init(image: CIImage) { - self = image.extent.size - } - - init(image: UIImage) { - self.init( - width: image.size.width * image.scale, - height: image.size.height * image.scale - ) - } - - var aspectRatio: PixelAspectRatio { - .init(width: CGFloat(width), height: CGFloat(height)) - } - - func scaled(maxPixelSize: CGFloat) -> CGSize { - - Geometry.sizeThatAspectFit(size: self, maxPixelSize: maxPixelSize) - } -} - -public struct PixelAspectRatio: Hashable { - - public static func == (lhs: Self, rhs: Self) -> Bool { - lhs._comparingValue == rhs._comparingValue - } - - public var width: CGFloat - public var height: CGFloat - - public init(width: CGFloat, height: CGFloat) { - self.width = width - self.height = height - } - - public init(_ cgSize: CGSize) { - self.init(width: cgSize.width, height: cgSize.height) - } - - public func height(forWidth: CGFloat) -> CGFloat { - forWidth * (height / width) - } - - public func width(forHeight: CGFloat) -> CGFloat { - forHeight * (width / height) - } - - public func size(byWidth: CGFloat) -> CGSize { - CGSize(width: byWidth, height: height(forWidth: byWidth)) - } - - public func size(byHeight: CGFloat) -> CGSize { - CGSize(width: width(forHeight: byHeight), height: byHeight) - } - - public func asCGSize() -> CGSize { - .init(width: width, height: height) - } - - /// Returns a new instance that swapped height and width - public func swapped() -> PixelAspectRatio { - .init(width: height, height: width) - } - - public func sizeThatFillRounding(in boundingSize: CGSize) -> CGSize { - let widthRatio = boundingSize.width / width - let heightRatio = boundingSize.height / height - var size = boundingSize - - if widthRatio < heightRatio { - size.height = boundingSize.width / width * height - } else if heightRatio < widthRatio { - size.width = boundingSize.height / height * width - } - - return CGSize( - width: size.width.rounded(.down), - height: size.height.rounded(.down) - ) - } - - public func sizeThatFitsWithRounding(in boundingSize: CGSize) -> CGSize { - - let widthRatio = boundingSize.width / width - let heightRatio = boundingSize.height / height - var size = boundingSize - - if widthRatio < heightRatio { - size.height = boundingSize.width / width * height - } else if heightRatio < widthRatio { - size.width = boundingSize.height / height * width - } - - return CGSize( - width: size.width.rounded(.down), - height: size.height.rounded(.down) - ) - - } - - public func rectThatFitsWithRounding(in boundingRect: CGRect) -> CGRect { - let size = sizeThatFitsWithRounding(in: boundingRect.size) - var origin = boundingRect.origin - origin.x += (boundingRect.size.width - size.width) / 2.0 - origin.y += (boundingRect.size.height - size.height) / 2.0 - - origin.x.round(.down) - origin.y.round(.down) - - return CGRect(origin: origin, size: size) - } - - public func rectThatFillWithRounding(in boundingRect: CGRect) -> CGRect { - let size = sizeThatFillRounding(in: boundingRect.size) - var origin = CGPoint.zero - origin.x = (boundingRect.size.width - size.width) / 2.0 - origin.y = (boundingRect.size.height - size.height) / 2.0 - - origin.x.round(.down) - origin.y.round(.down) - return CGRect(origin: origin, size: size) - } - - public func _minimized() -> Self { - func gcd(_ a: CGFloat, _ b: CGFloat) -> CGFloat { - let r = a.truncatingRemainder(dividingBy: b) - if r.isNormal { - return gcd(b, r) - } else { - return b - } - } - - let v = gcd(width, height) - - return .init(width: width / v, height: height / v) - } - - public var _comparingValue: CGFloat { - return (height / width) - } - - public var localizedText: String { - "\(width):\(height)" - } - - public static var square: Self { - .init(width: 1, height: 1) - } - -} - diff --git a/Package/Sources/BrightroomEngine/Library/InternalUtils.swift b/Package/Sources/BrightroomEngine/Library/InternalUtils.swift deleted file mode 100644 index 8e4d1dfb..00000000 --- a/Package/Sources/BrightroomEngine/Library/InternalUtils.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -public enum RequireError: Swift.Error { - case missingRequiredValue(failureDescription: String?, file: StaticString, function: StaticString, line: UInt) -} - -extension Optional { - - @inline(__always) - func require(_ failureDescription: String? = nil, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) throws -> Wrapped { - switch self { - case .none: - throw RequireError.missingRequiredValue(failureDescription: failureDescription, file: file, function: function, line: line) - case .some(let value): - return value - } - } - - @inline(__always) - func unsafeRequire(_ failureDescription: String? = nil, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) -> Wrapped { - switch self { - case .none: - fatalError("\(RequireError.missingRequiredValue(failureDescription: failureDescription, file: file, function: function, line: line))") - case .some(let value): - return value - } - } -} diff --git a/Package/Sources/BrightroomEngine/Library/Optional+.swift b/Package/Sources/BrightroomEngine/Library/Optional+.swift deleted file mode 100644 index 21ff4477..00000000 --- a/Package/Sources/BrightroomEngine/Library/Optional+.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright (c) 2021 Hiroshi Kimura(Muukii) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -extension Optional { - internal func unwrap( - orThrow debugDescription: String? = nil, - file: StaticString = #file, - function: StaticString = #function, - line: UInt = #line - ) throws -> Wrapped { - if let value = self { - return value - } - throw Optional.BrightroomUnwrappedNilError( - debugDescription, - file: file, - function: function, - line: line - ) - } - - public struct BrightroomUnwrappedNilError: Swift.Error, CustomDebugStringConvertible { - let file: StaticString - let function: StaticString - let line: UInt - - // MARK: Public - - public init( - _ debugDescription: String? = nil, - file: StaticString = #file, - function: StaticString = #function, - line: UInt = #line - ) { - self.debugDescription = debugDescription ?? "Failed to unwrap on \(file):\(function):\(line)" - self.file = file - self.function = function - self.line = line - } - - // MARK: CustomDebugStringConvertible - - public let debugDescription: String - } -} diff --git a/Package/Sources/BrightroomEngine/Library/Orientation.swift b/Package/Sources/BrightroomEngine/Library/Orientation.swift deleted file mode 100644 index c2a3bd89..00000000 --- a/Package/Sources/BrightroomEngine/Library/Orientation.swift +++ /dev/null @@ -1,70 +0,0 @@ -import CoreGraphics -import UIKit - -extension CGSize { - - func applying(cgOrientation: CGImagePropertyOrientation) -> CGSize { - switch cgOrientation { - case .up, .upMirrored, .down, .downMirrored: - return self - case .left, .leftMirrored, .right, .rightMirrored: - return .init(width: height, height: width) - } - } - -} - -extension CGImagePropertyOrientation { - init(_ uiOrientation: UIImage.Orientation) { - switch uiOrientation { - case .up: self = .up - case .upMirrored: self = .upMirrored - case .down: self = .down - case .downMirrored: self = .downMirrored - case .left: self = .left - case .leftMirrored: self = .leftMirrored - case .right: self = .right - case .rightMirrored: self = .rightMirrored - @unknown default: - fatalError() - } - } -} - -extension UIImage.Orientation { - init(_ cgOrientation: CGImagePropertyOrientation) { - switch cgOrientation { - case .up: self = .up - case .upMirrored: self = .upMirrored - case .down: self = .down - case .downMirrored: self = .downMirrored - case .left: self = .left - case .leftMirrored: self = .leftMirrored - case .right: self = .right - case .rightMirrored: self = .rightMirrored - } - } -} - -private func imageOrientationToTiffOrientation(_ value: UIImage.Orientation) -> Int32 { - switch value { - case .up: - return 1 - case .down: - return 3 - case .left: - return 8 - case .right: - return 6 - case .upMirrored: - return 2 - case .downMirrored: - return 4 - case .leftMirrored: - return 5 - case .rightMirrored: - return 7 - @unknown default: - fatalError() - } -} diff --git a/Package/Sources/BrightroomEngine/Library/_BrightroomDebounce.swift b/Package/Sources/BrightroomEngine/Library/_BrightroomDebounce.swift deleted file mode 100644 index a86f9ade..00000000 --- a/Package/Sources/BrightroomEngine/Library/_BrightroomDebounce.swift +++ /dev/null @@ -1,38 +0,0 @@ -import Foundation - -public final class _BrightroomDebounce { - private var timerReference: DispatchSourceTimer? - - let interval: TimeInterval - let queue: DispatchQueue - - private var lastSendTime: Date? - - public init(interval: TimeInterval, queue: DispatchQueue = .main) { - self.interval = interval - self.queue = queue - } - - public func on(handler: @escaping () -> Void) { - let deadline = DispatchTime.now() + DispatchTimeInterval.milliseconds(Int(interval * 1000.0)) - - timerReference?.cancel() - - let timer = DispatchSource.makeTimerSource(queue: queue) - timer.schedule(deadline: deadline) - - timer.setEventHandler(handler: { [weak timer, weak self] in - self?.lastSendTime = nil - handler() - timer?.cancel() - self?.timerReference = nil - }) - timer.resume() - - timerReference = timer - } - - public func cancel() { - timerReference = nil - } -} diff --git a/Package/Sources/BrightroomEngine/Preset/PresetStorage.swift b/Package/Sources/BrightroomEngine/Preset/PresetStorage.swift deleted file mode 100644 index 4c150352..00000000 --- a/Package/Sources/BrightroomEngine/Preset/PresetStorage.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation - -open class PresetStorage { - - public static let `default` = PresetStorage(presets: []) - - public var presets: [FilterPreset] = [] - - public init( - presets: [FilterPreset] - ) { - self.presets = presets - } -} - -extension PresetStorage { - - public func loadLUTs(fromBundle bundle: Bundle = .main) throws { - - let loader = ColorCubeLoader(bundle: bundle) - let filters = try loader.load() - - self.presets = filters - .map { - FilterPreset( - name: $0.name, - identifier: $0.identifier, - filters: [$0.asAny()], - userInfo: [:] - ) - } - } - -} diff --git a/Package/Sources/BrightroomUI/BrightroomUI.h b/Package/Sources/BrightroomUI/BrightroomUI.h deleted file mode 100644 index 45555dde..00000000 --- a/Package/Sources/BrightroomUI/BrightroomUI.h +++ /dev/null @@ -1,10 +0,0 @@ - -#import - -//! Project version number for PixelEditor. -FOUNDATION_EXPORT double PixelEditorVersionNumber; - -//! Project version string for PixelEditor. -FOUNDATION_EXPORT const unsigned char PixelEditorVersionString[]; - - diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/ClassicImageEditOptions.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/ClassicImageEditOptions.swift deleted file mode 100644 index 40bb17b2..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/ClassicImageEditOptions.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif - -public struct ClassicImageEditOptions { - - public static let `default`: ClassicImageEditOptions = .init() - - public static var current: ClassicImageEditOptions = .init() - - public var croppingAspectRatio: PixelAspectRatio? = .square - public var isFaceDetectionEnabled: Bool = false - - public var classes: Classes = .init() - - public init() {} -} - -extension ClassicImageEditOptions { - public struct Classes { - - public struct Control { - - /** - You might use `ClassicImageEditNoPresetRootControl` if you do not need using Filter(Preset) panel. - */ - public var rootControl: ClassicImageEditRootControlBase.Type = ClassicImageEditRootControl.self - - public var presetListControl: ClassicImageEditPresetListControlBase.Type = ClassicImageEditPresetListControl.self - public var editMenuControl: ClassicImageEditEditMenuControlBase.Type = ClassicImageEditEditMenu.EditMenuControl.self - - public var exposureControl: ClassicImageEditExposureControlBase.Type = ClassicImageEditExposureControl.self - public var gaussianBlurControl: ClassicImageEditGaussianBlurControlBase.Type = ClassicImageEditGaussianBlurControl.self - public var saturationControl: ClassicImageEditSaturationControlBase.Type = ClassicImageEditSaturationControl.self - public var contrastControl: ClassicImageEditContrastControlBase.Type = ClassicImageEditContrastControl.self - public var temperatureControl: ClassicImageEditTemperatureControlBase.Type = ClassicImageEditTemperatureControl.self - public var vignetteControl: ClassicImageEditVignetteControlBase.Type = ClassicImageEditVignetteControl.self - public var highlightsControl: ClassicImageEditHighlightsControlBase.Type = ClassicImageEditHighlightsControl.self - public var shadowsControl: ClassicImageEditShadowsControlBase.Type = ClassicImageEditShadowsControl.self - public var fadeControl: ClassicImageEditFadeControlBase.Type = ClassicImageEditFadeControl.self - public var clarityControl: ClassicImageEditClarityControlBase.Type = ClassicImageEditClarityControl.self - public var sharpenControl: ClassicImageEditSharpenControlBase.Type = ClassicImageEditSharpenControl.self - - public var ignoredEditMenus: Set = [] - public var editMenus: [ClassicImageEditEditMenu] = ClassicImageEditEditMenu.allCases - - public init() { - - } - } - - public var control: Control = .init() - - public init() { - - } - } -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/ClassicImageEditStyle.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/ClassicImageEditStyle.swift deleted file mode 100644 index 4f1f8c05..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/ClassicImageEditStyle.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -public struct ClassicImageEditStyle { - - public static let `default` = ClassicImageEditStyle() - - public struct Control { - - public var backgroundColor = UIColor(white: 0.98, alpha: 1) - - public init() { - - } - } - - public var control = Control() - - public var black = UIColor(white: 0.05, alpha: 1) - - public init() { - - } - -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/ClassicImageEditViewController.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/ClassicImageEditViewController.swift deleted file mode 100644 index eeb3e27f..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/ClassicImageEditViewController.swift +++ /dev/null @@ -1,439 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import Photos -import UIKit -import Verge - -#if !COCOAPODS - import BrightroomEngine -#endif - -@available(*, deprecated, renamed: "ClassicImageEditViewController") -public typealias PixelEditViewController = ClassicImageEditViewController - -public final class ClassicImageEditViewController: UIViewController { - - /** - - TODO: property names are not comprehensibility. - */ - public struct LocalizedStrings { - - // Deprecates - - @available(*, deprecated, renamed: "control_preset_normal_name") - public var normal: String { - get { - control_preset_normal_name - } - set { - control_preset_normal_name = newValue - } - } - - public var done = "Done" - - public var control_preset_normal_name = "Normal" - - public var cancel = "Cancel" - public var filter = "Filter" - public var edit = "Edit" - - public var editAdjustment = "Adjust" - public var editMask = "Mask" - public var editHighlights = "Highlights" - public var editShadows = "Shadows" - public var editSaturation = "Saturation" - public var editContrast = "Contrast" - public var editBlur = "Blur" - public var editTemperature = "Temperature" - public var editBrightness = "Brightness" - public var editVignette = "Vignette" - public var editFade = "Fade" - public var editClarity = "Clarity" - public var editSharpen = "Sharpen" - public var brushSizeSmall = "◦" - public var brushSizeLarge = "◯" - public var clear = "Clear" - - public init() {} - } - - public struct Handlers { - public var didEndEditing: (ClassicImageEditViewController, EditingStack) -> Void = { _, _ in } - public var didCancelEditing: (ClassicImageEditViewController) -> Void = { _ in } - } - - public var handlers: Handlers = .init() - - // MARK: - Private Propaties - - private let maskingView: BlurryMaskingView - - private let previewView: ImagePreviewView - - private let cropView: CropView - - private let editContainerView = UIView() - - private let controlContainerView = UIView() - - private let cropButton = UIButton(type: .system) - - private let stackView = ClassicImageEditControlStackView() - - private lazy var doneButton = UIBarButtonItem( - title: viewModel.localizedStrings.done, - style: .done, - target: self, - action: #selector(didTapDoneButton) - ) - - private lazy var cancelButton = UIBarButtonItem( - title: viewModel.localizedStrings.cancel, - style: .plain, - target: self, - action: #selector(didTapCancelButton) - ) - - private var subscriptions: Set = .init() - - private lazy var loadingView = LoadingBlurryOverlayView( - effect: UIBlurEffect(style: .dark), - activityIndicatorStyle: .whiteLarge - ) - private lazy var touchGuardOverlayView = UIView() - - private let viewModel: ClassicImageEditViewModel - - // MARK: - Initializers - - public convenience init( - imageProvider: ImageProvider, - options: ClassicImageEditOptions = .default, - localizedStrings: LocalizedStrings = .init() - ) { - let editingStack = EditingStack(imageProvider: imageProvider) - - self.init( - editingStack: editingStack, - options: options, - localizedStrings: localizedStrings - ) - } - - public convenience init( - editingStack: EditingStack, - options: ClassicImageEditOptions = .default, - localizedStrings: LocalizedStrings = .init() - ) { - self.init( - viewModel: .init( - editingStack: editingStack, - options: options, - localizedStrings: localizedStrings - ) - ) - } - - public init(viewModel: ClassicImageEditViewModel) { - self.viewModel = viewModel - cropView = .init(editingStack: viewModel.editingStack, contentInset: .zero) - previewView = .init(editingStack: viewModel.editingStack) - maskingView = .init(editingStack: viewModel.editingStack) - super.init(nibName: nil, bundle: nil) - } - - @available(*, unavailable) - public required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Functions - - override public func viewDidLoad() { - // FIXME: Check loading - - super.viewDidLoad() - - cropView.setCropOutsideOverlay( - .init()&>.do { - $0.backgroundColor = .white - } - ) - cropView.setCropInsideOverlay(nil) - cropView.isGuideInteractionEnabled = false - cropView.isAutoApplyEditingStackEnabled = false - - cropView.setCroppingAspectRatio(viewModel.options.croppingAspectRatio) - - maskingView.isBackdropImageViewHidden = true - - layout: do { - root: do { - view.backgroundColor = .white - - let guide = UILayoutGuide() - - view.addLayoutGuide(guide) - - view.addSubview(editContainerView) - view.addSubview(controlContainerView) - - editContainerView.accessibilityIdentifier = "app.muukii.pixel.editContainerView" - controlContainerView.accessibilityIdentifier = "app.muukii.pixel.controlContainerView" - - editContainerView.translatesAutoresizingMaskIntoConstraints = false - controlContainerView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - guide.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - guide.rightAnchor.constraint(equalTo: view.rightAnchor), - guide.leftAnchor.constraint(equalTo: view.leftAnchor), - guide.widthAnchor.constraint(equalTo: guide.heightAnchor, multiplier: 1), - - { - let c = editContainerView.topAnchor.constraint(equalTo: guide.topAnchor) - c.priority = .defaultHigh - return c - }(), - { - let c = editContainerView.rightAnchor.constraint(equalTo: guide.rightAnchor) - c.priority = .defaultHigh - return c - }(), - { - let c = editContainerView.leftAnchor.constraint(equalTo: guide.leftAnchor) - c.priority = .defaultHigh - return c - }(), - { - let c = editContainerView.bottomAnchor.constraint(equalTo: guide.bottomAnchor) - c.priority = .defaultHigh - return c - }(), - - editContainerView.centerXAnchor.constraint(equalTo: guide.centerXAnchor), - editContainerView.centerYAnchor.constraint(equalTo: guide.centerYAnchor), - - controlContainerView.topAnchor.constraint(equalTo: guide.bottomAnchor), - controlContainerView.rightAnchor.constraint(equalTo: view.rightAnchor), - controlContainerView.leftAnchor.constraint(equalTo: view.leftAnchor), - { - if #available(iOS 11.0, *) { - return controlContainerView.bottomAnchor.constraint( - equalTo: view.safeAreaLayoutGuide.bottomAnchor - ) - } else { - return controlContainerView.bottomAnchor.constraint(equalTo: view.bottomAnchor) - } - }(), - ]) - } - - root: do { - view.backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - } - - edit: do { - [ - cropView, - previewView, - maskingView, - ].forEach { view in - view.translatesAutoresizingMaskIntoConstraints = false - editContainerView.addSubview(view) - NSLayoutConstraint.activate([ - view.topAnchor.constraint(equalTo: view.superview!.topAnchor), - view.rightAnchor.constraint(equalTo: view.superview!.rightAnchor), - view.bottomAnchor.constraint(equalTo: view.superview!.bottomAnchor), - view.leftAnchor.constraint(equalTo: view.superview!.leftAnchor), - ]) - } - } - - control: do { - controlContainerView.addSubview(stackView) - - stackView.frame = stackView.bounds - stackView.autoresizingMask = [.flexibleHeight, .flexibleWidth] - } - } - - stackView.push( - viewModel.options.classes.control.rootControl.init( - viewModel: viewModel, - editMenuControl: viewModel.options.classes.control.editMenuControl.init( - viewModel: viewModel - ), - presetListControl: viewModel.options.classes.control.presetListControl.init( - viewModel: viewModel - ) - ), - animated: false - ) - - viewModel.sinkState(queue: .mainIsolated()) { [weak self] state in - - guard let self = self else { return } - - self.updateUI(state: state) - } - .store(in: &subscriptions) - - cropView.store.sinkState { [viewModel] state in - - state.ifChanged(\.proposedCrop) { value in - guard let value = value else { return } - viewModel.setProposedCrop(value) - } - } - .store(in: &subscriptions) - - viewModel.editingStack.start() - } - - override public func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - view.layoutIfNeeded() - } - - // MARK: - Private Functions - - @objc - private func didTapDoneButton() { - handlers.didEndEditing(self, viewModel.editingStack) - } - - @objc - private func didTapCancelButton() { - handlers.didCancelEditing(self) - } - - private func updateUI(state: Changes) { - state.ifChanged(\.title) { title in - navigationItem.title = title - } - - state.ifChanged(\.maskingBrushSize) { - maskingView.setBrushSize($0) - } - - state.ifChanged(\.proposedCrop) { value in - guard let value = value else { return } - cropView.setCrop(value) - } - - state.ifChanged(\.mode) { mode in - switch mode { - case .crop: - - navigationItem.rightBarButtonItem = nil - navigationItem.leftBarButtonItem = nil - - cropView.isHidden = false - previewView.isHidden = true - - maskingView.isHidden = true - maskingView.isblurryImageViewHidden = true - - maskingView.isUserInteractionEnabled = false - - case .masking: - - navigationItem.rightBarButtonItem = nil - navigationItem.leftBarButtonItem = nil - - cropView.isHidden = true - previewView.isHidden = false - maskingView.isHidden = false - maskingView.isblurryImageViewHidden = false - - maskingView.isUserInteractionEnabled = true - - case .editing: - - navigationItem.rightBarButtonItem = nil - navigationItem.leftBarButtonItem = nil - - cropView.isHidden = true - previewView.isHidden = false - maskingView.isHidden = true - maskingView.isblurryImageViewHidden = true - - maskingView.isUserInteractionEnabled = false - - case .preview: - - navigationItem.setHidesBackButton(true, animated: false) - navigationItem.rightBarButtonItem = doneButton - navigationItem.leftBarButtonItem = cancelButton - - previewView.isHidden = false - cropView.isHidden = true - maskingView.isHidden = false - maskingView.isblurryImageViewHidden = false - - maskingView.isUserInteractionEnabled = false - } - } - - let editingState = state.map(\.editingState) - - editingState.ifChanged(\.isLoading) { isLoading in - - switch isLoading { - case true: - - let loadingView = self.loadingView - let touchGuardOverlayView = self.touchGuardOverlayView - - touchGuardOverlayView.backgroundColor = .init(white: 1, alpha: 0.5) - - view.addSubview(loadingView) - view.addSubview(touchGuardOverlayView) - - touchGuardOverlayView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - loadingView.leadingAnchor.constraint(equalTo: previewView.leadingAnchor), - loadingView.trailingAnchor.constraint(equalTo: previewView.trailingAnchor), - loadingView.topAnchor.constraint(equalTo: previewView.topAnchor), - loadingView.bottomAnchor.constraint(equalTo: previewView.bottomAnchor), - touchGuardOverlayView.leadingAnchor.constraint( - equalTo: controlContainerView.leadingAnchor - ), - touchGuardOverlayView.trailingAnchor.constraint( - equalTo: controlContainerView.trailingAnchor - ), - touchGuardOverlayView.topAnchor.constraint(equalTo: controlContainerView.topAnchor), - touchGuardOverlayView.bottomAnchor.constraint(equalTo: controlContainerView.bottomAnchor), - ]) - self.doneButton.isEnabled = false - - case false: - self.loadingView.removeFromSuperview() - self.touchGuardOverlayView.removeFromSuperview() - self.doneButton.isEnabled = true - } - } - } -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/ClassicImageEditViewModel.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/ClassicImageEditViewModel.swift deleted file mode 100644 index 0d48201f..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/ClassicImageEditViewModel.swift +++ /dev/null @@ -1,152 +0,0 @@ -// -// Copyright (c) 2021 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit -import Verge -#if !COCOAPODS -import BrightroomEngine -#endif - -public final class ClassicImageEditViewModel: Equatable, StoreComponentType { - public static func == (lhs: ClassicImageEditViewModel, rhs: ClassicImageEditViewModel) -> Bool { - lhs === rhs - } - - public enum Mode { - case crop - case masking - case editing - case preview - } - - public struct State: Equatable { - public var editingState: Changes - - public fileprivate(set) var title: String = "" - public fileprivate(set) var mode: Mode = .preview - - public fileprivate(set) var maskingBrushSize: CanvasView.BrushSize = .point(30) - - // TODO: Rename - fileprivate var drawnPaths: [DrawnPath] = [] - fileprivate(set) var proposedCrop: EditingCrop? - } - - public let options: ClassicImageEditOptions - - public let store: DefaultStore - - public let editingStack: EditingStack - - private var subscriptions: Set = .init() - - public let localizedStrings: ClassicImageEditViewController.LocalizedStrings - - public init( - editingStack: EditingStack, - options: ClassicImageEditOptions, - localizedStrings: ClassicImageEditViewController.LocalizedStrings - ) { - self.localizedStrings = localizedStrings - self.options = options - self.editingStack = editingStack - store = .init(initialState: .init(editingState: editingStack.state)) - - if options.isFaceDetectionEnabled { - editingStack.cropModifier = .faceDetection(aspectRatio: options.croppingAspectRatio) - } else if let aspectRatio = options.croppingAspectRatio { - editingStack.cropModifier = .init { image, crop, completion in - var new = crop - new.updateCropExtentIfNeeded(toFitAspectRatio: aspectRatio) - completion(new) - } - } - - editingStack.assign(to: assignee(\.editingState)).store(in: &subscriptions) - } - - func setTitle(_ title: String) { - commit { - $0.title = title - } - } - - func setMode(_ mode: Mode) { - commit { - $0.mode = mode - - switch mode { - case .crop: - $0.title = localizedStrings.editAdjustment - case .masking: - $0.title = localizedStrings.editMask - case .editing: - break - case .preview: - $0.title = "" - } - } - } - - func endMasking(save: Bool) { - if save { - editingStack.takeSnapshot() - } else { - editingStack.revertEdit() - } - } - - func setBrushSize(_ brushSize: CGFloat) { - commit { - $0.maskingBrushSize = .point(brushSize) - } - } - - func setDrawinPaths(_ drawnPaths: [DrawnPath]) { - commit { - $0.drawnPaths = drawnPaths - } - } - - func endCrop(save: Bool) { - if save { - if let proposed = state.proposedCrop { - editingStack.crop(proposed) - editingStack.takeSnapshot() - } - - } else { - commit { - guard let loadedState = $0.editingState.loadedState else { - assertionFailure() - return - } - $0.proposedCrop = loadedState.currentEdit.crop - } - } - } - - func setProposedCrop(_ proposedCrop: EditingCrop) { - commit { - $0.proposedCrop = proposedCrop - } - } -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditControlStackView.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditControlStackView.swift deleted file mode 100644 index 8d58bee3..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditControlStackView.swift +++ /dev/null @@ -1,154 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif - -protocol ClassicImageEditControlChildViewType { -} - -extension ClassicImageEditControlChildViewType where Self : UIView { - - private func find() -> ClassicImageEditControlStackView { - - var _super: UIView? - _super = superview - while _super != nil { - if let target = _super as? ClassicImageEditControlStackView { - return target - } - _super = _super?.superview - } - - fatalError() - - } - - func push(_ view: UIView & ClassicImageEditControlChildViewType, animated: Bool) { - let controlStackView = find() - controlStackView.push(view, animated: animated) - } - - func pop(animated: Bool) { - find().pop(animated: animated) - } - -} - - -final class ClassicImageEditControlStackView : UIView { - - private var latestNotifiedEdit: EditingStack.Edit? - - func push(_ view: UIView & ClassicImageEditControlChildViewType, animated: Bool) { - - let currentTop = subviews.last - - addSubview(view) - view.frame = bounds - view.autoresizingMask = [.flexibleWidth, .flexibleHeight] - - if animated { - foreground: do { - view.alpha = 0 - view.transform = CGAffineTransform(translationX: 0, y: 8) - UIView.animate( - withDuration: 0.3, - delay: 0, - usingSpringWithDamping: 1, - initialSpringVelocity: 0, - options: [.beginFromCurrentState, .curveEaseOut, .allowUserInteraction], - animations: { - view.alpha = 1 - view.transform = .identity - }, completion: nil) - } - - background: do { - - if let view = currentTop { - UIView.animate( - withDuration: 0.3, - delay: 0, - usingSpringWithDamping: 1, - initialSpringVelocity: 0, - options: [.beginFromCurrentState, .curveEaseOut, .allowUserInteraction], - animations: { - view.transform = CGAffineTransform(translationX: 0, y: 8) - }, completion: { _ in - view.transform = .identity - }) - } - - } - } - } - - func pop(animated: Bool) { - - guard let currentTop = subviews.last else { - return - } - - let background = subviews.dropLast().last - - let remove = { - currentTop.removeFromSuperview() - } - - if animated { - UIView.animate( - withDuration: 0.3, - delay: 0, - usingSpringWithDamping: 1, - initialSpringVelocity: 0, - options: [.beginFromCurrentState, .curveEaseOut, .allowUserInteraction], - animations: { - currentTop.alpha = 0 - currentTop.transform = CGAffineTransform(translationX: 0, y: 8) - }, completion: { _ in - remove() - }) - - if let view = background { - view.transform = CGAffineTransform(translationX: 0, y: 8) - UIView.animate( - withDuration: 0.3, - delay: 0, - usingSpringWithDamping: 1, - initialSpringVelocity: 0, - options: [.beginFromCurrentState, .curveEaseOut, .allowUserInteraction], - animations: { - view.transform = .identity - }, completion: { _ in - }) - } - } - else { - remove() - } - } - -} - diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditControlViewBase.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditControlViewBase.swift deleted file mode 100644 index 158585b1..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditControlViewBase.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import Verge - -open class ClassicImageEditControlBase : UIView, ClassicImageEditControlChildViewType { - - open func didReceiveCurrentEdit(state: Changes) { - - } - - public let viewModel: ClassicImageEditViewModel - - private var subscriptions: Set = .init() - - public init(viewModel: ClassicImageEditViewModel) { - self.viewModel = viewModel - super.init(frame: .zero) - setup() - - viewModel.sinkState { [weak self] (state) in - self?.didReceiveCurrentEdit(state: state) - } - .store(in: &subscriptions) - } - - @available(*, unavailable) - public required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - open func setup() { - - } -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditCropControl.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditCropControl.swift deleted file mode 100644 index 7be12e59..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditCropControl.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -open class ClassicImageEditCropControlBase : ClassicImageEditControlBase { - -} - -public final class ClassicImageEditCropControl : ClassicImageEditCropControlBase { - - private lazy var navigationView = ClassicImageEditNavigationView(saveText: viewModel.localizedStrings.done, cancelText: viewModel.localizedStrings.cancel) - - public override func setup() { - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - addSubview(navigationView) - - navigationView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - navigationView.rightAnchor.constraint(equalTo: navigationView.superview!.rightAnchor), - navigationView.leftAnchor.constraint(equalTo: navigationView.superview!.leftAnchor), - navigationView.bottomAnchor.constraint(equalTo: navigationView.superview!.bottomAnchor), - navigationView.topAnchor.constraint(greaterThanOrEqualTo: navigationView.superview!.topAnchor), - ]) - - navigationView.didTapCancelButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.endCrop(save: false) - self.viewModel.setMode(.preview) - self.pop(animated: true) - } - - navigationView.didTapDoneButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.endCrop(save: true) - self.viewModel.setMode(.preview) - self.pop(animated: true) - } - } - - public override func willMove(toSuperview newSuperview: UIView?) { - super.willMove(toSuperview: newSuperview) - if newSuperview != nil { - viewModel.setMode(.crop) - } - } - -} - diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditDoodleControlView.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditDoodleControlView.swift deleted file mode 100644 index f9ac655e..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditDoodleControlView.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -import UIKit - -open class ClassicImageEditDoodleControlBase : ClassicImageEditControlBase { - -} - -public final class ClassicImageEditDoodleControl : ClassicImageEditDoodleControlBase { - - private lazy var navigationView = ClassicImageEditNavigationView(saveText: viewModel.localizedStrings.done, cancelText: viewModel.localizedStrings.cancel) - - public override func setup() { - - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - addSubview(navigationView) - - navigationView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - navigationView.rightAnchor.constraint(equalTo: navigationView.superview!.rightAnchor), - navigationView.leftAnchor.constraint(equalTo: navigationView.superview!.leftAnchor), - navigationView.bottomAnchor.constraint(equalTo: navigationView.superview!.bottomAnchor), - navigationView.topAnchor.constraint(greaterThanOrEqualTo: navigationView.superview!.topAnchor), - ]) - - navigationView.didTapCancelButton = { [weak self] in - - self?.pop(animated: true) - } - - navigationView.didTapDoneButton = { [weak self] in - - self?.pop(animated: true) - } - } -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditEditMenuControlView.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditEditMenuControlView.swift deleted file mode 100644 index e6e04338..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditEditMenuControlView.swift +++ /dev/null @@ -1,504 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import Verge - -open class ClassicImageEditEditMenuControlBase: ClassicImageEditControlBase { - override public required init(viewModel: ClassicImageEditViewModel) { - super.init(viewModel: viewModel) - } -} - -public enum ClassicImageEditEditMenu: CaseIterable { - case adjustment - case mask - case exposure - case contrast - case clarity - case temperature - case saturation - case fade - case highlights - case shadows - case vignette - case sharpen - case gaussianBlur - - open class EditMenuControl: ClassicImageEditEditMenuControlBase { - public let contentView = UIView() - public let itemsView = UIStackView() - public let scrollView = UIScrollView() - - public lazy var adjustmentButton: ButtonView = { - let button = ButtonView( - name: viewModel.localizedStrings.editAdjustment, - image: UIImage(named: "adjustment", in: bundle, compatibleWith: nil)! - ) - button.addTarget(self, action: #selector(adjustment), for: .touchUpInside) - return button - }() - - public lazy var maskButton: ButtonView = { - let button = ButtonView( - name: viewModel.localizedStrings.editMask, - image: UIImage(named: "mask", in: bundle, compatibleWith: nil)! - ) - button.addTarget(self, action: #selector(masking), for: .touchUpInside) - return button - }() - - public lazy var exposureButton: ButtonView = { - let button = ButtonView( - name: viewModel.localizedStrings.editBrightness, - image: UIImage(named: "brightness", in: bundle, compatibleWith: nil)! - ) - button.addTarget(self, action: #selector(brightness), for: .touchUpInside) - return button - }() - - public lazy var gaussianBlurButton: ButtonView = { - let button = ButtonView( - name: viewModel.localizedStrings.editBlur, - image: UIImage(named: "blur", in: bundle, compatibleWith: nil)! - ) - button.addTarget(self, action: #selector(blur), for: .touchUpInside) - return button - }() - - public lazy var contrastButton: ButtonView = { - let button = ButtonView( - name: viewModel.localizedStrings.editContrast, - image: UIImage(named: "contrast", in: bundle, compatibleWith: nil)! - ) - button.addTarget(self, action: #selector(contrast), for: .touchUpInside) - return button - }() - - public lazy var temperatureButton: ButtonView = { - let button = ButtonView( - name: viewModel.localizedStrings.editTemperature, - image: UIImage(named: "temperature", in: bundle, compatibleWith: nil)! - ) - button.addTarget(self, action: #selector(warmth), for: .touchUpInside) - return button - }() - - public lazy var saturationButton: ButtonView = { - let button = ButtonView( - name: viewModel.localizedStrings.editSaturation, - image: UIImage(named: "saturation", in: bundle, compatibleWith: nil)! - ) - button.addTarget(self, action: #selector(saturation), for: .touchUpInside) - return button - }() - - public lazy var highlightsButton: ButtonView = { - let button = ButtonView( - name: viewModel.localizedStrings.editHighlights, - image: UIImage(named: "highlights", in: bundle, compatibleWith: nil)! - ) - button.addTarget(self, action: #selector(highlights), for: .touchUpInside) - return button - }() - - public lazy var shadowsButton: ButtonView = { - let button = ButtonView( - name: viewModel.localizedStrings.editShadows, - image: UIImage(named: "shadows", in: bundle, compatibleWith: nil)! - ) - button.addTarget(self, action: #selector(shadows), for: .touchUpInside) - return button - }() - - public lazy var vignetteButton: ButtonView = { - let button = ButtonView( - name: viewModel.localizedStrings.editVignette, - image: UIImage(named: "vignette", in: bundle, compatibleWith: nil)! - ) - button.addTarget(self, action: #selector(vignette), for: .touchUpInside) - return button - }() - - public lazy var fadeButton: ButtonView = { - let button = ButtonView( - name: viewModel.localizedStrings.editFade, - image: UIImage(named: "fade", in: bundle, compatibleWith: nil)! - ) - button.addTarget(self, action: #selector(fade), for: .touchUpInside) - return button - }() - - public lazy var sharpenButton: ButtonView = { - let button = ButtonView( - name: viewModel.localizedStrings.editSharpen, - image: UIImage(named: "sharpen", in: bundle, compatibleWith: nil)! - ) - button.addTarget(self, action: #selector(sharpen), for: .touchUpInside) - return button - }() - - public lazy var clarityButton: ButtonView = { - let button = ButtonView( - name: viewModel.localizedStrings.editClarity, - image: UIImage(named: "structure", in: bundle, compatibleWith: nil)! - ) - button.addTarget(self, action: #selector(clarity), for: .touchUpInside) - return button - }() - - override open func setup() { - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - layout: do { - scrollView.showsVerticalScrollIndicator = false - scrollView.showsHorizontalScrollIndicator = false - if #available(iOS 11.0, *) { - scrollView.contentInsetAdjustmentBehavior = .never - } - scrollView.contentInset.right = 36 - scrollView.contentInset.left = 36 - addSubview(scrollView) - - scrollView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - scrollView.topAnchor.constraint(equalTo: scrollView.superview!.topAnchor), - scrollView.rightAnchor.constraint(equalTo: scrollView.superview!.rightAnchor), - scrollView.leftAnchor.constraint(equalTo: scrollView.superview!.leftAnchor), - scrollView.bottomAnchor.constraint(equalTo: scrollView.superview!.bottomAnchor), - ]) - - scrollView.addSubview(contentView) - - contentView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - contentView.widthAnchor.constraint( - greaterThanOrEqualTo: contentView.superview!.widthAnchor, - constant: -(scrollView.contentInset.right + scrollView.contentInset.left) - ), - contentView.heightAnchor.constraint(equalTo: contentView.superview!.heightAnchor), - contentView.topAnchor.constraint(equalTo: contentView.superview!.topAnchor), - contentView.rightAnchor.constraint(equalTo: contentView.superview!.rightAnchor), - contentView.leftAnchor.constraint(equalTo: contentView.superview!.leftAnchor), - contentView.bottomAnchor.constraint(equalTo: contentView.superview!.bottomAnchor), - ]) - - contentView.addSubview(itemsView) - - itemsView.axis = .horizontal - itemsView.alignment = .center - itemsView.distribution = .fillEqually - itemsView.spacing = 16 - - itemsView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - itemsView.heightAnchor.constraint(equalTo: itemsView.superview!.heightAnchor), - itemsView.topAnchor.constraint(equalTo: itemsView.superview!.topAnchor), - itemsView.rightAnchor.constraint(lessThanOrEqualTo: itemsView.superview!.rightAnchor), - itemsView.leftAnchor.constraint(greaterThanOrEqualTo: itemsView.superview!.leftAnchor), - itemsView.bottomAnchor.constraint(equalTo: itemsView.superview!.bottomAnchor), - itemsView.centerXAnchor.constraint(equalTo: itemsView.superview!.centerXAnchor), - ]) - } - - item: do { - let ignoredEditMenus: Set = viewModel.options.classes.control.ignoredEditMenus - let displayedMenus = viewModel.options.classes.control.editMenus.filter { !ignoredEditMenus.contains($0) } - - var buttons: [ButtonView] = [] - - for editMenu in displayedMenus { - switch editMenu { - case .adjustment: - buttons.append(adjustmentButton) - case .mask: - buttons.append(maskButton) - case .exposure: - buttons.append(exposureButton) - case .gaussianBlur: - buttons.append(gaussianBlurButton) - case .contrast: - buttons.append(contrastButton) - case .temperature: - buttons.append(temperatureButton) - case .saturation: - buttons.append(saturationButton) - case .highlights: - buttons.append(highlightsButton) - case .shadows: - buttons.append(shadowsButton) - case .vignette: - buttons.append(vignetteButton) - case .fade: - buttons.append(fadeButton) - case .sharpen: - buttons.append(sharpenButton) - case .clarity: - buttons.append(clarityButton) - } - } - - for button in buttons { - itemsView.addArrangedSubview(button) - } - - /* - - color: do { - let button = ButtonView(name: TODOL10n("Color"), image: .init()) - button.addTarget(self, action: #selector(color), for: .touchUpInside) - itemsView.addArrangedSubview(button) - } - - */ - hls: do { - // http://flexmonkey.blogspot.com/2016/03/creating-selective-hsl-adjustment.html - } - } - } - - override open func didReceiveCurrentEdit(state: Changes) { - if let edit = state.editingState.loadedState?.currentEdit { - maskButton.hasChanges = !edit.drawings.blurredMaskPaths.isEmpty - - contrastButton.hasChanges = edit.filters.contrast != nil - exposureButton.hasChanges = edit.filters.exposure != nil - temperatureButton.hasChanges = edit.filters.temperature != nil - saturationButton.hasChanges = edit.filters.saturation != nil - highlightsButton.hasChanges = edit.filters.highlights != nil - shadowsButton.hasChanges = edit.filters.shadows != nil - vignetteButton.hasChanges = edit.filters.vignette != nil - gaussianBlurButton.hasChanges = edit.filters.gaussianBlur != nil - fadeButton.hasChanges = edit.filters.fade != nil - sharpenButton.hasChanges = edit.filters.sharpen != nil - clarityButton.hasChanges = edit.filters.unsharpMask != nil - } - } - - @objc - private func adjustment() { - push(ClassicImageEditCropControl(viewModel: viewModel), animated: true) - } - - @objc - private func masking() { - push(ClassicImageEditMaskControl(viewModel: viewModel), animated: true) - } - - @objc - private func doodle() { - push(ClassicImageEditDoodleControl(viewModel: viewModel), animated: true) - } - - @objc - private func brightness() { - push( - viewModel.options.classes.control.exposureControl.init(viewModel: viewModel), - animated: true - ) - } - - @objc - private func blur() { - push( - viewModel.options.classes.control.gaussianBlurControl.init(viewModel: viewModel), - animated: true - ) - } - - @objc - private func contrast() { - push( - viewModel.options.classes.control.contrastControl.init(viewModel: viewModel), - animated: true - ) - } - - @objc - private func clarity() { - push( - viewModel.options.classes.control.clarityControl.init(viewModel: viewModel), - animated: true - ) - } - - @objc - private func warmth() { - push( - viewModel.options.classes.control.temperatureControl.init(viewModel: viewModel), - animated: true - ) - } - - @objc - private func saturation() { - push( - viewModel.options.classes.control.saturationControl.init(viewModel: viewModel), - animated: true - ) - } - - @objc - private func color() {} - - @objc - private func fade() { - push(viewModel.options.classes.control.fadeControl.init(viewModel: viewModel), animated: true) - } - - @objc - private func highlights() { - push( - viewModel.options.classes.control.highlightsControl.init(viewModel: viewModel), - animated: true - ) - } - - @objc - private func shadows() { - push( - viewModel.options.classes.control.shadowsControl.init(viewModel: viewModel), - animated: true - ) - } - - @objc - private func vignette() { - push( - viewModel.options.classes.control.vignetteControl.init(viewModel: viewModel), - animated: true - ) - } - - @objc - private func sharpen() { - push( - viewModel.options.classes.control.sharpenControl.init(viewModel: viewModel), - animated: true - ) - } - - open class ButtonView: UIControl { - public let nameLabel = UILabel() - - public let imageView = UIImageView() - - public let changesMarkView = UIView() - - private let feedbackGenerator = UISelectionFeedbackGenerator() - - public var hasChanges: Bool = false { - didSet { - guard oldValue != hasChanges else { return } - changesMarkView.isHidden = !hasChanges - } - } - - public init(name: String, image: UIImage) { - super.init(frame: .zero) - - layout: do { - addSubview(nameLabel) - addSubview(imageView) - addSubview(changesMarkView) - - nameLabel.translatesAutoresizingMaskIntoConstraints = false - imageView.translatesAutoresizingMaskIntoConstraints = false - changesMarkView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - changesMarkView.topAnchor.constraint(equalTo: topAnchor, constant: 0), - changesMarkView.centerXAnchor.constraint(equalTo: centerXAnchor), - changesMarkView.widthAnchor.constraint(equalToConstant: 4), - changesMarkView.heightAnchor.constraint(equalToConstant: 4), - - imageView.topAnchor.constraint(equalTo: changesMarkView.bottomAnchor, constant: 8), - imageView.rightAnchor.constraint(lessThanOrEqualTo: rightAnchor, constant: -4), - imageView.leftAnchor.constraint(greaterThanOrEqualTo: leftAnchor, constant: 4), - imageView.centerXAnchor.constraint(equalTo: centerXAnchor), - imageView.widthAnchor.constraint(equalToConstant: 50), - imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor), - - nameLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 12), - nameLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: 0), - nameLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 0), - nameLabel.bottomAnchor.constraint(equalTo: bottomAnchor), - - ]) - } - - style: do { - imageView.contentMode = .scaleAspectFill - imageView.tintColor = ClassicImageEditStyle.default.black - nameLabel.font = UIFont.systemFont(ofSize: 12, weight: .medium) - nameLabel.textColor = ClassicImageEditStyle.default.black - nameLabel.textAlignment = .center - - changesMarkView.layer.cornerRadius = 2 - changesMarkView.backgroundColor = ClassicImageEditStyle.default.black - changesMarkView.isHidden = true - } - - body: do { - imageView.image = image - nameLabel.text = name - } - - addTarget(self, action: #selector(didTapSelf), for: .touchUpInside) - } - - public required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override open var isHighlighted: Bool { - didSet { - UIView.animate( - withDuration: 0.16, - delay: 0, - options: [.beginFromCurrentState], - animations: { - if self.isHighlighted { - self.alpha = 0.6 - } else { - self.alpha = 1 - } - }, - completion: nil - ) - } - } - - @objc private func didTapSelf() { - feedbackGenerator.selectionChanged() - } - } - } -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditMaskControlView.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditMaskControlView.swift deleted file mode 100644 index 4f955528..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditMaskControlView.swift +++ /dev/null @@ -1,179 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -import UIKit -#if !COCOAPODS -import BrightroomEngine -#endif - -open class ClassicImageEditMaskControlBase : ClassicImageEditControlBase { - -} - -open class ClassicImageEditMaskControl : ClassicImageEditMaskControlBase { - - private let contentView = UIView() - private lazy var navigationView = ClassicImageEditNavigationView(saveText: viewModel.localizedStrings.done, cancelText: viewModel.localizedStrings.cancel) - - private let clearButton = UIButton(type: .system) - private let slider = ClassicImageEditStepSlider() - private let sizeIndicator = UIView() - - open override func setup() { - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - base: do { - - addSubview(contentView) - addSubview(navigationView) - - contentView.translatesAutoresizingMaskIntoConstraints = false - navigationView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - - contentView.topAnchor.constraint(equalTo: contentView.superview!.topAnchor), - contentView.rightAnchor.constraint(equalTo: contentView.superview!.rightAnchor), - contentView.leftAnchor.constraint(equalTo: contentView.superview!.leftAnchor), - - navigationView.topAnchor.constraint(equalTo: contentView.bottomAnchor), - navigationView.rightAnchor.constraint(equalTo: navigationView.superview!.rightAnchor), - navigationView.leftAnchor.constraint(equalTo: navigationView.superview!.leftAnchor), - navigationView.bottomAnchor.constraint(equalTo: navigationView.superview!.bottomAnchor), - ]) - - } - - clearButton: do { - - contentView.addSubview(clearButton) - clearButton.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - clearButton.centerXAnchor.constraint(equalTo: clearButton.superview!.centerXAnchor), - clearButton.topAnchor.constraint(equalTo: clearButton.superview!.topAnchor, constant: 16), - ]) - - clearButton.addTarget(self, action: #selector(didTapRemoveAllButton), for: .touchUpInside) - clearButton.setTitle(viewModel.localizedStrings.clear, for: .normal) - clearButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 17) - - } - - sizeSlider: do { - slider.set(value: 0, min: -0.5, max: 0.5) - slider.mode = .plusAndMinus - slider.isStepLabelHidden = true - valueChanged() - let smallLabel = UILabel() - let largeLabel = UILabel() - - smallLabel.translatesAutoresizingMaskIntoConstraints = false - largeLabel.translatesAutoresizingMaskIntoConstraints = false - slider.translatesAutoresizingMaskIntoConstraints = false - - smallLabel.text = viewModel.localizedStrings.brushSizeSmall - smallLabel.textColor = .black - largeLabel.textColor = .black - largeLabel.text = viewModel.localizedStrings.brushSizeLarge - - smallLabel.setContentHuggingPriority(.defaultLow, for: .horizontal) - largeLabel.setContentHuggingPriority(.defaultLow, for: .horizontal) - - contentView.addSubview(smallLabel) - contentView.addSubview(largeLabel) - contentView.addSubview(slider) - NSLayoutConstraint.activate([ - smallLabel.leadingAnchor.constraint(greaterThanOrEqualTo: contentView.layoutMarginsGuide.leadingAnchor), - slider.leadingAnchor.constraint(equalTo: smallLabel.trailingAnchor, constant: 8), - largeLabel.leadingAnchor.constraint(equalTo: slider.trailingAnchor, constant: 8), - slider.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - largeLabel.trailingAnchor.constraint(lessThanOrEqualTo: contentView.layoutMarginsGuide.trailingAnchor), - smallLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - largeLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - slider.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), - slider.widthAnchor.constraint(equalTo: contentView.superview!.widthAnchor, multiplier: 2.0/3.0) - ]) - slider.addTarget(self, action: #selector(valueChanged), for: .valueChanged) - } - - sizeIndicator: do { - contentView.addSubview(sizeIndicator) - sizeIndicator.translatesAutoresizingMaskIntoConstraints = false - sizeIndicator.layer.cornerRadius = 50 / 2 - sizeIndicator.clipsToBounds = false - sizeIndicator.backgroundColor = .white - sizeIndicator.layer.borderColor = UIColor.black.cgColor - sizeIndicator.layer.borderWidth = 1 - NSLayoutConstraint.activate([ - sizeIndicator.widthAnchor.constraint(equalToConstant: 50), - sizeIndicator.heightAnchor.constraint(equalToConstant: 50), - sizeIndicator.topAnchor.constraint(equalTo: slider.bottomAnchor, constant: 8), - sizeIndicator.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), - ]) - } - - navigationView.didTapCancelButton = { [weak self] in - - guard let self = self else { return } - - self.pop(animated: true) - self.viewModel.endMasking(save: false) - } - - navigationView.didTapDoneButton = { [weak self] in - - guard let self = self else { return } - - self.pop(animated: true) - self.viewModel.endMasking(save: true) - } - - } - - open override func didMoveToSuperview() { - super.didMoveToSuperview() - - if superview != nil { - viewModel.setMode(.masking) - } else { - viewModel.setMode(.preview) - } - } - - @objc - private func valueChanged() { - let position = CGFloat(slider.transition(min: -0.5, max: 0.5) + 0.5) - let min = CGFloat(5) - let max = CGFloat(50) - let size = (min + position * (max - min)).rounded() - - sizeIndicator.transform = .init(scaleX: size / max, y: size / max) - viewModel.setBrushSize(size) - } - - @objc - private func didTapRemoveAllButton() { - viewModel.editingStack.set(blurringMaskPaths: []) - viewModel.editingStack.takeSnapshot() - } -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditNavigationView.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditNavigationView.swift deleted file mode 100644 index 6e681e94..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditNavigationView.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -import UIKit - -open class ClassicImageEditNavigationView : UIStackView { - - public var didTapDoneButton: () -> Void = {} - public var didTapCancelButton: () -> Void = {} - - private let saveButton = UIButton(type: .system) - private let cancelButton = UIButton(type: .system) - - private let feedbacker = UIImpactFeedbackGenerator(style: .light) - - public init(saveText: String, cancelText: String) { - - super.init(frame: .zero) - - axis = .horizontal - distribution = .fillEqually - - heightAnchor.constraint(equalToConstant: 50).isActive = true - - addArrangedSubview(cancelButton) - addArrangedSubview(saveButton) - - cancelButton.setTitle(cancelText, for: .normal) - saveButton.setTitle(saveText, for: .normal) - - cancelButton.setTitleColor(ClassicImageEditStyle.default.black, for: .normal) - saveButton.setTitleColor(ClassicImageEditStyle.default.black, for: .normal) - - cancelButton.titleLabel!.font = UIFont.systemFont(ofSize: 17) - saveButton.titleLabel!.font = UIFont.boldSystemFont(ofSize: 17) - - cancelButton.addTarget(self, action: #selector(_didTapCancelButton), for: .touchUpInside) - saveButton.addTarget(self, action: #selector(_didTapSaveButton), for: .touchUpInside) - } - - public required init(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc - private func _didTapSaveButton() { - didTapDoneButton() - feedbacker.impactOccurred() - } - - @objc - private func _didTapCancelButton() { - didTapCancelButton() - feedbacker.impactOccurred() - } -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditPresetListControl.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditPresetListControl.swift deleted file mode 100644 index 8c5c361a..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditPresetListControl.swift +++ /dev/null @@ -1,420 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif - -import Verge - -open class ClassicImageEditPresetListControlBase : ClassicImageEditControlBase { - - public required override init( - viewModel: ClassicImageEditViewModel - ) { - super.init(viewModel: viewModel) - } -} - -open class ClassicImageEditPresetListControl: ClassicImageEditPresetListControlBase, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource { - - private enum Section : Int, CaseIterable { - - case original - case selections - } - - private struct State: Equatable { - - struct Content: Equatable { - var previews: [PreviewFilterPreset] - var originalImage: CIImage - } - - var content: Content? - - var currentSelected: FilterPreset? - } - - // MARK: - Properties - - public lazy var collectionView: UICollectionView = self.makeCollectionView() - - private let feedbackGenerator = UISelectionFeedbackGenerator() - - private let store: Store - - private var subscriptions: Set = .init() - - // MARK: - Functions - - public required init( - viewModel: ClassicImageEditViewModel - ) { - - self.store = .init(initialState: .init(content: nil, currentSelected: nil)) - - super.init(viewModel: viewModel) - - viewModel.sinkState { [weak self] state in - - guard let self = self else { return } - - self.store.commit { viewState in - - if let state = state.mapIfPresent(\.editingState.loadedState) { - - state.ifChanged(\.thumbnailImage, \.previewFilterPresets) { image, filters in - - viewState.content = .init(previews: filters, originalImage: image) - } - - } - - } - - } - .store(in: &subscriptions) - - store.sinkState { [weak self] (state) in - - guard let self = self else { return } - - if state.hasChanges(\.content) { - self.collectionView.reloadData() - } - - state.ifChanged(\.currentSelected) { value in - self.collectionView.visibleCells.forEach { - self.updateSelected(cell: $0, selectedItem: value) - } - self.scrollTo(selectedItem: value, animated: true) - } - - } - .store(in: &subscriptions) - - } - - open override func setup() { - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - addSubview(collectionView) - - let itemSize = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.itemSize - collectionView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - collectionView.topAnchor.constraint(greaterThanOrEqualTo: collectionView.superview!.topAnchor), - collectionView.rightAnchor.constraint(equalTo: collectionView.superview!.rightAnchor), - collectionView.leftAnchor.constraint(equalTo: collectionView.superview!.leftAnchor), - collectionView.bottomAnchor.constraint(lessThanOrEqualTo: collectionView.superview!.bottomAnchor), - collectionView.centerYAnchor.constraint(equalTo: collectionView.superview!.centerYAnchor), - collectionView.heightAnchor.constraint(equalToConstant: itemSize?.height ?? 100), - ]) - - collectionView.dataSource = self - collectionView.delegate = self - - } - - open func makeCollectionView() -> UICollectionView { - - let collectionView = UICollectionView(frame: .zero, collectionViewLayout: makeCollectionViewLayout()) - - if #available(iOS 11.0, *) { - collectionView.contentInsetAdjustmentBehavior = .never - } else { - // Fallback on earlier versions - } - collectionView.backgroundColor = .clear - collectionView.showsVerticalScrollIndicator = false - collectionView.showsHorizontalScrollIndicator = false - collectionView.contentInset.right = 44 - collectionView.contentInset.left = 44 - collectionView.delaysContentTouches = false - - collectionView.register(NormalCell.self, forCellWithReuseIdentifier: NormalCell.identifier) - collectionView.register(SelectionCell.self, forCellWithReuseIdentifier: SelectionCell.identifier) - - return collectionView - } - - open func makeCollectionViewLayout() -> UICollectionViewLayout { - - let layout = UICollectionViewFlowLayout() - - layout.scrollDirection = .horizontal - layout.minimumLineSpacing = 16 - layout.minimumInteritemSpacing = 0 - layout.itemSize = CGSize(width: 64, height: 100) - - return layout - } - - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.preset) { value in - store.commit { - $0.currentSelected = value - } - } - - } - - open override func layoutSubviews() { - super.layoutSubviews() - collectionView.collectionViewLayout.invalidateLayout() - scrollTo(selectedItem: store.state.currentSelected, animated: false) - } - - // MARK: - UICollectionViewDeleagte / DataSource - - open func numberOfSections(in collectionView: UICollectionView) -> Int { - guard store.state.content != nil else { - return 0 - } - return Section.allCases.count - } - - open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - - guard let content = store.state.content else { - return 0 - } - - switch Section.allCases[section] { - case .original: - return 1 - case .selections: - return content.previews.count - } - } - - private func updateSelected(cell: UICollectionViewCell, selectedItem: FilterPreset?) { - switch cell { - case let cell as NormalCell: - cell._isSelected = selectedItem == nil - case let cell as SelectionCell: - cell._isSelected = selectedItem == cell.preview?.filter - default: - break - } - } - - open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - - guard let content = store.state.content else { - preconditionFailure() - } - - let currentSelected = store.state.currentSelected - - switch Section.allCases[indexPath.section] { - case .original: - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NormalCell.identifier, for: indexPath) as! NormalCell - cell.set(originalImage: content.originalImage, name: viewModel.localizedStrings.control_preset_normal_name) - updateSelected(cell: cell, selectedItem: currentSelected) - return cell - case .selections: - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SelectionCell.identifier, for: indexPath) as! SelectionCell - let filter = content.previews[indexPath.item] - cell.set(preview: filter) - updateSelected(cell: cell, selectedItem: currentSelected) - return cell - } - - } - - open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - - guard let content = store.state.content else { - preconditionFailure() - } - - switch Section.allCases[indexPath.section] { - case .original: - - viewModel.editingStack.set(filters: { - $0.preset = nil - }) - - viewModel.editingStack.takeSnapshot() - - case .selections: - - viewModel.editingStack.set(filters: { - let filter = content.previews[indexPath.item] - $0.preset = filter.filter - }) - - viewModel.editingStack.takeSnapshot() - } - - feedbackGenerator.selectionChanged() - - } - - public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { - - switch Section.allCases[section] { - case .original: - return .zero - case .selections: - return UIEdgeInsets(top: 0, left: 24, bottom: 0, right: 0) - } - } - - private func scrollTo(selectedItem: FilterPreset?, animated: Bool) { - - guard let content = store.state.content else { - - return - } - - if let current = selectedItem, let index = content.previews.firstIndex(where: { $0.filter == current }) { - collectionView.scrollToItem( - at: IndexPath.init(item: index, section: Section.selections.rawValue), - at: .centeredHorizontally, - animated: animated - ) - } else { - collectionView.scrollToItem( - at: IndexPath.init(item: 0, section: Section.original.rawValue), - at: .centeredHorizontally, - animated: animated - ) - } - } - - // MARK: - Nested Types - - open class CellBase : UICollectionViewCell { - - let nameLabel: UILabel = .init() - - #if false - let imageView = _ImageView() - #else - let imageView = MetalImageView() - #endif - - public override init(frame: CGRect) { - super.init(frame: frame) - - layout: do { - imageView.contentMode = .scaleAspectFill - imageView.clipsToBounds = true - - contentView.addSubview(nameLabel) - contentView.addSubview(imageView) - nameLabel.translatesAutoresizingMaskIntoConstraints = false - imageView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - - imageView.topAnchor.constraint(equalTo: contentView.topAnchor), - imageView.rightAnchor.constraint(equalTo: contentView.rightAnchor), - imageView.leftAnchor.constraint(equalTo: contentView.leftAnchor), - imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor), - - nameLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 12), - nameLabel.rightAnchor.constraint(lessThanOrEqualTo: contentView.rightAnchor, constant: -2), - nameLabel.leftAnchor.constraint(greaterThanOrEqualTo: contentView.leftAnchor, constant: 2), - nameLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), - - ]) - } - - style: do { - - nameLabel.textAlignment = .center - nameLabel.font = UIFont.systemFont(ofSize: 12, weight: .medium) - nameLabel.textColor = ClassicImageEditStyle.default.black - - } - - initialStyle: do { - - nameLabel.alpha = 0.3 - } - } - - public required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - open override func prepareForReuse() { - super.prepareForReuse() - nameLabel.text = nil - _isSelected = false - } - - open var _isSelected: Bool = false { - didSet { - nameLabel.alpha = _isSelected ? 1 : 0.3 - } - } - - open override var isHighlighted: Bool { - get { - - return super.isHighlighted - } - set { - - super.isHighlighted = newValue - - } - } - } - - open class NormalCell : CellBase { - - static let identifier = "me.muukii.PixelEditor.FilterCellNormal" - - open func set(originalImage: CIImage, name: String) { - - nameLabel.text = name - imageView.display(image: originalImage) - } - - } - - open class SelectionCell : CellBase { - - static let identifier = "me.muukii.PixelEditor.FilterCell" - - open var preview: PreviewFilterPreset? - - open func set(preview: PreviewFilterPreset) { - - self.preview = preview - - nameLabel.text = preview.filter.name - imageView.display(image: preview.image) - } - - } -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditRootControlView.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditRootControlView.swift deleted file mode 100644 index 3b4a86c0..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditRootControlView.swift +++ /dev/null @@ -1,224 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -import UIKit - -open class ClassicImageEditRootControlBase: ClassicImageEditControlBase { - - public required init( - viewModel: ClassicImageEditViewModel, - editMenuControl: ClassicImageEditEditMenuControlBase, - presetListControl: ClassicImageEditPresetListControlBase - ) { - super.init(viewModel: viewModel) - } -} - -open class ClassicImageEditRootControl: ClassicImageEditRootControlBase { - - public enum DisplayType { - case filter - case edit - } - - public var displayType: DisplayType = .filter { - didSet { - guard oldValue != displayType else { return } - set(displayType: displayType) - } - } - - public let filtersButton = UIButton(type: .system) - - public let editButton = UIButton(type: .system) - - private let containerView = UIView() - - public let presetListControl: ClassicImageEditPresetListControlBase - - public let editMenuControl: ClassicImageEditEditMenuControlBase - - // MARK: - Initializers - - public required init( - viewModel: ClassicImageEditViewModel, - editMenuControl: ClassicImageEditEditMenuControlBase, - presetListControl: ClassicImageEditPresetListControlBase - ) { - - self.presetListControl = presetListControl - self.editMenuControl = editMenuControl - - super.init( - viewModel: viewModel, - editMenuControl: editMenuControl, - presetListControl: presetListControl - ) - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - layout: do { - - let stackView = UIStackView(arrangedSubviews: [filtersButton, editButton]) - stackView.axis = .horizontal - stackView.distribution = .fillEqually - - addSubview(containerView) - addSubview(stackView) - - containerView.translatesAutoresizingMaskIntoConstraints = false - stackView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - - containerView.topAnchor.constraint(equalTo: containerView.superview!.topAnchor), - containerView.leftAnchor.constraint(equalTo: containerView.superview!.leftAnchor), - containerView.rightAnchor.constraint(equalTo: containerView.superview!.rightAnchor), - - stackView.topAnchor.constraint(equalTo: containerView.bottomAnchor), - stackView.leftAnchor.constraint(equalTo: stackView.superview!.leftAnchor), - stackView.rightAnchor.constraint(equalTo: stackView.superview!.rightAnchor), - stackView.bottomAnchor.constraint(equalTo: stackView.superview!.bottomAnchor), - stackView.heightAnchor.constraint(equalToConstant: 50), - ]) - - } - - body: do { - - filtersButton.setTitle(viewModel.localizedStrings.filter, for: .normal) - editButton.setTitle(viewModel.localizedStrings.edit, for: .normal) - - filtersButton.tintColor = .clear - editButton.tintColor = .clear - - filtersButton.setTitleColor(UIColor.black.withAlphaComponent(0.5), for: .normal) - editButton.setTitleColor(UIColor.black.withAlphaComponent(0.5), for: .normal) - - filtersButton.setTitleColor(.black, for: .selected) - editButton.setTitleColor(.black, for: .selected) - - filtersButton.titleLabel!.font = UIFont.boldSystemFont(ofSize: 17) - editButton.titleLabel!.font = UIFont.boldSystemFont(ofSize: 17) - - filtersButton.addTarget(self, action: #selector(didTapFilterButton), for: .touchUpInside) - editButton.addTarget(self, action: #selector(didTapEditButton), for: .touchUpInside) - } - - } - - // MARK: - Functions - - open override func didMoveToSuperview() { - super.didMoveToSuperview() - - if superview != nil { - set(displayType: displayType) - } - } - - @objc - private func didTapFilterButton() { - - displayType = .filter - } - - @objc - private func didTapEditButton() { - - displayType = .edit - } - - private func set(displayType: DisplayType) { - - containerView.subviews.forEach { $0.removeFromSuperview() } - - filtersButton.isSelected = false - editButton.isSelected = false - - switch displayType { - case .filter: - - presetListControl.frame = containerView.bounds - presetListControl.autoresizingMask = [.flexibleWidth, .flexibleHeight] - containerView.addSubview(presetListControl) - - filtersButton.isSelected = true - - case .edit: - - editMenuControl.frame = containerView.bounds - editMenuControl.autoresizingMask = [.flexibleWidth, .flexibleHeight] - - containerView.addSubview(editMenuControl) - - editButton.isSelected = true - } - } - -} - -/// A view that disabled preset and edit selection button. -/// It displays the edit panel directly. -open class ClassicImageEditNoPresetRootControl: ClassicImageEditRootControlBase { - - private let containerView = UIView() - - public let editMenuControl: ClassicImageEditEditMenuControlBase - - // MARK: - Initializers - - public required init( - viewModel: ClassicImageEditViewModel, - editMenuControl: ClassicImageEditEditMenuControlBase, - presetListControl: ClassicImageEditPresetListControlBase - ) { - - self.editMenuControl = editMenuControl - - super.init( - viewModel: viewModel, - editMenuControl: editMenuControl, - presetListControl: presetListControl - ) - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - layout: do { - - addSubview(containerView) - - containerView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - containerView.topAnchor.constraint(equalTo: containerView.superview!.topAnchor), - containerView.leftAnchor.constraint(equalTo: containerView.superview!.leftAnchor), - containerView.rightAnchor.constraint(equalTo: containerView.superview!.rightAnchor), - containerView.bottomAnchor.constraint(equalTo: containerView.superview!.bottomAnchor), - ]) - - editMenuControl.frame = containerView.bounds - editMenuControl.autoresizingMask = [.flexibleWidth, .flexibleHeight] - containerView.addSubview(editMenuControl) - } - - } - -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditStepSlider.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditStepSlider.swift deleted file mode 100644 index f6545748..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/ClassicImageEditStepSlider.swift +++ /dev/null @@ -1,386 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import TransitionPatch - -public final class ClassicImageEditStepSlider : UIControl { - - public enum Mode { - case plus - case plusAndMinus - case minus - } - - public var mode: Mode = .plusAndMinus { - didSet { - setupValues() - } - } - - public var isStepLabelHidden: Bool { - get { - return internalSlider.stepLabel.isHidden - } - set { - internalSlider.stepLabel.isHidden = newValue - } - } - - private var minStep: Int = -100 - - private var maxStep: Int = 100 - - public override var intrinsicContentSize: CGSize { - return internalSlider.intrinsicContentSize - } - - private let feedbacker = UIImpactFeedbackGenerator(style: .light) - private let feedbackGenerator = UISelectionFeedbackGenerator() - - private let offset: Double = 0.05 - - public private(set) var step: Int = 0 { - didSet { - if oldValue != step { - if step == 0, internalSlider.isTracking { - feedbackGenerator.selectionChanged() - feedbackGenerator.prepare() - } - } - updateStepLabel() - - sendActions(for: .valueChanged) - } - } - - private let internalSlider = _StepSlider(frame: .zero) - - public override init(frame: CGRect) { - super.init(frame: .zero) - setup() - feedbackGenerator.prepare() - } - - public required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setup() { - - internalSlider.addTarget(self, action: #selector(__didChangeValue), for: .valueChanged) - - addSubview(internalSlider) - internalSlider.frame = bounds - internalSlider.autoresizingMask = [.flexibleWidth, .flexibleHeight] - - setupValues() - } - - private func setupValues() { - - switch mode { - case .plus: - maxStep = 100 - minStep = 0 - case .plusAndMinus: - maxStep = 100 - minStep = -100 - case .minus: - maxStep = 0 - minStep = -100 - } - - internalSlider.dotLocation = mode - - if minStep < 0 { - internalSlider.minimumValue = -1 - } else { - internalSlider.minimumValue = 0 - } - - if maxStep > 0 { - internalSlider.maximumValue = 1 - } else { - internalSlider.maximumValue = 0 - } - - step = 0 - } - - public func set(value: Double, min: Double, max: Double) { - - guard !internalSlider.isTracking else { return } - - if value == 0 { - internalSlider.value = 0 - } else { - if value > 0 { - internalSlider.value = Float( - ValuePatch(CGFloat(value)) - .progress(start: 0, end: CGFloat(max)) - .transition(start: CGFloat(offset), end: CGFloat(internalSlider.maximumValue)) - .value - ) - } else { - internalSlider.value = Float( - ValuePatch(CGFloat(value)) - .progress(start: 0, end: CGFloat(min)) - .transition(start: CGFloat(-offset), end: CGFloat(internalSlider.minimumValue)) - .value - ) - } - } - - __didChangeValue() - - } - - public func transition(min: Double, max: Double) -> Double { - - if step == 0 { - return 0 - } else { - if step > 0 { - return - Double( - ValuePatch(CGFloat(step)) - .progress(start: CGFloat(0), end: CGFloat(maxStep)) - .transition(start: 0, end: CGFloat(max)) - .value - ) - - } else { - return - Double( - ValuePatch(CGFloat(step)) - .progress(start: CGFloat(0), end: CGFloat(minStep)) - .transition(start: 0, end: CGFloat(min)) - .value - ) - } - } - } - - @objc - private func __didChangeValue() { - - let value = internalSlider.value - - if case -offset ... offset = Double(value) { - if self.step != 0 { - self.step = 0 - } - // To fix thumb - internalSlider.value = 0 - return - } - - let step: Int - - if value > 0 { - - step = Int( - ValuePatch(CGFloat(value)) - .progress(start: CGFloat(offset), end: CGFloat(internalSlider.maximumValue)) - .transition(start: 0, end: CGFloat(maxStep)) - .value - .rounded() - ) - - } else { - - step = Int( - ValuePatch(CGFloat(value)) - .progress(start: CGFloat(-offset), end: CGFloat(internalSlider.minimumValue)) - .transition(start: 0, end: CGFloat(minStep)) - .value - .rounded() - ) - - } - - self.step = Int(step) - } - - private func updateStepLabel() { - if step == 0 { - internalSlider.stepLabel.text = "" - } else { - internalSlider.stepLabel.text = "\(step)" - } - internalSlider.stepLabel.sizeToFit() - internalSlider.updateStepLabel() - } -} - -extension ClassicImageEditStepSlider { - - public func set(value: Double, in range: ParameterRange) { - - set(value: value, min: range.min, max: range.max) - } - - public func transition(in range: ParameterRange) -> Double { - return transition(min: range.min, max: range.max) - } -} - -private final class _StepSlider: UISlider { - - let stepLabel: UILabel = .init() - - private var _trackImageView: UIImageView? - - var dotLocation: ClassicImageEditStepSlider.Mode = .plus { - didSet { - setNeedsDisplay() - } - } - - override init(frame: CGRect) { - - super.init(frame: frame) - self.setup() - } - - required init?(coder aDecoder: NSCoder) { - fatalError() - } - - override func touchesMoved(_ touches: Set, with event: UIEvent?) { - super.touchesMoved(touches, with: event) - updateStepLabel() - } - - override func draw(_ rect: CGRect) { - - guard let context = UIGraphicsGetCurrentContext() else { - return - } - - let scale = contentScaleFactor - - guard - let layer = CGLayer(context, size: CGSize(width: bounds.size.width * scale, height: bounds.size.height * scale), auxiliaryInfo: nil), - let layerContext = layer.context - else { - return - } - - UIGraphicsPushContext(layerContext) - - layerContext.scaleBy(x: scale, y: scale) - - UIColor(white: 0, alpha: 1).setStroke() - UIColor(white: 0, alpha: 1).setFill() - - line: do { - let path = UIBezierPath() - path.move(to: CGPoint.init(x: 10, y: bounds.midY)) - path.addLine(to: CGPoint.init(x: bounds.maxX - 10, y: bounds.midY)) - path.lineWidth = 1 - path.stroke() - } - - dot: do { - - switch dotLocation { - case .plus: - let path = UIBezierPath(ovalIn: .init(origin: .init(x: 10 - 3, y: bounds.midY - 3), size: .init(width: 6, height: 6))) - path.fill() - case .plusAndMinus: - let path = UIBezierPath(ovalIn: .init(origin: .init(x: bounds.midX - 3, y: bounds.midY - 3), size: .init(width: 6, height: 6))) - path.fill() - case .minus: - let path = UIBezierPath(ovalIn: .init(origin: .init(x: bounds.maxX - 10 - 3, y: bounds.midY - 3), size: .init(width: 6, height: 6))) - path.fill() - } - - } - - UIGraphicsPopContext() - - context.setAlpha(0.2) - context.draw(layer, in: bounds) - } - - private func setup() { - - minimumTrackTintColor = UIColor.clear - maximumTrackTintColor = UIColor.clear - setThumbImage(UIImage(named: "slider_thumb", in: bundle, compatibleWith: nil), for: []) - tintColor = ClassicImageEditStyle.default.black - - let label = stepLabel - label.backgroundColor = UIColor.clear - label.font = UIFont.monospacedDigitSystemFont(ofSize: 12, weight: .medium) - label.textColor = UIColor.black - label.textAlignment = .center - - self.addSubview(label) - } - - override func layoutSubviews() { - super.layoutSubviews() - updateStepLabel() - } - - func updateStepLabel() { - - func __findTrackViewIfNeeded() { - guard _trackImageView == nil else { - return - } - - if #available(iOS 14, *) { - for imageView in self.subviews.first?.subviews ?? [] where imageView is UIImageView { - - if imageView.bounds.width == imageView.bounds.height { - _trackImageView = imageView as? UIImageView - } - } - } else { - for imageView in self.subviews where imageView is UIImageView { - - if imageView.bounds.width == imageView.bounds.height { - _trackImageView = imageView as? UIImageView - } - } - } - - } - - __findTrackViewIfNeeded() - - guard let trackImageView = _trackImageView else { - return - } - - let center = CGPoint(x: trackImageView.frame.midX, y: -16) - self.stepLabel.center = center - } - -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditClarityControl.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditClarityControl.swift deleted file mode 100644 index 524fed66..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditClarityControl.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import Verge - -open class ClassicImageEditClarityControlBase : ClassicImageEditFilterControlBase { - - public required init(viewModel: ClassicImageEditViewModel) { - super.init(viewModel: viewModel) - } -} - -open class ClassicImageEditClarityControl : ClassicImageEditClarityControlBase { - - open override var title: String { - return viewModel.localizedStrings.editClarity - } - - private lazy var navigationView = ClassicImageEditNavigationView(saveText: viewModel.localizedStrings.done, cancelText: viewModel.localizedStrings.cancel) - - public let slider = ClassicImageEditStepSlider(frame: .zero) - - open override func setup() { - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - TempCode.layout(navigationView: navigationView, slider: slider, in: self) - - slider.mode = .plus - slider.addTarget(self, action: #selector(valueChanged), for: .valueChanged) - - navigationView.didTapCancelButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.revertEdit() - self.pop(animated: true) - } - - navigationView.didTapDoneButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.takeSnapshot() - self.pop(animated: true) - } - } - - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.unsharpMask) { value in - slider.set(value: value?.intensity ?? 0, in: FilterUnsharpMask.Params.intensity) - } - - } - - @objc - private func valueChanged() { - - let value = slider.transition(in: FilterUnsharpMask.Params.intensity) - - guard value != 0 else { - viewModel.editingStack.set(filters: { - $0.unsharpMask = nil - }) - return - } - - viewModel.editingStack.set(filters: { - var f = FilterUnsharpMask() - f.intensity = value - f.radius = 0.12 - $0.unsharpMask = f - }) - } - -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditContrastControl.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditContrastControl.swift deleted file mode 100644 index 20096e1e..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditContrastControl.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import Verge - -open class ClassicImageEditContrastControlBase : ClassicImageEditFilterControlBase { - public required init(viewModel: ClassicImageEditViewModel) { - super.init(viewModel: viewModel) - } -} - -open class ClassicImageEditContrastControl : ClassicImageEditContrastControlBase { - - open override var title: String { - return viewModel.localizedStrings.editContrast - } - - private lazy var navigationView = ClassicImageEditNavigationView(saveText: viewModel.localizedStrings.done, cancelText: viewModel.localizedStrings.cancel) - - public let slider = ClassicImageEditStepSlider(frame: .zero) - - open override func setup() { - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - TempCode.layout(navigationView: navigationView, slider: slider, in: self) - - slider.addTarget(self, action: #selector(valueChanged), for: .valueChanged) - - navigationView.didTapCancelButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.revertEdit() - self.pop(animated: true) - } - - navigationView.didTapDoneButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.takeSnapshot() - self.pop(animated: true) - } - - } - - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.contrast) { value in - slider.set(value: value?.value ?? 0, in: FilterContrast.range) - } - - } - - @objc - private func valueChanged() { - - let value = slider.transition(in: FilterContrast.range) - - guard value != 0 else { - viewModel.editingStack.set(filters: { - $0.contrast = nil - }) - return - } - - viewModel.editingStack.set(filters: { - var f = FilterContrast() - f.value = value - $0.contrast = f - }) - } - -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditExposureControl.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditExposureControl.swift deleted file mode 100644 index 5f1c4fc1..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditExposureControl.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import Verge - -open class ClassicImageEditExposureControlBase : ClassicImageEditFilterControlBase { - - public required init(viewModel: ClassicImageEditViewModel) { - super.init(viewModel: viewModel) - } -} - -open class ClassicImageEditExposureControl : ClassicImageEditExposureControlBase { - - open override var title: String { - return viewModel.localizedStrings.editBrightness - } - - private lazy var navigationView = ClassicImageEditNavigationView(saveText: viewModel.localizedStrings.done, cancelText: viewModel.localizedStrings.cancel) - - public let slider = ClassicImageEditStepSlider(frame: .zero) - - open override func setup() { - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - TempCode.layout(navigationView: navigationView, slider: slider, in: self) - - slider.addTarget(self, action: #selector(valueChanged), for: .valueChanged) - - navigationView.didTapCancelButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.revertEdit() - self.pop(animated: true) - } - - navigationView.didTapDoneButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.takeSnapshot() - self.pop(animated: true) - } - } - - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.exposure) { value in - slider.set(value: value?.value ?? 0, in: FilterExposure.range) - } - - } - - @objc - private func valueChanged() { - - let value = slider.transition(in: FilterExposure.range) - guard value != 0 else { - viewModel.editingStack.set(filters: { - $0.exposure = nil - }) - return - } - - viewModel.editingStack.set(filters: { - var f = FilterExposure() - f.value = value - $0.exposure = f - }) - - } -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditFadeControl.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditFadeControl.swift deleted file mode 100644 index e4cc9ab9..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditFadeControl.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import Verge - -open class ClassicImageEditFadeControlBase : ClassicImageEditFilterControlBase { - - public required init(viewModel: ClassicImageEditViewModel) { - super.init(viewModel: viewModel) - } -} - -open class ClassicImageEditFadeControl : ClassicImageEditFadeControlBase { - - open override var title: String { - return viewModel.localizedStrings.editFade - } - - private lazy var navigationView = ClassicImageEditNavigationView(saveText: viewModel.localizedStrings.done, cancelText: viewModel.localizedStrings.cancel) - - public let slider = ClassicImageEditStepSlider(frame: .zero) - - open override func setup() { - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - TempCode.layout(navigationView: navigationView, slider: slider, in: self) - - slider.mode = .plus - slider.addTarget(self, action: #selector(valueChanged), for: .valueChanged) - - navigationView.didTapCancelButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.revertEdit() - self.pop(animated: true) - } - - navigationView.didTapDoneButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.takeSnapshot() - self.pop(animated: true) - } - } - - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.fade) { value in - slider.set(value: value?.intensity ?? 0, in: FilterFade.Params.intensity) - } - - } - - @objc - private func valueChanged() { - - let value = slider.transition(in: FilterFade.Params.intensity) - - guard value != 0 else { - viewModel.editingStack.set(filters: { - $0.fade = nil - }) - return - } - - viewModel.editingStack.set(filters: { - var f = FilterFade() - f.intensity = value - $0.fade = f - }) - - } - -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditFilterControlBase.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditFilterControlBase.swift deleted file mode 100644 index ea4e429f..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditFilterControlBase.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -open class ClassicImageEditFilterControlBase : ClassicImageEditControlBase { - - open var title: String { - fatalError("Must be overrided") - } - - public required override init(viewModel: ClassicImageEditViewModel) { - super.init(viewModel: viewModel) - } - - open override func didMoveToSuperview() { - super.didMoveToSuperview() - - if superview != nil { - viewModel.setMode(.editing) - viewModel.setTitle(title) - } else { - viewModel.setMode(.preview) - viewModel.setTitle("") - } - - } -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditGaussianBlurControl.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditGaussianBlurControl.swift deleted file mode 100644 index 898356ff..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditGaussianBlurControl.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import Verge - -open class ClassicImageEditGaussianBlurControlBase : ClassicImageEditFilterControlBase { - - public required init(viewModel: ClassicImageEditViewModel) { - super.init(viewModel: viewModel) - } -} - -open class ClassicImageEditGaussianBlurControl : ClassicImageEditGaussianBlurControlBase { - - open override var title: String { - return viewModel.localizedStrings.editBlur - } - - private lazy var navigationView = ClassicImageEditNavigationView(saveText: viewModel.localizedStrings.done, cancelText: viewModel.localizedStrings.cancel) - - public let slider = ClassicImageEditStepSlider(frame: .zero) - - open override func setup() { - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - TempCode.layout(navigationView: navigationView, slider: slider, in: self) - - slider.mode = .plus - slider.addTarget(self, action: #selector(valueChanged), for: .valueChanged) - - navigationView.didTapCancelButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.revertEdit() - self.pop(animated: true) - } - - navigationView.didTapDoneButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.takeSnapshot() - self.pop(animated: true) - } - } - - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.gaussianBlur) { value in - slider.set(value: value?.value ?? 0, in: FilterGaussianBlur.range) - } - - } - - @objc - private func valueChanged() { - - let value = slider.transition(in: FilterGaussianBlur.range) - - guard value != 0 else { - viewModel.editingStack.set(filters: { - $0.gaussianBlur = nil - }) - return - } - - viewModel.editingStack.set(filters: { - var f = FilterGaussianBlur() - f.value = value - $0.gaussianBlur = f - }) - - } - -} - diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditHighlightsControl.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditHighlightsControl.swift deleted file mode 100644 index 97d9d167..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditHighlightsControl.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import Verge - -open class ClassicImageEditHighlightsControlBase : ClassicImageEditFilterControlBase { - - public required init(viewModel: ClassicImageEditViewModel) { - super.init(viewModel: viewModel) - } -} - -open class ClassicImageEditHighlightsControl : ClassicImageEditHighlightsControlBase { - - open override var title: String { - return viewModel.localizedStrings.editHighlights - } - - private lazy var navigationView = ClassicImageEditNavigationView(saveText: viewModel.localizedStrings.done, cancelText: viewModel.localizedStrings.cancel) - - public let slider = ClassicImageEditStepSlider(frame: .zero) - - open override func setup() { - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - TempCode.layout(navigationView: navigationView, slider: slider, in: self) - - slider.mode = .plus - slider.addTarget(self, action: #selector(valueChanged), for: .valueChanged) - - navigationView.didTapCancelButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.revertEdit() - self.pop(animated: true) - } - - navigationView.didTapDoneButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.takeSnapshot() - self.pop(animated: true) - } - } - - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.highlights) { value in - slider.set(value: value?.value ?? 0, in: FilterHighlights.range) - } - - } - - @objc - private func valueChanged() { - - let value = slider.transition(in: FilterHighlights.range) - - guard value != 0 else { - viewModel.editingStack.set(filters: { - $0.highlights = nil - }) - return - } - - viewModel.editingStack.set(filters: { - var f = FilterHighlights() - f.value = value - $0.highlights = f - }) - } - -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditSaturationControl.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditSaturationControl.swift deleted file mode 100644 index a3024b3c..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditSaturationControl.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import Verge - -open class ClassicImageEditSaturationControlBase : ClassicImageEditFilterControlBase { - - public required init(viewModel: ClassicImageEditViewModel) { - super.init(viewModel: viewModel) - } -} - -open class ClassicImageEditSaturationControl : ClassicImageEditSaturationControlBase { - - open override var title: String { - return viewModel.localizedStrings.editSaturation - } - - private lazy var navigationView = ClassicImageEditNavigationView(saveText: viewModel.localizedStrings.done, cancelText: viewModel.localizedStrings.cancel) - - public let slider = ClassicImageEditStepSlider(frame: .zero) - - open override func setup() { - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - TempCode.layout(navigationView: navigationView, slider: slider, in: self) - - slider.addTarget(self, action: #selector(valueChanged), for: .valueChanged) - - navigationView.didTapCancelButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.revertEdit() - self.pop(animated: true) - } - - navigationView.didTapDoneButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.takeSnapshot() - self.pop(animated: true) - } - } - - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.saturation) { value in - slider.set(value: value?.value ?? 0, in: FilterSaturation.range) - } - - } - - @objc - private func valueChanged() { - - let value = slider.transition(in: FilterSaturation.range ) - - guard value != 0 else { - viewModel.editingStack.set(filters: { - $0.saturation = nil - }) - return - } - - viewModel.editingStack.set(filters: { - var f = FilterSaturation() - f.value = value - $0.saturation = f - }) - } - -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditShadowsControl.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditShadowsControl.swift deleted file mode 100644 index cecda980..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditShadowsControl.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import Verge - -open class ClassicImageEditShadowsControlBase : ClassicImageEditFilterControlBase { - - public required init(viewModel: ClassicImageEditViewModel) { - super.init(viewModel: viewModel) - } -} - -open class ClassicImageEditShadowsControl : ClassicImageEditShadowsControlBase { - - open override var title: String { - return viewModel.localizedStrings.editShadows - } - - private lazy var navigationView = ClassicImageEditNavigationView(saveText: viewModel.localizedStrings.done, cancelText: viewModel.localizedStrings.cancel) - - public let slider = ClassicImageEditStepSlider(frame: .zero) - - open override func setup() { - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - TempCode.layout(navigationView: navigationView, slider: slider, in: self) - - slider.addTarget(self, action: #selector(valueChanged), for: .valueChanged) - - navigationView.didTapCancelButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.revertEdit() - self.pop(animated: true) - } - - navigationView.didTapDoneButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.takeSnapshot() - self.pop(animated: true) - } - } - - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.shadows) { value in - slider.set(value: value?.value ?? 0, in: FilterShadows.range) - } - - } - - @objc - private func valueChanged() { - - let value = slider.transition(in: FilterShadows.range) - - guard value != 0 else { - viewModel.editingStack.set(filters: { $0.shadows = nil }) - return - } - - viewModel.editingStack.set(filters: { - var f = FilterShadows() - f.value = value - $0.shadows = f - }) - - } - -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditSharpenControl.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditSharpenControl.swift deleted file mode 100644 index 086a4486..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditSharpenControl.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import Verge - -open class ClassicImageEditSharpenControlBase : ClassicImageEditFilterControlBase { - - public required init(viewModel: ClassicImageEditViewModel) { - super.init(viewModel: viewModel) - } -} - -open class ClassicImageEditSharpenControl : ClassicImageEditSharpenControlBase { - - open override var title: String { - return viewModel.localizedStrings.editSharpen - } - - private lazy var navigationView = ClassicImageEditNavigationView(saveText: viewModel.localizedStrings.done, cancelText: viewModel.localizedStrings.cancel) - - public let slider = ClassicImageEditStepSlider(frame: .zero) - - open override func setup() { - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - TempCode.layout(navigationView: navigationView, slider: slider, in: self) - - slider.mode = .plus - slider.addTarget(self, action: #selector(valueChanged), for: .valueChanged) - - navigationView.didTapCancelButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.revertEdit() - self.pop(animated: true) - } - - navigationView.didTapDoneButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.takeSnapshot() - self.pop(animated: true) - } - } - - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.sharpen) { value in - slider.set(value: value?.sharpness ?? 0, in: FilterSharpen.Params.sharpness) - } - - } - - @objc - private func valueChanged() { - - let value = slider.transition(in: FilterSharpen.Params.sharpness) - - guard value != 0 else { - viewModel.editingStack.set(filters: { - $0.sharpen = nil - }) - return - } - - viewModel.editingStack.set(filters: { - var f = FilterSharpen() - f.sharpness = value - f.radius = 1.2 - $0.sharpen = f - }) - } - -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditTemperatureControl.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditTemperatureControl.swift deleted file mode 100644 index f4323fbe..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditTemperatureControl.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import Verge - -open class ClassicImageEditTemperatureControlBase : ClassicImageEditFilterControlBase { - - public required init(viewModel: ClassicImageEditViewModel) { - super.init(viewModel: viewModel) - } -} - -open class ClassicImageEditTemperatureControl : ClassicImageEditTemperatureControlBase { - - open override var title: String { - return viewModel.localizedStrings.editTemperature - } - - private lazy var navigationView = ClassicImageEditNavigationView(saveText: viewModel.localizedStrings.done, cancelText: viewModel.localizedStrings.cancel) - - public let slider = ClassicImageEditStepSlider(frame: .zero) - - open override func setup() { - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - TempCode.layout(navigationView: navigationView, slider: slider, in: self) - - slider.addTarget(self, action: #selector(valueChanged), for: .valueChanged) - - navigationView.didTapCancelButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.revertEdit() - self.pop(animated: true) - } - - navigationView.didTapDoneButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.takeSnapshot() - self.pop(animated: true) - } - } - - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.temperature) { value in - - slider.set(value: value?.value ?? 0, in: FilterTemperature.range) - } - - } - - @objc - private func valueChanged() { - - let value = slider.transition(in: FilterTemperature.range) - - guard value != 0 else { - viewModel.editingStack.set(filters: { $0.temperature = nil }) - return - } - - viewModel.editingStack.set(filters: { - var f = FilterTemperature() - f.value = value - $0.temperature = f - }) - } - -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditVignetteControl.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditVignetteControl.swift deleted file mode 100644 index bddc37c6..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/ClassicImageEditVignetteControl.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import Verge - -open class ClassicImageEditVignetteControlBase : ClassicImageEditFilterControlBase { - - public required init(viewModel: ClassicImageEditViewModel) { - super.init(viewModel: viewModel) - } -} - -open class ClassicImageEditVignetteControl : ClassicImageEditVignetteControlBase { - - open override var title: String { - return viewModel.localizedStrings.editVignette - } - - private lazy var navigationView = ClassicImageEditNavigationView(saveText: viewModel.localizedStrings.done, cancelText: viewModel.localizedStrings.cancel) - - public let slider = ClassicImageEditStepSlider(frame: .zero) - - open override func setup() { - super.setup() - - backgroundColor = ClassicImageEditStyle.default.control.backgroundColor - - TempCode.layout(navigationView: navigationView, slider: slider, in: self) - - slider.mode = .plus - slider.addTarget(self, action: #selector(valueChanged), for: .valueChanged) - - navigationView.didTapCancelButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.revertEdit() - self.pop(animated: true) - } - - navigationView.didTapDoneButton = { [weak self] in - - guard let self = self else { return } - - self.viewModel.editingStack.takeSnapshot() - self.pop(animated: true) - } - } - - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.vignette) { value in - slider.set(value: value?.value ?? 0, in: FilterVignette.range) - } - } - - @objc - private func valueChanged() { - - let value = slider.transition(in: FilterVignette.range) - - guard value != 0 else { - viewModel.editingStack.set(filters: { $0.vignette = nil }) - return - } - - viewModel.editingStack.set( - filters: { - var f = FilterVignette() - f.value = value - $0.vignette = f - }) - - } - -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/TempCode.swift b/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/TempCode.swift deleted file mode 100644 index 9650d2aa..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/ClassicImageEdit/Components/FilterControl/TempCode.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -enum TempCode { - - static func layout(navigationView: ClassicImageEditNavigationView, slider: ClassicImageEditStepSlider, in view: UIView) { - - let containerGuide = UILayoutGuide() - - view.addLayoutGuide(containerGuide) - view.addSubview(slider) - view.addSubview(navigationView) - - slider.translatesAutoresizingMaskIntoConstraints = false - - navigationView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - - containerGuide.topAnchor.constraint(equalTo: slider.superview!.topAnchor), - containerGuide.rightAnchor.constraint(equalTo: slider.superview!.rightAnchor, constant: -44), - containerGuide.leftAnchor.constraint(equalTo: slider.superview!.leftAnchor, constant: 44), - - slider.topAnchor.constraint(greaterThanOrEqualTo: containerGuide.topAnchor), - slider.rightAnchor.constraint(equalTo: containerGuide.rightAnchor), - slider.leftAnchor.constraint(equalTo: containerGuide.leftAnchor), - slider.bottomAnchor.constraint(lessThanOrEqualTo: containerGuide.bottomAnchor), - slider.centerYAnchor.constraint(equalTo: containerGuide.centerYAnchor), - - navigationView.topAnchor.constraint(equalTo: containerGuide.bottomAnchor), - navigationView.rightAnchor.constraint(equalTo: navigationView.superview!.rightAnchor), - navigationView.leftAnchor.constraint(equalTo: navigationView.superview!.leftAnchor), - navigationView.bottomAnchor.constraint(equalTo: navigationView.superview!.bottomAnchor), - ]) - - } -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/PhotosCrop/PhotosCropAspectRatioControl.swift b/Package/Sources/BrightroomUI/Built-in UI/PhotosCrop/PhotosCropAspectRatioControl.swift deleted file mode 100644 index 76e2e4e3..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/PhotosCrop/PhotosCropAspectRatioControl.swift +++ /dev/null @@ -1,564 +0,0 @@ -// -// Copyright (c) 2021 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit -import SwiftUI - -import Verge - -#if !COCOAPODS -import BrightroomEngine -#endif - -final class PhotosCropAspectRatioControl: PixelEditorCodeBasedView { - struct State: Equatable { - enum Direction { - /** - +----+ - | | - | | - | | - | | - | | - | | - +----+ - */ - case vertical - /** - +---------------+ - | | - | | - +---------------+ - */ - case horizontal - - mutating func swap() { - switch self { - case .horizontal: self = .vertical - case .vertical: self = .horizontal - } - } - } - - /** - +---------------+ - | | - | | - +---------------+ - */ - let horizontalRectangleApectRatios: [PixelAspectRatio] = [ - .init(width: 16, height: 9), - .init(width: 10, height: 8), - .init(width: 7, height: 5), - .init(width: 4, height: 3), - .init(width: 5, height: 3), - .init(width: 3, height: 2), - ] - - let originalAspectRatio: PixelAspectRatio - let originalDirection: Direction - - var selectedAspectRatio: PixelAspectRatio? - - var direction: Direction { - didSet { - if let selectedAspectRatio = selectedAspectRatio { - if direction != selectedAspectRatio.direction { - self.selectedAspectRatio = selectedAspectRatio.swapped() - } - } - } - } - - var rotation: EditingCrop.Rotation = .angle_0 - - var canSelectDirection: Bool { - guard let selectedAspectRatio = selectedAspectRatio else { - return false - } - - guard selectedAspectRatio != .square else { - return false - } - - return true - } - } - - struct Handlers { - - var didSelectAspectRatio: (PixelAspectRatio) -> Void = { _ in } - var didSelectFreeform: () -> Void = {} - - } - - var handlers: Handlers = .init() - - private let horizontalButton = AspectRatioDirectionButton(direction: .horizontal) - private let verticalButton = AspectRatioDirectionButton(direction: .vertical) - - private let scrollView = UIScrollView() - - private let originalButton = AspectRatioButton() - private let freeformButton = AspectRatioButton() - private let aspectSquareButton = AspectRatioButton() - - private let store: UIStateStore - private var subscriptions = Set() - - private var isSupressingHandlers = false - private let localizedStrings: PhotosCropViewController.LocalizedStrings - - init( - originalAspectRatio: PixelAspectRatio, - localizedStrings: PhotosCropViewController.LocalizedStrings - ) { - - self.localizedStrings = localizedStrings - self.store = .init( - initialState: .init( - originalAspectRatio: originalAspectRatio, - originalDirection: originalAspectRatio.width > originalAspectRatio.height ? .horizontal : .vertical, - direction: originalAspectRatio.width > originalAspectRatio.height ? .horizontal : .vertical - ) - ) - - super.init(frame: .zero) - - let directionStackView = UIStackView()&>.do { - $0.addArrangedSubview(verticalButton) - $0.addArrangedSubview(horizontalButton) - $0.distribution = .equalCentering - $0.spacing = 16 - $0.alignment = .center - } - - var buttons: [CGFloat: UIButton] = [:] - - buttons[ratioValue(from: store.state.originalAspectRatio)] = originalButton - buttons[ratioValue(from: .square)] = aspectSquareButton - - let itemsStackView = UIStackView()&>.do { stackView in - - stackView.spacing = 12 - - [ - originalButton, - freeformButton, - aspectSquareButton, - ] - .forEach { - stackView.addArrangedSubview($0) - } - - store.state.horizontalRectangleApectRatios.forEach { ratio in - - let button = AspectRatioButton() - - stackView.addArrangedSubview(button) - - button.onTap { [unowned store] in - store.commit { - - switch $0.direction { - case .horizontal: - - $0.selectedAspectRatio = ratio - - case .vertical: - - $0.selectedAspectRatio = ratio.swapped() - } - - } - } - - buttons[ratioValue(from: ratio)] = button - } - } - - horizontalButton.onTap { [unowned self] in - store.commit { - switch $0.rotation { - case .angle_0, .angle_180: - $0.direction = .horizontal - case .angle_90, .angle_270: - $0.direction = .vertical - } - } - } - - verticalButton.onTap { [unowned self] in - store.commit { - switch $0.rotation { - case .angle_0, .angle_180: - $0.direction = .vertical - case .angle_90, .angle_270: - $0.direction = .horizontal - } - } - } - - originalButton&>.do { - $0.setTitle(localizedStrings.button_aspectratio_original, for: .normal) - $0.onTap { [unowned store] in - store.commit { - $0.selectedAspectRatio = $0.originalAspectRatio - } - } - - } - - freeformButton&>.do { - $0.setTitle(localizedStrings.button_aspectratio_freeform, for: .normal) - $0.onTap { [unowned store] in - store.commit { - $0.selectedAspectRatio = nil - } - } - } - - aspectSquareButton&>.do { - $0.setTitle(localizedStrings.button_aspectratio_square, for: .normal) - $0.onTap { [unowned store] in - store.commit { - $0.selectedAspectRatio = .square - } - } - } - - addSubview(directionStackView) - addSubview(scrollView) - scrollView.addSubview(itemsStackView) - - itemsStackView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - itemsStackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor), - itemsStackView.rightAnchor.constraint(equalTo: scrollView.contentLayoutGuide.rightAnchor), - itemsStackView.leftAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leftAnchor), - itemsStackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor), - - itemsStackView.topAnchor.constraint(equalTo: scrollView.frameLayoutGuide.topAnchor), - itemsStackView.bottomAnchor.constraint(equalTo: scrollView.frameLayoutGuide.bottomAnchor), - ]) - - directionStackView&>.do { - $0.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: topAnchor, constant: 24), - $0.rightAnchor.constraint(lessThanOrEqualTo: rightAnchor), - $0.leftAnchor.constraint(greaterThanOrEqualTo: leftAnchor), - $0.centerXAnchor.constraint(equalTo: centerXAnchor), - ]) - } - - scrollView&>.do { - $0.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: directionStackView.bottomAnchor, constant: 24), - $0.rightAnchor.constraint(equalTo: rightAnchor), - $0.leftAnchor.constraint(equalTo: leftAnchor), - $0.bottomAnchor.constraint(equalTo:bottomAnchor), - ]) - $0.contentInset = .init(top: 0, left: 24, bottom: 0, right: 24) - $0.showsHorizontalScrollIndicator = false - } - - store.sinkState { [weak self] state in - - guard let self = self else { return } - - state.ifChanged(\.selectedAspectRatio) { selected in - - guard let selected = selected else { - // Freeform - - buttons.forEach { - $0.value.isSelected = false - } - - self.freeformButton.isSelected = true - - self.handlers.didSelectFreeform() - - return - } - - self.freeformButton.isSelected = false - - buttons.forEach { - if $0.key == ratioValue(from: selected) { - $0.value.isSelected = true - } else { - $0.value.isSelected = false - } - } - - self.handlers.didSelectAspectRatio(selected) - - } - - state.ifChanged(\.canSelectDirection) { canSelectDirection in - - self.horizontalButton.isEnabled = canSelectDirection - self.verticalButton.isEnabled = canSelectDirection - } - - state.ifChanged(\.direction, \.rotation) { direction, rotation in - - var d = direction - - switch rotation { - case .angle_0, .angle_180: - break - case .angle_90, .angle_270: - d.swap() - } - - /// Changes display according to image's rectangle direction. - switch d { - case .horizontal: - - self.horizontalButton.isSelected = true - self.verticalButton.isSelected = false - - state.horizontalRectangleApectRatios.forEach { ratio in - buttons[ratioValue(from: ratio)]?.setTitle("\(Int(ratio.width)):\(Int(ratio.height))", for: .normal) - } - - case .vertical: - - self.horizontalButton.isSelected = false - self.verticalButton.isSelected = true - - state.horizontalRectangleApectRatios.forEach { ratio in - buttons[ratioValue(from: ratio)]?.setTitle("\(Int(ratio.height)):\(Int(ratio.width))", for: .normal) - } - } - - - - } - } - .store(in: &subscriptions) - } - - func setSelected(_ aspectRatio: PixelAspectRatio?) { - - guard let fixed = aspectRatio else { - store.commit { - if $0.selectedAspectRatio != nil { - $0.selectedAspectRatio = nil - } - } - return - } - - store.commit { - if $0.selectedAspectRatio != fixed { - $0.selectedAspectRatio = fixed - } - } - } - - func setRotation(_ rotation: EditingCrop.Rotation) { - - isSupressingHandlers = true - defer { - isSupressingHandlers = false - } - - store.commit { - $0.rotation = rotation - } - - } -} - -private final class AspectRatioButton: UIButton { - - private let backdropView = UIView() - - override var isSelected: Bool { - didSet { - setNeedsLayout() - } - } - - convenience init() { - self.init(frame: .zero) - - setTitleColor(.init(white: 1, alpha: 0.5), for: .normal) - setTitleColor(.white, for: .selected) - titleLabel?.font = .systemFont(ofSize: 12) - - } - - override init(frame: CGRect) { - super.init(frame: .zero) - - if #available(iOS 13.0, *) { - backdropView.layer.cornerCurve = .continuous - } else { - // Fallback on earlier versions - } - - backdropView.backgroundColor = .init(white: 1, alpha: 0.5) - - titleEdgeInsets = .init(top: -2, left: 6, bottom: -2, right: 6) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func layoutSubviews() { - super.layoutSubviews() - - insertSubview(backdropView, at: 0) - backdropView.frame = bounds - backdropView.layer.cornerRadius = bounds.height / 2 - - backdropView.isHidden = !isSelected - } - - override var intrinsicContentSize: CGSize { - let originalContentSize = super.intrinsicContentSize - let adjustedWidth = originalContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right - let adjustedHeight = originalContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom - return CGSize(width: adjustedWidth, height: adjustedHeight) - } - -} - -private final class AspectRatioDirectionButton: UIControl { - - private let shapeLayer = CAShapeLayer() - private let iconImageView = UIImageView(image: UIImage(named: "check", in: bundle, compatibleWith: nil)) - - override var isHighlighted: Bool { - didSet { - guard oldValue != isHighlighted else { - return - } - - UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in - self.alpha = isHighlighted ? 0.4 : 1 - } - .startAnimation() - } - } - - override var isEnabled: Bool { - didSet { - update() - } - } - - override var isSelected: Bool { - didSet { - update() - } - } - - init(direction: PhotosCropAspectRatioControl.State.Direction) { - super.init(frame: .zero) - - layer.addSublayer(shapeLayer) - iconImageView.tintColor = UIColor(white: 0, alpha: 0.8) - - addSubview(iconImageView) - - switch direction { - case .horizontal: - iconImageView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - iconImageView.topAnchor.constraint(equalTo: topAnchor, constant: 2), - iconImageView.rightAnchor.constraint(equalTo: rightAnchor, constant: -6), - iconImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: 6), - iconImageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -2), - ]) - - case .vertical: - iconImageView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - iconImageView.topAnchor.constraint(equalTo: topAnchor, constant: 6), - iconImageView.rightAnchor.constraint(equalTo: rightAnchor, constant: -2), - iconImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: 2), - iconImageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -6), - ]) - - } - - update() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func layoutSubviews() { - super.layoutSubviews() - shapeLayer.frame = bounds - - let path = UIBezierPath(roundedRect: bounds, cornerRadius: 4) - shapeLayer.path = path.cgPath - } - - private func update() { - - guard isEnabled else { - shapeLayer.strokeColor = UIColor(white: 0.6, alpha: 0.3).cgColor - shapeLayer.fillColor = UIColor(white: 0, alpha: 0.3).cgColor - iconImageView.isHidden = true - return - } - - if isSelected { - - shapeLayer.strokeColor = UIColor(white: 0.6, alpha: 1).cgColor - shapeLayer.fillColor = UIColor(white: 0.6, alpha: 1).cgColor - iconImageView.isHidden = false - - } else { - shapeLayer.strokeColor = UIColor(white: 0.6, alpha: 1).cgColor - shapeLayer.fillColor = UIColor(white: 0, alpha: 0.6).cgColor - iconImageView.isHidden = true - - } - - } - -} - -extension PixelAspectRatio { - var direction: PhotosCropAspectRatioControl.State.Direction { - if height > width { - return .vertical - } else { - return .horizontal - } - } -} - -func ratioValue(from ratio: PixelAspectRatio) -> CGFloat { - ratio.height * ratio.width -} diff --git a/Package/Sources/BrightroomUI/Built-in UI/PhotosCrop/PhotosCropViewController.swift b/Package/Sources/BrightroomUI/Built-in UI/PhotosCrop/PhotosCropViewController.swift deleted file mode 100644 index a0f8f680..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/PhotosCrop/PhotosCropViewController.swift +++ /dev/null @@ -1,423 +0,0 @@ -// -// Copyright (c) 2021 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit -import SwiftUI - -import Verge - -#if !COCOAPODS -import BrightroomEngine -#endif - -/** - Apple's Photos app like crop view controller. - - You might use `CropView` to create a fully customized user interface. - */ -public final class PhotosCropViewController: UIViewController { - - public struct LocalizedStrings { - public var button_done_title: String = "Done" - public var button_cancel_title: String = "Cancel" - public var button_reset_title: String = "Reset" - public var button_aspectratio_original: String = "ORIGINAL" - public var button_aspectratio_freeform: String = "FREEFORM" - public var button_aspectratio_square: String = "SQUARE" - - public init() {} - } - - public struct Options: Equatable { - - public enum AspectRatioOptions: Equatable { - case selectable - case fixed(PixelAspectRatio?) - } - - public var aspectRatioOptions: AspectRatioOptions = .selectable - - public init() { - - } - } - - public struct Handlers { - public var didFinish: (PhotosCropViewController) -> Void = { _ in } - public var didCancel: (PhotosCropViewController) -> Void = { _ in } - } - - private struct State: Equatable { - var isSelectingAspectRatio = false - var options: Options - } - - // MARK: - Properties - - private let store: UIStateStore - - private let cropView: CropView - private var aspectRatioControl: PhotosCropAspectRatioControl? - - public let editingStack: EditingStack - public var handlers = Handlers() - - private let doneButton = UIButton(type: .system) - private let cancelButton = UIButton(type: .system) - private let aspectRatioButton = UIButton(type: .system) - private let resetButton = UIButton(type: .system) - private let rotateButton = UIButton(type: .system) - - private let aspectRatioControlLayoutGuide = UILayoutGuide() - - private var subscriptions = Set() - private var hasSetupLoadedUICompleted = false - - public var localizedStrings: LocalizedStrings - - // MARK: - Initializers - - public init( - editingStack: EditingStack, - options: Options = .init(), - localizedStrings: LocalizedStrings = .init() - ) { - self.localizedStrings = localizedStrings - self.store = .init(initialState: .init(options: options)) - self.editingStack = editingStack - cropView = .init(editingStack: editingStack) - super.init(nibName: nil, bundle: nil) - } - - /** - Creates an instance for using as standalone. - - This initializer offers us to get cropping function without detailed setup. - To get a result image, call `renderImage()`. - */ - public convenience init( - imageProvider: ImageProvider, - options: Options = .init(), - localizedStrings: LocalizedStrings = .init() - ) { - self.init( - editingStack: .init( - imageProvider: imageProvider - ), - options: options, - localizedStrings: localizedStrings - ) - } - - @available(*, unavailable) - public required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Functions - - /** - Renders an image according to the editing. - - - Attension: This operation can be run background-thread. - */ - public func renderImage(options: BrightRoomImageRenderer.Options, completion: @escaping (Result) -> Void) { - do { - try editingStack.makeRenderer().render(options: options, completion: completion) - } catch { - completion(.failure(error)) - } - } - - override public func viewDidLoad() { - super.viewDidLoad() - - editingStack.start() - cropView.isAutoApplyEditingStackEnabled = true - view.backgroundColor = .black - view.clipsToBounds = true - - aspectRatioButton&>.do { - $0.setImage(UIImage(named: "aspectratio", in: bundle, compatibleWith: nil), for: .normal) - $0.tintColor = .systemGray - $0.addTarget(self, action: #selector(handleAspectRatioButton), for: .touchUpInside) - } - - resetButton&>.do { - // TODO: Localize - $0.setTitle(localizedStrings.button_reset_title, for: .normal) - $0.titleLabel?.font = UIFont.systemFont(ofSize: 14) - $0.setTitleColor(UIColor.systemYellow, for: .normal) - $0.addTarget(self, action: #selector(handleResetButton), for: .touchUpInside) - $0.isHidden = true - } - - rotateButton&>.do { - $0.setImage(UIImage(named: "rotate", in: bundle, compatibleWith: nil), for: .normal) - $0.tintColor = .systemGray - $0.addTarget(self, action: #selector(handleRotateButton), for: .touchUpInside) - } - - let topStackView = UIStackView()&>.do { - $0.addArrangedSubview(rotateButton) - $0.addArrangedSubview(resetButton) - $0.addArrangedSubview(aspectRatioButton) - $0.distribution = .equalSpacing - } - - cancelButton&>.do { - $0.setTitle(localizedStrings.button_cancel_title, for: .normal) - $0.titleLabel?.font = UIFont.systemFont(ofSize: 17) - $0.setTitleColor(UIColor.white, for: .normal) - $0.addTarget(self, action: #selector(handleCancelButton), for: .touchUpInside) - } - - doneButton&>.do { - $0.setTitle(localizedStrings.button_done_title, for: .normal) - $0.titleLabel?.font = UIFont.systemFont(ofSize: 17) - $0.setTitleColor(UIColor.systemYellow, for: .normal) - $0.setTitleColor(UIColor.darkGray, for: .disabled) - $0.addTarget(self, action: #selector(handleDoneButton), for: .touchUpInside) - } - - let bottomStackView = UIStackView()&>.do { - $0.addArrangedSubview(cancelButton) - $0.addArrangedSubview(doneButton) - $0.distribution = .equalSpacing - $0.axis = .horizontal - $0.alignment = .fill - } - - view.addSubview(cropView) - view.addSubview(topStackView) - view.addSubview(bottomStackView) - - view.addLayoutGuide(aspectRatioControlLayoutGuide) - - topStackView&>.do { - $0.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - $0.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16), - $0.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16), - ]) - } - - cropView&>.do { - $0.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: topStackView.bottomAnchor), - $0.leftAnchor.constraint(equalTo: view.leftAnchor), - $0.rightAnchor.constraint(equalTo: view.rightAnchor), - ]) - } - - aspectRatioControlLayoutGuide&>.do { - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: cropView.bottomAnchor), - $0.leftAnchor.constraint(equalTo: view.leftAnchor), - $0.rightAnchor.constraint(equalTo: view.rightAnchor), - ]) - } - - bottomStackView&>.do { - $0.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: aspectRatioControlLayoutGuide.bottomAnchor), - $0.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16), - $0.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16), - $0.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), - $0.heightAnchor.constraint(equalToConstant: 50), - ]) - } - - UIView.performWithoutAnimation { - view.layoutIfNeeded() - } - - editingStack.sinkState { [weak self] state in - - guard let self = self else { return } - - if let state = state.mapIfPresent(\.loadedState) { - - /// Loaded - - self.setUpLoadedUI(state: state.primitive) - - state.ifChanged(\.hasUncommitedChanges) { hasChanges in - self.resetButton.isHidden = !hasChanges - } - - } else { - - /// Loading - - self.aspectRatioButton.isEnabled = false - self.resetButton.isEnabled = false - self.rotateButton.isEnabled = false - self.doneButton.isEnabled = false - - } - - } - .store(in: &subscriptions) - - store.sinkState { [weak self] state in - guard let self = self else { return } - self.update(with: state) - } - .store(in: &subscriptions) - - editingStack.start() - } - - private func setUpLoadedUI(state: EditingStack.State.Loaded) { - - guard hasSetupLoadedUICompleted == false else { - return - } - hasSetupLoadedUICompleted = true - - /** - Setup - */ - - self.aspectRatioButton.isEnabled = true - self.resetButton.isEnabled = true - self.rotateButton.isEnabled = true - self.doneButton.isEnabled = true - - let control = PhotosCropAspectRatioControl( - originalAspectRatio: .init(state.imageSize), - localizedStrings: localizedStrings - ) - - control.handlers.didSelectAspectRatio = { [unowned self] aspectRatio in - cropView.setCroppingAspectRatio(aspectRatio) - } - - control.handlers.didSelectFreeform = { [unowned self] in - cropView.setCroppingAspectRatio(nil) - } - - self.aspectRatioControl = control - - view.addSubview(control) - - control&>.do { - $0.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: aspectRatioControlLayoutGuide.topAnchor), - $0.leftAnchor.constraint(equalTo: aspectRatioControlLayoutGuide.leftAnchor), - $0.rightAnchor.constraint(equalTo: aspectRatioControlLayoutGuide.rightAnchor), - $0.bottomAnchor.constraint(equalTo: aspectRatioControlLayoutGuide.bottomAnchor), - ]) - } - - /// refresh current state - UIView.performWithoutAnimation { - update(with: store.state) - } - - cropView.store.sinkState { [weak self] (state) in - - guard let self = self else { return } - - state.ifChanged(\.proposedCrop?.rotation) { rotation in - guard let rotation = rotation else { return } - self.aspectRatioControl?.setRotation(rotation) - } - - state.ifChanged(\.preferredAspectRatio) { ratio in - self.aspectRatioControl?.setSelected(ratio) - } - - } - .store(in: &subscriptions) - - } - - private func update(with state: Changes) { - - let options = state.map(\.options) - - options.ifChanged(\.aspectRatioOptions) { value in - switch value { - case .fixed(let aspectRatio): - aspectRatioButton.alpha = 0 - cropView.setCroppingAspectRatio(aspectRatio) - case .selectable: - aspectRatioButton.alpha = 1 - cropView.setCroppingAspectRatio(nil) - } - } - - state.ifChanged(\.isSelectingAspectRatio) { value in - UIViewPropertyAnimator.init(duration: 0.4, dampingRatio: 1) { [self] in - if value { - aspectRatioControl?.alpha = 1 - aspectRatioButton.tintColor = .systemYellow - } else { - aspectRatioControl?.alpha = 0 - aspectRatioButton.tintColor = .systemGray - } - } - .startAnimation() - } - - } - - @objc private func handleRotateButton() { - guard let proposedCrop = cropView.store.state.proposedCrop else { - return - } - - let rotation = proposedCrop.rotation.next() - cropView.setRotation(rotation) - } - - @objc private func handleAspectRatioButton() { - store.commit { - $0.isSelectingAspectRatio.toggle() - } - } - - @objc private func handleResetButton() { - switch store.state.options.aspectRatioOptions { - case .fixed(let ratio): - cropView.setCroppingAspectRatio(ratio) - case .selectable: - cropView.setCroppingAspectRatio(nil) - } - cropView.resetCrop() - } - - @objc private func handleCancelButton() { - handlers.didCancel(self) - } - - @objc private func handleDoneButton() { - cropView.applyEditingStack() - handlers.didFinish(self) - } -} - diff --git a/Package/Sources/BrightroomUI/Built-in UI/PhotosCrop/SwiftUIPhotosCropView.swift b/Package/Sources/BrightroomUI/Built-in UI/PhotosCrop/SwiftUIPhotosCropView.swift deleted file mode 100644 index 1296a4e8..00000000 --- a/Package/Sources/BrightroomUI/Built-in UI/PhotosCrop/SwiftUIPhotosCropView.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright (c) 2021 Hiroshi Kimura(Muukii) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import SwiftUI - -#if !COCOAPODS -import BrightroomEngine -#endif - -/** - Apple's Photos app like crop view controller. - - You might use `CropView` to create a fully customized user interface. - */ -@available(iOS 13, *) -public struct SwiftUIPhotosCropView: UIViewControllerRepresentable { - public typealias UIViewControllerType = PhotosCropViewController - - private let editingStack: EditingStack - private let onDone: () -> Void - private let onCancel: () -> Void - - public init(editingStack: EditingStack, onDone: @escaping () -> Void, onCancel: @escaping () -> Void) { - self.editingStack = editingStack - self.onDone = onDone - self.onCancel = onCancel - editingStack.start() - } - - public func makeUIViewController(context: Context) -> PhotosCropViewController { - let cropViewController = PhotosCropViewController(editingStack: editingStack) - cropViewController.handlers.didFinish = { _ in - onDone() - } - cropViewController.handlers.didCancel = { _ in - onCancel() - } - return cropViewController - } - - public func updateUIViewController(_ uiViewController: PhotosCropViewController, context: Context) {} -} diff --git a/Package/Sources/BrightroomUI/Info.plist b/Package/Sources/BrightroomUI/Info.plist deleted file mode 100644 index e1fe4cfb..00000000 --- a/Package/Sources/BrightroomUI/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/Package/Sources/BrightroomUI/Media.xcassets/adjustment.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/adjustment.imageset/Contents.json deleted file mode 100644 index 2a4a8c24..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/adjustment.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "adjustment.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template", - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/adjustment.imageset/adjustment.pdf b/Package/Sources/BrightroomUI/Media.xcassets/adjustment.imageset/adjustment.pdf deleted file mode 100644 index 6cdbd3c7..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/adjustment.imageset/adjustment.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/blur.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/blur.imageset/Contents.json deleted file mode 100644 index 1a021a12..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/blur.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "blur.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template", - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/blur.imageset/blur.pdf b/Package/Sources/BrightroomUI/Media.xcassets/blur.imageset/blur.pdf deleted file mode 100644 index 9e62e4f9..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/blur.imageset/blur.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/brightness.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/brightness.imageset/Contents.json deleted file mode 100644 index 9612ae9a..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/brightness.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "brightness.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template", - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/brightness.imageset/brightness.pdf b/Package/Sources/BrightroomUI/Media.xcassets/brightness.imageset/brightness.pdf deleted file mode 100644 index 335d8566..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/brightness.imageset/brightness.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/color.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/color.imageset/Contents.json deleted file mode 100644 index d9d2c361..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/color.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "color.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template", - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/color.imageset/color.pdf b/Package/Sources/BrightroomUI/Media.xcassets/color.imageset/color.pdf deleted file mode 100644 index 2368b137..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/color.imageset/color.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/contrast.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/contrast.imageset/Contents.json deleted file mode 100644 index c913acdb..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/contrast.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "contrast.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template", - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/contrast.imageset/contrast.pdf b/Package/Sources/BrightroomUI/Media.xcassets/contrast.imageset/contrast.pdf deleted file mode 100644 index 2907a19b..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/contrast.imageset/contrast.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/crop/aspectratio.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/crop/aspectratio.imageset/Contents.json deleted file mode 100644 index edbda1a4..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/crop/aspectratio.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "filename" : "aspectratio.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "template" - } -} diff --git a/Package/Sources/BrightroomUI/Media.xcassets/crop/aspectratio.imageset/aspectratio.pdf b/Package/Sources/BrightroomUI/Media.xcassets/crop/aspectratio.imageset/aspectratio.pdf deleted file mode 100644 index 467b2ce3..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/crop/aspectratio.imageset/aspectratio.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/crop/check.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/crop/check.imageset/Contents.json deleted file mode 100644 index 215a8754..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/crop/check.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "filename" : "check.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "template" - } -} diff --git a/Package/Sources/BrightroomUI/Media.xcassets/crop/check.imageset/check.pdf b/Package/Sources/BrightroomUI/Media.xcassets/crop/check.imageset/check.pdf deleted file mode 100644 index f12afcbf..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/crop/check.imageset/check.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/crop/rotate.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/crop/rotate.imageset/Contents.json deleted file mode 100644 index 88a7cd80..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/crop/rotate.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "filename" : "rotate.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "template" - } -} diff --git a/Package/Sources/BrightroomUI/Media.xcassets/crop/rotate.imageset/rotate.pdf b/Package/Sources/BrightroomUI/Media.xcassets/crop/rotate.imageset/rotate.pdf deleted file mode 100644 index ed13fd7f..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/crop/rotate.imageset/rotate.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/fade.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/fade.imageset/Contents.json deleted file mode 100644 index ac564841..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/fade.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "fade.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template", - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/fade.imageset/fade.pdf b/Package/Sources/BrightroomUI/Media.xcassets/fade.imageset/fade.pdf deleted file mode 100644 index 5c38e196..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/fade.imageset/fade.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/highlights.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/highlights.imageset/Contents.json deleted file mode 100644 index 0ec46c44..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/highlights.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "highlights.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template", - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/highlights.imageset/highlights.pdf b/Package/Sources/BrightroomUI/Media.xcassets/highlights.imageset/highlights.pdf deleted file mode 100644 index 6b6273f0..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/highlights.imageset/highlights.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/magic.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/magic.imageset/Contents.json deleted file mode 100644 index fc0a3ab3..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/magic.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "magic.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template", - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/magic.imageset/magic.pdf b/Package/Sources/BrightroomUI/Media.xcassets/magic.imageset/magic.pdf deleted file mode 100644 index 89ad52d6..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/magic.imageset/magic.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/mask.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/mask.imageset/Contents.json deleted file mode 100644 index 14b64ff6..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/mask.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "mask.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template", - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/mask.imageset/mask.pdf b/Package/Sources/BrightroomUI/Media.xcassets/mask.imageset/mask.pdf deleted file mode 100644 index 24b4e071..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/mask.imageset/mask.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/saturation.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/saturation.imageset/Contents.json deleted file mode 100644 index eaf90c9d..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/saturation.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "saturation.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template", - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/saturation.imageset/saturation.pdf b/Package/Sources/BrightroomUI/Media.xcassets/saturation.imageset/saturation.pdf deleted file mode 100644 index 81d3a754..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/saturation.imageset/saturation.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/shadows.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/shadows.imageset/Contents.json deleted file mode 100644 index 1b4e13d3..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/shadows.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "shadows.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template", - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/shadows.imageset/shadows.pdf b/Package/Sources/BrightroomUI/Media.xcassets/shadows.imageset/shadows.pdf deleted file mode 100644 index 28fbf5ab..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/shadows.imageset/shadows.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/sharpen.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/sharpen.imageset/Contents.json deleted file mode 100644 index 8afead1f..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/sharpen.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "sharpen.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template", - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/sharpen.imageset/sharpen.pdf b/Package/Sources/BrightroomUI/Media.xcassets/sharpen.imageset/sharpen.pdf deleted file mode 100644 index 2f40a93a..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/sharpen.imageset/sharpen.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/slider_thumb.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/slider_thumb.imageset/Contents.json deleted file mode 100644 index 6bd74bc9..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/slider_thumb.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "slider_thumb.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template", - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/slider_thumb.imageset/slider_thumb.pdf b/Package/Sources/BrightroomUI/Media.xcassets/slider_thumb.imageset/slider_thumb.pdf deleted file mode 100644 index 3379342e..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/slider_thumb.imageset/slider_thumb.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/structure.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/structure.imageset/Contents.json deleted file mode 100644 index 01fd7b5d..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/structure.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "structure.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template", - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/structure.imageset/structure.pdf b/Package/Sources/BrightroomUI/Media.xcassets/structure.imageset/structure.pdf deleted file mode 100644 index 6a924fc1..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/structure.imageset/structure.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/temperature.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/temperature.imageset/Contents.json deleted file mode 100644 index bfd8eb25..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/temperature.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "temperature.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template", - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/temperature.imageset/temperature.pdf b/Package/Sources/BrightroomUI/Media.xcassets/temperature.imageset/temperature.pdf deleted file mode 100644 index 3eea63c0..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/temperature.imageset/temperature.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Media.xcassets/vignette.imageset/Contents.json b/Package/Sources/BrightroomUI/Media.xcassets/vignette.imageset/Contents.json deleted file mode 100644 index 61e94da0..00000000 --- a/Package/Sources/BrightroomUI/Media.xcassets/vignette.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "vignette.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template" - } -} \ No newline at end of file diff --git a/Package/Sources/BrightroomUI/Media.xcassets/vignette.imageset/vignette.pdf b/Package/Sources/BrightroomUI/Media.xcassets/vignette.imageset/vignette.pdf deleted file mode 100644 index 08f47cec..00000000 Binary files a/Package/Sources/BrightroomUI/Media.xcassets/vignette.imageset/vignette.pdf and /dev/null differ diff --git a/Package/Sources/BrightroomUI/Shared/BrightroomUI.swift b/Package/Sources/BrightroomUI/Shared/BrightroomUI.swift deleted file mode 100644 index e0c5abbf..00000000 --- a/Package/Sources/BrightroomUI/Shared/BrightroomUI.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !RELEASE - -public typealias TODOL10n = String - -extension TODOL10n { - - public init(raw: String, _ key: String) { - self = raw - } - - public init(raw: String) { - self = raw - } -} -#endif - -public typealias NonL10n = String - -#if COCOAPODS -let bundle = Bundle.init(for: Dummy.self) - .path(forResource: "BrightroomUI", ofType: "bundle") - .map { - Bundle.init(path: $0) -}! -#elseif SWIFT_PACKAGE_MANAGER -let bundle = Bundle.init(for: Dummy.self) - .path(forResource: "Brightroom_BrightroomUI", ofType: "bundle") - .map { - Bundle.init(path: $0) - }! -#else -let bundle = Bundle.init(for: Dummy.self) -#endif - -public let BrightroomUIBundle = bundle - -fileprivate final class Dummy {} - -@inline(__always) -func _pixeleditor_ensureMainThread() { - assert(Thread.isMainThread) -} diff --git a/Package/Sources/BrightroomUI/Shared/Components/Crop/CropView.CropInsideOverlay.swift b/Package/Sources/BrightroomUI/Shared/Components/Crop/CropView.CropInsideOverlay.swift deleted file mode 100644 index 9f1b8fa2..00000000 --- a/Package/Sources/BrightroomUI/Shared/Components/Crop/CropView.CropInsideOverlay.swift +++ /dev/null @@ -1,247 +0,0 @@ -// -// CropView.GuideOverlays.swift -// PixelEditor -// -// Created by Muukii on 2021/03/03. -// Copyright © 2021 muukii. All rights reserved. -// - -import SwiftUI -import UIKit - -/// https://havecamerawilltravel.com/lightroom/crop-overlays/ -extension CropView { - public final class CropOverlayHandlesView: PixelEditorCodeBasedView { - - public let edgeShapeLayer = UIView() - - private let cornerTopLeftHorizontalShapeLayer = UIView() - private let cornerTopLeftVerticalShapeLayer = UIView() - - private let cornerTopRightHorizontalShapeLayer = UIView() - private let cornerTopRightVerticalShapeLayer = UIView() - - private let cornerBottomLeftHorizontalShapeLayer = UIView() - private let cornerBottomLeftVerticalShapeLayer = UIView() - - private let cornerBottomRightHorizontalShapeLayer = UIView() - private let cornerBottomRightVerticalShapeLayer = UIView() - - public init() { - super.init(frame: .zero) - - isUserInteractionEnabled = false - - addSubview(edgeShapeLayer) - [ - cornerTopLeftHorizontalShapeLayer, - cornerTopLeftVerticalShapeLayer, - cornerTopRightHorizontalShapeLayer, - cornerTopRightVerticalShapeLayer, - cornerBottomLeftHorizontalShapeLayer, - cornerBottomLeftVerticalShapeLayer, - cornerBottomRightHorizontalShapeLayer, - cornerBottomRightVerticalShapeLayer, - ].forEach { - addSubview($0) - $0.backgroundColor = UIColor.white - } - } - - override public func layoutSubviews() { - super.layoutSubviews() - - edgeShapeLayer&>.do { - $0.frame = bounds.insetBy(dx: -1, dy: -1) - $0.layer.borderWidth = 1 - $0.layer.borderColor = UIColor.white.cgColor - } - - do { - - let lineWidth: CGFloat = 3 - let lineLength: CGFloat = 20 - - do { - cornerTopLeftHorizontalShapeLayer.frame = .init( - origin: .init(x: -lineWidth, y: -lineWidth), - size: .init(width: lineLength, height: lineWidth) - ) - cornerTopLeftVerticalShapeLayer.frame = .init( - origin: .init(x: -lineWidth, y: -lineWidth), - size: .init(width: lineWidth, height: lineLength) - ) - } - - do { - cornerTopRightHorizontalShapeLayer.frame = .init( - origin: .init(x: bounds.maxX - lineLength + lineWidth, y: -lineWidth), - size: .init(width: lineLength, height: lineWidth) - ) - cornerTopRightVerticalShapeLayer.frame = .init( - origin: .init(x: bounds.maxX, y: -lineWidth), - size: .init(width: lineWidth, height: lineLength) - ) - } - - do { - cornerBottomRightHorizontalShapeLayer.frame = .init( - origin: .init(x: bounds.maxX - lineLength + lineWidth, y: bounds.maxY), - size: .init(width: lineLength, height: lineWidth) - ) - cornerBottomRightVerticalShapeLayer.frame = .init( - origin: .init(x: bounds.maxX, y: bounds.maxY - lineLength + lineWidth), - size: .init(width: lineWidth, height: lineLength) - ) - } - - do { - cornerBottomLeftHorizontalShapeLayer.frame = .init( - origin: .init(x: -lineWidth, y: bounds.maxY), - size: .init(width: lineLength, height: lineWidth) - ) - cornerBottomLeftVerticalShapeLayer.frame = .init( - origin: .init(x: -lineWidth, y: bounds.maxY - lineLength + lineWidth), - size: .init(width: lineWidth, height: lineLength) - ) - } - - } - - } - } - - open class CropInsideOverlayBase: PixelEditorCodeBasedView { - - public init() { - super.init(frame: .zero) - } - - open func didBeginAdjustment(kind: CropView.State.AdjustmentKind) { - - } - - open func didEndAdjustment(kind: CropView.State.AdjustmentKind) { - - } - - } - - @available(iOS 14, *) - open class SwiftUICropInsideOverlay: CropInsideOverlayBase { - - private let controller: UIHostingController - - public init(controller: UIHostingController) { - self.controller = controller - super.init() - addSubview(controller.view) - AutoLayoutTools.setEdge(controller.view, self) - } - - } - - public final class RuleOfThirdsView: PixelEditorCodeBasedView { - - private let verticalLine1 = UIView() - private let verticalLine2 = UIView() - - private let horizontalLine1 = UIView() - private let horizontalLine2 = UIView() - - public init(lineColor: UIColor = UIColor(white: 1, alpha: 0.3)) { - super.init(frame: .zero) - - isUserInteractionEnabled = false - - lines() - .forEach { - addSubview($0) - $0.backgroundColor = lineColor - } - - } - - private func lines() -> [UIView] { - [ - verticalLine1, - verticalLine2, - horizontalLine1, - horizontalLine2, - ] - } - - public override func layoutSubviews() { - - super.layoutSubviews() - - let width = (bounds.width / 3).rounded(.down) - let height = (bounds.height / 3).rounded(.down) - - do { - - verticalLine1.frame = .init( - origin: .init(x: width, y: 0), - size: .init(width: 1, height: bounds.height) - ) - - verticalLine2.frame = .init( - origin: .init(x: width * 2, y: 0), - size: .init(width: 1, height: bounds.height) - ) - } - - do { - horizontalLine1.frame = .init( - origin: .init(x: 0, y: height), - size: .init(width: bounds.width, height: 1) - ) - - horizontalLine2.frame = .init( - origin: .init(x: 0, y: height * 2), - size: .init(width: bounds.width, height: 1) - ) - - } - } - - } - - public final class CropInsideOverlayRuleOfThirdsView: CropInsideOverlayBase { - - private let handlesView = CropOverlayHandlesView() - private let guideView = RuleOfThirdsView() - - private var currentAnimator: UIViewPropertyAnimator? - - public override init() { - super.init() - - isUserInteractionEnabled = false - addSubview(handlesView) - addSubview(guideView) - AutoLayoutTools.setEdge(handlesView, self) - AutoLayoutTools.setEdge(guideView, self) - - guideView.alpha = 0 - } - - public override func didBeginAdjustment(kind: CropView.State.AdjustmentKind) { - currentAnimator?.stopAnimation(true) - currentAnimator = UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { [weak self] in - self?.guideView.alpha = 1 - }&>.do { - $0.startAnimation() - } - } - - public override func didEndAdjustment(kind: CropView.State.AdjustmentKind) { - currentAnimator?.stopAnimation(true) - currentAnimator = UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { [weak self] in - self?.guideView.alpha = 0 - }&>.do { - $0.startAnimation() - } - } - } -} diff --git a/Package/Sources/BrightroomUI/Shared/Components/Crop/CropView.CropOutsideOverlay.swift b/Package/Sources/BrightroomUI/Shared/Components/Crop/CropView.CropOutsideOverlay.swift deleted file mode 100644 index 794084b6..00000000 --- a/Package/Sources/BrightroomUI/Shared/Components/Crop/CropView.CropOutsideOverlay.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// CropView.CropOutsideOverlay.swift -// PixelEditor -// -// Created by Muukii on 2021/03/03. -// Copyright © 2021 muukii. All rights reserved. -// - -import UIKit - -extension CropView { - - open class CropOutsideOverlayBase: PixelEditorCodeBasedView { - - open func didBeginAdjustment(kind: CropView.State.AdjustmentKind) { - - } - - open func didEndAdjustment(kind: CropView.State.AdjustmentKind) { - - } - - } - - public final class CropOutsideOverlayBlurredView: CropOutsideOverlayBase { - - private let effectView: UIVisualEffectView - private let dimmingView: UIView - - private var currentAnimator: UIViewPropertyAnimator? - - public init( - blurEffect: UIBlurEffect = UIBlurEffect(style: .dark), - dimmingColor: UIColor = .init(white: 0, alpha: 0.6) - ) { - - self.effectView = UIVisualEffectView(effect: blurEffect) - self.dimmingView = UIView()&>.do { - $0.backgroundColor = dimmingColor - } - - super.init(frame: .zero) - - addSubview(dimmingView) - addSubview(effectView) - - AutoLayoutTools.setEdge(dimmingView, self) - AutoLayoutTools.setEdge(effectView, self) - } - - public override func didBeginAdjustment(kind: CropView.State.AdjustmentKind) { - - if kind == .guide { - - currentAnimator?.stopAnimation(true) - currentAnimator = UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { [weak self] in - self?.effectView.alpha = 0 - }&>.do { - $0.startAnimation() - } - } - } - - public override func didEndAdjustment(kind: CropView.State.AdjustmentKind) { - - if kind == .guide { - currentAnimator?.stopAnimation(true) - currentAnimator = UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { [weak self] in - self?.effectView.alpha = 1 - }&>.do { - $0.startAnimation(afterDelay: 1) - } - } - } - } - -} diff --git a/Package/Sources/BrightroomUI/Shared/Components/Crop/CropView._CropScrollView.swift b/Package/Sources/BrightroomUI/Shared/Components/Crop/CropView._CropScrollView.swift deleted file mode 100644 index d7bc960c..00000000 --- a/Package/Sources/BrightroomUI/Shared/Components/Crop/CropView._CropScrollView.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) 2021 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -extension CropView { - - /** - Internal UIScrollView's subclass. - */ - final class _CropScrollView: UIScrollView { - override init(frame: CGRect) { - super.init(frame: frame) - - initialize() - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError() - } - - private func initialize() { - if #available(iOS 11.0, *) { - contentInsetAdjustmentBehavior = .never - } else { - // Fallback on earlier versions - } - showsVerticalScrollIndicator = false - showsHorizontalScrollIndicator = false - bouncesZoom = true - decelerationRate = UIScrollView.DecelerationRate.fast - clipsToBounds = false - alwaysBounceVertical = true - alwaysBounceHorizontal = true - scrollsToTop = false - } - } - -} diff --git a/Package/Sources/BrightroomUI/Shared/Components/Crop/CropView._InteractiveCropGuideView.swift b/Package/Sources/BrightroomUI/Shared/Components/Crop/CropView._InteractiveCropGuideView.swift deleted file mode 100644 index e98cfbd5..00000000 --- a/Package/Sources/BrightroomUI/Shared/Components/Crop/CropView._InteractiveCropGuideView.swift +++ /dev/null @@ -1,979 +0,0 @@ -// -// Copyright (c) 2021 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit -#if !COCOAPODS -import BrightroomEngine -#endif - -extension CropView { - - private final class TapExpandedView: PixelEditorCodeBasedView { - - let horizontal: CGFloat - let vertical: CGFloat - - init(horizontal: CGFloat, vertical: CGFloat) { - self.horizontal = horizontal - self.vertical = vertical - super.init(frame: .zero) - } - - override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - bounds.insetBy(dx: -horizontal, dy: -vertical).contains(point) - } - } - - final class _InteractiveCropGuideView: PixelEditorCodeBasedView, UIGestureRecognizerDelegate { - var willChange: () -> Void = {} - var didChange: () -> Void = {} - - private let topLeftControlPointView = TapExpandedView(horizontal: 16, vertical: 16) - private let topRightControlPointView = TapExpandedView(horizontal: 16, vertical: 16) - private let bottomLeftControlPointView = TapExpandedView(horizontal: 16, vertical: 16) - private let bottomRightControlPointView = TapExpandedView(horizontal: 16, vertical: 16) - - private let topControlPointView = TapExpandedView(horizontal: 0, vertical: 16) - private let rightControlPointView = TapExpandedView(horizontal: 16, vertical: 0) - private let leftControlPointView = TapExpandedView(horizontal: 16, vertical: 0) - private let bottomControlPointView = TapExpandedView(horizontal: 0, vertical: 16) - - private weak var cropInsideOverlay: CropInsideOverlayBase? - private weak var cropOutsideOverlay: CropOutsideOverlayBase? - - private unowned let containerView: CropView - private unowned let imageView: UIView - - private lazy var invertedMaskShapeLayerView = MaskView() - - private var maximumRect: CGRect? - - private(set) var lockedAspectRatio: PixelAspectRatio? - - private let minimumSize = CGSize(width: 80, height: 80) - - private let insetOfGuideFlexibility: UIEdgeInsets - - init( - containerView: CropView, - imageView: UIView, - insetOfGuideFlexibility: UIEdgeInsets - ) { - self.containerView = containerView - self.imageView = imageView - self.insetOfGuideFlexibility = insetOfGuideFlexibility - - super.init(frame: .zero) - - [ - topLeftControlPointView, - topRightControlPointView, - bottomLeftControlPointView, - bottomRightControlPointView, - - topControlPointView, - rightControlPointView, - leftControlPointView, - bottomControlPointView, - ].forEach { view in - view.translatesAutoresizingMaskIntoConstraints = false - addSubview(view) - } - - cornerGestures: do { - do { - let panGesture = UIPanGestureRecognizer( - target: self, - action: #selector(handlePanGestureInTopLeft(gesture:)) - ) - panGesture.delegate = self - topLeftControlPointView.addGestureRecognizer(panGesture) - } - - do { - let panGesture = UIPanGestureRecognizer( - target: self, - action: #selector(handlePanGestureInTopRight(gesture:)) - ) - panGesture.delegate = self - topRightControlPointView.addGestureRecognizer(panGesture) - } - - do { - let panGesture = UIPanGestureRecognizer( - target: self, - action: #selector(handlePanGestureInBottomLeft(gesture:)) - ) - panGesture.delegate = self - bottomLeftControlPointView.addGestureRecognizer(panGesture) - } - - do { - let panGesture = UIPanGestureRecognizer( - target: self, - action: #selector(handlePanGestureInBottomRight(gesture:)) - ) - panGesture.delegate = self - bottomRightControlPointView.addGestureRecognizer(panGesture) - } - } - - edgeGestures: do { - do { - let panGesture = UIPanGestureRecognizer( - target: self, - action: #selector(handlePanGestureInTop(gesture:)) - ) - panGesture.delegate = self - topControlPointView.addGestureRecognizer(panGesture) - } - - do { - let panGesture = UIPanGestureRecognizer( - target: self, - action: #selector(handlePanGestureInRight(gesture:)) - ) - panGesture.delegate = self - rightControlPointView.addGestureRecognizer(panGesture) - } - - do { - let panGesture = UIPanGestureRecognizer( - target: self, - action: #selector(handlePanGestureInLeft(gesture:)) - ) - panGesture.delegate = self - leftControlPointView.addGestureRecognizer(panGesture) - } - - do { - let panGesture = UIPanGestureRecognizer( - target: self, - action: #selector(handlePanGestureInBottom(gesture:)) - ) - panGesture.delegate = self - bottomControlPointView.addGestureRecognizer(panGesture) - } - } - - let length: CGFloat = 1 - - topLeftControlPointView&>.do { - NSLayoutConstraint.activate([ - $0.leftAnchor.constraint(equalTo: leftAnchor), - $0.topAnchor.constraint(equalTo: topAnchor), - $0.heightAnchor.constraint(equalToConstant: length)&>.do { - $0.priority = .defaultHigh - }, - $0.widthAnchor.constraint(equalToConstant: length)&>.do { - $0.priority = .defaultHigh - }, - ]) - } - - topRightControlPointView&>.do { - NSLayoutConstraint.activate([ - $0.rightAnchor.constraint(equalTo: rightAnchor), - $0.topAnchor.constraint(equalTo: topAnchor), - $0.heightAnchor.constraint(equalToConstant: length)&>.do { - $0.priority = .defaultHigh - }, - $0.widthAnchor.constraint(equalToConstant: length)&>.do { - $0.priority = .defaultHigh - }, - ]) - } - - bottomLeftControlPointView&>.do { - NSLayoutConstraint.activate([ - $0.leftAnchor.constraint(equalTo: leftAnchor), - $0.bottomAnchor.constraint(equalTo: bottomAnchor), - $0.heightAnchor.constraint(equalToConstant: length)&>.do { - $0.priority = .defaultHigh - }, - $0.widthAnchor.constraint(equalToConstant: length)&>.do { - $0.priority = .defaultHigh - }, - ]) - } - - bottomRightControlPointView&>.do { - NSLayoutConstraint.activate([ - $0.rightAnchor.constraint(equalTo: rightAnchor), - $0.bottomAnchor.constraint(equalTo: bottomAnchor), - $0.heightAnchor.constraint(equalToConstant: length)&>.do { - $0.priority = .defaultHigh - }, - $0.widthAnchor.constraint(equalToConstant: length)&>.do { - $0.priority = .defaultHigh - }, - ]) - } - - topControlPointView&>.do { - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: topAnchor, constant: 0), - $0.leftAnchor.constraint(equalTo: topLeftControlPointView.rightAnchor, constant: 16), - $0.rightAnchor.constraint(equalTo: topRightControlPointView.leftAnchor, constant: -16), - $0.heightAnchor.constraint(equalToConstant: length), - ]) - } - - rightControlPointView&>.do { - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: topRightControlPointView.bottomAnchor, constant: 16), - $0.bottomAnchor.constraint(equalTo: bottomRightControlPointView.topAnchor, constant: -16), - $0.rightAnchor.constraint(equalTo: rightAnchor), - $0.widthAnchor.constraint(equalToConstant: length), - ]) - } - - bottomControlPointView&>.do { - NSLayoutConstraint.activate([ - $0.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0), - $0.leftAnchor.constraint(equalTo: bottomLeftControlPointView.rightAnchor, constant: 16), - $0.rightAnchor.constraint(equalTo: bottomRightControlPointView.leftAnchor, constant: -16), - $0.heightAnchor.constraint(equalToConstant: length), - ]) - } - - leftControlPointView&>.do { - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: topLeftControlPointView.bottomAnchor, constant: 16), - $0.bottomAnchor.constraint(equalTo: bottomLeftControlPointView.topAnchor, constant: -16), - $0.leftAnchor.constraint(equalTo: leftAnchor), - $0.widthAnchor.constraint(equalToConstant: length), - ]) - } - } - - // MARK: - Functions - - /** - Displays a view as an overlay. - e.g. grid view - */ - func setCropInsideOverlay(_ newOverlay: CropInsideOverlayBase?) { - cropInsideOverlay?.removeFromSuperview() - - if let overlay = newOverlay { - overlay.isUserInteractionEnabled = false - addSubview(overlay) - cropInsideOverlay = overlay - } - } - - func setCropOutsideOverlay(_ view: CropOutsideOverlayBase?) { - defer { - setNeedsLayout() - layoutIfNeeded() - } - - guard let view = view else { - cropOutsideOverlay = nil - return - } - - assert(view.superview != nil) - assert(view.superview is CropView) - - cropOutsideOverlay = view - - } - - func setLockedAspectRatio(_ aspectRatio: PixelAspectRatio?) { - lockedAspectRatio = aspectRatio - } - - override func layoutSubviews() { - super.layoutSubviews() - - cropInsideOverlay?.frame = bounds - - if let outOfBoundsOverlayView = cropOutsideOverlay { - // Take care `outOfBoundsOverlayView` has the latest layout. - let frame = convert(bounds, to: outOfBoundsOverlayView) - invertedMaskShapeLayerView.frame = outOfBoundsOverlayView.bounds - invertedMaskShapeLayerView.setUnmaskRect(frame) - - if outOfBoundsOverlayView.mask == nil { - outOfBoundsOverlayView.mask = invertedMaskShapeLayerView - } - } - -// EditorLog.debug("[CropGuide] \(frame)") - } - - override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - bounds.insetBy(dx: -16, dy: -16).contains(point) - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - - let view = super.hitTest(point, with: event) - - if view == self { - return nil - } - - return view - } - - func willBeginScrollViewAdjustment() { - cropInsideOverlay?.didBeginAdjustment(kind: .scrollView) - cropOutsideOverlay?.didBeginAdjustment(kind: .scrollView) - } - - func didEndScrollViewAdjustment() { - cropInsideOverlay?.didEndAdjustment(kind: .scrollView) - cropOutsideOverlay?.didEndAdjustment(kind: .scrollView) - } - - @inline(__always) - private func updateMaximumRect() { - maximumRect = imageView.convert(imageView.bounds, to: containerView) - .intersection(containerView.bounds.inset(by: insetOfGuideFlexibility)) - } - - private var isTracking = false - - private func onGestureTrackingStarted() { - - isTracking = true - - translatesAutoresizingMaskIntoConstraints = false - - updateMaximumRect() - willChange() - cropInsideOverlay?.didBeginAdjustment(kind: .guide) - cropOutsideOverlay?.didBeginAdjustment(kind: .guide) - } - - private func onGestureTrackingEnded() { - - isTracking = false - - deactivateAllConstraints() - didChange() - cropInsideOverlay?.didEndAdjustment(kind: .guide) - cropOutsideOverlay?.didEndAdjustment(kind: .guide) - } - - private var widthConstraint: NSLayoutConstraint! - private var heightConstraint: NSLayoutConstraint! - - private var activeConstraints: [NSLayoutConstraint] = [] - - private func activateRightConstraint() { - translatesAutoresizingMaskIntoConstraints = false - - activeConstraints.append( - rightAnchor.constraint( - equalTo: superview!.rightAnchor, - constant: frame.maxX - superview!.bounds.maxX - )&>.do { - $0.isActive = true - } - ) - } - - private func activateLeftMaxConstraint() { - translatesAutoresizingMaskIntoConstraints = false - - activeConstraints.append( - leftAnchor.constraint( - greaterThanOrEqualTo: superview!.leftAnchor, - constant: maximumRect!.minX - )&>.do { - $0.isActive = true - } - ) - } - - private func activateRightMaxConstraint() { - translatesAutoresizingMaskIntoConstraints = false - - activeConstraints.append( - rightAnchor.constraint( - lessThanOrEqualTo: superview!.rightAnchor, - constant: maximumRect!.maxX - superview!.bounds.maxX - )&>.do { - $0.isActive = true - } - ) - } - - private func activateTopMaxConstraint() { - translatesAutoresizingMaskIntoConstraints = false - - activeConstraints.append( - topAnchor.constraint( - greaterThanOrEqualTo: superview!.topAnchor, - constant: maximumRect!.minY - )&>.do { - $0.isActive = true - } - ) - } - - private func activateBottomMaxConstraint() { - translatesAutoresizingMaskIntoConstraints = false - - activeConstraints.append( - bottomAnchor.constraint( - lessThanOrEqualTo: superview!.bottomAnchor, - constant: maximumRect!.maxY - superview!.bounds.maxY - )&>.do { - $0.isActive = true - }) - } - - private func activateLeftConstraint() { - translatesAutoresizingMaskIntoConstraints = false - - activeConstraints.append( - leftAnchor.constraint( - equalTo: superview!.leftAnchor, - constant: frame.minX - superview!.bounds.minX - )&>.do { - $0.isActive = true - } - ) - } - - private func activateBottomConstraint() { - translatesAutoresizingMaskIntoConstraints = false - - activeConstraints.append( - bottomAnchor.constraint( - equalTo: superview!.bottomAnchor, - constant: frame.maxY - superview!.bounds.maxY - )&>.do { - $0.isActive = true - } - ) - } - - private func activateTopConstraint() { - translatesAutoresizingMaskIntoConstraints = false - - activeConstraints.append( - topAnchor.constraint( - equalTo: superview!.topAnchor, - constant: frame.minY - superview!.bounds.minY - )&>.do { - $0.isActive = true - } - ) - } - - private func activateCenterXConstraint() { - activeConstraints.append( - centerXAnchor.constraint( - equalTo: superview!.centerXAnchor, - constant: frame.midX - superview!.bounds.midX - )&>.do { - $0.priority = .defaultLow - $0.isActive = true - } - ) - } - - private func activateCenterYConstraint() { - activeConstraints.append( - centerYAnchor.constraint( - equalTo: superview!.centerYAnchor, - constant: frame.midY - superview!.bounds.midY - )&>.do { - $0.priority = .defaultLow - $0.isActive = true - } - ) - } - - private func activateWidthConstraint() { - translatesAutoresizingMaskIntoConstraints = false - - widthConstraint = widthAnchor.constraint(equalToConstant: bounds.width)&>.do { - $0.priority = .defaultLow - $0.isActive = true - } - - activeConstraints.append( - widthAnchor.constraint(greaterThanOrEqualToConstant: minimumSize.width)&>.do { - $0.isActive = true - } - ) - } - - private func activateHeightConstraint() { - translatesAutoresizingMaskIntoConstraints = false - - heightConstraint = heightAnchor.constraint(equalToConstant: bounds.height)&>.do { - $0.priority = .defaultLow - $0.isActive = true - } - - activeConstraints.append( - heightAnchor.constraint(greaterThanOrEqualToConstant: minimumSize.height)&>.do { - $0.isActive = true - } - ) - } - - - private func activateAspectRatioConstraint() { - if let aspectRatio = lockedAspectRatio { - activeConstraints.append(widthAnchor.constraint( - equalTo: heightAnchor, - multiplier: aspectRatio.width / aspectRatio.height, - constant: 1 - )&>.do { - $0.isActive = true - }) - } - } - - private func deactivateAllConstraints() { - translatesAutoresizingMaskIntoConstraints = true - - NSLayoutConstraint.deactivate([ - widthConstraint, - heightConstraint, - - ].compactMap { $0 } + activeConstraints) - - layoutIfNeeded() - } - - @objc - private func handlePanGestureInTopLeft(gesture: UIPanGestureRecognizer) { - assert(containerView == superview) - - switch gesture.state { - case .began: - onGestureTrackingStarted() - - activateConstraints: do { - activateAspectRatioConstraint() - - activateTopMaxConstraint() - activateLeftMaxConstraint() - - activateBottomConstraint() - activateRightConstraint() - activateWidthConstraint() - activateHeightConstraint() - } - - fallthrough - case .changed: - defer { - gesture.setTranslation(.zero, in: self) - } - - let translation = gesture.translation(in: self) - - widthConstraint.constant -= translation.x - heightConstraint.constant -= translation.y - - case .cancelled, - .ended, - .failed: - - onGestureTrackingEnded() - default: - break - } - } - - @objc - private func handlePanGestureInTopRight(gesture: UIPanGestureRecognizer) { - assert(containerView == superview) - - switch gesture.state { - case .began: - onGestureTrackingStarted() - - activateConstraints: do { - activateAspectRatioConstraint() - - activateTopMaxConstraint() - activateRightMaxConstraint() - - activateBottomConstraint() - activateLeftConstraint() - - activateWidthConstraint() - activateHeightConstraint() - } - - fallthrough - case .changed: - defer { - gesture.setTranslation(.zero, in: self) - } - let translation = gesture.translation(in: self) - - widthConstraint.constant += translation.x - heightConstraint.constant -= translation.y - - case .cancelled, - .ended, - .failed: - onGestureTrackingEnded() - - default: - break - } - } - - @objc - private func handlePanGestureInBottomLeft(gesture: UIPanGestureRecognizer) { - assert(containerView == superview) - - switch gesture.state { - case .began: - onGestureTrackingStarted() - - activateConstraints: do { - activateAspectRatioConstraint() - - activateBottomMaxConstraint() - activateLeftMaxConstraint() - - activateTopConstraint() - activateRightConstraint() - - activateWidthConstraint() - activateHeightConstraint() - } - - fallthrough - case .changed: - defer { - gesture.setTranslation(.zero, in: self) - } - - let translation = gesture.translation(in: self) - - widthConstraint.constant -= translation.x - heightConstraint.constant += translation.y - - case .cancelled, - .ended, - .failed: - onGestureTrackingEnded() - default: - break - } - } - - @objc - private func handlePanGestureInBottomRight(gesture: UIPanGestureRecognizer) { - assert(containerView == superview) - - switch gesture.state { - case .began: - onGestureTrackingStarted() - - activateConstraints: do { - activateAspectRatioConstraint() - - activateBottomMaxConstraint() - activateRightMaxConstraint() - - activateTopConstraint() - activateLeftConstraint() - - activateWidthConstraint() - activateHeightConstraint() - } - - fallthrough - case .changed: - defer { - gesture.setTranslation(.zero, in: self) - } - - let translation = gesture.translation(in: self) - - widthConstraint.constant += translation.x - heightConstraint.constant += translation.y - - case .cancelled, - .ended, - .failed: - onGestureTrackingEnded() - default: - break - } - } - - @objc - private func handlePanGestureInTop(gesture: UIPanGestureRecognizer) { - assert(containerView == superview) - - switch gesture.state { - case .began: - onGestureTrackingStarted() - - activateConstraints: do { - activateAspectRatioConstraint() - - activateTopMaxConstraint() - activateCenterXConstraint() - - activateRightMaxConstraint() - activateLeftMaxConstraint() - - activateBottomConstraint() - - if lockedAspectRatio == nil { - activateWidthConstraint() - } - activateHeightConstraint() - } - - fallthrough - case .changed: - - defer { - gesture.setTranslation(.zero, in: self) - } - - let translation = gesture.translation(in: self) - - heightConstraint.constant -= translation.y - - case .cancelled, - .ended, - .failed: - onGestureTrackingEnded() - default: - break - } - } - - @objc - private func handlePanGestureInRight(gesture: UIPanGestureRecognizer) { - assert(containerView == superview) - - switch gesture.state { - case .began: - onGestureTrackingStarted() - - activateConstraints: do { - activateAspectRatioConstraint() - - activateRightMaxConstraint() - activateCenterYConstraint() - - activateTopMaxConstraint() - activateBottomMaxConstraint() - - activateLeftConstraint() - - activateWidthConstraint() - if lockedAspectRatio == nil { - activateHeightConstraint() - } - } - - fallthrough - case .changed: - defer { - gesture.setTranslation(.zero, in: self) - } - - let translation = gesture.translation(in: self) - - widthConstraint.constant += translation.x - - case .cancelled, - .ended, - .failed: - onGestureTrackingEnded() - default: - break - } - } - - @objc - private func handlePanGestureInLeft(gesture: UIPanGestureRecognizer) { - assert(containerView == superview) - - switch gesture.state { - case .began: - onGestureTrackingStarted() - - activateConstraints: do { - activateAspectRatioConstraint() - - activateLeftMaxConstraint() - activateCenterYConstraint() - - activateTopMaxConstraint() - activateBottomMaxConstraint() - - activateRightConstraint() - - activateWidthConstraint() - if lockedAspectRatio == nil { - activateHeightConstraint() - } - } - - fallthrough - case .changed: - - defer { - gesture.setTranslation(.zero, in: self) - } - - let translation = gesture.translation(in: self) - - widthConstraint.constant -= translation.x - - case .cancelled, - .ended, - .failed: - onGestureTrackingEnded() - default: - break - } - } - - @objc - private func handlePanGestureInBottom(gesture: UIPanGestureRecognizer) { - assert(containerView == superview) - - switch gesture.state { - case .began: - onGestureTrackingStarted() - - activateConstraints: do { - activateAspectRatioConstraint() - - activateBottomMaxConstraint() - activateCenterXConstraint() - - activateRightMaxConstraint() - activateLeftMaxConstraint() - - activateTopConstraint() - - if lockedAspectRatio == nil { - activateWidthConstraint() - } - activateHeightConstraint() - } - - fallthrough - case .changed: - defer { - gesture.setTranslation(.zero, in: self) - } - - let translation = gesture.translation(in: self) - - heightConstraint.constant += translation.y - - case .cancelled, - .ended, - .failed: - onGestureTrackingEnded() - default: - break - } - } - - override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - return isTracking == false - } - - } - -} - -private final class MaskView: PixelEditorCodeBasedView { - private let topView = UIView() - private let rightView = UIView() - private let leftView = UIView() - private let bottomView = UIView() - - init() { - super.init(frame: .zero) - - backgroundColor = .clear - [ - topView, - rightView, - leftView, - bottomView, - ].forEach { - addSubview($0) - $0.backgroundColor = .white - } - } - - func setUnmaskRect(_ rect: CGRect) { - topView.frame = .init(origin: .zero, size: .init(width: bounds.width, height: rect.minY)) - rightView.frame = .init( - origin: .init(x: rect.maxX, y: rect.minY), - size: .init(width: bounds.width - rect.maxX, height: rect.height) - ) - leftView.frame = .init( - origin: .init(x: 0, y: rect.minY), - size: .init(width: rect.minX, height: rect.height) - ) - bottomView.frame = .init( - origin: .init(x: 0, y: rect.maxY), - size: .init(width: bounds.width, height: bounds.height - rect.maxY) - ) - } -} - -/* -extension UIPanGestureRecognizer { - - func pointTranslation(in view: UIView?) -> CGPoint { - - let translation = self.translation(in: view) - - var point = CGPoint() - - if abs(translation.x) >= 1 { - point.x = translation.x - self.setTranslation(.init(x: 0, y: translation.y), in: view) - } - - if abs(translation.y) >= 1 { - point.y = translation.y - self.setTranslation(.init(x: translation.x, y: 0), in: view) - } - - return point - - } - -} -*/ diff --git a/Package/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift b/Package/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift deleted file mode 100644 index 68f280f6..00000000 --- a/Package/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift +++ /dev/null @@ -1,774 +0,0 @@ -// -// Copyright (c) 2021 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import CoreImage - -import UIKit -import Verge - -#if !COCOAPODS -import BrightroomEngine -#endif - -/** - A view that previews how crops the image. - - The cropping adjustument is avaibleble from 2 ways: - - Scrolling image - - Panning guide - - - TODO: - - Implicit animations occurs in first time load with remote image. - */ -public final class CropView: UIView, UIScrollViewDelegate { - public struct State: Equatable { - public enum AdjustmentKind: Equatable { - case scrollView - case guide - } - - public fileprivate(set) var proposedCrop: EditingCrop? - - public fileprivate(set) var frame: CGRect = .zero - - fileprivate var isGuideInteractionEnabled: Bool = true - fileprivate var layoutVersion: UInt64 = 0 - - /** - Returns aspect ratio. - Would not be affected by rotation. - */ - var preferredAspectRatio: PixelAspectRatio? - } - - /** - A view that covers the area out of cropping extent. - */ - public private(set) weak var cropOutsideOverlay: UIView? - - public let store: UIStateStore - - /** - A Boolean value that indicates whether the guide is interactive. - If false, cropping adjustment is available only way from scrolling image-view. - */ - public var isGuideInteractionEnabled: Bool { - get { - store.state.isGuideInteractionEnabled - } - set { - store.commit { - $0.isGuideInteractionEnabled = newValue - } - } - } - - public let editingStack: EditingStack - - /** - An image view that displayed in the scroll view. - */ - private let imageView = UIImageView() - - /** - Internal scroll view - */ - private let scrollView = _CropScrollView() - - /** - A background view for scroll view. - It provides the frame to scroll view. - */ - private let scrollBackdropView = UIView() - - private var hasSetupScrollViewCompleted = false - - /** - a guide view that displayed on guide container view. - */ - private lazy var guideView = _InteractiveCropGuideView( - containerView: self, - imageView: self.imageView, - insetOfGuideFlexibility: contentInset - ) - - private var subscriptions = Set() - - /// A throttling timer to apply guide changed event. - /// - /// This's waiting for Combine availability in minimum iOS Version. - private let debounce = _BrightroomDebounce(interval: 0.8) - - private let contentInset: UIEdgeInsets - - private var loadingOverlayFactory: (() -> UIView)? - private weak var currentLoadingOverlay: UIView? - - private var isBinding = false - - var isAutoApplyEditingStackEnabled = false - - // MARK: - Initializers - - /** - Creates an instance for using as standalone. - - This initializer offers us to get cropping function without detailed setup. - To get a result image, call `renderImage()`. - */ - public convenience init( - image: UIImage, - contentInset: UIEdgeInsets = .init(top: 20, left: 20, bottom: 20, right: 20) - ) throws { - self.init( - editingStack: .init( - imageProvider: .init(image: image) - ), - contentInset: contentInset - ) - } - - public init( - editingStack: EditingStack, - contentInset: UIEdgeInsets = .init(top: 20, left: 20, bottom: 20, right: 20) - ) { - _pixeleditor_ensureMainThread() - - self.editingStack = editingStack - self.contentInset = contentInset - - self.store = .init(initialState: .init(), logger: nil) - - super.init(frame: .zero) - - scrollBackdropView.accessibilityIdentifier = "scrollBackdropView" - - clipsToBounds = false - - addSubview(scrollBackdropView) - addSubview(scrollView) - addSubview(guideView) - - imageView.isUserInteractionEnabled = true - scrollView.addSubview(imageView) - scrollView.delegate = self - - guideView.didChange = { [weak self] in - guard let self = self else { return } - self.didChangeGuideViewWithDelay() - } - - guideView.willChange = { [weak self] in - guard let self = self else { return } - self.willChangeGuideView() - } - - #if false - store.sinkState { state in - EditorLog.debug(state.primitive) - } - .store(in: &subscriptions) - #endif - - defaultAppearance: do { - setCropInsideOverlay(CropView.CropInsideOverlayRuleOfThirdsView()) - setCropOutsideOverlay(CropView.CropOutsideOverlayBlurredView()) - setLoadingOverlay(factory: { - LoadingBlurryOverlayView(effect: UIBlurEffect(style: .dark), activityIndicatorStyle: .whiteLarge) - }) - } - } - - @available(*, unavailable) - public required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Functions - - public override func willMove(toSuperview newSuperview: UIView?) { - super.willMove(toSuperview: newSuperview) - - if isBinding == false { - isBinding = true - - editingStack.start() - - binding: do { - store.sinkState(queue: .mainIsolated()) { [weak self] state in - - guard let self = self else { return } - - state.ifChanged({ - ( - $0.frame, - $0.layoutVersion - ) - }, .init(==)) { (frame, _) in - - guard let crop = state.proposedCrop else { - return - } - - guard frame != .zero else { - return - } - - setupScrollViewOnce: do { - if self.hasSetupScrollViewCompleted == false { - self.hasSetupScrollViewCompleted = true - - let scrollView = self.scrollView - - self.imageView.bounds = .init(origin: .zero, size: crop.scrollViewContentSize()) - - // Do we need this? it seems ImageView's bounds changes contentSize automatically. not sure. - UIView.performWithoutAnimation { - let currentZoomScale = scrollView.zoomScale - let contentSize = crop.scrollViewContentSize() - if scrollView.contentSize != contentSize { - scrollView.contentInset = .zero - scrollView.zoomScale = 1 - scrollView.contentSize = contentSize - scrollView.zoomScale = currentZoomScale - } - } - } - } - - self.updateScrollContainerView( - by: crop, - preferredAspectRatio: state.preferredAspectRatio, - animated: state.previous?.proposedCrop != nil /* whether first time load */, - animatesRotation: state.hasChanges(\.proposedCrop?.rotation) - ) - - } - - if self.isAutoApplyEditingStackEnabled { - state.ifChanged(\.proposedCrop) { crop in - guard let crop = crop else { - return - } - self.editingStack.crop(crop) - } - } - - state.ifChanged(\.isGuideInteractionEnabled) { value in - self.guideView.isUserInteractionEnabled = value - } - } - .store(in: &subscriptions) - - var appliedCrop = false - - // To restore current crop from editing-stack - editingStack.sinkState { [weak self] state in - - guard let self = self else { return } - - if let loaded = state.mapIfPresent(\.loadedState) { - - loaded.ifChanged(\.imageForCrop) { image in - self.setImage(image) - } - - if appliedCrop == false { - appliedCrop = true - self.setCrop(loaded.currentEdit.crop) - } - - } - - state.ifChanged(\.isLoading) { isLoading in - self.updateLoadingState(displays: isLoading) - } - - } - .store(in: &subscriptions) - } - - } - - } - - private func updateLoadingState(displays: Bool) { - - if displays, let factory = self.loadingOverlayFactory { - - guideView.alpha = 0 - scrollView.alpha = 0 - - let loadingOverlay = factory() - self.currentLoadingOverlay = loadingOverlay - self.addSubview(loadingOverlay) - AutoLayoutTools.setEdge(loadingOverlay, self) - - loadingOverlay.alpha = 0 - UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { - loadingOverlay.alpha = 1 - } - .startAnimation() - - } else { - - if let view = currentLoadingOverlay { - - layoutIfNeeded() - UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { - view.alpha = 0 - self.guideView.alpha = 1 - self.scrollView.alpha = 1 - }&>.do { - $0.addCompletion { _ in - view.removeFromSuperview() - } - $0.startAnimation(afterDelay: 0.2) - } - } - - } - - } - - /** - Renders an image according to the editing. - - - Attension: This operation can be run background-thread. - */ - public func renderImage() throws -> BrightRoomImageRenderer.Rendered? { - applyEditingStack() - return try editingStack.makeRenderer().render() - } - - /** - Applies the current state to the EditingStack. - */ - public func applyEditingStack() { - guard let crop = store.state.proposedCrop else { - EditorLog.warn("EditingStack has not completed loading.") - return - } - editingStack.crop(crop) - } - - public func resetCrop() { - _pixeleditor_ensureMainThread() - - store.commit { - if let proposedCrop = $0.proposedCrop { - $0.proposedCrop = proposedCrop.makeInitial() - if let ratio = $0.preferredAspectRatio { - $0.proposedCrop!.updateCropExtentIfNeeded(toFitAspectRatio: ratio) - } - $0.layoutVersion += 1 - } - } - - guideView.setLockedAspectRatio(nil) - } - - public func setRotation(_ rotation: EditingCrop.Rotation) { - _pixeleditor_ensureMainThread() - - store.commit { - $0.proposedCrop?.rotation = rotation - $0.layoutVersion += 1 - } - } - - public func setCrop(_ crop: EditingCrop) { - _pixeleditor_ensureMainThread() - - store.commit { - guard $0.proposedCrop != crop else { - return - } - - $0.proposedCrop = crop - if let ratio = $0.preferredAspectRatio { - $0.proposedCrop?.updateCropExtentIfNeeded(toFitAspectRatio: ratio) - } - $0.layoutVersion += 1 - } - } - - public func setCroppingAspectRatio(_ ratio: PixelAspectRatio?) { - _pixeleditor_ensureMainThread() - - store.commit { - - guard $0.preferredAspectRatio != ratio else { - return - } - - $0.preferredAspectRatio = ratio - if let ratio = ratio { - $0.proposedCrop?.updateCropExtentIfNeeded(toFitAspectRatio: ratio) - } - $0.layoutVersion += 1 - } - } - - /** - Displays a view as an overlay. - e.g. grid view - - - Parameters: - - view: In case of no needs to display overlay, pass nil. - */ - public func setCropInsideOverlay(_ view: CropInsideOverlayBase?) { - _pixeleditor_ensureMainThread() - - guideView.setCropInsideOverlay(view) - } - - /** - Displays an overlay that covers the area out of cropping extent. - Given view's frame would be adjusted automatically. - - - Attention: view's userIntereactionEnabled turns off - - Parameters: - - view: In case of no needs to display overlay, pass nil. - */ - public func setCropOutsideOverlay(_ view: CropOutsideOverlayBase?) { - _pixeleditor_ensureMainThread() - - cropOutsideOverlay?.removeFromSuperview() - - guard let view = view else { - // just removing - return - } - - cropOutsideOverlay = view - view.isUserInteractionEnabled = false - - // TODO: Unstable operation. - insertSubview(view, aboveSubview: scrollView) - - guideView.setCropOutsideOverlay(view) - - setNeedsLayout() - layoutIfNeeded() - } - - public func setLoadingOverlay(factory: (() -> UIView)?) { - _pixeleditor_ensureMainThread() - loadingOverlayFactory = factory - } - -} - -// MARK: Internal - -extension CropView { - private func setImage(_ cgImage: CGImage) { - imageView.image = UIImage(cgImage: cgImage, scale: 1, orientation: .up) - } - - override public func layoutSubviews() { - super.layoutSubviews() - - if let outOfBoundsOverlay = cropOutsideOverlay { - // TODO: Get an optimized size - outOfBoundsOverlay.frame.size = .init(width: UIScreen.main.bounds.width * 1.5, height: UIScreen.main.bounds.height * 1.5) - outOfBoundsOverlay.center = center - } - - /// to update masking with cropOutsideOverlay - guideView.setNeedsLayout() - - store.commit { - if $0.frame != frame { - $0.frame = frame - } - } - - } - - private func updateScrollContainerView( - by crop: EditingCrop, - preferredAspectRatio: PixelAspectRatio?, - animated: Bool, - animatesRotation: Bool - ) { - func perform() { - frame: do { - let bounds = self.bounds.inset(by: contentInset) - - let size: CGSize - let aspectRatio = PixelAspectRatio(crop.cropExtent.size) - switch crop.rotation { - case .angle_0: - size = aspectRatio.sizeThatFitsWithRounding(in: bounds.size) - guideView.setLockedAspectRatio(preferredAspectRatio) - case .angle_90: - size = aspectRatio.swapped().sizeThatFitsWithRounding(in: bounds.size) - guideView.setLockedAspectRatio(preferredAspectRatio?.swapped()) - case .angle_180: - size = aspectRatio.sizeThatFitsWithRounding(in: bounds.size) - guideView.setLockedAspectRatio(preferredAspectRatio) - case .angle_270: - size = aspectRatio.swapped().sizeThatFitsWithRounding(in: bounds.size) - guideView.setLockedAspectRatio(preferredAspectRatio?.swapped()) - } - - scrollView.transform = crop.rotation.transform - - scrollView.frame = .init( - origin: .init( - x: contentInset.left + ((bounds.width - size.width) / 2) /* centering offset */, - y: contentInset.top + ((bounds.height - size.height) / 2) /* centering offset */ - ), - size: size - ) - - scrollBackdropView.frame = scrollView.frame - } - - applyLayoutDescendants: do { - guideView.frame = scrollView.frame - } - - zoom: do { - - let (min, max) = crop.calculateZoomScale(scrollViewSize: scrollView.bounds.size) - - scrollView.minimumZoomScale = min - scrollView.maximumZoomScale = max - - scrollView.contentInset = .zero - scrollView.zoom(to: crop.cropExtent, animated: false) - // WORKAROUND: - // Fixes `zoom to rect` does not apply the correct state when restoring the state from first-time displaying view. - scrollView.zoom(to: crop.cropExtent, animated: false) - } - } - - if animated { - layoutIfNeeded() - - if animatesRotation { - UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { - perform() - }&>.do { - $0.isUserInteractionEnabled = false - $0.startAnimation() - } - - UIViewPropertyAnimator(duration: 0.12, dampingRatio: 1) { - self.guideView.alpha = 0 - }&>.do { - $0.isUserInteractionEnabled = false - $0.addCompletion { _ in - UIViewPropertyAnimator(duration: 0.5, dampingRatio: 1) { - self.guideView.alpha = 1 - } - .startAnimation(afterDelay: 0.8) - } - $0.startAnimation() - } - - } else { - UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { [self] in - perform() - layoutIfNeeded() - }&>.do { - $0.startAnimation() - } - } - - } else { - UIView.performWithoutAnimation { - layoutIfNeeded() - perform() - } - } - } - - @inline(__always) - private func willChangeGuideView() { - debounce.on { /* for debounce */ } - } - - @inline(__always) - private func didChangeGuideViewWithDelay() { - - func applyCropRotation(rotation: EditingCrop.Rotation, insets: UIEdgeInsets) -> UIEdgeInsets { - switch rotation { - case .angle_0: - return insets - case .angle_90: - return .init( - top: insets.left, - left: insets.bottom, - bottom: insets.right, - right: insets.top - ) - case .angle_180: - return .init( - top: insets.bottom, - left: insets.right, - bottom: insets.top, - right: insets.left - ) - case .angle_270: - return .init( - top: insets.right, - left: insets.top, - bottom: insets.left, - right: insets.bottom - ) - } - } - - guard let currentProposedCrop = store.state.proposedCrop else { - return - } - - let visibleRect = guideView.convert(guideView.bounds, to: imageView) - - updateContentInset: do { - let rect = self.guideView.convert(self.guideView.bounds, to: scrollBackdropView) - - let bounds = scrollBackdropView.bounds - let insets = UIEdgeInsets.init( - top: rect.minY, - left: rect.minX, - bottom: bounds.maxY - rect.maxY, - right: bounds.maxX - rect.maxX - ) - - let resolvedInsets = applyCropRotation(rotation: currentProposedCrop.rotation, insets: insets) - - scrollView.contentInset = resolvedInsets - } - - EditorLog.debug("[CropView] visbleRect : \(visibleRect), guideViewFrame: \(guideView.frame)") - - store.commit { - // TODO: Might cause wrong cropping if set the invalid size or origin. For example, setting width:0, height: 0 by too zoomed in. - $0.proposedCrop?.updateCropExtentNormalizing(visibleRect, respectingAspectRatio: $0.preferredAspectRatio) - } - - /// Triggers layout update later - debounce.on { [weak self] in - - guard let self = self else { return } - - self.store.commit { - $0.layoutVersion += 1 - } - } - } - - @inline(__always) - private func didChangeScrollView() { - store.commit { - let rect = guideView.convert(guideView.bounds, to: imageView) - // TODO: Might cause wrong cropping if set the invalid size or origin. For example, setting width:0, height: 0 by too zoomed in. - $0.proposedCrop?.updateCropExtentNormalizing(rect, respectingAspectRatio: $0.preferredAspectRatio) - } - } - - // MARK: UIScrollViewDelegate - - public func viewForZooming(in scrollView: UIScrollView) -> UIView? { - return imageView - } - - public func scrollViewDidZoom(_ scrollView: UIScrollView) { - func adjustFrameToCenterOnZooming() { - var frameToCenter = imageView.frame - - // center horizontally - if frameToCenter.size.width < scrollView.bounds.width { - frameToCenter.origin.x = (scrollView.bounds.width - frameToCenter.size.width) / 2 - } else { - frameToCenter.origin.x = 0 - } - - // center vertically - if frameToCenter.size.height < scrollView.bounds.height { - frameToCenter.origin.y = (scrollView.bounds.height - frameToCenter.size.height) / 2 - } else { - frameToCenter.origin.y = 0 - } - - imageView.frame = frameToCenter - } - - adjustFrameToCenterOnZooming() - - debounce.on { [weak self] in - - guard let self = self else { return } - - self.store.commit { - $0.layoutVersion += 1 - } - } - } - - public func scrollViewDidScroll(_ scrollView: UIScrollView) { - - debounce.on { [weak self] in - - guard let self = self else { return } - - guard self.scrollView.isTracking == false else { return } - - self.store.commit { - $0.layoutVersion += 1 - } - } - } - - public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - guideView.willBeginScrollViewAdjustment() - } - - public func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) { - guideView.willBeginScrollViewAdjustment() - } - - public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - if !decelerate { - didChangeScrollView() - guideView.didEndScrollViewAdjustment() - } - } - - public func scrollViewDidEndZooming( - _ scrollView: UIScrollView, - with view: UIView?, - atScale scale: CGFloat - ) { - didChangeScrollView() - guideView.didEndScrollViewAdjustment() - } - - public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { - didChangeScrollView() - guideView.didEndScrollViewAdjustment() - } -} diff --git a/Package/Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift b/Package/Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift deleted file mode 100644 index c0a6e72a..00000000 --- a/Package/Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// Copyright (c) 2021 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit -import SwiftUI -#if !COCOAPODS -import BrightroomEngine -#endif - -public final class _PixelEditor_WrapperViewController: UIViewController { - - private let bodyView: BodyView - - init(bodyView: BodyView) { - self.bodyView = bodyView - super.init(nibName: nil, bundle: nil) - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public override func viewDidLoad() { - super.viewDidLoad() - - view.addSubview(bodyView) - AutoLayoutTools.setEdge(bodyView, view) - } -} - -/** - Still in development - */ -@available(iOS 14, *) -public struct SwiftUICropView: UIViewControllerRepresentable { - - public typealias UIViewControllerType = _PixelEditor_WrapperViewController - - private let cropInsideOverlay: AnyView? - - private let factory: () -> CropView - - public init( - editingStack: EditingStack, - cropInsideOverlay: AnyView? = nil - ) { - self.cropInsideOverlay = cropInsideOverlay - - self.factory = { - CropView(editingStack: editingStack) - } - } - - public func makeUIViewController(context: Context) -> _PixelEditor_WrapperViewController { - let view = factory() - view.isAutoApplyEditingStackEnabled = true - - let controller = _PixelEditor_WrapperViewController.init(bodyView: view) - - if let cropInsideOverlay = cropInsideOverlay { - - let hosting = UIHostingController.init(rootView: cropInsideOverlay) - - hosting.view.backgroundColor = .clear - hosting.view.preservesSuperviewLayoutMargins = false - - view.setCropInsideOverlay(CropView.SwiftUICropInsideOverlay(controller: hosting)) - - controller.addChild(hosting) - hosting.didMove(toParent: controller) - } - - return controller - } - - public func updateUIViewController(_ uiViewController: _PixelEditor_WrapperViewController, context: Context) { - - } - -} diff --git a/Package/Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift b/Package/Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift deleted file mode 100644 index fa51ed22..00000000 --- a/Package/Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift +++ /dev/null @@ -1,469 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import Verge - -public final class BlurryMaskingView: PixelEditorCodeBasedView, UIScrollViewDelegate { - private struct State: Equatable { - fileprivate(set) var frame: CGRect = .zero - fileprivate(set) var bounds: CGRect = .zero - - fileprivate var hasLoaded = false - - fileprivate(set) var proposedCrop: EditingCrop? - - fileprivate(set) var brushSize: CanvasView.BrushSize = .point(30) - - fileprivate let contentInset: UIEdgeInsets = .zero - - func scrollViewFrame() -> CGRect? { - - guard let proposedCrop = proposedCrop else { - return nil - } - - let bounds = self.bounds.inset(by: contentInset) - - let size: CGSize - let aspectRatio = PixelAspectRatio(proposedCrop.cropExtent.size) - switch proposedCrop.rotation { - case .angle_0: - size = aspectRatio.sizeThatFitsWithRounding(in: bounds.size) - case .angle_90: - size = aspectRatio.swapped().sizeThatFitsWithRounding(in: bounds.size) - case .angle_180: - size = aspectRatio.sizeThatFitsWithRounding(in: bounds.size) - case .angle_270: - size = aspectRatio.swapped().sizeThatFitsWithRounding(in: bounds.size) - } - - return .init( - origin: .init( - x: contentInset.left + ((bounds.width - size.width) / 2) /* centering offset */, - y: contentInset.top + ((bounds.height - size.height) / 2) /* centering offset */ - ), - size: size - ) - } - - func brushPixelSize() -> CGFloat? { - - guard let proposedCrop = proposedCrop, let size = scrollViewFrame()?.size else { - return nil - } - - let (min, _) = proposedCrop.calculateZoomScale(scrollViewSize: size) - - switch brushSize { - case let .point(points): - return points / min - case let .pixel(pixels): - return pixels - } - } - } - - private final class ContainerView: PixelEditorCodeBasedView { - func addContent(_ view: UIView) { - addSubview(view) - view.frame = bounds - view.autoresizingMask = [.flexibleHeight, .flexibleWidth] - } - } - - public var isBackdropImageViewHidden: Bool { - get { - backdropImageView.isHidden - } - set { - backdropImageView.isHidden = newValue - } - } - - public var isblurryImageViewHidden: Bool { - get { - blurryImageView.isHidden - } - set { - blurryImageView.isHidden = newValue - } - } - - private let scrollView = CropView._CropScrollView() - - private let containerView = ContainerView() - - private let backdropImageView = _ImageView() - - private let blurryImageView = _ImageView() - - private let drawingView = SmoothPathDrawingView() - - private let canvasView = CanvasView() - - private var subscriptions = Set() - - private let editingStack: EditingStack - - private var hasSetupScrollViewCompleted = false - - private let store: UIStateStore - - private var currentBrush: OvalBrush? - - private var loadingOverlayFactory: (() -> UIView)? - private weak var currentLoadingOverlay: UIView? - - private var isBinding = false - - // MARK: - Initializers - - public init(editingStack: EditingStack) { - - self.editingStack = editingStack - store = .init( - initialState: .init(), - logger: nil - ) - - super.init(frame: .zero) - - setUp: do { - backgroundColor = .clear - - addSubview(scrollView) - - scrollView.clipsToBounds = true - scrollView.delegate = self - scrollView.isScrollEnabled = false - - scrollView.addSubview(containerView) - - containerView.addContent(backdropImageView) - containerView.addContent(blurryImageView) - containerView.addContent(canvasView) - containerView.addContent(drawingView) - - backdropImageView.accessibilityIdentifier = "backdropImageView" - backdropImageView.isUserInteractionEnabled = false - backdropImageView.contentMode = .scaleAspectFit - - blurryImageView.accessibilityIdentifier = "blurryImageView" - blurryImageView.isUserInteractionEnabled = false - blurryImageView.contentMode = .scaleAspectFit - - blurryImageView.mask = canvasView - clipsToBounds = true - } - - drawingView.handlers = drawingView.handlers&>.modify { - $0.willBeginPan = { [unowned self] path in - - guard let pixelSize = store.state.primitive.brushPixelSize() else { - assertionFailure("It seems currently loading state.") - return - } - - currentBrush = .init(color: .black, pixelSize: pixelSize) - - let drawnPath = DrawnPath(brush: currentBrush!, path: path) - canvasView.previewDrawnPath = drawnPath - } - $0.panning = { [unowned self] path in - canvasView.updatePreviewDrawing() - } - $0.didFinishPan = { [unowned self] path in - canvasView.updatePreviewDrawing() - - let _path = (path.copy() as! UIBezierPath) - - let drawnPath = DrawnPath(brush: currentBrush!, path: _path) - - canvasView.previewDrawnPath = nil - editingStack.append(blurringMaskPaths: CollectionOfOne(drawnPath)) - - currentBrush = nil - } - } - - editingStack.sinkState { [weak self] (state: Changes) in - - guard let self = self else { return } - - if let state = state.mapIfPresent(\.loadedState) { - - state.ifChanged(\.currentEdit.crop) { cropRect in - - /** - To avoid running pending layout operations from User Initiated actions. - */ - if cropRect != self.store.state.proposedCrop { - self.store.commit { - $0.proposedCrop = cropRect - } - } - } - } - - } - .store(in: &subscriptions) - - defaultAppearance: do { - setLoadingOverlay(factory: { - LoadingBlurryOverlayView(effect: UIBlurEffect(style: .dark), activityIndicatorStyle: .whiteLarge) - }) - } - } - - override public func willMove(toSuperview newSuperview: UIView?) { - super.willMove(toSuperview: newSuperview) - - guard newSuperview != nil else { return } - - if isBinding == false { - isBinding = true - - editingStack.start() - - binding: do { - store.sinkState(queue: .mainIsolated()) { [weak self] state in - - guard let self = self else { return } - - state.ifChanged(\.frame, \.proposedCrop) { frame, crop in - - guard let crop = crop else { return } - - guard frame != .zero else { return } - - setupScrollViewOnce: do { - if self.hasSetupScrollViewCompleted == false { - self.hasSetupScrollViewCompleted = true - - let scrollView = self.scrollView - - self.containerView.bounds = .init( - origin: .zero, - size: crop.scrollViewContentSize() - ) - - // Do we need this? it seems ImageView's bounds changes contentSize automatically. not sure. - UIView.performWithoutAnimation { - let currentZoomScale = scrollView.zoomScale - let contentSize = crop.scrollViewContentSize() - if scrollView.contentSize != contentSize { - scrollView.contentInset = .zero - scrollView.zoomScale = 1 - scrollView.contentSize = contentSize - scrollView.zoomScale = currentZoomScale - } - } - } - } - - self.updateScrollContainerView( - by: crop, - animated: state.hasLoaded, - animatesRotation: state.hasChanges(\.proposedCrop?.rotation) - ) - } - } - .store(in: &subscriptions) - - editingStack.sinkState { [weak self] (state: Changes) in - - guard let self = self else { return } - - state.ifChanged(\.isLoading) { isLoading in - self.updateLoadingOverlay(displays: isLoading) - } - - if let state = state.mapIfPresent(\.loadedState) { - - state.ifChanged(\.editingPreviewImage) { image in - self.backdropImageView.display(image: image) - self.blurryImageView.display(image: BlurredMask.blur(image: image)) - } - - state.ifChanged(\.currentEdit.drawings.blurredMaskPaths) { paths in - self.canvasView.setResolvedDrawnPaths(paths) - } - - } - - } - .store(in: &subscriptions) - } - } - } - - public func setLoadingOverlay(factory: (() -> UIView)?) { - _pixeleditor_ensureMainThread() - loadingOverlayFactory = factory - } - - public func setBrushSize(_ size: CanvasView.BrushSize) { - store.commit { - $0.brushSize = size - } - } - - private func updateLoadingOverlay(displays: Bool) { - - if displays, let factory = self.loadingOverlayFactory { - - scrollView.isHidden = true - - let loadingOverlay = factory() - self.currentLoadingOverlay = loadingOverlay - self.addSubview(loadingOverlay) - AutoLayoutTools.setEdge(loadingOverlay, self) - - loadingOverlay.alpha = 0 - UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { - loadingOverlay.alpha = 1 - } - .startAnimation() - - } else { - - scrollView.isHidden = false - - if let view = currentLoadingOverlay { - UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { - view.alpha = 0 - }&>.do { - $0.addCompletion { _ in - view.removeFromSuperview() - } - $0.startAnimation() - } - } - - } - - } - - - private func updateScrollContainerView( - by crop: EditingCrop, - animated: Bool, - animatesRotation: Bool - ) { - - func perform() { - - guard let scrollViewFrame = store.state.primitive.scrollViewFrame() else { - return - } - - frame: do { - scrollView.transform = crop.rotation.transform - scrollView.frame = scrollViewFrame - } - - zoom: do { - let (min, max) = crop.calculateZoomScale(scrollViewSize: scrollView.bounds.size) - - scrollView.minimumZoomScale = min - scrollView.maximumZoomScale = max - - scrollView.contentInset = .zero - scrollView.zoom(to: crop.cropExtent, animated: false) - // WORKAROUND: - // Fixes `zoom to rect` does not apply the correct state when restoring the state from first-time displaying view. - scrollView.zoom(to: crop.cropExtent, animated: false) - - disableZooming: do { - let zoomedScale = scrollView.zoomScale - scrollView.minimumZoomScale = zoomedScale - scrollView.maximumZoomScale = zoomedScale - } - } - } - - if animated { - layoutIfNeeded() - - UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { [self] in - perform() - layoutIfNeeded() - }&>.do { - $0.startAnimation() - } - - } else { - UIView.performWithoutAnimation { - layoutIfNeeded() - perform() - } - } - } - - override public func layoutSubviews() { - super.layoutSubviews() - - store.commit { - if $0.frame != frame { - $0.frame = frame - } - if $0.bounds != bounds { - $0.bounds = bounds - } - } - - } - - // MARK: UIScrollViewDelegate - - public func viewForZooming(in scrollView: UIScrollView) -> UIView? { - return containerView - } - - public func scrollViewDidZoom(_ scrollView: UIScrollView) { - func adjustFrameToCenterOnZooming() { - var frameToCenter = containerView.frame - - // center horizontally - if frameToCenter.size.width < scrollView.bounds.width { - frameToCenter.origin.x = (scrollView.bounds.width - frameToCenter.size.width) / 2 - } else { - frameToCenter.origin.x = 0 - } - - // center vertically - if frameToCenter.size.height < scrollView.bounds.height { - frameToCenter.origin.y = (scrollView.bounds.height - frameToCenter.size.height) / 2 - } else { - frameToCenter.origin.y = 0 - } - - containerView.frame = frameToCenter - } - - adjustFrameToCenterOnZooming() - } -} diff --git a/Package/Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift b/Package/Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift deleted file mode 100644 index a70ff135..00000000 --- a/Package/Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift +++ /dev/null @@ -1,151 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS -import BrightroomEngine -#endif -import Verge - -public final class CanvasView: PixelEditorCodeBasedView { - - public enum BrushSize: Equatable { - case point(CGFloat) - case pixel(CGFloat) - } - - private struct State: Equatable { - var resolvedDrawnPaths: [DrawnPath] = [] - } - - public override class var layerClass: AnyClass { - #if false - return CATiledLayer.self - #else - return CALayer.self - #endif - } - - private let store = UIStateStore(initialState: .init()) - private var subscriptions: Set = .init() - - private var resolvedShapeLayers: [CAShapeLayer] = [] - private var previewShapeLayer: CAShapeLayer? - - override init(frame: CGRect) { - super.init(frame: frame) - isOpaque = false - - if let tiledLayer = layer as? CATiledLayer { - tiledLayer.tileSize = .init(width: 512, height: 512) - } - - store.sinkState { [weak self] (state) in - - guard let self = self else { return } - - state.ifChanged(\.resolvedDrawnPaths) { paths in - - let layers = paths.map { path -> CAShapeLayer in - let layer = Self.makeShapeLayer(for: path.brush) - layer.path = path.bezierPath.cgPath - return layer - } - - // TODO: Get better way for perfromance - self.resolvedShapeLayers.forEach { - $0.removeFromSuperlayer() - } - - layers.forEach { - self.layer.addSublayer($0) - } - self.resolvedShapeLayers = layers - - } - - } - .store(in: &subscriptions) - - } - - private static func makeShapeLayer(for brush: OvalBrush) -> CAShapeLayer { - - let layer = CAShapeLayer() - - layer.lineWidth = brush.pixelSize - layer.strokeColor = brush.color.cgColor - layer.opacity = Float(brush.alpha) - layer.lineCap = .round - layer.fillColor = UIColor.clear.cgColor - layer.drawsAsynchronously = true - - return layer - - } - - public var previewDrawnPath: DrawnPath? { - didSet { - - previewShapeLayer?.removeFromSuperlayer() - previewShapeLayer = nil - - if let path = previewDrawnPath { - let layer = Self.makeShapeLayer(for: path.brush) - layer.path = path.bezierPath.cgPath - self.layer.addSublayer(layer) - self.previewShapeLayer = layer - } - - updatePreviewDrawing() - } - } - - public func updatePreviewDrawing() { - - guard let drawnPath = previewDrawnPath else { - return - } - - let path = drawnPath.bezierPath - let cgPath = path.cgPath - - self.previewShapeLayer?.path = cgPath - - } - - public func setResolvedDrawnPaths(_ paths: [DrawnPath]) { - store.commit { - $0.resolvedDrawnPaths = paths - } - } - - public override func layoutSubviews() { - super.layoutSubviews() - resolvedShapeLayers.forEach { - $0.frame = bounds - } - previewShapeLayer?.frame = bounds - } - -} - diff --git a/Package/Sources/BrightroomUI/Shared/Components/Drawing/SmoothPathDrawingView.swift b/Package/Sources/BrightroomUI/Shared/Components/Drawing/SmoothPathDrawingView.swift deleted file mode 100644 index c9e2bfea..00000000 --- a/Package/Sources/BrightroomUI/Shared/Components/Drawing/SmoothPathDrawingView.swift +++ /dev/null @@ -1,130 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -/** - It does not render anything, just giving the paths smoothified. - */ -public class SmoothPathDrawingView : PixelEditorCodeBasedView { - - public struct Handlers { - public var willBeginPan: (UIBezierPath) -> Void = { _ in } - public var panning: (UIBezierPath) -> Void = { _ in } - public var didFinishPan: (UIBezierPath) -> Void = { _ in } - } - - public var handlers = Handlers() - - private var currentBezierPath: UIBezierPath? - private var controlPoint: Int = 0 - private var points = [CGPoint](repeating: CGPoint(), count: 5) - - // MARK: - Initializers - - public init() { - super.init(frame: .zero) - backgroundColor = .clear - } - - public func willBeginPan(path: UIBezierPath) { - handlers.willBeginPan(path) - } - - public func panning(path: UIBezierPath) { - handlers.panning(path) - } - - public func didFinishPan(path: UIBezierPath) { - handlers.didFinishPan(path) - } - - // MARK: - Touch - public override func touchesBegan(_ touches: Set, with event: UIEvent?) { - - guard let touch = touches.first else { return } - - let touchPoint = touch.location(in: self) - currentBezierPath = UIBezierPath() - - willBeginPan(path: currentBezierPath!) - - controlPoint = 0 - points[0] = touchPoint - } - - public override func touchesMoved(_ touches: Set, with event: UIEvent?) { - - guard let touch = touches.first else { return } - let touchPoint = touch.location(in: self) - - controlPoint += 1 - points[controlPoint] = touchPoint - - if controlPoint == 4 { - - points[3] = CGPoint( - x: (points[2].x + points[4].x)/2.0, - y: (points[2].y + points[4].y)/2.0) - - currentBezierPath!.move(to: points[0]) - - currentBezierPath!.addCurve( - to: self.points[3], - controlPoint1: points[1], - controlPoint2: points[2]) - - setNeedsDisplay() - panning(path: currentBezierPath!) - - points[0] = points[3] - points[1] = points[4] - - controlPoint = 1 - } - - setNeedsDisplay() - panning(path: currentBezierPath!) - } - - public override func touchesEnded(_ touches: Set, with event: UIEvent?) { - - if controlPoint == 0 { - let touchPoint = points[0] - - currentBezierPath!.move(to: CGPoint( - x: touchPoint.x-1.0, - y: touchPoint.y)) - - currentBezierPath!.addLine(to: CGPoint( - x: touchPoint.x+1.0, - y: touchPoint.y)) - - setNeedsDisplay() - - } else { - controlPoint = 0 - } - - didFinishPan(path: currentBezierPath!) - currentBezierPath = nil - } -} diff --git a/Package/Sources/BrightroomUI/Shared/Components/ImageViews/CIImageDisplaying.swift b/Package/Sources/BrightroomUI/Shared/Components/ImageViews/CIImageDisplaying.swift deleted file mode 100644 index 8f089101..00000000 --- a/Package/Sources/BrightroomUI/Shared/Components/ImageViews/CIImageDisplaying.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) 2021 Hiroshi Kimura(Muukii) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -#if !COCOAPODS - import BrightroomEngine -#endif - -public protocol CIImageDisplaying: AnyObject { - func display(image: CIImage?) - var postProcessing: (CIImage) -> CIImage { get set } -} diff --git a/Package/Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift b/Package/Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift deleted file mode 100644 index 641766a4..00000000 --- a/Package/Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift +++ /dev/null @@ -1,263 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#if !COCOAPODS -import BrightroomEngine -#endif -import UIKit -import Verge - -/** - A view that displays the edited image, plus displays original image for comparison with touch-down interaction. - */ -public final class ImagePreviewView: PixelEditorCodeBasedView { - // MARK: - Properties - - #if false - private let imageView = _PreviewImageView() - private let originalImageView = _PreviewImageView() - #else - private let imageView = MetalImageView() - private let originalImageView = MetalImageView() - #endif - - private let editingStack: EditingStack - private var subscriptions = Set() - - private var loadingOverlayFactory: (() -> UIView)? - private weak var currentLoadingOverlay: UIView? - - private var isBinding = false - - // MARK: - Initializers - - public init(editingStack: EditingStack) { - // FIXME: Loading State - - self.editingStack = editingStack - - super.init(frame: .zero) - - originalImageView.accessibilityIdentifier = "pixel.originalImageView" - - imageView.accessibilityIdentifier = "pixel.editedImageView" - - clipsToBounds = true - - [ - originalImageView, - imageView, - ].forEach { imageView in - addSubview(imageView) - imageView.clipsToBounds = true - imageView.contentMode = .scaleAspectFit - imageView.isOpaque = false - imageView.frame = bounds - imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight] - } - - originalImageView.isHidden = true - - defaultAppearance: do { - setLoadingOverlay(factory: { - LoadingBlurryOverlayView( - effect: UIBlurEffect(style: .dark), - activityIndicatorStyle: .whiteLarge - ) - }) - } - } - - // MARK: - Functions - - public func setLoadingOverlay(factory: (() -> UIView)?) { - _pixeleditor_ensureMainThread() - loadingOverlayFactory = factory - } - - override public func willMove(toWindow newWindow: UIWindow?) { - super.willMove(toWindow: newWindow) - - if newWindow != nil { - editingStack.start() - - if isBinding == false { - isBinding = true - editingStack.sinkState { [weak self] state in - - guard let self = self else { return } - - state.ifChanged(\.isLoading) { isLoading in - self.updateLoadingOverlay(displays: isLoading) - } - - UIView.performWithoutAnimation { - if let state = state.mapIfPresent(\.loadedState) { - if state.hasChanges({ ($0.currentEdit) }, .init(==)) { - self.requestPreviewImage(state: state.primitive) - } - } - } - } - .store(in: &subscriptions) - } - } - } - - private func requestPreviewImage(state: EditingStack.State.Loaded) { - let croppedImage = state.makeCroppedImage() - imageView.display(image: croppedImage) - imageView.postProcessing = state.currentEdit.filters.apply - originalImageView.display(image: croppedImage) - } - - private func updateLoadingOverlay(displays: Bool) { - if displays, let factory = loadingOverlayFactory { - let loadingOverlay = factory() - currentLoadingOverlay = loadingOverlay - addSubview(loadingOverlay) - AutoLayoutTools.setEdge(loadingOverlay, self) - - loadingOverlay.alpha = 0 - UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { - loadingOverlay.alpha = 1 - } - .startAnimation() - - } else { - if let view = currentLoadingOverlay { - UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { - view.alpha = 0 - }&>.do { - $0.addCompletion { _ in - view.removeFromSuperview() - } - $0.startAnimation() - } - } - } - } - - override public func layoutSubviews() { - super.layoutSubviews() - - if let loaded = editingStack.store.state.loadedState { - requestPreviewImage(state: loaded) - } - } - - override public func touchesBegan(_ touches: Set, with event: UIEvent?) { - super.touchesBegan(touches, with: event) - originalImageView.isHidden = false - imageView.isHidden = true - } - - override public func touchesEnded(_ touches: Set, with event: UIEvent?) { - super.touchesEnded(touches, with: event) - originalImageView.isHidden = true - imageView.isHidden = false - } - - override public func touchesCancelled(_ touches: Set, with event: UIEvent?) { - super.touchesCancelled(touches, with: event) - originalImageView.isHidden = true - imageView.isHidden = false - } -} - -final class _PreviewImageView: UIImageView, CIImageDisplaying { - var postProcessing: (CIImage) -> CIImage = { $0 } { - didSet { - update() - } - } - - init() { - super.init(frame: .zero) - layer.drawsAsynchronously = true - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private var ciImage: CIImage? - - override var isHidden: Bool { - didSet { - if isHidden == false { - update() - } - } - } - - func display(image: CIImage?) { - ciImage = image - - if isHidden == false { - update() - } - } - - private func update() { - guard let _image = ciImage else { - image = nil - return - } - - EditorLog.debug("[ImageImageView] Update") - - let uiImage: UIImage - - if let cgImage = postProcessing(_image).cgImage { - uiImage = UIImage(cgImage: cgImage, scale: 1, orientation: .up) - } else { - // assertionFailure() - // Displaying will be slow in iOS13 - - let fixed = _image.removingExtentOffset() - - var pixelBounds = bounds - pixelBounds.size.width *= UIScreen.main.scale - pixelBounds.size.height *= UIScreen.main.scale - - let targetSize = Geometry.sizeThatAspectFit(size: fixed.extent.size, maxPixelSize: max(pixelBounds.width, pixelBounds.height)) - - let scaleX = targetSize.width / fixed.extent.width - let scaleY = targetSize.height / fixed.extent.height - let scale = min(scaleX, scaleY) - - let resolvedImage = fixed - .transformed(by: CGAffineTransform(scaleX: scale, y: scale)) - - let processed = postProcessing(resolvedImage.removingExtentOffset()) - - uiImage = UIImage( - ciImage: processed, - scale: 1, - orientation: .up - ) - } - - assert(uiImage.scale == 1) - image = uiImage - } -} diff --git a/Package/Sources/BrightroomUI/Shared/Components/ImageViews/MetalImageView.swift b/Package/Sources/BrightroomUI/Shared/Components/ImageViews/MetalImageView.swift deleted file mode 100644 index 47080333..00000000 --- a/Package/Sources/BrightroomUI/Shared/Components/ImageViews/MetalImageView.swift +++ /dev/null @@ -1,232 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import MetalKit -import UIKit - -#if !COCOAPODS - import BrightroomEngine -#endif - -/// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf -open class MetalImageView: MTKView, CIImageDisplaying, MTKViewDelegate { - public var postProcessing: (CIImage) -> CIImage = { $0 } { - didSet { - setNeedsDisplay() - } - } - - private let defaultColorSpace = CGColorSpaceCreateDeviceRGB() - private var image: CIImage? - - private lazy var commandQueue: MTLCommandQueue = { [unowned self] in - self.device!.makeCommandQueue()! - }() - - private lazy var ciContext: CIContext = { - [unowned self] in - CIContext(mtlDevice: self.device!) - }() - - override open var contentMode: UIView.ContentMode { - didSet { - setNeedsDisplay() - } - } - - override public init( - frame frameRect: CGRect, - device: MTLDevice? - ) { - super.init( - frame: frameRect, - device: device ?? MTLCreateSystemDefaultDevice() - ) - if super.device == nil { - fatalError("Device doesn't support Metal") - } - isOpaque = false - backgroundColor = .clear - framebufferOnly = false - delegate = self - enableSetNeedsDisplay = true - autoResizeDrawable = true - contentMode = .scaleAspectFill - clearColor = .init(red: 0, green: 0, blue: 0, alpha: 0) - clearsContextBeforeDrawing = true - - #if targetEnvironment(simulator) - #else - /// For supporting wide-color - extended sRGB -// colorPixelFormat = .bgra10_xr - #endif - - } - - public required init( - coder: NSCoder - ) { - fatalError("init(coder:) has not been implemented") - } - - public func display(image: CIImage?) { - self.image = image - setNeedsDisplay() - } - - override open var frame: CGRect { - didSet { - setNeedsDisplay() - } - } - - public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {} - - public func draw(in view: MTKView) { - renderImage() - } - - func renderImage() { - guard - let image = image, - let targetTexture = currentDrawable?.texture, - let commandBuffer = commandQueue.makeCommandBuffer(), - let renderPassDescriptor = currentRenderPassDescriptor, - let drawable = currentDrawable - else { - return - } - - EditorLog.debug(.imageView, "[MetalImageView] Render") - - #if DEBUG - // if image.cgImage != nil { - // EditorLog.debug("[MetalImageView] the backing storage of the image is in CPU, Render by metal might be slow.") - // } - #endif - - let bounds = CGRect( - origin: .zero, - size: drawableSize - ) - - let fixedImage = image.removingExtentOffset() - - let resolvedImage = downsample(image: fixedImage, bounds: bounds, contentMode: contentMode) - - let processedImage = postProcessing(resolvedImage) - - clearContents: do { - - // renderPassDescriptor.colorAttachments[0].texture = drawable.texture - renderPassDescriptor.colorAttachments[0].clearColor = .init(red: 0, green: 0, blue: 0, alpha: 0) - renderPassDescriptor.colorAttachments[0].loadAction = .clear - renderPassDescriptor.colorAttachments[0].storeAction = .store - - let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)! - commandEncoder.endEncoding() - } - - EditorLog.debug(.imageView, "ColorSpace => \(processedImage.colorSpace as Any)") - - ciContext.render( - processedImage, - to: targetTexture, - commandBuffer: commandBuffer, - bounds: bounds, - colorSpace: processedImage.colorSpace ?? defaultColorSpace - ) - - commandBuffer.present(drawable) - commandBuffer.commit() - } - - func downsample(image: CIImage, bounds: CGRect, contentMode: UIView.ContentMode) -> CIImage { - - let targetRect: CGRect - - switch contentMode { - case .scaleAspectFill: - targetRect = Geometry.rectThatAspectFill( - aspectRatio: image.extent.size, - minimumRect: bounds - ) - case .scaleAspectFit: - targetRect = Geometry.rectThatAspectFit( - aspectRatio: image.extent.size, - boundingRect: bounds - ) - default: - targetRect = Geometry.rectThatAspectFit( - aspectRatio: image.extent.size, - boundingRect: bounds - ) - assertionFailure("ContentMode:\(contentMode) is not supported.") - } - - let scaleX = targetRect.width / image.extent.width - let scaleY = targetRect.height / image.extent.height - let scale = min(scaleX, scaleY) - - let resolvedImage: CIImage - - #if targetEnvironment(simulator) - // Fixes geometry in Metal - resolvedImage = - image - .transformed( - by: CGAffineTransform(scaleX: 1, y: -1) - .concatenating(.init(translationX: 0, y: image.extent.height)) - .concatenating(.init(scaleX: scale, y: scale)) - .concatenating(.init(translationX: targetRect.origin.x, y: targetRect.origin.y)) - ) - - #else - resolvedImage = - image - // .resizedSmooth(targetSize: targetRect.size) - .transformed(by: CGAffineTransform(scaleX: scale, y: scale)) - .transformed(by: CGAffineTransform(translationX: targetRect.origin.x, y: targetRect.origin.y)) - - #endif - - return resolvedImage - } - -} - -extension CIImage { - - fileprivate func resizedSmooth(targetSize: CGSize) -> CIImage { - - let resizeFilter = CIFilter(name: "CILanczosScaleTransform")! - - let scale = targetSize.height / (extent.height) - let aspectRatio = targetSize.width / ((extent.width) * scale) - - resizeFilter.setValue(self, forKey: kCIInputImageKey) - resizeFilter.setValue(scale, forKey: kCIInputScaleKey) - resizeFilter.setValue(aspectRatio, forKey: kCIInputAspectRatioKey) - let outputImage = resizeFilter.outputImage - - return outputImage! - } -} diff --git a/Package/Sources/BrightroomUI/Shared/Components/ImageViews/_ImageView.swift b/Package/Sources/BrightroomUI/Shared/Components/ImageViews/_ImageView.swift deleted file mode 100644 index e57d68e8..00000000 --- a/Package/Sources/BrightroomUI/Shared/Components/ImageViews/_ImageView.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// _ImageView.swift -// PixelEditor -// -// Created by Muukii on 2021/03/07. -// Copyright © 2021 muukii. All rights reserved. -// - -import UIKit -#if !COCOAPODS -import BrightroomEngine -#endif - -final class _ImageView: UIImageView, CIImageDisplaying { - var postProcessing: (CIImage) -> CIImage = { $0 } { - didSet { - update() - } - } - - init() { - super.init(frame: .zero) - layer.drawsAsynchronously = true - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private var ciImage: CIImage? - - override var isHidden: Bool { - didSet { - if isHidden == false { - update() - } - } - } - - func display(image: CIImage?) { - ciImage = image - - if isHidden == false { - update() - } - } - - private func update() { - guard let _image = ciImage else { - image = nil - return - } - - EditorLog.debug("[_ImageView] Update") - - let uiImage: UIImage - - if let cgImage = postProcessing(_image).cgImage { - uiImage = UIImage(cgImage: cgImage, scale: 1, orientation: .up) - } else { - let processed = postProcessing(_image) - uiImage = UIImage( - ciImage: processed, - scale: 1, - orientation: .up - ) - } - - assert(uiImage.scale == 1) - image = uiImage - } -} - diff --git a/Package/Sources/BrightroomUI/Shared/Components/LoadingBlurryOverlayView.swift b/Package/Sources/BrightroomUI/Shared/Components/LoadingBlurryOverlayView.swift deleted file mode 100644 index cfc37576..00000000 --- a/Package/Sources/BrightroomUI/Shared/Components/LoadingBlurryOverlayView.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) 2021 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -public final class LoadingBlurryOverlayView: PixelEditorCodeBasedView { - - public init( - effect: UIVisualEffect, - activityIndicatorStyle: UIActivityIndicatorView.Style - ) { - super.init(frame: .zero) - - let effectView = UIVisualEffectView(effect: effect) - let activityIndicatorView = UIActivityIndicatorView(style: activityIndicatorStyle) - - backgroundColor = .clear - - addSubview(effectView) - addSubview(activityIndicatorView) - - activityIndicatorView.startAnimating() - activityIndicatorView.hidesWhenStopped = false - activityIndicatorView.isHidden = false - - [effectView, activityIndicatorView].forEach { - $0.translatesAutoresizingMaskIntoConstraints = false - } - - NSLayoutConstraint.activate([ - effectView.leadingAnchor.constraint(equalTo: leadingAnchor), - effectView.trailingAnchor.constraint(equalTo: trailingAnchor), - effectView.topAnchor.constraint(equalTo: topAnchor), - effectView.bottomAnchor.constraint(equalTo: bottomAnchor), - activityIndicatorView.centerYAnchor.constraint(equalTo: centerYAnchor), - activityIndicatorView.centerXAnchor.constraint(equalTo: centerXAnchor), - ]) - } -} diff --git a/Package/Sources/BrightroomUI/Shared/Utils/AutoLayout.swift b/Package/Sources/BrightroomUI/Shared/Utils/AutoLayout.swift deleted file mode 100644 index 25136fab..00000000 --- a/Package/Sources/BrightroomUI/Shared/Utils/AutoLayout.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// AutoLayout.swift -// PixelEditor -// -// Created by Muukii on 2021/02/27. -// Copyright © 2021 muukii. All rights reserved. -// - -import UIKit - -enum AutoLayoutTools { - - static func setEdge(_ contentView: UIView, _ targetView: UIView) { - contentView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - contentView.rightAnchor.constraint(equalTo: targetView.rightAnchor), - contentView.leftAnchor.constraint(equalTo: targetView.leftAnchor), - contentView.bottomAnchor.constraint(equalTo: targetView.bottomAnchor), - contentView.topAnchor.constraint(equalTo: targetView.topAnchor), - ]) - } -} diff --git a/Package/Sources/BrightroomUI/Shared/Utils/EditingCrop+.swift b/Package/Sources/BrightroomUI/Shared/Utils/EditingCrop+.swift deleted file mode 100644 index 0a070703..00000000 --- a/Package/Sources/BrightroomUI/Shared/Utils/EditingCrop+.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// EditingCrop+.swift -// PixelEditor -// -// Created by Muukii on 2021/03/19. -// Copyright © 2021 muukii. All rights reserved. -// - -import Foundation -import CoreGraphics - -#if !COCOAPODS -import BrightroomEngine -#endif - -extension EditingCrop { - func scrollViewContentSize() -> CGSize { - imageSize - } - - func calculateZoomScale(scrollViewSize: CGSize) -> (min: CGFloat, max: CGFloat) { - let minXScale = scrollViewSize.width / imageSize.width - let minYScale = scrollViewSize.height / imageSize.height - - /** - max meaning scale aspect fill - */ - let minScale = max(minXScale, minYScale) - - return (min: minScale, max: .greatestFiniteMagnitude) - } -} diff --git a/Package/Sources/BrightroomUI/Shared/Utils/EditorLog.swift b/Package/Sources/BrightroomUI/Shared/Utils/EditorLog.swift deleted file mode 100644 index 714b5ac1..00000000 --- a/Package/Sources/BrightroomUI/Shared/Utils/EditorLog.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// Copyright (c) 2018 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit - -import os.log - -enum EditorLog { - - private static let osLog = OSLog.init(subsystem: "PixelEditor", category: "Editor") - private static let queue = DispatchQueue.init(label: "me.muukii.PixelEditor.Log") - - static func debug(_ object: Any...) { - - queue.async { - if #available(iOS 12.0, *) { - os_log(.debug, log: osLog, "%@", object.map { "\($0)" }.joined(separator: " ")) - } else { - os_log("%@", log: osLog, type: .debug, object.map { "\($0)" }.joined(separator: " ")) - } - } - } - - static func warn(_ object: Any...) { - - queue.async { - if #available(iOS 12.0, *) { - os_log(.error, log: osLog, "%@", object.map { "\($0)" }.joined(separator: " ")) - } else { - os_log("%@", log: osLog, type: .error, object.map { "\($0)" }.joined(separator: " ")) - } - } - } - - static func debug(_ log: OSLog, _ object: Any...) { - os_log(.debug, log: log, "%@", object.map { "\($0)" }.joined(separator: " ")) - } - - static func error(_ log: OSLog, _ object: Any...) { - os_log(.error, log: log, "%@", object.map { "\($0)" }.joined(separator: " ")) - } -} - -extension OSLog { - - static let imageView: OSLog = { - #if false - return OSLog.init(subsystem: "BrightroomUI", category: "ImageView") - #else - return .disabled - #endif - }() - -} diff --git a/Package/Sources/BrightroomUI/Shared/Utils/PixelEditorCodeBasedView.swift b/Package/Sources/BrightroomUI/Shared/Utils/PixelEditorCodeBasedView.swift deleted file mode 100644 index 36c8c945..00000000 --- a/Package/Sources/BrightroomUI/Shared/Utils/PixelEditorCodeBasedView.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// CodeBasedView.swift -// PixelEditor -// -// Created by Muukii on 2021/03/03. -// Copyright © 2021 muukii. All rights reserved. -// - -import UIKit - -/** - A view that can be initializde only from code. (No supports to init from Nib) - */ -open class PixelEditorCodeBasedView : UIView { - - public override init(frame: CGRect) { - super.init(frame: frame) - } - - @available(*, unavailable) - public required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/Package/Sources/BrightroomUI/Shared/Utils/UIButton+Closure.swift b/Package/Sources/BrightroomUI/Shared/Utils/UIButton+Closure.swift deleted file mode 100644 index d7406957..00000000 --- a/Package/Sources/BrightroomUI/Shared/Utils/UIButton+Closure.swift +++ /dev/null @@ -1,41 +0,0 @@ -import UIKit -import Foundation - -private final class Proxy { - static var key: Void? - private weak var base: UIControl? - - init(_ base: UIControl) { - self.base = base - } - - var onTouchUpInside: (() -> Void)? { - didSet { - base?.addTarget(self, action: #selector(touchUpInside), for: .touchUpInside) - } - } - - @objc private func touchUpInside(sender: AnyObject) { - onTouchUpInside?() - } -} - -extension UIControl { - func onTap(_ closure: @escaping () -> Swift.Void) { - tapable.onTouchUpInside = closure - } - - private var tapable: Proxy { - get { - if let handler = objc_getAssociatedObject(self, &Proxy.key) as? Proxy { - return handler - } else { - self.tapable = Proxy(self) - return self.tapable - } - } - set { - objc_setAssociatedObject(self, &Proxy.key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } - } -} diff --git a/Package/Sources/BrightroomUI/Shared/Utils/UIStateStore.swift b/Package/Sources/BrightroomUI/Shared/Utils/UIStateStore.swift deleted file mode 100644 index 83dd6fff..00000000 --- a/Package/Sources/BrightroomUI/Shared/Utils/UIStateStore.swift +++ /dev/null @@ -1,13 +0,0 @@ - -import Verge - -public final class UIStateStore: Store { - - public init( - initialState: State, - logger: StoreLogger? = nil - ) { - super.init(initialState: initialState, backingStorageRecursiveLock: VergeNoLock().asAny(), logger: logger) - } - -} diff --git a/Package/Sources/BrightroomUI/Shared/Wrap.swift b/Package/Sources/BrightroomUI/Shared/Wrap.swift deleted file mode 100644 index cb7edd99..00000000 --- a/Package/Sources/BrightroomUI/Shared/Wrap.swift +++ /dev/null @@ -1,46 +0,0 @@ - -/// https://github.com/VergeGroup/Wrap/edit/main/Sources/Chain/Chain.swift - -postfix operator &> - -postfix func &> (argument: T) -> Wrap { - .init(argument) -} - -struct Wrap { - - let value: Value - - init(_ value: Value) { - self.value = value - } - -} - -extension Wrap { - - func map(_ transform: (Value) throws -> U) rethrows -> U { - try transform(value) - } - - @discardableResult - func `do`(_ applier: (Value) throws -> Void) rethrows -> Value where Value : AnyObject { - try applier(value) - return value - } - - func modify(_ modifier: (inout Value) throws -> Void) rethrows -> Value { - var v = value - try modifier(&v) - return v - } - - func filter(_ filter: (Value) -> Bool) -> Value? { - guard filter(value) else { - return nil - } - return value - } - -} - diff --git a/Sources/BrightroomEngine/Core/EditingCrop.swift b/Sources/BrightroomEngine/Core/EditingCrop.swift index c6fefef4..ebc89141 100644 --- a/Sources/BrightroomEngine/Core/EditingCrop.swift +++ b/Sources/BrightroomEngine/Core/EditingCrop.swift @@ -183,6 +183,14 @@ public struct EditingCrop: Equatable { */ public mutating func updateCropExtentIfNeeded(toFitAspectRatio newAspectRatio: PixelAspectRatio) { + // FIXME: it won't perform correctly. won't match values as using floating point value + /* + Depends on the size of image, it can not always express the exact value of given aspect-ratio. + - image-size: (7864.0, 5248.0) + - aspect ratio: 1.0:1.2 (0.8333333333) + - calculated fitting image size with ratio: (1745.0, 0.0, 4373.0, 5247.0) + - (4373.0 : 5247.0) -> 0.8334286259 + */ guard PixelAspectRatio(cropExtent.size) != newAspectRatio else { return } @@ -334,8 +342,8 @@ public struct EditingCrop: Equatable { #if DEBUG EngineLog.debug( """ - Normalizing CropExtent - => \(fixed) + [Normalizing CropExtent] + Output: \(fixed) - resultAspectRatio: \(PixelAspectRatio(fixed.size)._minimized().localizedText) - source: \(rect) - imageSize: \(imageSize) diff --git a/Sources/BrightroomEngine/Library/Geometry.swift b/Sources/BrightroomEngine/Library/Geometry.swift index ce299acd..532c5e2b 100644 --- a/Sources/BrightroomEngine/Library/Geometry.swift +++ b/Sources/BrightroomEngine/Library/Geometry.swift @@ -135,7 +135,7 @@ extension CGSize { public struct PixelAspectRatio: Hashable { public static func == (lhs: Self, rhs: Self) -> Bool { - (lhs.height / lhs.width) == (rhs.height / rhs.width) + lhs._comparingValue == rhs._comparingValue } public var width: CGFloat @@ -249,6 +249,10 @@ public struct PixelAspectRatio: Hashable { return .init(width: width / v, height: height / v) } + public var _comparingValue: CGFloat { + return (height / width) + } + public var localizedText: String { "\(width):\(height)" } diff --git a/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift b/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift index 02d213e4..68f280f6 100644 --- a/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift +++ b/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift @@ -399,6 +399,10 @@ public final class CropView: UIView, UIScrollViewDelegate { _pixeleditor_ensureMainThread() store.commit { + guard $0.proposedCrop != crop else { + return + } + $0.proposedCrop = crop if let ratio = $0.preferredAspectRatio { $0.proposedCrop?.updateCropExtentIfNeeded(toFitAspectRatio: ratio) @@ -411,6 +415,11 @@ public final class CropView: UIView, UIScrollViewDelegate { _pixeleditor_ensureMainThread() store.commit { + + guard $0.preferredAspectRatio != ratio else { + return + } + $0.preferredAspectRatio = ratio if let ratio = ratio { $0.proposedCrop?.updateCropExtentIfNeeded(toFitAspectRatio: ratio) diff --git a/Sources/SharedForDemo/Assets.xcassets/Contents.json b/Sources/SharedForDemo/Assets.xcassets/Contents.json deleted file mode 100644 index 73c00596..00000000 --- a/Sources/SharedForDemo/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Sources/SwiftUIDemo/Preview Content/Preview Assets.xcassets/Contents.json b/Sources/SwiftUIDemo/Preview Content/Preview Assets.xcassets/Contents.json deleted file mode 100644 index 73c00596..00000000 --- a/Sources/SwiftUIDemo/Preview Content/Preview Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -}