diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c461b5c --- /dev/null +++ b/.gitignore @@ -0,0 +1,79 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +.build/ +Pods/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ diff --git a/PixelEngine/BlurredMask.swift b/PixelEngine/BlurredMask.swift new file mode 100644 index 0000000..13779de --- /dev/null +++ b/PixelEngine/BlurredMask.swift @@ -0,0 +1,110 @@ +// +// 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 UIKit +import CoreGraphics + +public struct BlurredMask : GraphicsDrawing { + + public var paths: [DrawnPathInRect] + + public init(paths: [DrawnPathInRect]) { + self.paths = paths + } + + public func draw(in context: CGContext, canvasSize: CGSize) { + + guard !paths.isEmpty else { + return + } + + let mainContext = context + let size = canvasSize + + guard + let cglayer = CGLayer(mainContext, size: size, auxiliaryInfo: nil), + let layerContext = cglayer.context else { + assert(false, "Failed to create CGLayer") + return + } + + let ciContext = CIContext(cgContext: layerContext, options: [:]) + let ciImage = BlurredMask.blur(image: CIImage(image: UIGraphicsGetImageFromCurrentImageContext()!)!)! + + UIGraphicsPushContext(layerContext) + + paths.forEach { path in + layerContext.saveGState() + + let scale = Geometry.diagonalRatio(to: canvasSize, from: path.inRect.size) + + layerContext.scaleBy(x: scale, y: scale) + path.draw(in: layerContext, canvasSize: canvasSize) + + layerContext.restoreGState() + } + + layerContext.saveGState() + + layerContext.setBlendMode(.sourceIn) + layerContext.translateBy(x: 0, y: canvasSize.height) + layerContext.scaleBy(x: 1, y: -1) + + ciContext.draw(ciImage, in: ciImage.extent, from: ciImage.extent) + + layerContext.restoreGState() + + UIGraphicsPopContext() + + UIGraphicsPushContext(mainContext) + + mainContext.draw(cglayer, at: .zero) + + 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/PixelEngine/ColorCube.swift b/PixelEngine/ColorCube.swift new file mode 100644 index 0000000..b4b82a7 --- /dev/null +++ b/PixelEngine/ColorCube.swift @@ -0,0 +1,160 @@ +// +// 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 +import CoreImage +import UIKit + +#if os(macOS) +import AppKit +public typealias Image = NSImage +#elseif os(iOS) +import UIKit +public typealias Image = UIImage +#endif + +public enum ColorCube { + + public static func makeColorCubeFilter( + lutImage: Image, + dimension: Int, + colorSpace: CGColorSpace + ) -> CIFilter { + + let data = cubeData( + lutImage: lutImage, + dimension: dimension, + colorSpace: colorSpace + )! + + let filter = CIFilter( + name: "CIColorCubeWithColorSpace", + parameters: [ + "inputCubeDimension" : dimension, + "inputCubeData" : data, + "inputColorSpace" : colorSpace, + ] + ) + + return filter! + } + + private static func createBitmap(image: CGImage, colorSpace: CGColorSpace) -> UnsafeMutablePointer? { + + let width = image.width + let height = image.height + + let bitsPerComponent = 8 + let bytesPerRow = width * 4 + + let bitmapSize = bytesPerRow * height + + guard let data = malloc(bitmapSize) else { + return nil + } + + guard let context = CGContext( + data: data, + width: width, + height: height, + bitsPerComponent: bitsPerComponent, + bytesPerRow: bytesPerRow, + space: colorSpace, + bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue, + releaseCallback: nil, + releaseInfo: nil) else { + return nil + } + + context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height)) + + return data.bindMemory(to: UInt8.self, capacity: bitmapSize) + } + + // Imported from Objective-C code. + // TODO: Improve more swifty. + public static func cubeData(lutImage: Image, dimension: Int, colorSpace: CGColorSpace) -> Data? { + + guard let cgImage = lutImage.cgImage else { + return nil + } + + guard let bitmap = createBitmap(image: cgImage, colorSpace: colorSpace) else { + return nil + } + + let width = cgImage.width + let height = cgImage.height + let rowNum = width / dimension + let columnNum = height / dimension + + let dataSize = dimension * dimension * dimension * MemoryLayout.size * 4 + + var array = Array(repeating: 0, count: dataSize) + + var bitmapOffest: Int = 0 + var z: Int = 0 + + for _ in stride(from: 0, to: rowNum, by: 1) { + for y in stride(from: 0, to: dimension, by: 1) { + let tmp = z + for _ in stride(from: 0, to: columnNum, by: 1) { + for x in stride(from: 0, to: dimension, by: 1) { + + let dataOffset = (z * dimension * dimension + y * dimension + x) * 4 + + let position = bitmap + .advanced(by: bitmapOffest) + + array[dataOffset + 0] = Float(position + .advanced(by: 0) + .pointee) / 255 + + array[dataOffset + 1] = Float(position + .advanced(by: 1) + .pointee) / 255 + + array[dataOffset + 2] = Float(position + .advanced(by: 2) + .pointee) / 255 + + array[dataOffset + 3] = Float(position + .advanced(by: 3) + .pointee) / 255 + + bitmapOffest += 4 + + } + z += 1 + } + z = tmp + } + z += columnNum + } + + free(bitmap) + + let data = Data.init(bytes: array, count: dataSize) + return data + } +} + diff --git a/PixelEngine/DrawnPath.swift b/PixelEngine/DrawnPath.swift new file mode 100644 index 0000000..aa4d735 --- /dev/null +++ b/PixelEngine/DrawnPath.swift @@ -0,0 +1,83 @@ +// +// 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 +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.width + + return _bezierPath + } + + public func draw(in context: CGContext, canvasSize: CGSize) { + UIGraphicsPushContext(context) + context.saveGState() + defer { + context.restoreGState() + UIGraphicsPopContext() + } + + draw() + } + + private func draw() { + + guard let context = UIGraphicsGetCurrentContext() else { + return + } + + context.saveGState() + defer { + context.restoreGState() + } + + brush.color.setStroke() + let bezierPath = brushedPath() + bezierPath.stroke(with: brush.blendMode, alpha: brush.alpha) + } + +} diff --git a/PixelEngine/DrawnPathInRect.swift b/PixelEngine/DrawnPathInRect.swift new file mode 100644 index 0000000..0a7e506 --- /dev/null +++ b/PixelEngine/DrawnPathInRect.swift @@ -0,0 +1,40 @@ +// +// 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 +import CoreImage +import UIKit + +public struct DrawnPathInRect : GraphicsDrawing, Equatable { + + public let inRect: CGRect + public let path: DrawnPath + + public init(path: DrawnPath, in rect: CGRect) { + self.path = path + self.inRect = rect + } + + public func draw(in context: CGContext, canvasSize: CGSize) { + path.draw(in: context, canvasSize: canvasSize) + } +} diff --git a/PixelEngine/EditingStack.swift b/PixelEngine/EditingStack.swift new file mode 100644 index 0000000..4d8735f --- /dev/null +++ b/PixelEngine/EditingStack.swift @@ -0,0 +1,386 @@ +// +// 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 +import CoreImage +import UIKit + +public protocol EditingStackDelegate : class { + func editingStack(_ stack: EditingStack, didUpdate imageSource: ImageSourceType) + func editingStack(_ stack: EditingStack, didChangeCurrentEdit edit: EditingStack.Edit) +} + +public extension EditingStackDelegate { + func editingStack(_ stack: EditingStack, didUpdate imageSource: ImageSourceType) {} +} + +open class EditingStack { + + // MARK: - Stored Properties + + public let source: ImageSourceType + + public weak var delegate: EditingStackDelegate? + + public let preferredPreviewSize: CGSize + + public let targetScreenScale: CGFloat + + private(set) public var previewImage: CIImage? + + private(set) public var originalPreviewImage: CIImage? { + didSet { + EngineLog.debug("Changed EditingStack.originalPreviewImage") + updatePreviewImage() + + } + } + + public var adjustmentImage: CIImage? + + public var aspectRatio: CGSize? { + return originalPreviewImage?.extent.size + } + + public var isDirty: Bool { + return draftEdit != nil + } + + public var canUndo: Bool { + return edits.count > 1 + } + + public var draftEdit: Edit? { + didSet { + if oldValue != draftEdit { + updatePreviewImage() + } + } + } + + public var currentEdit: Edit { + return draftEdit ?? edits.last! + } + + public private(set) var edits: [Edit] { + didSet { + EngineLog.debug("Edits changed count -> \(edits.count)") + } + } + + private let queue = DispatchQueue( + label: "me.muukii.PixelEngine", + qos: .default, + attributes: [] + ) + + // MARK: - Initializers + + public init( + source: ImageSourceType, + previewSize: CGSize, + screenScale: CGFloat = UIScreen.main.scale + ) { + + self.source = source + + self.targetScreenScale = screenScale + self.preferredPreviewSize = previewSize + self.adjustmentImage = source.imageSource?.image + + self.edits = [.init()] + + initialCrop() + commit() + removeAllHistory() + self.source.setImageUpdateListener { [weak self] in + guard let self = self else { return } + self.adjustmentImage = $0.imageSource?.image + self.initialCrop() + guard $0.imageSource?.image != nil else { return } + + self.delegate?.editingStack(self, didUpdate: self.source) + } + } + + open func initialCrop() { + guard let image = source.imageSource?.image else { return } + setAdjustment(cropRect: image.extent) + } + + // MARK: - Functions + + public func requestApplyingFilterImage() -> CIImage { + fatalError() + } + + private func makeDraft() { + draftEdit = edits.last ?? .init() + } + + public func commit() { + guard let edit = draftEdit else { + EngineLog.debug("No draft, no needs commit") + return + } + guard edits.last != edit else { return } + edits.append(edit) + draftEdit = nil + } + + public func revert() { + draftEdit = nil + } + + public func undo() { + edits.removeLast() + updatePreviewImage() + } + + public func removeAllHistory() { + edits = [edits.last].compactMap { $0 } + } + + public func set(filters: (inout Edit.Filters) -> Void) { + applyIfChanged { + filters(&$0.filters) + } + } + + public func setAdjustment(cropRect: CGRect) { + + guard let originalImage = source.imageSource?.image else { return } //XXX check for croppability ? + + var _cropRect = cropRect + + _cropRect.origin.x.round(.up) + _cropRect.origin.y.round(.up) + _cropRect.size.width.round(.up) + _cropRect.size.height.round(.up) + + applyIfChanged { + $0.cropRect = _cropRect + } + + _cropRect.origin.y = originalImage.extent.height - _cropRect.minY - _cropRect.height + + let croppedImage = originalImage + .cropped(to: _cropRect) + + let result = ImageTool.resize( + to: Geometry.sizeThatAspectFit( + aspectRatio: croppedImage.extent.size, + boundingSize: CGSize( + width: preferredPreviewSize.width * targetScreenScale, + height: preferredPreviewSize.height * targetScreenScale + ) + ), + from: croppedImage + ) + + originalPreviewImage = result + } + + public func set(blurringMaskPaths: [DrawnPathInRect]) { + + applyIfChanged { + $0.blurredMaskPaths = blurringMaskPaths + } + } + + + public func makeRenderer() -> ImageRenderer { + let renderer = ImageRenderer(source: source) + + let edit = currentEdit + + renderer.edit.croppingRect = edit.cropRect + renderer.edit.drawer = [ + BlurredMask(paths: edit.blurredMaskPaths) + ] + + renderer.edit.modifiers = edit.makeFilters() + + return renderer + } + + + private func applyIfChanged(_ perform: (inout Edit) -> Void) { + + if draftEdit == nil { + makeDraft() + } + + var draft = draftEdit! + perform(&draft) + + guard draftEdit != draft else { return } + + draftEdit = draft + + } + + private func updatePreviewImage() { + + guard let sourceImage = originalPreviewImage else { + previewImage = nil + return + } + + let filters = self.currentEdit + .makeFilters() + + let result = filters.reduce(sourceImage) { (image, filter) -> CIImage in + filter.apply(to: image, sourceImage: sourceImage) + } + + self.previewImage = result + self.delegate?.editingStack(self, didChangeCurrentEdit: self.currentEdit) + + // TODO: Ignore vignette and blur (convolutions) + // adjustmentImage = filters.reduce(source.image) { (image, filter) -> CIImage in + // filter.apply(to: image, sourceImage: source.image).insertingIntermediateIfCanUse() + // } + + } + +} + +open class SquareEditingStack : EditingStack { + + open override func initialCrop() { + guard let image = source.imageSource?.image else { return } + let cropRect = Geometry.rectThatAspectFit( + aspectRatio: .init(width: 1, height: 1), + boundingRect: image.extent + ) + + setAdjustment(cropRect: cropRect) + } +} + +private func _ratio(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 EditingStack { + + public struct Edit : Equatable { + + public struct Filters : Equatable { + + public var colorCube: FilterColorCube? + + public var color: FilterColor? + 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 whiteBalance: FilterWhiteBalance? + + public var sharpen: FilterSharpen? + public var gaussianBlur: FilterGaussianBlur? + public var unsharpMask: FilterUnsharpMask? + + public var vignette: FilterVignette? + public var fade: FilterFade? + public var highlightShadowTint: FilterHighlightShadowTint? + public var hls:FilterHLS? + + func makeFilters() -> [Filtering] { + return ([ + + // Before + exposure, + color, + temperature, + highlights, + shadows, + saturation, + contrast, + colorCube, + + // After + sharpen, + unsharpMask, + gaussianBlur, + fade, + vignette, + // Custom + + highlightShadowTint, + hls, + whiteBalance, + ] as [Optional]) + .compactMap { $0 } + } + } + + public var cropRect: CGRect? + public var blurredMaskPaths: [DrawnPathInRect] = [] + + public var filters: Filters = .init() + + func makeFilters() -> [Filtering] { + return filters.makeFilters() + } + } + +} + +extension CIImage { + + fileprivate func insertingIntermediateIfCanUse() -> CIImage { + if #available(iOS 12.0, *) { + return self.insertingIntermediate(cache: true) + } else { + return self + } + + } +} + +extension Collection where Index == Int { + + fileprivate func concurrentMap(_ transform: (Element) -> U) -> [U] { + var buffer = [U?].init(repeating: nil, count: count) + let lock = NSLock() + DispatchQueue.concurrentPerform(iterations: count) { i in + let e = self[i] + let r = transform(e) + lock.lock() + buffer[i] = r + lock.unlock() + } + return buffer.compactMap { $0 } + } +} + diff --git a/PixelEngine/Engine/ImageRenderer.swift b/PixelEngine/Engine/ImageRenderer.swift new file mode 100644 index 0000000..35053e1 --- /dev/null +++ b/PixelEngine/Engine/ImageRenderer.swift @@ -0,0 +1,128 @@ +// +// 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 +import CoreImage +import UIKit + +public final class ImageRenderer { + + public enum Resolution { + case full + case resize(boundingSize: CGSize) + } + + public struct Edit { + public var croppingRect: CGRect? + public var modifiers: [Filtering] = [] + public var drawer: [GraphicsDrawing] = [] + } + + private let cicontext = CIContext(options: [ + .useSoftwareRenderer : false, + .highQualityDownsample : true, + ]) + + public let source: ImageSourceType + + public var edit: Edit = .init() + + public init(source: ImageSourceType) { + self.source = source + } + + public func render(resolution: Resolution = .full) -> UIImage { + guard let targetImage = source.imageSource?.image else { + preconditionFailure("Nothing to render") + } + let resultImage: CIImage = { + + let sourceImage: CIImage + + if var croppingRect = edit.croppingRect { + croppingRect.origin.x.round(.up) + croppingRect.origin.y.round(.up) + croppingRect.size.width.round(.up) + croppingRect.size.height.round(.up) + croppingRect.origin.y = targetImage.extent.height - croppingRect.minY - croppingRect.height + sourceImage = targetImage.cropped(to: croppingRect) + } else { + sourceImage = targetImage + } + + let result = edit.modifiers.reduce(sourceImage, { image, modifier in + return modifier.apply(to: image, sourceImage: sourceImage) + }) + + return result + + }() + + let canvasSize: CGSize + + switch resolution { + case .full: + canvasSize = resultImage.extent.size + case .resize(let boundingSize): + canvasSize = Geometry.sizeThatAspectFit(aspectRatio: resultImage.extent.size, boundingSize: boundingSize) + } + + let format: UIGraphicsImageRendererFormat + if #available(iOS 11.0, *) { + format = UIGraphicsImageRendererFormat.preferred() + } else { + format = UIGraphicsImageRendererFormat.default() + } + format.scale = 1 + format.opaque = true + if #available(iOS 12.0, *) { + format.preferredRange = .extended + } else { + format.prefersExtendedRange = false + } + + let image = autoreleasepool { () -> UIImage in + + UIGraphicsImageRenderer.init(size: canvasSize, format: format) + .image { c in + + let cgContext = UIGraphicsGetCurrentContext()! + + let cgImage = cicontext.createCGImage(resultImage, from: resultImage.extent, format: .RGBA8, colorSpace: resultImage.colorSpace ?? CGColorSpaceCreateDeviceRGB())! + + cgContext.saveGState() + cgContext.translateBy(x: 0, y: canvasSize.height) + cgContext.scaleBy(x: 1, y: -1) + cgContext.draw(cgImage, in: CGRect(origin: .zero, size: canvasSize)) + cgContext.restoreGState() + + self.edit.drawer.forEach { drawer in + drawer.draw(in: cgContext, canvasSize: canvasSize) + } + } + + } + + return image + } +} + diff --git a/PixelEngine/Engine/ImageTool.swift b/PixelEngine/Engine/ImageTool.swift new file mode 100644 index 0000000..2e1ab54 --- /dev/null +++ b/PixelEngine/Engine/ImageTool.swift @@ -0,0 +1,135 @@ +// +// 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 AVFoundation + +public enum ImageTool { + + private static let ciContext = CIContext(options: [ + .useSoftwareRenderer : false, + .highQualityDownsample: true, + .workingColorSpace : CGColorSpaceCreateDeviceRGB() + ] + ) + + public static func resize(to pixelSize: CGSize, from image: CIImage) -> CIImage? { + + var targetSize = pixelSize + targetSize.height.round(.down) + targetSize.width.round(.down) + + let scaleX = targetSize.width / image.extent.width + let scaleY = targetSize.height / image.extent.height + + if false, #available(iOS 12, *) { + + // This code does not work well. + // In UIImageView, display 1px white line. + + let resized = image + .transformed(by: .init(scaleX: scaleX, y: scaleY)) + + // TODO: round extent + + let result = resized + .transformed(by: .init( + translationX: -(resized.extent.minX - resized.extent.minX.rounded(.down)), + y: -(resized.extent.minY - resized.extent.minY.rounded(.down)) + ) + ) + .insertingIntermediate() + + return result + + } else { + + return + autoreleasepool { () -> CIImage? in + + let originalExtent = image.extent + + let format: UIGraphicsImageRendererFormat + if #available(iOS 11.0, *) { + format = UIGraphicsImageRendererFormat.preferred() + } else { + format = UIGraphicsImageRendererFormat.default() + } + format.scale = 1 + format.opaque = true + if #available(iOS 12.0, *) { + format.preferredRange = .automatic + } else { + format.prefersExtendedRange = false + } + + let uiImage = UIGraphicsImageRenderer.init(size: targetSize, format: format) + .image { c in + + autoreleasepool { + let rect = CGRect(origin: .zero, size: targetSize) + if let cgImage = image.cgImage { + c.cgContext.translateBy(x: 0, y: targetSize.height) + c.cgContext.scaleBy(x: 1, y: -1) + c.cgContext.draw(cgImage, in: rect) + + } else { + + if #available(iOS 13, *) { + c.cgContext.translateBy(x: 0, y: targetSize.height) + c.cgContext.scaleBy(x: 1, y: -1) + let context = CIContext(cgContext: c.cgContext, options: [:]) + context.draw(image, in: rect, from: image.extent) + } else { + UIImage(ciImage: image).draw(in: rect) + } + + } + } + } + + let resizedImage: CIImage + + if #available(iOS 12, *) { + resizedImage = CIImage(image: uiImage)! + .insertingIntermediate(cache: true) + } else { + resizedImage = uiImage + .pngData() + .flatMap { + CIImage(data: $0, options: [.colorSpace : image.colorSpace ?? CGColorSpaceCreateDeviceRGB()]) + }! + } + + let r = resizedImage.transformed(by: .init( + translationX: (originalExtent.origin.x * scaleX).rounded(.down), + y: (originalExtent.origin.y * scaleY).rounded(.down) + ) + ) + + return r + } + + } + } + +} diff --git a/PixelEngine/EngineLog.swift b/PixelEngine/EngineLog.swift new file mode 100644 index 0000000..6166110 --- /dev/null +++ b/PixelEngine/EngineLog.swift @@ -0,0 +1,41 @@ +// +// 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 os.log + +enum EngineLog { + + private static let osLog = OSLog.init(subsystem: "PixelEngine", category: "Engine") + private static let queue = DispatchQueue.init(label: "me.muukii.PixelEngine.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: " ")) + } + } + } +} diff --git a/PixelEngine/Filter/FilterColor.swift b/PixelEngine/Filter/FilterColor.swift new file mode 100644 index 0000000..c9e3cb8 --- /dev/null +++ b/PixelEngine/Filter/FilterColor.swift @@ -0,0 +1,52 @@ +// +// 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 CoreImage + +public struct FilterColor: Filtering, Equatable, Codable { + + public static let rangeSaturation: ParameterRange = .init(min: 0, max: 2) + public static let rangeBrightness: ParameterRange = .init(min: -0.2, max: 0.2) + public static let rangeContrast: ParameterRange = .init(min: 0, max: 2) + + public var valueSaturation: Double = 1 + public var valueBrightness: Double = 0 + public var valueContrast: Double = 1 + + public init() { + + } + + public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { + return + image + .applyingFilter( + "CIColorControls", + parameters: [ + "inputSaturation": valueSaturation, + "inputBrightness": valueBrightness, + "inputContrast": valueContrast, + ] + ) + } + +} diff --git a/PixelEngine/Filter/FilterColorCube.swift b/PixelEngine/Filter/FilterColorCube.swift new file mode 100644 index 0000000..095bd6e --- /dev/null +++ b/PixelEngine/Filter/FilterColorCube.swift @@ -0,0 +1,117 @@ +// +// 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 CoreImage +import SwiftUI + +public struct PreviewFilterColorCube : Equatable { + + private enum Static { + static let ciContext = CIContext(options: [.useSoftwareRenderer : false]) + static let heatingQueue = DispatchQueue.init(label: "me.muukii.PixelEngine.Preheat", attributes: [.concurrent]) + } + + public let image: CIImage + public let filter: FilterColorCube + public let cgImage: CGImage + public init(sourceImage: CIImage, filter: FilterColorCube) { + self.filter = filter + self.image = filter.apply(to: sourceImage, sourceImage: sourceImage) + self.cgImage = Static.ciContext.createCGImage(self.image, from: self.image.extent)! + } + + public func preheat() { + Static.heatingQueue.async { + _ = Static.ciContext.createCGImage(self.image, from: self.image.extent) + + } + } +} + +/// A Filter using LUT Image (backed by CIColorCubeWithColorSpace) +/// About LUT Image -> https://en.wikipedia.org/wiki/Lookup_table +public struct FilterColorCube : Filtering, Equatable { + + public static let range: ParameterRange = .init(min: 0, max: 1) + + public let filter: CIFilter + + public let name: String + public let identifier: String + public var amount: Double = 1 + + public init( + name: String, + identifier: String, + lutImage: Image, + dimension: Int, + amount: Double = 1, + colorSpace: CGColorSpace = CGColorSpace.init(name: CGColorSpace.sRGB) ?? CGColorSpaceCreateDeviceRGB() + ) { + let filter = ColorCube.makeColorCubeFilter(lutImage: lutImage, dimension: dimension, colorSpace: colorSpace) + self.init(name: name, identifier: identifier, filter: filter, amount: amount) + } + + public init(name: String, + identifier: String, + filter: CIFilter, + amount: Double = 1 + ){ + self.name = name + self.identifier = identifier + self.filter = filter + self.amount = amount + } + + + + + public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { + + let f = filter.copy() as! CIFilter + + f.setValue(image, forKeyPath: kCIInputImageKey) + if let colorSpace = image.colorSpace { + f.setValue(colorSpace, forKeyPath: "inputColorSpace") + } + + 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! + + } +} diff --git a/PixelEngine/Filter/FilterContrast.swift b/PixelEngine/Filter/FilterContrast.swift new file mode 100644 index 0000000..f75439a --- /dev/null +++ b/PixelEngine/Filter/FilterContrast.swift @@ -0,0 +1,45 @@ +// +// 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 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/PixelEngine/Filter/FilterExposure.swift b/PixelEngine/Filter/FilterExposure.swift new file mode 100644 index 0000000..0be568e --- /dev/null +++ b/PixelEngine/Filter/FilterExposure.swift @@ -0,0 +1,46 @@ +// +// 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 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/PixelEngine/Filter/FilterFade.swift b/PixelEngine/Filter/FilterFade.swift new file mode 100644 index 0000000..2e6b1e6 --- /dev/null +++ b/PixelEngine/Filter/FilterFade.swift @@ -0,0 +1,57 @@ +// +// 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 CoreImage + +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/PixelEngine/Filter/FilterGaussianBlur.swift b/PixelEngine/Filter/FilterGaussianBlur.swift new file mode 100644 index 0000000..09d7711 --- /dev/null +++ b/PixelEngine/Filter/FilterGaussianBlur.swift @@ -0,0 +1,49 @@ +// +// 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 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/PixelEngine/Filter/FilterHLS.swift b/PixelEngine/Filter/FilterHLS.swift new file mode 100644 index 0000000..61f13a9 --- /dev/null +++ b/PixelEngine/Filter/FilterHLS.swift @@ -0,0 +1,45 @@ +// +// FilterHLS.swift +// PixelEngine +// +// Created by macOS on 7/7/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import Foundation +import CoreImage + +public struct FilterHLS : Filtering, Equatable { + + public static var defaultValue:[CIVector] = [ CIVector(x: 0, y: 1, z: 1), + CIVector(x: 0, y: 1, z: 1), + CIVector(x: 0, y: 1, z: 1), + CIVector(x: 0, y: 1, z: 1), + CIVector(x: 0, y: 1, z: 1), + CIVector(x: 0, y: 1, z: 1), + CIVector(x: 0, y: 1, z: 1), + CIVector(x: 0, y: 1, z: 1),] + + public var inputShift:[CIVector] = FilterHLS.defaultValue + + + public init() { + + } + + public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { + + let hsv:MultiBandHSV = MultiBandHSV() + hsv.inputImage = image + hsv.inputRedShift = inputShift[0] + hsv.inputOrangeShift = inputShift[1] + hsv.inputYellowShift = inputShift[2] + hsv.inputGreenShift = inputShift[3] + hsv.inputAquaShift = inputShift[4] + hsv.inputBlueShift = inputShift[5] + hsv.inputPurpleShift = inputShift[6] + hsv.inputMagentaShift = inputShift[7] + return hsv.outputImage! + } + +} diff --git a/PixelEngine/Filter/FilterHighlightShadowTint.swift b/PixelEngine/Filter/FilterHighlightShadowTint.swift new file mode 100644 index 0000000..e6bfe6f --- /dev/null +++ b/PixelEngine/Filter/FilterHighlightShadowTint.swift @@ -0,0 +1,90 @@ +// +// 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 UIKit + +public typealias ColorName = UInt32 + +public struct FilterHighlightShadowTint : Filtering, Equatable { + + public enum HighlightTintColors: ColorName, CaseIterable { + case Color1 = 0xE6E377 + case Color2 = 0xE6BB78 + case Color3 = 0xE67777 + case Color4 = 0xEA8CB7 + case Color5 = 0xB677E6 + case Color6 = 0x7781E6 + case Color7 = 0x77D2E5 + case Color8 = 0x77E681 + } + + public enum ShadowTintColors: ColorName, CaseIterable { + case Color1 = 0xC7C12E + case Color2 = 0xC78B2E + case Color3 = 0xC72E2E + case Color4 = 0xC4417E + case Color5 = 0x852EC7 + case Color6 = 0x2E3CC7 + case Color7 = 0x2EABC7 + case Color8 = 0x2EC73C + } + + 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/PixelEngine/Filter/FilterHighlights.swift b/PixelEngine/Filter/FilterHighlights.swift new file mode 100644 index 0000000..9fbec2f --- /dev/null +++ b/PixelEngine/Filter/FilterHighlights.swift @@ -0,0 +1,40 @@ +// +// 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 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/PixelEngine/Filter/FilterSaturation.swift b/PixelEngine/Filter/FilterSaturation.swift new file mode 100644 index 0000000..f2c681a --- /dev/null +++ b/PixelEngine/Filter/FilterSaturation.swift @@ -0,0 +1,45 @@ +// +// 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 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/PixelEngine/Filter/FilterShadows.swift b/PixelEngine/Filter/FilterShadows.swift new file mode 100644 index 0000000..3bf0f07 --- /dev/null +++ b/PixelEngine/Filter/FilterShadows.swift @@ -0,0 +1,41 @@ +// +// 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 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/PixelEngine/Filter/FilterSharpen.swift b/PixelEngine/Filter/FilterSharpen.swift new file mode 100644 index 0000000..879033e --- /dev/null +++ b/PixelEngine/Filter/FilterSharpen.swift @@ -0,0 +1,51 @@ +// +// 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 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/PixelEngine/Filter/FilterTemperature.swift b/PixelEngine/Filter/FilterTemperature.swift new file mode 100644 index 0000000..4a0b7af --- /dev/null +++ b/PixelEngine/Filter/FilterTemperature.swift @@ -0,0 +1,48 @@ +// +// 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 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/PixelEngine/Filter/FilterUnsharpMask.swift b/PixelEngine/Filter/FilterUnsharpMask.swift new file mode 100644 index 0000000..a637a25 --- /dev/null +++ b/PixelEngine/Filter/FilterUnsharpMask.swift @@ -0,0 +1,51 @@ +// +// 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/PixelEngine/Filter/FilterVignette.swift b/PixelEngine/Filter/FilterVignette.swift new file mode 100644 index 0000000..53e0867 --- /dev/null +++ b/PixelEngine/Filter/FilterVignette.swift @@ -0,0 +1,46 @@ +// +// 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 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/PixelEngine/Filter/FilterWhiteBalance.swift b/PixelEngine/Filter/FilterWhiteBalance.swift new file mode 100644 index 0000000..eb8294e --- /dev/null +++ b/PixelEngine/Filter/FilterWhiteBalance.swift @@ -0,0 +1,37 @@ +// +// FilterWhiteBalance.swift +// PixelEngine +// +// Created by macOS on 7/18/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import Foundation +import CoreImage + +public struct FilterWhiteBalance: Filtering, Equatable, Codable { + + public static let range: ParameterRange = .init(min: -3000, max: 3000) + + public var valueTemperature: Double = 0 + + public var valueTint: Double = 0 + + public init() { + + } + + public func apply(to image: CIImage, sourceImage: CIImage) -> CIImage { + return + image + .applyingFilter( + "CITemperatureAndTint", + parameters: [ + "inputNeutral": CIVector.init(x: CGFloat(valueTemperature) + 6500, y: 0), + "inputTargetNeutral": CIVector.init(x: CGFloat(valueTint) + 6500, y: 0), + ] + ) + } + + +} diff --git a/PixelEngine/Filtering.swift b/PixelEngine/Filtering.swift new file mode 100644 index 0000000..bdd70d8 --- /dev/null +++ b/PixelEngine/Filtering.swift @@ -0,0 +1,39 @@ +// +// 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 CoreImage + +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 { + + func apply(to image: CIImage, sourceImage: CIImage) -> CIImage +} diff --git a/PixelEngine/Geometry.swift b/PixelEngine/Geometry.swift new file mode 100644 index 0000000..c1765be --- /dev/null +++ b/PixelEngine/Geometry.swift @@ -0,0 +1,92 @@ +// +// 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 +import CoreImage +import UIKit + +public enum Geometry { + + 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 + } + +} diff --git a/PixelEngine/GraphicDrawing.swift b/PixelEngine/GraphicDrawing.swift new file mode 100644 index 0000000..1050c09 --- /dev/null +++ b/PixelEngine/GraphicDrawing.swift @@ -0,0 +1,30 @@ +// +// 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 +import CoreImage +import UIKit + +public protocol GraphicsDrawing { + + func draw(in context: CGContext, canvasSize: CGSize) +} diff --git a/PixelEngine/ImageSource.swift b/PixelEngine/ImageSource.swift new file mode 100644 index 0000000..2e44e88 --- /dev/null +++ b/PixelEngine/ImageSource.swift @@ -0,0 +1,147 @@ +// +// 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 CoreImage + +#if canImport(UIKit) +import UIKit +#endif + +public enum ImageSource { + case previewOnly(CIImage) + case editable(CIImage) + + var image: CIImage { + switch self { + case .editable(let image): + return image + case .previewOnly(let image) : + return image + } + } +} + +public protocol ImageSourceType { + func setImageUpdateListener(_ listner: @escaping (ImageSourceType) -> Void) + var imageSource: ImageSource? { get } +} + +#if canImport(Photos) +import Photos + +public final class PHAssetImageSource: ImageSourceType { + private var listner: ((ImageSourceType) -> Void) = { _ in } + public var imageSource: ImageSource? { + didSet { + listner(self) + } + } + + public init(_ asset: PHAsset) { + let previewRequestOptions = PHImageRequestOptions() + previewRequestOptions.deliveryMode = .highQualityFormat + previewRequestOptions.isNetworkAccessAllowed = true + previewRequestOptions.version = .current + previewRequestOptions.resizeMode = .fast + let finalImageRequestOptions = PHImageRequestOptions() + finalImageRequestOptions.deliveryMode = .highQualityFormat + finalImageRequestOptions.isNetworkAccessAllowed = true + finalImageRequestOptions.version = .current + finalImageRequestOptions.resizeMode = .none + //TODO cancellation, Error handeling + + PHImageManager.default().requestImage(for: asset, targetSize: CGSize(width: 360, height: 360), contentMode: .aspectFit, options: previewRequestOptions) { [weak self] (image, _) in + guard let image = image, let self = self else { return } + let ciImage = image.ciImage ?? CIImage(cgImage: image.cgImage!) + self.imageSource = .previewOnly(ciImage) + } + PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFit, options: finalImageRequestOptions) { [weak self] (image, _) in + guard let image = image, let self = self else { return } + let ciImage = image.ciImage ?? CIImage(cgImage: image.cgImage!) + self.imageSource = .editable(ciImage) + } + } + + public func setImageUpdateListener(_ listner: @escaping (ImageSourceType) -> Void) { + if imageSource != nil { + listner(self) + } + self.listner = listner + } +} + +#endif + +public struct StaticImageSource: ImageSourceType { + private let image: CIImage + public var imageSource: ImageSource? { + return .editable(image) + } + + public func setImageUpdateListener(_ listner: @escaping (ImageSourceType) -> Void) { + listner(self) + } + + + #if os(iOS) + + public init(source: UIImage) { + + let image = CIImage(image: source)! + let fixedOriantationImage = image.oriented(forExifOrientation: imageOrientationToTiffOrientation(source.imageOrientation)) + + self.init(source: fixedOriantationImage) + } + + #endif + + public init(source: CIImage) { + + precondition(source.extent.origin == .zero) + self.image = source + } + +} + +fileprivate 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 + default: + return 1 + } +} diff --git a/PixelEngine/ImageView/GLImageView.swift b/PixelEngine/ImageView/GLImageView.swift new file mode 100644 index 0000000..fe560ed --- /dev/null +++ b/PixelEngine/ImageView/GLImageView.swift @@ -0,0 +1,93 @@ +// +// 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 GLKit +import AVFoundation + +public class GLImageView : GLKView, HardwareImageViewType { + + // MARK: - Properties + + let coreImageContext: CIContext + + public var image: CIImage? { + didSet { + self.update() + } + } + + // MARK: - Initializers + + public override convenience init(frame: CGRect) { + let eaglContext = EAGLContext(api: EAGLRenderingAPI.openGLES2) + self.init(frame: frame, context: eaglContext!) + } + + public override init(frame: CGRect, context eaglContext: EAGLContext) { + coreImageContext = CIContext(eaglContext: eaglContext, options: [:]) + + super.init(frame: frame, context: eaglContext) + backgroundColor = UIColor.clear + drawableDepthFormat = .format24 + layer.contentsScale = UIScreen.main.scale + } + + public required init(coder aDecoder: NSCoder) { + fatalError("") + } + + // MARK: - Functions + + public override func layoutSubviews() { + super.layoutSubviews() + setNeedsDisplay() + } + + func update() { + + self.setNeedsDisplay() + } + + public override func draw(_ rect: CGRect) { + + super.draw(rect) + + glClearColor(0, 0, 0, 0) + glClear(GLbitfield(GL_COLOR_BUFFER_BIT)) + + guard let image = image, self.window != nil else { + return + } + + var _bounds = bounds + _bounds.size.width *= contentScaleFactor + _bounds.size.height *= contentScaleFactor + + let targetRect = Geometry.rectThatAspectFill( + aspectRatio: image.extent.size, + minimumRect: _bounds + ) + + coreImageContext.draw(image, in: targetRect, from: image.extent) + } +} diff --git a/PixelEngine/ImageView/HardwareImageView.swift b/PixelEngine/ImageView/HardwareImageView.swift new file mode 100644 index 0000000..eb52174 --- /dev/null +++ b/PixelEngine/ImageView/HardwareImageView.swift @@ -0,0 +1,27 @@ +// +// 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 CoreImage + +public protocol HardwareImageViewType : class { + var image: CIImage? { get set } +} diff --git a/PixelEngine/ImageView/MetalImageView.swift b/PixelEngine/ImageView/MetalImageView.swift new file mode 100644 index 0000000..82c9e9c --- /dev/null +++ b/PixelEngine/ImageView/MetalImageView.swift @@ -0,0 +1,122 @@ +// +// 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 +import CoreImage +import UIKit + +#if canImport(MetalKit) && !targetEnvironment(simulator) +import MetalKit + +open class MetalImageView : MTKView, HardwareImageViewType { + + public var image: CIImage? { + didSet { + renderImage() + } + } + + private let colorSpace = CGColorSpaceCreateDeviceRGB() + + private lazy var commandQueue: MTLCommandQueue = { [unowned self] in + return self.device!.makeCommandQueue()! + }() + + private lazy var ciContext: CIContext = { + [unowned self] in + return CIContext(mtlDevice: self.device!) + }() + + public override init( + frame frameRect: CGRect, + device: MTLDevice? + ) { + super.init( + frame: frameRect, + device: device ?? + MTLCreateSystemDefaultDevice() + ) + if super.device == nil { + fatalError("Device doesn't support Metal") + } + framebufferOnly = false + } + + public required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func renderImage() { + guard + let image = image, + let targetTexture = currentDrawable?.texture else + { + return + } + + let commandBuffer = commandQueue.makeCommandBuffer() + + let bounds = CGRect( + origin: .zero, + size: drawableSize + ) + + let targetRect = Geometry.rectThatAspectFill( + aspectRatio: image.extent.size, + minimumRect: bounds + ) + + let originX = targetRect.origin.x + let originY = targetRect.origin.y + let scaleX = targetRect.width / image.extent.width + let scaleY = targetRect.height / image.extent.height + let scale = min(scaleX, scaleY) + let scaledImage = image + .transformed(by: CGAffineTransform(scaleX: scale, y: scale)) + .transformed(by: CGAffineTransform(translationX: originX, y: originY)) + + ciContext.render( + scaledImage, + to: targetTexture, + commandBuffer: commandBuffer, + bounds: bounds, + colorSpace: colorSpace + ) + + commandBuffer?.present(currentDrawable!) + commandBuffer?.commit() + } +} + +private func makeCGAffineTransform(from: CGRect, to: CGRect) -> CGAffineTransform { + + return .init( + a: to.width / from.width, + b: 0, + c: 0, + d: to.height / from.height, + tx: to.midX - from.midX, + ty: to.midY - from.midY + ) +} + +#endif diff --git a/PixelEngine/Info.plist b/PixelEngine/Info.plist new file mode 100644 index 0000000..c0701c6 --- /dev/null +++ b/PixelEngine/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/PixelEngine/InternalUtils.swift b/PixelEngine/InternalUtils.swift new file mode 100644 index 0000000..5f2cae8 --- /dev/null +++ b/PixelEngine/InternalUtils.swift @@ -0,0 +1,49 @@ +// +// 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 + +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/PixelEngine/Kernel/MultiBandHSV.swift b/PixelEngine/Kernel/MultiBandHSV.swift new file mode 100644 index 0000000..83483dc --- /dev/null +++ b/PixelEngine/Kernel/MultiBandHSV.swift @@ -0,0 +1,184 @@ +// +// MultiBandHSV.swift +// PixelEngine +// +// Created by macOS on 7/7/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import Foundation +import CoreImage +import UIKit + +class MultiBandHSV: CIFilter +{ + let multiBandHSVKernel: CIColorKernel = + { + + let red = UIColor(red: 0.8980392156862745, green: 0, blue: 0, alpha: 1).hue() + let orange = UIColor(red: 0.9764705882352941, green: 0.45098039215686275, blue: 0.023529411764705882, alpha: 1).hue() + let yellow = UIColor(red: 1, green: 1, blue: 0.0784313725490196, alpha: 1).hue() + let green = UIColor(red: 0.08235294117647059, green: 0.6901960784313725, blue: 0.10196078431372549, alpha: 1).hue() + let aqua = UIColor(red: 0.07450980392156863, green: 0.9176470588235294, blue: 0.788235294117647, alpha: 1).hue() + let blue = UIColor(red: 0.011764705882352941, green: 0.2627450980392157, blue: 0.8745098039215686, alpha: 1).hue() + let purple = UIColor(red: 0.49411764705882355, green: 0.11764705882352941, blue: 0.611764705882353, alpha: 1).hue() + let magenta = UIColor(red: 0.7607843137254902, green: 0, blue: 0.47058823529411764, alpha: 1).hue() + + var shaderString = "" + + shaderString += "#define red \(red) \n" + shaderString += "#define orange \(orange) \n" + shaderString += "#define yellow \(yellow) \n" + shaderString += "#define green \(green) \n" + shaderString += "#define aqua \(aqua) \n" + shaderString += "#define blue \(blue) \n" + shaderString += "#define purple \(purple) \n" + shaderString += "#define magenta \(magenta) \n" + + shaderString += "vec3 rgb2hsv(vec3 c)" + shaderString += "{" + shaderString += " vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);" + shaderString += " vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));" + shaderString += " vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));" + + shaderString += " float d = q.x - min(q.w, q.y);" + shaderString += " float e = 1.0e-10;" + shaderString += " return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);" + shaderString += "}" + + shaderString += "vec3 hsv2rgb(vec3 c)" + shaderString += "{" + shaderString += " vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);" + shaderString += " vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);" + shaderString += " return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);" + shaderString += "}" + + shaderString += "vec3 smoothTreatment(vec3 hsv, float hueEdge0, float hueEdge1, vec3 shiftEdge0, vec3 shiftEdge1)" + shaderString += "{" + shaderString += " float smoothedHue = smoothstep(hueEdge0, hueEdge1, hsv.x);" + shaderString += " float hue = hsv.x + (shiftEdge0.x + ((shiftEdge1.x - shiftEdge0.x) * smoothedHue));" + shaderString += " float sat = hsv.y * (shiftEdge0.y + ((shiftEdge1.y - shiftEdge0.y) * smoothedHue));" + shaderString += " float lum = hsv.z * (shiftEdge0.z + ((shiftEdge1.z - shiftEdge0.z) * smoothedHue));" + shaderString += " return vec3(hue, sat, lum);" + shaderString += "}" + + shaderString += "kernel vec4 kernelFunc(__sample pixel," + shaderString += " vec3 redShift, vec3 orangeShift, vec3 yellowShift, vec3 greenShift," + shaderString += " vec3 aquaShift, vec3 blueShift, vec3 purpleShift, vec3 magentaShift)" + + shaderString += "{" + shaderString += " vec3 hsv = rgb2hsv(pixel.rgb); \n" + + shaderString += " if (hsv.x < orange){ hsv = smoothTreatment(hsv, 0.0, orange, redShift, orangeShift);} \n" + shaderString += " else if (hsv.x >= orange && hsv.x < yellow){ hsv = smoothTreatment(hsv, orange, yellow, orangeShift, yellowShift); } \n" + shaderString += " else if (hsv.x >= yellow && hsv.x < green){ hsv = smoothTreatment(hsv, yellow, green, yellowShift, greenShift); } \n" + shaderString += " else if (hsv.x >= green && hsv.x < aqua){ hsv = smoothTreatment(hsv, green, aqua, greenShift, aquaShift);} \n" + shaderString += " else if (hsv.x >= aqua && hsv.x < blue){ hsv = smoothTreatment(hsv, aqua, blue, aquaShift, blueShift);} \n" + shaderString += " else if (hsv.x >= blue && hsv.x < purple){ hsv = smoothTreatment(hsv, blue, purple, blueShift, purpleShift);} \n" + shaderString += " else if (hsv.x >= purple && hsv.x < magenta){ hsv = smoothTreatment(hsv, purple, magenta, purpleShift, magentaShift);} \n" + shaderString += " else { hsv = smoothTreatment(hsv, magenta, 1.0, magentaShift, redShift); }; \n" + + shaderString += "return vec4(hsv2rgb(hsv), 1.0);" + shaderString += "}" + + return CIColorKernel(source: shaderString)! + }() + + var inputImage: CIImage? + + var inputRedShift = CIVector(x: 0, y: 1, z: 1) + var inputOrangeShift = CIVector(x: 0, y: 1, z: 1) + var inputYellowShift = CIVector(x: 0, y: 1, z: 1) + var inputGreenShift = CIVector(x: 0, y: 1, z: 1) + var inputAquaShift = CIVector(x: 0, y: 1, z: 1) + var inputBlueShift = CIVector(x: 0, y: 1, z: 1) + var inputPurpleShift = CIVector(x: 0, y: 1, z: 1) + var inputMagentaShift = CIVector(x: 0, y: 1, z: 1) + + override var attributes: [String : Any] + { + return [ + kCIAttributeFilterDisplayName: "MultiBandHSV", + + "inputImage": [kCIAttributeIdentity: 0, + kCIAttributeClass: "CIImage", + kCIAttributeDisplayName: "Image", + kCIAttributeType: kCIAttributeTypeImage], + + "inputRedShift": [kCIAttributeIdentity: 0, + kCIAttributeClass: "CIVector", + kCIAttributeDisplayName: "Red Shift (HSL)", + kCIAttributeDescription: "Set the hue, saturation and lightness for this color band.", + kCIAttributeDefault: CIVector(x: 0, y: 1, z: 1), + kCIAttributeType: kCIAttributeTypePosition3], + + "inputOrangeShift": [kCIAttributeIdentity: 0, + kCIAttributeClass: "CIVector", + kCIAttributeDisplayName: "Orange Shift (HSL)", + kCIAttributeDescription: "Set the hue, saturation and lightness for this color band.", + kCIAttributeDefault: CIVector(x: 0, y: 1, z: 1), + kCIAttributeType: kCIAttributeTypePosition3], + + "inputYellowShift": [kCIAttributeIdentity: 0, + kCIAttributeClass: "CIVector", + kCIAttributeDisplayName: "Yellow Shift (HSL)", + kCIAttributeDescription: "Set the hue, saturation and lightness for this color band.", + kCIAttributeDefault: CIVector(x: 0, y: 1, z: 1), + kCIAttributeType: kCIAttributeTypePosition3], + + "inputGreenShift": [kCIAttributeIdentity: 0, + kCIAttributeClass: "CIVector", + kCIAttributeDisplayName: "Green Shift (HSL)", + kCIAttributeDescription: "Set the hue, saturation and lightness for this color band.", + kCIAttributeDefault: CIVector(x: 0, y: 1, z: 1), + kCIAttributeType: kCIAttributeTypePosition3], + + "inputAquaShift": [kCIAttributeIdentity: 0, + kCIAttributeClass: "CIVector", + kCIAttributeDisplayName: "Aqua Shift (HSL)", + kCIAttributeDescription: "Set the hue, saturation and lightness for this color band.", + kCIAttributeDefault: CIVector(x: 0, y: 1, z: 1), + kCIAttributeType: kCIAttributeTypePosition3], + + "inputBlueShift": [kCIAttributeIdentity: 0, + kCIAttributeClass: "CIVector", + kCIAttributeDisplayName: "Blue Shift (HSL)", + kCIAttributeDescription: "Set the hue, saturation and lightness for this color band.", + kCIAttributeDefault: CIVector(x: 0, y: 1, z: 1), + kCIAttributeType: kCIAttributeTypePosition3], + + "inputPurpleShift": [kCIAttributeIdentity: 0, + kCIAttributeClass: "CIVector", + kCIAttributeDisplayName: "Purple Shift (HSL)", + kCIAttributeDescription: "Set the hue, saturation and lightness for this color band.", + kCIAttributeDefault: CIVector(x: 0, y: 1, z: 1), + kCIAttributeType: kCIAttributeTypePosition3], + + "inputMagentaShift": [kCIAttributeIdentity: 0, + kCIAttributeClass: "CIVector", + kCIAttributeDisplayName: "Magenta Shift (HSL)", + kCIAttributeDescription: "Set the hue, saturation and lightness for this color band.", + kCIAttributeDefault: CIVector(x: 0, y: 1, z: 1), + kCIAttributeType: kCIAttributeTypePosition3], + ] + } + + override var outputImage: CIImage? + { + guard let inputImage = inputImage else + { + return nil + } + + return multiBandHSVKernel.apply(extent: inputImage.extent, + arguments: [inputImage, + inputRedShift, + inputOrangeShift, + inputYellowShift, + inputGreenShift, + inputAquaShift, + inputBlueShift, + inputPurpleShift, + inputMagentaShift]) + } +} diff --git a/PixelEngine/OvalBrush.swift b/PixelEngine/OvalBrush.swift new file mode 100644 index 0000000..ab099fd --- /dev/null +++ b/PixelEngine/OvalBrush.swift @@ -0,0 +1,57 @@ +// +// 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 +import CoreImage +import UIKit + +public struct OvalBrush : Equatable { + + public static func == (lhs: OvalBrush, rhs: OvalBrush) -> Bool { + guard lhs.color == rhs.color else { return false } + guard lhs.width == rhs.width else { return false } + guard lhs.alpha == rhs.alpha else { return false } + guard lhs.blendMode == rhs.blendMode else { return false } + return true + } + + // MARK: - Properties + + public let color: UIColor + public let width: CGFloat + public let alpha: CGFloat + public let blendMode: CGBlendMode + + // MARK: - Initializers + + public init( + color: UIColor, + width: CGFloat, + alpha: CGFloat = 1, + blendMode: CGBlendMode = .normal + ) { + + self.color = color + self.width = width + self.alpha = alpha + self.blendMode = blendMode + } +} diff --git a/PixelEngine/ParameterRange.swift b/PixelEngine/ParameterRange.swift new file mode 100644 index 0000000..bcf21cd --- /dev/null +++ b/PixelEngine/ParameterRange.swift @@ -0,0 +1,33 @@ +// +// 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 + +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/PixelEngine/PixelEngine.h b/PixelEngine/PixelEngine.h new file mode 100644 index 0000000..486ffef --- /dev/null +++ b/PixelEngine/PixelEngine.h @@ -0,0 +1,19 @@ +// +// PixelEngine.h +// PixelEngine +// +// Created by macOS on 7/3/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +#import + +//! Project version number for PixelEngine. +FOUNDATION_EXPORT double PixelEngineVersionNumber; + +//! Project version string for PixelEngine. +FOUNDATION_EXPORT const unsigned char PixelEngineVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/PixelEngine/PixelEngine.swift b/PixelEngine/PixelEngine.swift new file mode 100644 index 0000000..c7e8046 --- /dev/null +++ b/PixelEngine/PixelEngine.swift @@ -0,0 +1,21 @@ +// +// 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 diff --git a/PixelEngine/Resource/YUCIColorLookup.cikernel b/PixelEngine/Resource/YUCIColorLookup.cikernel new file mode 100644 index 0000000..8d56051 --- /dev/null +++ b/PixelEngine/Resource/YUCIColorLookup.cikernel @@ -0,0 +1,33 @@ +kernel vec4 filterKernel(sampler inputImage, sampler inputLUT, float intensity) { + vec4 textureColor = sample(inputImage,samplerCoord(inputImage)); + textureColor = clamp(textureColor, vec4(0.0), vec4(1.0)); + + float blueColor = textureColor.b * 63.0; + + vec2 quad1; + quad1.y = floor(floor(blueColor) / 8.0); + quad1.x = floor(blueColor) - (quad1.y * 8.0); + + vec2 quad2; + quad2.y = floor(ceil(blueColor) / 8.0); + quad2.x = ceil(blueColor) - (quad2.y * 8.0); + + vec2 texPos1; + texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r); + texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g); + + vec2 texPos2; + texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r); + texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g); + + texPos1.y = 1.0 - texPos1.y; + texPos2.y = 1.0 - texPos2.y; + + vec4 inputLUTExtent = samplerExtent(inputLUT); + + vec4 newColor1 = sample(inputLUT, samplerTransform(inputLUT, texPos1 * vec2(512.0) + inputLUTExtent.xy)); + vec4 newColor2 = sample(inputLUT, samplerTransform(inputLUT, texPos2 * vec2(512.0) + inputLUTExtent.xy)); + + vec4 newColor = mix(newColor1, newColor2, fract(blueColor)); + return mix(textureColor, vec4(newColor.rgb, textureColor.a), intensity); +} diff --git a/PixelEngine/Utils/UIColor+HEX.swift b/PixelEngine/Utils/UIColor+HEX.swift new file mode 100644 index 0000000..2bf27ea --- /dev/null +++ b/PixelEngine/Utils/UIColor+HEX.swift @@ -0,0 +1,52 @@ +// +// UIColor+HUE.swift +// PixelEngine +// +// Created by macOS on 7/3/20. +// Copyright © 2020 muukii. All rights reserved. +// + +import Foundation +import UIKit + +public extension UIColor { + + convenience init?(hex: String) { + let r, g, b, a: CGFloat + + if hex.hasPrefix("#") { + let start = hex.index(hex.startIndex, offsetBy: 1) + let hexColor = String(hex[start...]) + + if hexColor.count == 8 { + let scanner = Scanner(string: hexColor) + var hexNumber: UInt64 = 0 + + if scanner.scanHexInt64(&hexNumber) { + r = CGFloat((hexNumber & 0xff000000) >> 24) / 255 + g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255 + b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255 + a = CGFloat(hexNumber & 0x000000ff) / 255 + + self.init(red: r, green: g, blue: b, alpha: a) + return + } + } + } + + return nil + } + + func hue()-> CGFloat + { + var hue: CGFloat = 0 + var saturation: CGFloat = 0 + var brightness: CGFloat = 0 + var alpha: CGFloat = 0 + self.getHue(&hue, + saturation: &saturation, + brightness: &brightness, + alpha: &alpha) + return hue + } +} diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..8ee4337 --- /dev/null +++ b/Podfile @@ -0,0 +1,14 @@ +# Uncomment the next line to define a global platform for your project +platform :ios, '10.0' + +target 'PixelEngine' do + use_frameworks! + pod 'TransitionPatch' +end + +target 'colorful-room' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + # Pods for colorful-room + +end diff --git a/README.md b/README.md new file mode 100644 index 0000000..249758f --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ + + + +# Photo editor with SwiftUI and PixelEngine - muukii + +- CoreImage +- CoreGraphics +- My app with this source code in store: [AppStore](https://apps.apple.com/us/app/id1525852224) + +- [Pixel Engine](https://github.com/muukii/Pixel) + - as Muukii's repository. He use the old user interface - Storyboard. So I cloned his "Pixel Engine" and upgrade UI to swiftUI +- User interface: SwiftUI + + +## Features + +### Adjustment + +* [ ] Crop +* [ ] Straighten +* [ ] Perspective + +### Filter +* [x] ColorCube + * [x] Intensity + - Click again on lut to edit Intensity + + +#### Edits + +* [x] Brightness +* [x] Contrast +* [x] Saturation +* [x] Highlights +* [x] Shadows +* [x] Temperature +* [x] GaussianBlur +* [x] Vignette +* [x] Fade +* [x] Sharpen +* [x] Clarity +* [x] HLS + [About HLS](https://dzone.com/articles/creating-a-selective-hsl-adjustment-filter-in-core) \ No newline at end of file diff --git a/Resources/preview.png b/Resources/preview.png new file mode 100644 index 0000000..11e485b Binary files /dev/null and b/Resources/preview.png differ diff --git a/colorful-room.xcodeproj/project.pbxproj b/colorful-room.xcodeproj/project.pbxproj new file mode 100644 index 0000000..879da84 --- /dev/null +++ b/colorful-room.xcodeproj/project.pbxproj @@ -0,0 +1,1142 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXBuildFile section */ + 2208239124C92C4900E4D460 /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2208239024C92C4900E4D460 /* ExportView.swift */; }; + 226708E724C96AFD00A96CA7 /* RaiseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226708E624C96AFD00A96CA7 /* RaiseButton.swift */; }; + 2271885924B4738800610D8C /* MultiBandHSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2271885824B4738800610D8C /* MultiBandHSV.swift */; }; + 2271885B24B4777400610D8C /* FilterHLS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2271885A24B4777400610D8C /* FilterHLS.swift */; }; + 22787FC824C00792002CC5C3 /* ListTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22787FC724C00792002CC5C3 /* ListTitle.swift */; }; + 228A90B524AF27BB000CB78C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228A90B424AF27BB000CB78C /* AppDelegate.swift */; }; + 228A90B724AF27BB000CB78C /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228A90B624AF27BB000CB78C /* SceneDelegate.swift */; }; + 228A90BA24AF27BB000CB78C /* colorful_room.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 228A90B824AF27BB000CB78C /* colorful_room.xcdatamodeld */; }; + 228A90BC24AF27BB000CB78C /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228A90BB24AF27BB000CB78C /* ContentView.swift */; }; + 228A90BE24AF27BD000CB78C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 228A90BD24AF27BD000CB78C /* Assets.xcassets */; }; + 228A90C124AF27BD000CB78C /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 228A90C024AF27BD000CB78C /* Preview Assets.xcassets */; }; + 228A90C424AF27BD000CB78C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 228A90C224AF27BD000CB78C /* LaunchScreen.storyboard */; }; + 228DE32E24C352B100B748DF /* FilterWhiteBalance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228DE32D24C352B100B748DF /* FilterWhiteBalance.swift */; }; + 228E392224BF06A40038B988 /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228E392124BF06A40038B988 /* Collection.swift */; }; + 22B432C324B5682500BCC728 /* CoreImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22B432C224B5682500BCC728 /* CoreImage.framework */; }; + 22B432C524B5683100BCC728 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22B432C424B5683100BCC728 /* CoreGraphics.framework */; }; + 22B432C724B5683A00BCC728 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22B432C624B5683A00BCC728 /* AVFoundation.framework */; }; + 22B432C924B5683F00BCC728 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22B432C824B5683F00BCC728 /* Foundation.framework */; }; + 22B9F9D224C42C7C0036FBCE /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B9F9D124C42C7C0036FBCE /* ActivityIndicator.swift */; }; + 22B9F9D424C437E70036FBCE /* SupportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B9F9D324C437E70036FBCE /* SupportView.swift */; }; + 22C190EB24AF30A900C3502D /* PixelEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = 22C190E924AF30A900C3502D /* PixelEngine.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 22C190EE24AF30A900C3502D /* PixelEngine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22C190E724AF30A900C3502D /* PixelEngine.framework */; }; + 22C190EF24AF30A900C3502D /* PixelEngine.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 22C190E724AF30A900C3502D /* PixelEngine.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 22C1911D24AF30E900C3502D /* EngineLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C190F324AF30E800C3502D /* EngineLog.swift */; }; + 22C1911E24AF30E900C3502D /* GraphicDrawing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C190F424AF30E800C3502D /* GraphicDrawing.swift */; }; + 22C1911F24AF30E900C3502D /* BlurredMask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C190F524AF30E800C3502D /* BlurredMask.swift */; }; + 22C1912124AF30E900C3502D /* ImageSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C190F724AF30E800C3502D /* ImageSource.swift */; }; + 22C1912224AF30E900C3502D /* FilterVignette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C190F924AF30E800C3502D /* FilterVignette.swift */; }; + 22C1912324AF30E900C3502D /* FilterHighlightShadowTint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C190FA24AF30E800C3502D /* FilterHighlightShadowTint.swift */; }; + 22C1912424AF30E900C3502D /* FilterColorCube.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C190FB24AF30E800C3502D /* FilterColorCube.swift */; }; + 22C1912524AF30E900C3502D /* FilterShadows.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C190FC24AF30E800C3502D /* FilterShadows.swift */; }; + 22C1912624AF30E900C3502D /* FilterSaturation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C190FD24AF30E800C3502D /* FilterSaturation.swift */; }; + 22C1912724AF30E900C3502D /* FilterTemperature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C190FE24AF30E800C3502D /* FilterTemperature.swift */; }; + 22C1912824AF30E900C3502D /* FilterColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C190FF24AF30E800C3502D /* FilterColor.swift */; }; + 22C1912924AF30E900C3502D /* FilterExposure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1910024AF30E800C3502D /* FilterExposure.swift */; }; + 22C1912A24AF30E900C3502D /* FilterSharpen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1910124AF30E800C3502D /* FilterSharpen.swift */; }; + 22C1912B24AF30E900C3502D /* FilterHighlights.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1910224AF30E800C3502D /* FilterHighlights.swift */; }; + 22C1912C24AF30E900C3502D /* FilterContrast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1910324AF30E800C3502D /* FilterContrast.swift */; }; + 22C1912D24AF30E900C3502D /* FilterGaussianBlur.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1910424AF30E800C3502D /* FilterGaussianBlur.swift */; }; + 22C1912E24AF30E900C3502D /* FilterFade.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1910524AF30E800C3502D /* FilterFade.swift */; }; + 22C1912F24AF30E900C3502D /* FilterUnsharpMask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1910624AF30E800C3502D /* FilterUnsharpMask.swift */; }; + 22C1913024AF30E900C3502D /* Geometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1910724AF30E800C3502D /* Geometry.swift */; }; + 22C1913124AF30E900C3502D /* DrawnPathInRect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1910824AF30E800C3502D /* DrawnPathInRect.swift */; }; + 22C1913224AF30E900C3502D /* EditingStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1910924AF30E800C3502D /* EditingStack.swift */; }; + 22C1913324AF30E900C3502D /* ImageTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1910B24AF30E800C3502D /* ImageTool.swift */; }; + 22C1913424AF30E900C3502D /* ImageRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1910C24AF30E800C3502D /* ImageRenderer.swift */; }; + 22C1913624AF30E900C3502D /* ParameterRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1910E24AF30E900C3502D /* ParameterRange.swift */; }; + 22C1913724AF30E900C3502D /* YUCIColorLookup.cikernel in Resources */ = {isa = PBXBuildFile; fileRef = 22C1911024AF30E900C3502D /* YUCIColorLookup.cikernel */; }; + 22C1913824AF30E900C3502D /* MetalImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1911224AF30E900C3502D /* MetalImageView.swift */; }; + 22C1913924AF30E900C3502D /* HardwareImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1911324AF30E900C3502D /* HardwareImageView.swift */; }; + 22C1913A24AF30E900C3502D /* GLImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1911424AF30E900C3502D /* GLImageView.swift */; }; + 22C1913B24AF30E900C3502D /* DrawnPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1911524AF30E900C3502D /* DrawnPath.swift */; }; + 22C1913C24AF30E900C3502D /* ColorCube.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1911624AF30E900C3502D /* ColorCube.swift */; }; + 22C1913D24AF30E900C3502D /* PixelEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1911724AF30E900C3502D /* PixelEngine.swift */; }; + 22C1913E24AF30E900C3502D /* Filtering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1911824AF30E900C3502D /* Filtering.swift */; }; + 22C1913F24AF30E900C3502D /* InternalUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1911924AF30E900C3502D /* InternalUtils.swift */; }; + 22C1914024AF30E900C3502D /* OvalBrush.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1911A24AF30E900C3502D /* OvalBrush.swift */; }; + 22C1914124AF30E900C3502D /* UIColor+HEX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C1911C24AF30E900C3502D /* UIColor+HEX.swift */; }; + 22C191AE24AF396200C3502D /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C191A824AF396200C3502D /* ImagePicker.swift */; }; + 22C191AF24AF396200C3502D /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C191AA24AF396200C3502D /* Data.swift */; }; + 22C191B024AF396200C3502D /* ImageSaver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C191AB24AF396200C3502D /* ImageSaver.swift */; }; + 22CEC04824BEE7D3009D4134 /* AppTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22CEC04724BEE7D3009D4134 /* AppTheme.swift */; }; + 22EF90BD24B569E0003AE15E /* PhotoEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22EF90BC24B569E0003AE15E /* PhotoEditView.swift */; }; + 552CDE60B9B17AAA180F5BDB /* Pods_PixelEngine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99B7617A0187A9C3BD12A9C9 /* Pods_PixelEngine.framework */; }; + 5972CD0F24DD601B005C0B0A /* PhotoEditorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCEB24DD601B005C0B0A /* PhotoEditorController.swift */; }; + 5972CD1024DD601B005C0B0A /* PhotoEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCEC24DD601B005C0B0A /* PhotoEditorView.swift */; }; + 5972CD1124DD601B005C0B0A /* ImagePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCEE24DD601B005C0B0A /* ImagePreviewView.swift */; }; + 5972CD1224DD601B005C0B0A /* CustomSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCF124DD601B005C0B0A /* CustomSlider.swift */; }; + 5972CD1324DD601B005C0B0A /* LUTButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCF224DD601B005C0B0A /* LUTButton.swift */; }; + 5972CD1424DD601B005C0B0A /* IconButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCF324DD601B005C0B0A /* IconButton.swift */; }; + 5972CD1524DD601B005C0B0A /* CollectionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCF424DD601B005C0B0A /* CollectionButton.swift */; }; + 5972CD1624DD601B005C0B0A /* ButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCF524DD601B005C0B0A /* ButtonView.swift */; }; + 5972CD1724DD601B005C0B0A /* LutMenuUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCF624DD601B005C0B0A /* LutMenuUI.swift */; }; + 5972CD1824DD601B005C0B0A /* ExposureControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCF824DD601B005C0B0A /* ExposureControl.swift */; }; + 5972CD1924DD601B005C0B0A /* SharpenControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCF924DD601B005C0B0A /* SharpenControl.swift */; }; + 5972CD1A24DD601B005C0B0A /* ToneControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCFB24DD601B005C0B0A /* ToneControl.swift */; }; + 5972CD1B24DD601B005C0B0A /* WhiteBalanceControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCFC24DD601B005C0B0A /* WhiteBalanceControl.swift */; }; + 5972CD1C24DD601B005C0B0A /* ColorControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCFD24DD601B005C0B0A /* ColorControl.swift */; }; + 5972CD1D24DD601B005C0B0A /* SaturationControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCFE24DD601B005C0B0A /* SaturationControl.swift */; }; + 5972CD1E24DD601B005C0B0A /* ColorCubeControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CCFF24DD601B005C0B0A /* ColorCubeControl.swift */; }; + 5972CD1F24DD601B005C0B0A /* HighlightsControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CD0024DD601B005C0B0A /* HighlightsControl.swift */; }; + 5972CD2024DD601B005C0B0A /* VignetteControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CD0124DD601B005C0B0A /* VignetteControl.swift */; }; + 5972CD2124DD601B005C0B0A /* ColorSelectButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CD0324DD601B005C0B0A /* ColorSelectButton.swift */; }; + 5972CD2224DD601B005C0B0A /* HLSControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CD0424DD601B005C0B0A /* HLSControl.swift */; }; + 5972CD2324DD601B005C0B0A /* GaussianBlurControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CD0524DD601B005C0B0A /* GaussianBlurControl.swift */; }; + 5972CD2424DD601B005C0B0A /* UnsharpMaskControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CD0624DD601B005C0B0A /* UnsharpMaskControl.swift */; }; + 5972CD2524DD601B005C0B0A /* TemperatureControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CD0724DD601B005C0B0A /* TemperatureControl.swift */; }; + 5972CD2624DD601B005C0B0A /* ContrastControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CD0824DD601B005C0B0A /* ContrastControl.swift */; }; + 5972CD2724DD601B005C0B0A /* ShadowsControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CD0924DD601B005C0B0A /* ShadowsControl.swift */; }; + 5972CD2824DD601B005C0B0A /* FadeControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CD0A24DD601B005C0B0A /* FadeControl.swift */; }; + 5972CD2924DD601B005C0B0A /* FilterMenuUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CD0B24DD601B005C0B0A /* FilterMenuUI.swift */; }; + 5972CD2A24DD601B005C0B0A /* EditMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CD0C24DD601B005C0B0A /* EditMenuView.swift */; }; + 5972CD2B24DD601B005C0B0A /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5972CD0E24DD601B005C0B0A /* Utility.swift */; }; + 5998814D24E51EF1001BE5CE /* EditMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5998814C24E51EF1001BE5CE /* EditMenu.swift */; }; + 59D930F824D7E9D500CDC68B /* RoadMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59D930F724D7E9D500CDC68B /* RoadMapView.swift */; }; + 9D6FCBC06B70895962238043 /* Pods_colorful_room.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 35168CD2399C980DC98CC76A /* Pods_colorful_room.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 22C190EC24AF30A900C3502D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 228A90A924AF27BB000CB78C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 22C190E624AF30A900C3502D; + remoteInfo = PixelEngine; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 22C190E124AF302100C3502D /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 22C190EF24AF30A900C3502D /* PixelEngine.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0FEBBECD582805E7B2040D91 /* Pods-PhotoEditor.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PhotoEditor.debug.xcconfig"; path = "Target Support Files/Pods-PhotoEditor/Pods-PhotoEditor.debug.xcconfig"; sourceTree = ""; }; + 1AE86049E48094D887353101 /* Pods-colorful-room.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-colorful-room.release.xcconfig"; path = "Target Support Files/Pods-colorful-room/Pods-colorful-room.release.xcconfig"; sourceTree = ""; }; + 2208239024C92C4900E4D460 /* ExportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportView.swift; sourceTree = ""; }; + 226708E624C96AFD00A96CA7 /* RaiseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RaiseButton.swift; sourceTree = ""; }; + 2271885824B4738800610D8C /* MultiBandHSV.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiBandHSV.swift; sourceTree = ""; }; + 2271885A24B4777400610D8C /* FilterHLS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterHLS.swift; sourceTree = ""; }; + 22787FC724C00792002CC5C3 /* ListTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTitle.swift; sourceTree = ""; }; + 228A90B124AF27BB000CB78C /* LUTs.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LUTs.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 228A90B424AF27BB000CB78C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 228A90B624AF27BB000CB78C /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 228A90B924AF27BB000CB78C /* colorful_room.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = colorful_room.xcdatamodel; sourceTree = ""; }; + 228A90BB24AF27BB000CB78C /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 228A90BD24AF27BD000CB78C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 228A90C024AF27BD000CB78C /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 228A90C324AF27BD000CB78C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 228A90C524AF27BD000CB78C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 228DE32D24C352B100B748DF /* FilterWhiteBalance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterWhiteBalance.swift; sourceTree = ""; }; + 228E392124BF06A40038B988 /* Collection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = ""; }; + 22B432C224B5682500BCC728 /* CoreImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreImage.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/CoreImage.framework; sourceTree = DEVELOPER_DIR; }; + 22B432C424B5683100BCC728 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; + 22B432C624B5683A00BCC728 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; }; + 22B432C824B5683F00BCC728 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 22B9F9D124C42C7C0036FBCE /* ActivityIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = ""; }; + 22B9F9D324C437E70036FBCE /* SupportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportView.swift; sourceTree = ""; }; + 22C190E724AF30A900C3502D /* PixelEngine.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PixelEngine.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 22C190E924AF30A900C3502D /* PixelEngine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PixelEngine.h; sourceTree = ""; }; + 22C190EA24AF30A900C3502D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 22C190F324AF30E800C3502D /* EngineLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EngineLog.swift; sourceTree = ""; }; + 22C190F424AF30E800C3502D /* GraphicDrawing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphicDrawing.swift; sourceTree = ""; }; + 22C190F524AF30E800C3502D /* BlurredMask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurredMask.swift; sourceTree = ""; }; + 22C190F724AF30E800C3502D /* ImageSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageSource.swift; sourceTree = ""; }; + 22C190F924AF30E800C3502D /* FilterVignette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterVignette.swift; sourceTree = ""; }; + 22C190FA24AF30E800C3502D /* FilterHighlightShadowTint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterHighlightShadowTint.swift; sourceTree = ""; }; + 22C190FB24AF30E800C3502D /* FilterColorCube.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterColorCube.swift; sourceTree = ""; }; + 22C190FC24AF30E800C3502D /* FilterShadows.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterShadows.swift; sourceTree = ""; }; + 22C190FD24AF30E800C3502D /* FilterSaturation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterSaturation.swift; sourceTree = ""; }; + 22C190FE24AF30E800C3502D /* FilterTemperature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterTemperature.swift; sourceTree = ""; }; + 22C190FF24AF30E800C3502D /* FilterColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterColor.swift; sourceTree = ""; }; + 22C1910024AF30E800C3502D /* FilterExposure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterExposure.swift; sourceTree = ""; }; + 22C1910124AF30E800C3502D /* FilterSharpen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterSharpen.swift; sourceTree = ""; }; + 22C1910224AF30E800C3502D /* FilterHighlights.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterHighlights.swift; sourceTree = ""; }; + 22C1910324AF30E800C3502D /* FilterContrast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterContrast.swift; sourceTree = ""; }; + 22C1910424AF30E800C3502D /* FilterGaussianBlur.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterGaussianBlur.swift; sourceTree = ""; }; + 22C1910524AF30E800C3502D /* FilterFade.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterFade.swift; sourceTree = ""; }; + 22C1910624AF30E800C3502D /* FilterUnsharpMask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterUnsharpMask.swift; sourceTree = ""; }; + 22C1910724AF30E800C3502D /* Geometry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Geometry.swift; sourceTree = ""; }; + 22C1910824AF30E800C3502D /* DrawnPathInRect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawnPathInRect.swift; sourceTree = ""; }; + 22C1910924AF30E800C3502D /* EditingStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditingStack.swift; sourceTree = ""; }; + 22C1910B24AF30E800C3502D /* ImageTool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageTool.swift; sourceTree = ""; }; + 22C1910C24AF30E800C3502D /* ImageRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRenderer.swift; sourceTree = ""; }; + 22C1910E24AF30E900C3502D /* ParameterRange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParameterRange.swift; sourceTree = ""; }; + 22C1911024AF30E900C3502D /* YUCIColorLookup.cikernel */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = YUCIColorLookup.cikernel; sourceTree = ""; }; + 22C1911224AF30E900C3502D /* MetalImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalImageView.swift; sourceTree = ""; }; + 22C1911324AF30E900C3502D /* HardwareImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HardwareImageView.swift; sourceTree = ""; }; + 22C1911424AF30E900C3502D /* GLImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GLImageView.swift; sourceTree = ""; }; + 22C1911524AF30E900C3502D /* DrawnPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawnPath.swift; sourceTree = ""; }; + 22C1911624AF30E900C3502D /* ColorCube.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorCube.swift; sourceTree = ""; }; + 22C1911724AF30E900C3502D /* PixelEngine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PixelEngine.swift; sourceTree = ""; }; + 22C1911824AF30E900C3502D /* Filtering.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Filtering.swift; sourceTree = ""; }; + 22C1911924AF30E900C3502D /* InternalUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalUtils.swift; sourceTree = ""; }; + 22C1911A24AF30E900C3502D /* OvalBrush.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OvalBrush.swift; sourceTree = ""; }; + 22C1911C24AF30E900C3502D /* UIColor+HEX.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+HEX.swift"; sourceTree = ""; }; + 22C191A824AF396200C3502D /* ImagePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = ""; }; + 22C191AA24AF396200C3502D /* Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; }; + 22C191AB24AF396200C3502D /* ImageSaver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageSaver.swift; sourceTree = ""; }; + 22CEC04724BEE7D3009D4134 /* AppTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTheme.swift; sourceTree = ""; }; + 22EF90BC24B569E0003AE15E /* PhotoEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoEditView.swift; sourceTree = ""; }; + 35168CD2399C980DC98CC76A /* Pods_colorful_room.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_colorful_room.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 39B28A770BF277CAD04B1B5C /* Pods-PixelEngine.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PixelEngine.debug.xcconfig"; path = "Target Support Files/Pods-PixelEngine/Pods-PixelEngine.debug.xcconfig"; sourceTree = ""; }; + 4682A39EF92B8DE5335673D3 /* Pods-colorful-room.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-colorful-room.debug.xcconfig"; path = "Target Support Files/Pods-colorful-room/Pods-colorful-room.debug.xcconfig"; sourceTree = ""; }; + 4E1DBBBABAD3B2DC7ED1E74A /* Pods-PhotoEditor.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PhotoEditor.release.xcconfig"; path = "Target Support Files/Pods-PhotoEditor/Pods-PhotoEditor.release.xcconfig"; sourceTree = ""; }; + 56D37364123E5AC23935C76E /* Pods-PixelEngine.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PixelEngine.release.xcconfig"; path = "Target Support Files/Pods-PixelEngine/Pods-PixelEngine.release.xcconfig"; sourceTree = ""; }; + 5972CCEB24DD601B005C0B0A /* PhotoEditorController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoEditorController.swift; sourceTree = ""; }; + 5972CCEC24DD601B005C0B0A /* PhotoEditorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoEditorView.swift; sourceTree = ""; }; + 5972CCEE24DD601B005C0B0A /* ImagePreviewView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePreviewView.swift; sourceTree = ""; }; + 5972CCF124DD601B005C0B0A /* CustomSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomSlider.swift; sourceTree = ""; }; + 5972CCF224DD601B005C0B0A /* LUTButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LUTButton.swift; sourceTree = ""; }; + 5972CCF324DD601B005C0B0A /* IconButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IconButton.swift; sourceTree = ""; }; + 5972CCF424DD601B005C0B0A /* CollectionButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionButton.swift; sourceTree = ""; }; + 5972CCF524DD601B005C0B0A /* ButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonView.swift; sourceTree = ""; }; + 5972CCF624DD601B005C0B0A /* LutMenuUI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LutMenuUI.swift; sourceTree = ""; }; + 5972CCF824DD601B005C0B0A /* ExposureControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExposureControl.swift; sourceTree = ""; }; + 5972CCF924DD601B005C0B0A /* SharpenControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharpenControl.swift; sourceTree = ""; }; + 5972CCFB24DD601B005C0B0A /* ToneControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToneControl.swift; sourceTree = ""; }; + 5972CCFC24DD601B005C0B0A /* WhiteBalanceControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhiteBalanceControl.swift; sourceTree = ""; }; + 5972CCFD24DD601B005C0B0A /* ColorControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorControl.swift; sourceTree = ""; }; + 5972CCFE24DD601B005C0B0A /* SaturationControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaturationControl.swift; sourceTree = ""; }; + 5972CCFF24DD601B005C0B0A /* ColorCubeControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorCubeControl.swift; sourceTree = ""; }; + 5972CD0024DD601B005C0B0A /* HighlightsControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightsControl.swift; sourceTree = ""; }; + 5972CD0124DD601B005C0B0A /* VignetteControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VignetteControl.swift; sourceTree = ""; }; + 5972CD0324DD601B005C0B0A /* ColorSelectButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorSelectButton.swift; sourceTree = ""; }; + 5972CD0424DD601B005C0B0A /* HLSControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HLSControl.swift; sourceTree = ""; }; + 5972CD0524DD601B005C0B0A /* GaussianBlurControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GaussianBlurControl.swift; sourceTree = ""; }; + 5972CD0624DD601B005C0B0A /* UnsharpMaskControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnsharpMaskControl.swift; sourceTree = ""; }; + 5972CD0724DD601B005C0B0A /* TemperatureControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemperatureControl.swift; sourceTree = ""; }; + 5972CD0824DD601B005C0B0A /* ContrastControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContrastControl.swift; sourceTree = ""; }; + 5972CD0924DD601B005C0B0A /* ShadowsControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowsControl.swift; sourceTree = ""; }; + 5972CD0A24DD601B005C0B0A /* FadeControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FadeControl.swift; sourceTree = ""; }; + 5972CD0B24DD601B005C0B0A /* FilterMenuUI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterMenuUI.swift; sourceTree = ""; }; + 5972CD0C24DD601B005C0B0A /* EditMenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditMenuView.swift; sourceTree = ""; }; + 5972CD0E24DD601B005C0B0A /* Utility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; + 5998814C24E51EF1001BE5CE /* EditMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMenu.swift; sourceTree = ""; }; + 59D930F724D7E9D500CDC68B /* RoadMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RoadMapView.swift; path = view/RoadMapView.swift; sourceTree = SOURCE_ROOT; }; + 6E7228D9C1378FFF65CD251D /* Pods-PixelEditor.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PixelEditor.debug.xcconfig"; path = "Target Support Files/Pods-PixelEditor/Pods-PixelEditor.debug.xcconfig"; sourceTree = ""; }; + 73402399CD5279071905E1F0 /* Pods-PixelEditor.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PixelEditor.release.xcconfig"; path = "Target Support Files/Pods-PixelEditor/Pods-PixelEditor.release.xcconfig"; sourceTree = ""; }; + 99B7617A0187A9C3BD12A9C9 /* Pods_PixelEngine.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PixelEngine.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 228A90AE24AF27BB000CB78C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 22C190EE24AF30A900C3502D /* PixelEngine.framework in Frameworks */, + 9D6FCBC06B70895962238043 /* Pods_colorful_room.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 22C190E424AF30A900C3502D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 22B432C524B5683100BCC728 /* CoreGraphics.framework in Frameworks */, + 22B432C324B5682500BCC728 /* CoreImage.framework in Frameworks */, + 22B432C724B5683A00BCC728 /* AVFoundation.framework in Frameworks */, + 22B432C924B5683F00BCC728 /* Foundation.framework in Frameworks */, + 552CDE60B9B17AAA180F5BDB /* Pods_PixelEngine.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 16AA0BB32420E9D6BAFC866B /* Pods */ = { + isa = PBXGroup; + children = ( + 4682A39EF92B8DE5335673D3 /* Pods-colorful-room.debug.xcconfig */, + 1AE86049E48094D887353101 /* Pods-colorful-room.release.xcconfig */, + 6E7228D9C1378FFF65CD251D /* Pods-PixelEditor.debug.xcconfig */, + 73402399CD5279071905E1F0 /* Pods-PixelEditor.release.xcconfig */, + 0FEBBECD582805E7B2040D91 /* Pods-PhotoEditor.debug.xcconfig */, + 4E1DBBBABAD3B2DC7ED1E74A /* Pods-PhotoEditor.release.xcconfig */, + 39B28A770BF277CAD04B1B5C /* Pods-PixelEngine.debug.xcconfig */, + 56D37364123E5AC23935C76E /* Pods-PixelEngine.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 2271885724B4737200610D8C /* Kernel */ = { + isa = PBXGroup; + children = ( + 2271885824B4738800610D8C /* MultiBandHSV.swift */, + ); + path = Kernel; + sourceTree = ""; + }; + 22787FC624C00764002CC5C3 /* UI */ = { + isa = PBXGroup; + children = ( + 22787FC724C00792002CC5C3 /* ListTitle.swift */, + 22B9F9D124C42C7C0036FBCE /* ActivityIndicator.swift */, + 226708E624C96AFD00A96CA7 /* RaiseButton.swift */, + ); + path = UI; + sourceTree = ""; + }; + 228A90A824AF27BB000CB78C = { + isa = PBXGroup; + children = ( + 228A90B324AF27BB000CB78C /* colorful-room */, + 22C190E824AF30A900C3502D /* PixelEngine */, + 228A90B224AF27BB000CB78C /* Products */, + 16AA0BB32420E9D6BAFC866B /* Pods */, + DA3FF9FFA41A0732EABECA0E /* Frameworks */, + ); + sourceTree = ""; + }; + 228A90B224AF27BB000CB78C /* Products */ = { + isa = PBXGroup; + children = ( + 228A90B124AF27BB000CB78C /* LUTs.app */, + 22C190E724AF30A900C3502D /* PixelEngine.framework */, + ); + name = Products; + sourceTree = ""; + }; + 228A90B324AF27BB000CB78C /* colorful-room */ = { + isa = PBXGroup; + children = ( + 59EBB39D24DD5F2C00A4F9FD /* PhotoEditor */, + 22787FC624C00764002CC5C3 /* UI */, + 22CEC04624BEE7BF009D4134 /* Utility */, + 22C191A924AF396200C3502D /* Model */, + 22C191A624AF396200C3502D /* View */, + 228A90B424AF27BB000CB78C /* AppDelegate.swift */, + 228A90B624AF27BB000CB78C /* SceneDelegate.swift */, + 228A90BB24AF27BB000CB78C /* ContentView.swift */, + 228A90BD24AF27BD000CB78C /* Assets.xcassets */, + 228A90C224AF27BD000CB78C /* LaunchScreen.storyboard */, + 228A90C524AF27BD000CB78C /* Info.plist */, + 228A90B824AF27BB000CB78C /* colorful_room.xcdatamodeld */, + 228A90BF24AF27BD000CB78C /* Preview Content */, + ); + path = "colorful-room"; + sourceTree = ""; + }; + 228A90BF24AF27BD000CB78C /* Preview Content */ = { + isa = PBXGroup; + children = ( + 228A90C024AF27BD000CB78C /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 22C190E824AF30A900C3502D /* PixelEngine */ = { + isa = PBXGroup; + children = ( + 2271885724B4737200610D8C /* Kernel */, + 22C190F524AF30E800C3502D /* BlurredMask.swift */, + 22C1911624AF30E900C3502D /* ColorCube.swift */, + 22C1911524AF30E900C3502D /* DrawnPath.swift */, + 22C1910824AF30E800C3502D /* DrawnPathInRect.swift */, + 22C1910924AF30E800C3502D /* EditingStack.swift */, + 22C1910A24AF30E800C3502D /* Engine */, + 22C190F324AF30E800C3502D /* EngineLog.swift */, + 22C190F824AF30E800C3502D /* Filter */, + 22C1911824AF30E900C3502D /* Filtering.swift */, + 22C1910724AF30E800C3502D /* Geometry.swift */, + 22C190F424AF30E800C3502D /* GraphicDrawing.swift */, + 22C190F724AF30E800C3502D /* ImageSource.swift */, + 22C1911124AF30E900C3502D /* ImageView */, + 22C1911924AF30E900C3502D /* InternalUtils.swift */, + 22C1911A24AF30E900C3502D /* OvalBrush.swift */, + 22C1910E24AF30E900C3502D /* ParameterRange.swift */, + 22C1911724AF30E900C3502D /* PixelEngine.swift */, + 22C1910F24AF30E900C3502D /* Resource */, + 22C1911B24AF30E900C3502D /* Utils */, + 22C190E924AF30A900C3502D /* PixelEngine.h */, + 22C190EA24AF30A900C3502D /* Info.plist */, + ); + path = PixelEngine; + sourceTree = ""; + }; + 22C190F824AF30E800C3502D /* Filter */ = { + isa = PBXGroup; + children = ( + 22C190F924AF30E800C3502D /* FilterVignette.swift */, + 22C190FA24AF30E800C3502D /* FilterHighlightShadowTint.swift */, + 22C190FB24AF30E800C3502D /* FilterColorCube.swift */, + 22C190FC24AF30E800C3502D /* FilterShadows.swift */, + 22C190FD24AF30E800C3502D /* FilterSaturation.swift */, + 22C190FE24AF30E800C3502D /* FilterTemperature.swift */, + 22C190FF24AF30E800C3502D /* FilterColor.swift */, + 22C1910024AF30E800C3502D /* FilterExposure.swift */, + 22C1910124AF30E800C3502D /* FilterSharpen.swift */, + 22C1910224AF30E800C3502D /* FilterHighlights.swift */, + 22C1910324AF30E800C3502D /* FilterContrast.swift */, + 22C1910424AF30E800C3502D /* FilterGaussianBlur.swift */, + 22C1910524AF30E800C3502D /* FilterFade.swift */, + 22C1910624AF30E800C3502D /* FilterUnsharpMask.swift */, + 2271885A24B4777400610D8C /* FilterHLS.swift */, + 228DE32D24C352B100B748DF /* FilterWhiteBalance.swift */, + ); + path = Filter; + sourceTree = ""; + }; + 22C1910A24AF30E800C3502D /* Engine */ = { + isa = PBXGroup; + children = ( + 22C1910B24AF30E800C3502D /* ImageTool.swift */, + 22C1910C24AF30E800C3502D /* ImageRenderer.swift */, + ); + path = Engine; + sourceTree = ""; + }; + 22C1910F24AF30E900C3502D /* Resource */ = { + isa = PBXGroup; + children = ( + 22C1911024AF30E900C3502D /* YUCIColorLookup.cikernel */, + ); + path = Resource; + sourceTree = ""; + }; + 22C1911124AF30E900C3502D /* ImageView */ = { + isa = PBXGroup; + children = ( + 22C1911224AF30E900C3502D /* MetalImageView.swift */, + 22C1911324AF30E900C3502D /* HardwareImageView.swift */, + 22C1911424AF30E900C3502D /* GLImageView.swift */, + ); + path = ImageView; + sourceTree = ""; + }; + 22C1911B24AF30E900C3502D /* Utils */ = { + isa = PBXGroup; + children = ( + 22C1911C24AF30E900C3502D /* UIColor+HEX.swift */, + ); + path = Utils; + sourceTree = ""; + }; + 22C191A624AF396200C3502D /* View */ = { + isa = PBXGroup; + children = ( + 22C191A824AF396200C3502D /* ImagePicker.swift */, + 22EF90BC24B569E0003AE15E /* PhotoEditView.swift */, + 22B9F9D324C437E70036FBCE /* SupportView.swift */, + 2208239024C92C4900E4D460 /* ExportView.swift */, + 59D930F724D7E9D500CDC68B /* RoadMapView.swift */, + ); + path = View; + sourceTree = SOURCE_ROOT; + }; + 22C191A924AF396200C3502D /* Model */ = { + isa = PBXGroup; + children = ( + 22C191AA24AF396200C3502D /* Data.swift */, + 22C191AB24AF396200C3502D /* ImageSaver.swift */, + 228E392124BF06A40038B988 /* Collection.swift */, + ); + path = Model; + sourceTree = SOURCE_ROOT; + }; + 22CEC04624BEE7BF009D4134 /* Utility */ = { + isa = PBXGroup; + children = ( + 22CEC04724BEE7D3009D4134 /* AppTheme.swift */, + ); + path = Utility; + sourceTree = ""; + }; + 5972CCED24DD601B005C0B0A /* image */ = { + isa = PBXGroup; + children = ( + 5972CCEE24DD601B005C0B0A /* ImagePreviewView.swift */, + ); + path = image; + sourceTree = ""; + }; + 5972CCEF24DD601B005C0B0A /* Components */ = { + isa = PBXGroup; + children = ( + 5972CCF024DD601B005C0B0A /* UI */, + 5972CCF624DD601B005C0B0A /* LutMenuUI.swift */, + 5972CCF724DD601B005C0B0A /* Controls */, + 5972CD0B24DD601B005C0B0A /* FilterMenuUI.swift */, + 5972CD0C24DD601B005C0B0A /* EditMenuView.swift */, + ); + path = Components; + sourceTree = ""; + }; + 5972CCF024DD601B005C0B0A /* UI */ = { + isa = PBXGroup; + children = ( + 5972CCF124DD601B005C0B0A /* CustomSlider.swift */, + 5972CCF224DD601B005C0B0A /* LUTButton.swift */, + 5972CCF324DD601B005C0B0A /* IconButton.swift */, + 5972CCF424DD601B005C0B0A /* CollectionButton.swift */, + 5972CCF524DD601B005C0B0A /* ButtonView.swift */, + ); + path = UI; + sourceTree = ""; + }; + 5972CCF724DD601B005C0B0A /* Controls */ = { + isa = PBXGroup; + children = ( + 5972CCF824DD601B005C0B0A /* ExposureControl.swift */, + 5972CCF924DD601B005C0B0A /* SharpenControl.swift */, + 5972CCFA24DD601B005C0B0A /* Group */, + 5972CCFE24DD601B005C0B0A /* SaturationControl.swift */, + 5972CCFF24DD601B005C0B0A /* ColorCubeControl.swift */, + 5972CD0024DD601B005C0B0A /* HighlightsControl.swift */, + 5972CD0124DD601B005C0B0A /* VignetteControl.swift */, + 5972CD0224DD601B005C0B0A /* HLS */, + 5972CD0524DD601B005C0B0A /* GaussianBlurControl.swift */, + 5972CD0624DD601B005C0B0A /* UnsharpMaskControl.swift */, + 5972CD0724DD601B005C0B0A /* TemperatureControl.swift */, + 5972CD0824DD601B005C0B0A /* ContrastControl.swift */, + 5972CD0924DD601B005C0B0A /* ShadowsControl.swift */, + 5972CD0A24DD601B005C0B0A /* FadeControl.swift */, + ); + path = Controls; + sourceTree = ""; + }; + 5972CCFA24DD601B005C0B0A /* Group */ = { + isa = PBXGroup; + children = ( + 5972CCFB24DD601B005C0B0A /* ToneControl.swift */, + 5972CCFC24DD601B005C0B0A /* WhiteBalanceControl.swift */, + 5972CCFD24DD601B005C0B0A /* ColorControl.swift */, + ); + path = Group; + sourceTree = ""; + }; + 5972CD0224DD601B005C0B0A /* HLS */ = { + isa = PBXGroup; + children = ( + 5972CD0324DD601B005C0B0A /* ColorSelectButton.swift */, + 5972CD0424DD601B005C0B0A /* HLSControl.swift */, + ); + path = HLS; + sourceTree = ""; + }; + 5972CD0D24DD601B005C0B0A /* Utility */ = { + isa = PBXGroup; + children = ( + 5972CD0E24DD601B005C0B0A /* Utility.swift */, + ); + path = Utility; + sourceTree = ""; + }; + 59EBB39D24DD5F2C00A4F9FD /* PhotoEditor */ = { + isa = PBXGroup; + children = ( + 5972CD0D24DD601B005C0B0A /* Utility */, + 5972CCEF24DD601B005C0B0A /* Components */, + 5972CCED24DD601B005C0B0A /* image */, + 5972CCEB24DD601B005C0B0A /* PhotoEditorController.swift */, + 5972CCEC24DD601B005C0B0A /* PhotoEditorView.swift */, + 5998814C24E51EF1001BE5CE /* EditMenu.swift */, + ); + path = PhotoEditor; + sourceTree = ""; + }; + DA3FF9FFA41A0732EABECA0E /* Frameworks */ = { + isa = PBXGroup; + children = ( + 22B432C824B5683F00BCC728 /* Foundation.framework */, + 22B432C624B5683A00BCC728 /* AVFoundation.framework */, + 22B432C424B5683100BCC728 /* CoreGraphics.framework */, + 22B432C224B5682500BCC728 /* CoreImage.framework */, + 35168CD2399C980DC98CC76A /* Pods_colorful_room.framework */, + 99B7617A0187A9C3BD12A9C9 /* Pods_PixelEngine.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 22C190E224AF30A900C3502D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 22C190EB24AF30A900C3502D /* PixelEngine.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 228A90B024AF27BB000CB78C /* colorful-room */ = { + isa = PBXNativeTarget; + buildConfigurationList = 228A90C824AF27BD000CB78C /* Build configuration list for PBXNativeTarget "colorful-room" */; + buildPhases = ( + E27EFE4AB6B371D936E623B6 /* [CP] Check Pods Manifest.lock */, + 228A90AD24AF27BB000CB78C /* Sources */, + 228A90AE24AF27BB000CB78C /* Frameworks */, + 228A90AF24AF27BB000CB78C /* Resources */, + E7FF05A3B7E3DA75A3AF197B /* [CP] Embed Pods Frameworks */, + 22C190E124AF302100C3502D /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 22C190ED24AF30A900C3502D /* PBXTargetDependency */, + ); + name = "colorful-room"; + productName = "colorful-room"; + productReference = 228A90B124AF27BB000CB78C /* LUTs.app */; + productType = "com.apple.product-type.application"; + }; + 22C190E624AF30A900C3502D /* PixelEngine */ = { + isa = PBXNativeTarget; + buildConfigurationList = 22C190F024AF30A900C3502D /* Build configuration list for PBXNativeTarget "PixelEngine" */; + buildPhases = ( + E434A411C4CEB0CB6E254400 /* [CP] Check Pods Manifest.lock */, + 22C190E224AF30A900C3502D /* Headers */, + 22C190E324AF30A900C3502D /* Sources */, + 22C190E424AF30A900C3502D /* Frameworks */, + 22C190E524AF30A900C3502D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PixelEngine; + productName = PixelEngine; + productReference = 22C190E724AF30A900C3502D /* PixelEngine.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 228A90A924AF27BB000CB78C /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1150; + LastUpgradeCheck = 1150; + ORGANIZATIONNAME = PingAK9; + TargetAttributes = { + 228A90B024AF27BB000CB78C = { + CreatedOnToolsVersion = 11.5; + }; + 22C190E624AF30A900C3502D = { + CreatedOnToolsVersion = 11.5; + LastSwiftMigration = 1150; + }; + }; + }; + buildConfigurationList = 228A90AC24AF27BB000CB78C /* Build configuration list for PBXProject "colorful-room" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 228A90A824AF27BB000CB78C; + productRefGroup = 228A90B224AF27BB000CB78C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 228A90B024AF27BB000CB78C /* colorful-room */, + 22C190E624AF30A900C3502D /* PixelEngine */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 228A90AF24AF27BB000CB78C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 228A90C424AF27BD000CB78C /* LaunchScreen.storyboard in Resources */, + 228A90C124AF27BD000CB78C /* Preview Assets.xcassets in Resources */, + 228A90BE24AF27BD000CB78C /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 22C190E524AF30A900C3502D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 22C1913724AF30E900C3502D /* YUCIColorLookup.cikernel in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + E27EFE4AB6B371D936E623B6 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-colorful-room-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + E434A411C4CEB0CB6E254400 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PixelEngine-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + E7FF05A3B7E3DA75A3AF197B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-colorful-room/Pods-colorful-room-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-colorful-room/Pods-colorful-room-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-colorful-room/Pods-colorful-room-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 228A90AD24AF27BB000CB78C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 22C191AF24AF396200C3502D /* Data.swift in Sources */, + 22EF90BD24B569E0003AE15E /* PhotoEditView.swift in Sources */, + 5972CD1624DD601B005C0B0A /* ButtonView.swift in Sources */, + 5972CD1524DD601B005C0B0A /* CollectionButton.swift in Sources */, + 5972CD1024DD601B005C0B0A /* PhotoEditorView.swift in Sources */, + 5972CD2424DD601B005C0B0A /* UnsharpMaskControl.swift in Sources */, + 5972CD1924DD601B005C0B0A /* SharpenControl.swift in Sources */, + 5972CD1124DD601B005C0B0A /* ImagePreviewView.swift in Sources */, + 5972CD2224DD601B005C0B0A /* HLSControl.swift in Sources */, + 228A90B524AF27BB000CB78C /* AppDelegate.swift in Sources */, + 22B9F9D424C437E70036FBCE /* SupportView.swift in Sources */, + 5972CD1824DD601B005C0B0A /* ExposureControl.swift in Sources */, + 5972CD2124DD601B005C0B0A /* ColorSelectButton.swift in Sources */, + 228E392224BF06A40038B988 /* Collection.swift in Sources */, + 5972CD2324DD601B005C0B0A /* GaussianBlurControl.swift in Sources */, + 22B9F9D224C42C7C0036FBCE /* ActivityIndicator.swift in Sources */, + 5972CD2724DD601B005C0B0A /* ShadowsControl.swift in Sources */, + 5972CD1424DD601B005C0B0A /* IconButton.swift in Sources */, + 2208239124C92C4900E4D460 /* ExportView.swift in Sources */, + 5972CD2A24DD601B005C0B0A /* EditMenuView.swift in Sources */, + 5972CD2B24DD601B005C0B0A /* Utility.swift in Sources */, + 228A90BA24AF27BB000CB78C /* colorful_room.xcdatamodeld in Sources */, + 5972CD1A24DD601B005C0B0A /* ToneControl.swift in Sources */, + 5972CD2824DD601B005C0B0A /* FadeControl.swift in Sources */, + 5972CD1E24DD601B005C0B0A /* ColorCubeControl.swift in Sources */, + 22CEC04824BEE7D3009D4134 /* AppTheme.swift in Sources */, + 5972CD1D24DD601B005C0B0A /* SaturationControl.swift in Sources */, + 5972CD1B24DD601B005C0B0A /* WhiteBalanceControl.swift in Sources */, + 5972CD2924DD601B005C0B0A /* FilterMenuUI.swift in Sources */, + 5972CD1224DD601B005C0B0A /* CustomSlider.swift in Sources */, + 228A90BC24AF27BB000CB78C /* ContentView.swift in Sources */, + 5972CD0F24DD601B005C0B0A /* PhotoEditorController.swift in Sources */, + 5998814D24E51EF1001BE5CE /* EditMenu.swift in Sources */, + 5972CD1724DD601B005C0B0A /* LutMenuUI.swift in Sources */, + 5972CD2524DD601B005C0B0A /* TemperatureControl.swift in Sources */, + 22C191AE24AF396200C3502D /* ImagePicker.swift in Sources */, + 5972CD1C24DD601B005C0B0A /* ColorControl.swift in Sources */, + 5972CD2624DD601B005C0B0A /* ContrastControl.swift in Sources */, + 228A90B724AF27BB000CB78C /* SceneDelegate.swift in Sources */, + 5972CD1324DD601B005C0B0A /* LUTButton.swift in Sources */, + 22787FC824C00792002CC5C3 /* ListTitle.swift in Sources */, + 5972CD1F24DD601B005C0B0A /* HighlightsControl.swift in Sources */, + 22C191B024AF396200C3502D /* ImageSaver.swift in Sources */, + 59D930F824D7E9D500CDC68B /* RoadMapView.swift in Sources */, + 226708E724C96AFD00A96CA7 /* RaiseButton.swift in Sources */, + 5972CD2024DD601B005C0B0A /* VignetteControl.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 22C190E324AF30A900C3502D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 22C1912D24AF30E900C3502D /* FilterGaussianBlur.swift in Sources */, + 22C1914124AF30E900C3502D /* UIColor+HEX.swift in Sources */, + 2271885924B4738800610D8C /* MultiBandHSV.swift in Sources */, + 22C1912824AF30E900C3502D /* FilterColor.swift in Sources */, + 22C1912524AF30E900C3502D /* FilterShadows.swift in Sources */, + 228DE32E24C352B100B748DF /* FilterWhiteBalance.swift in Sources */, + 22C1913D24AF30E900C3502D /* PixelEngine.swift in Sources */, + 22C1913824AF30E900C3502D /* MetalImageView.swift in Sources */, + 22C1913124AF30E900C3502D /* DrawnPathInRect.swift in Sources */, + 22C1913924AF30E900C3502D /* HardwareImageView.swift in Sources */, + 22C1913224AF30E900C3502D /* EditingStack.swift in Sources */, + 22C1912C24AF30E900C3502D /* FilterContrast.swift in Sources */, + 22C1913F24AF30E900C3502D /* InternalUtils.swift in Sources */, + 22C1913B24AF30E900C3502D /* DrawnPath.swift in Sources */, + 22C1913A24AF30E900C3502D /* GLImageView.swift in Sources */, + 22C1912124AF30E900C3502D /* ImageSource.swift in Sources */, + 22C1912624AF30E900C3502D /* FilterSaturation.swift in Sources */, + 22C1912E24AF30E900C3502D /* FilterFade.swift in Sources */, + 22C1912924AF30E900C3502D /* FilterExposure.swift in Sources */, + 22C1913024AF30E900C3502D /* Geometry.swift in Sources */, + 22C1911D24AF30E900C3502D /* EngineLog.swift in Sources */, + 2271885B24B4777400610D8C /* FilterHLS.swift in Sources */, + 22C1911E24AF30E900C3502D /* GraphicDrawing.swift in Sources */, + 22C1914024AF30E900C3502D /* OvalBrush.swift in Sources */, + 22C1912424AF30E900C3502D /* FilterColorCube.swift in Sources */, + 22C1913324AF30E900C3502D /* ImageTool.swift in Sources */, + 22C1913424AF30E900C3502D /* ImageRenderer.swift in Sources */, + 22C1912A24AF30E900C3502D /* FilterSharpen.swift in Sources */, + 22C1912B24AF30E900C3502D /* FilterHighlights.swift in Sources */, + 22C1913E24AF30E900C3502D /* Filtering.swift in Sources */, + 22C1913C24AF30E900C3502D /* ColorCube.swift in Sources */, + 22C1911F24AF30E900C3502D /* BlurredMask.swift in Sources */, + 22C1912F24AF30E900C3502D /* FilterUnsharpMask.swift in Sources */, + 22C1912724AF30E900C3502D /* FilterTemperature.swift in Sources */, + 22C1913624AF30E900C3502D /* ParameterRange.swift in Sources */, + 22C1912224AF30E900C3502D /* FilterVignette.swift in Sources */, + 22C1912324AF30E900C3502D /* FilterHighlightShadowTint.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 22C190ED24AF30A900C3502D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 22C190E624AF30A900C3502D /* PixelEngine */; + targetProxy = 22C190EC24AF30A900C3502D /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 228A90C224AF27BD000CB78C /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 228A90C324AF27BD000CB78C /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 228A90C624AF27BD000CB78C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 228A90C724AF27BD000CB78C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 228A90C924AF27BD000CB78C /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4682A39EF92B8DE5335673D3 /* Pods-colorful-room.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 4; + DEVELOPMENT_ASSET_PATHS = "\"colorful-room/Preview Content\""; + DEVELOPMENT_TEAM = N8L3QK9894; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = "colorful-room/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.pingak9.luts; + PRODUCT_NAME = LUTs; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 228A90CA24AF27BD000CB78C /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1AE86049E48094D887353101 /* Pods-colorful-room.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 4; + DEVELOPMENT_ASSET_PATHS = "\"colorful-room/Preview Content\""; + DEVELOPMENT_TEAM = N8L3QK9894; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = "colorful-room/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.pingak9.luts; + PRODUCT_NAME = LUTs; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 22C190F124AF30A900C3502D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 39B28A770BF277CAD04B1B5C /* Pods-PixelEngine.debug.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = N8L3QK9894; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = PixelEngine/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = com.pingak9.pixelengine; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 22C190F224AF30A900C3502D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 56D37364123E5AC23935C76E /* Pods-PixelEngine.release.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = N8L3QK9894; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = PixelEngine/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = com.pingak9.pixelengine; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 228A90AC24AF27BB000CB78C /* Build configuration list for PBXProject "colorful-room" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 228A90C624AF27BD000CB78C /* Debug */, + 228A90C724AF27BD000CB78C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 228A90C824AF27BD000CB78C /* Build configuration list for PBXNativeTarget "colorful-room" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 228A90C924AF27BD000CB78C /* Debug */, + 228A90CA24AF27BD000CB78C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 22C190F024AF30A900C3502D /* Build configuration list for PBXNativeTarget "PixelEngine" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 22C190F124AF30A900C3502D /* Debug */, + 22C190F224AF30A900C3502D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCVersionGroup section */ + 228A90B824AF27BB000CB78C /* colorful_room.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + 228A90B924AF27BB000CB78C /* colorful_room.xcdatamodel */, + ); + currentVersion = 228A90B924AF27BB000CB78C /* colorful_room.xcdatamodel */; + path = colorful_room.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ + }; + rootObject = 228A90A924AF27BB000CB78C /* Project object */; +} diff --git a/colorful-room.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/colorful-room.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..22a9ef7 --- /dev/null +++ b/colorful-room.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/colorful-room.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/colorful-room.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/colorful-room.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/colorful-room.xcodeproj/xcshareddata/xcschemes/colorful-room.xcscheme b/colorful-room.xcodeproj/xcshareddata/xcschemes/colorful-room.xcscheme new file mode 100644 index 0000000..126285a --- /dev/null +++ b/colorful-room.xcodeproj/xcshareddata/xcschemes/colorful-room.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/colorful-room.xcworkspace/contents.xcworkspacedata b/colorful-room.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..2a055f0 --- /dev/null +++ b/colorful-room.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/colorful-room.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/colorful-room.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/colorful-room.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/colorful-room/AppDelegate.swift b/colorful-room/AppDelegate.swift new file mode 100644 index 0000000..d7b4aa2 --- /dev/null +++ b/colorful-room/AppDelegate.swift @@ -0,0 +1,80 @@ +// +// AppDelegate.swift +// colorful-room +// +// Created by macOS on 7/3/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import UIKit +import CoreData + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + // MARK: - Core Data stack + + lazy var persistentContainer: NSPersistentContainer = { + /* + The persistent container for the application. This implementation + creates and returns a container, having loaded the store for the + application to it. This property is optional since there are legitimate + error conditions that could cause the creation of the store to fail. + */ + let container = NSPersistentContainer(name: "colorful_room") + container.loadPersistentStores(completionHandler: { (storeDescription, error) in + if let error = error as NSError? { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + + /* + Typical reasons for an error here include: + * The parent directory does not exist, cannot be created, or disallows writing. + * The persistent store is not accessible, due to permissions or data protection when the device is locked. + * The device is out of space. + * The store could not be migrated to the current model version. + Check the error message to determine what the actual problem was. + */ + fatalError("Unresolved error \(error), \(error.userInfo)") + } + }) + return container + }() + + // MARK: - Core Data Saving support + + func saveContext () { + let context = persistentContainer.viewContext + if context.hasChanges { + do { + try context.save() + } catch { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + let nserror = error as NSError + fatalError("Unresolved error \(nserror), \(nserror.userInfo)") + } + } + } + +} + diff --git a/colorful-room/Assets.xcassets/AppIcon.appiconset/Contents.json b/colorful-room/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a511050 --- /dev/null +++ b/colorful-room/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "filename" : "Icon-40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "Icon-58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "Icon-80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "Icon-120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "Icon-180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "Icon-20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "Icon-40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "Icon-58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "Icon-80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "Icon-152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "Icon-167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "Icon-1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-1024.png b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-1024.png new file mode 100644 index 0000000..ae6bd26 Binary files /dev/null and b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-1024.png differ diff --git a/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-120.png b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-120.png new file mode 100644 index 0000000..b32d630 Binary files /dev/null and b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-120.png differ diff --git a/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-152.png b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-152.png new file mode 100644 index 0000000..f2283a3 Binary files /dev/null and b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-152.png differ diff --git a/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-167.png b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-167.png new file mode 100644 index 0000000..91f28b6 Binary files /dev/null and b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-167.png differ diff --git a/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-180.png b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-180.png new file mode 100644 index 0000000..e54d01f Binary files /dev/null and b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-180.png differ diff --git a/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-20.png b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-20.png new file mode 100644 index 0000000..06215e5 Binary files /dev/null and b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-20.png differ diff --git a/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-29.png b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-29.png new file mode 100644 index 0000000..0b8b72f Binary files /dev/null and b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-29.png differ diff --git a/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-40.png b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-40.png new file mode 100644 index 0000000..d2193d5 Binary files /dev/null and b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-40.png differ diff --git a/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-58.png b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-58.png new file mode 100644 index 0000000..e0cb2a3 Binary files /dev/null and b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-58.png differ diff --git a/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-60.png b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-60.png new file mode 100644 index 0000000..d495364 Binary files /dev/null and b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-60.png differ diff --git a/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-76.png b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-76.png new file mode 100644 index 0000000..3f945f8 Binary files /dev/null and b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-76.png differ diff --git a/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-80.png b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-80.png new file mode 100644 index 0000000..022f10b Binary files /dev/null and b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-80.png differ diff --git a/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-87.png b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-87.png new file mode 100644 index 0000000..5a351f4 Binary files /dev/null and b/colorful-room/Assets.xcassets/AppIcon.appiconset/Icon-87.png differ diff --git a/colorful-room/Assets.xcassets/Contents.json b/colorful-room/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/colorful-room/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/buttons/Contents.json b/colorful-room/Assets.xcassets/buttons/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/colorful-room/Assets.xcassets/buttons/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/buttons/adjustment-highlight.imageset/Contents.json b/colorful-room/Assets.xcassets/buttons/adjustment-highlight.imageset/Contents.json new file mode 100644 index 0000000..adf6ef5 --- /dev/null +++ b/colorful-room/Assets.xcassets/buttons/adjustment-highlight.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "adjustment-highlight.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/buttons/adjustment-highlight.imageset/adjustment-highlight.png b/colorful-room/Assets.xcassets/buttons/adjustment-highlight.imageset/adjustment-highlight.png new file mode 100644 index 0000000..1a55dbc Binary files /dev/null and b/colorful-room/Assets.xcassets/buttons/adjustment-highlight.imageset/adjustment-highlight.png differ diff --git a/colorful-room/Assets.xcassets/buttons/adjustment.imageset/Contents.json b/colorful-room/Assets.xcassets/buttons/adjustment.imageset/Contents.json new file mode 100644 index 0000000..37f1b5b --- /dev/null +++ b/colorful-room/Assets.xcassets/buttons/adjustment.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "adjustment.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/buttons/adjustment.imageset/adjustment.png b/colorful-room/Assets.xcassets/buttons/adjustment.imageset/adjustment.png new file mode 100644 index 0000000..e935035 Binary files /dev/null and b/colorful-room/Assets.xcassets/buttons/adjustment.imageset/adjustment.png differ diff --git a/colorful-room/Assets.xcassets/buttons/edit-color-highlight.imageset/Contents.json b/colorful-room/Assets.xcassets/buttons/edit-color-highlight.imageset/Contents.json new file mode 100644 index 0000000..5d100f4 --- /dev/null +++ b/colorful-room/Assets.xcassets/buttons/edit-color-highlight.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "edit-color-highlight.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/buttons/edit-color-highlight.imageset/edit-color-highlight.png b/colorful-room/Assets.xcassets/buttons/edit-color-highlight.imageset/edit-color-highlight.png new file mode 100644 index 0000000..c5b38ff Binary files /dev/null and b/colorful-room/Assets.xcassets/buttons/edit-color-highlight.imageset/edit-color-highlight.png differ diff --git a/colorful-room/Assets.xcassets/buttons/edit-color.imageset/Contents.json b/colorful-room/Assets.xcassets/buttons/edit-color.imageset/Contents.json new file mode 100644 index 0000000..54b31e6 --- /dev/null +++ b/colorful-room/Assets.xcassets/buttons/edit-color.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "edit-color.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/buttons/edit-color.imageset/edit-color.png b/colorful-room/Assets.xcassets/buttons/edit-color.imageset/edit-color.png new file mode 100644 index 0000000..437ddc3 Binary files /dev/null and b/colorful-room/Assets.xcassets/buttons/edit-color.imageset/edit-color.png differ diff --git a/colorful-room/Assets.xcassets/buttons/edit-effect.imageset/Contents.json b/colorful-room/Assets.xcassets/buttons/edit-effect.imageset/Contents.json new file mode 100644 index 0000000..eb5de04 --- /dev/null +++ b/colorful-room/Assets.xcassets/buttons/edit-effect.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "edit-effect.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/buttons/edit-effect.imageset/edit-effect.png b/colorful-room/Assets.xcassets/buttons/edit-effect.imageset/edit-effect.png new file mode 100644 index 0000000..7638fb8 Binary files /dev/null and b/colorful-room/Assets.xcassets/buttons/edit-effect.imageset/edit-effect.png differ diff --git a/colorful-room/Assets.xcassets/buttons/edit-lut-highlight.imageset/Contents.json b/colorful-room/Assets.xcassets/buttons/edit-lut-highlight.imageset/Contents.json new file mode 100644 index 0000000..55a6ad1 --- /dev/null +++ b/colorful-room/Assets.xcassets/buttons/edit-lut-highlight.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "edit-lut-highlight.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/buttons/edit-lut-highlight.imageset/edit-lut-highlight.png b/colorful-room/Assets.xcassets/buttons/edit-lut-highlight.imageset/edit-lut-highlight.png new file mode 100644 index 0000000..1548f0c Binary files /dev/null and b/colorful-room/Assets.xcassets/buttons/edit-lut-highlight.imageset/edit-lut-highlight.png differ diff --git a/colorful-room/Assets.xcassets/buttons/edit-lut.imageset/Contents.json b/colorful-room/Assets.xcassets/buttons/edit-lut.imageset/Contents.json new file mode 100644 index 0000000..2e90d72 --- /dev/null +++ b/colorful-room/Assets.xcassets/buttons/edit-lut.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "edit-lut.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/buttons/edit-lut.imageset/edit-lut.png b/colorful-room/Assets.xcassets/buttons/edit-lut.imageset/edit-lut.png new file mode 100644 index 0000000..6307d5b Binary files /dev/null and b/colorful-room/Assets.xcassets/buttons/edit-lut.imageset/edit-lut.png differ diff --git a/colorful-room/Assets.xcassets/buttons/icon-download.imageset/Contents.json b/colorful-room/Assets.xcassets/buttons/icon-download.imageset/Contents.json new file mode 100644 index 0000000..de7a226 --- /dev/null +++ b/colorful-room/Assets.xcassets/buttons/icon-download.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "icon-download.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/buttons/icon-download.imageset/icon-download.png b/colorful-room/Assets.xcassets/buttons/icon-download.imageset/icon-download.png new file mode 100644 index 0000000..c34e19f Binary files /dev/null and b/colorful-room/Assets.xcassets/buttons/icon-download.imageset/icon-download.png differ diff --git a/colorful-room/Assets.xcassets/buttons/icon-lut.imageset/Contents.json b/colorful-room/Assets.xcassets/buttons/icon-lut.imageset/Contents.json new file mode 100644 index 0000000..8b2f47a --- /dev/null +++ b/colorful-room/Assets.xcassets/buttons/icon-lut.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "icon-lut.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/buttons/icon-lut.imageset/icon-lut.png b/colorful-room/Assets.xcassets/buttons/icon-lut.imageset/icon-lut.png new file mode 100644 index 0000000..554dc54 Binary files /dev/null and b/colorful-room/Assets.xcassets/buttons/icon-lut.imageset/icon-lut.png differ diff --git a/colorful-room/Assets.xcassets/buttons/icon-photo-add.imageset/Contents.json b/colorful-room/Assets.xcassets/buttons/icon-photo-add.imageset/Contents.json new file mode 100644 index 0000000..33051a9 --- /dev/null +++ b/colorful-room/Assets.xcassets/buttons/icon-photo-add.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "icon-photo-add.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/buttons/icon-photo-add.imageset/icon-photo-add.png b/colorful-room/Assets.xcassets/buttons/icon-photo-add.imageset/icon-photo-add.png new file mode 100644 index 0000000..e33db1e Binary files /dev/null and b/colorful-room/Assets.xcassets/buttons/icon-photo-add.imageset/icon-photo-add.png differ diff --git a/colorful-room/Assets.xcassets/buttons/icon-undo.imageset/Contents.json b/colorful-room/Assets.xcassets/buttons/icon-undo.imageset/Contents.json new file mode 100644 index 0000000..f426bec --- /dev/null +++ b/colorful-room/Assets.xcassets/buttons/icon-undo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "icon-undo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/buttons/icon-undo.imageset/icon-undo.png b/colorful-room/Assets.xcassets/buttons/icon-undo.imageset/icon-undo.png new file mode 100644 index 0000000..5063595 Binary files /dev/null and b/colorful-room/Assets.xcassets/buttons/icon-undo.imageset/icon-undo.png differ diff --git a/colorful-room/Assets.xcassets/colors/Contents.json b/colorful-room/Assets.xcassets/colors/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/colorful-room/Assets.xcassets/colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/colors/accent.colorset/Contents.json b/colorful-room/Assets.xcassets/colors/accent.colorset/Contents.json new file mode 100644 index 0000000..8d4fe50 --- /dev/null +++ b/colorful-room/Assets.xcassets/colors/accent.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "extended-srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.750", + "green" : "0.831", + "red" : "0.068" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/colors/background.colorset/Contents.json b/colorful-room/Assets.xcassets/colors/background.colorset/Contents.json new file mode 100644 index 0000000..d3fdced --- /dev/null +++ b/colorful-room/Assets.xcassets/colors/background.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x20", + "green" : "0x1F", + "red" : "0x1E" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/colors/bronze.colorset/Contents.json b/colorful-room/Assets.xcassets/colors/bronze.colorset/Contents.json new file mode 100644 index 0000000..9a3288c --- /dev/null +++ b/colorful-room/Assets.xcassets/colors/bronze.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x56", + "red" : "0xB4" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/colors/button-dark.colorset/Contents.json b/colorful-room/Assets.xcassets/colors/button-dark.colorset/Contents.json new file mode 100644 index 0000000..4845573 --- /dev/null +++ b/colorful-room/Assets.xcassets/colors/button-dark.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x3D", + "green" : "0x3C", + "red" : "0x3B" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/colors/divider.colorset/Contents.json b/colorful-room/Assets.xcassets/colors/divider.colorset/Contents.json new file mode 100644 index 0000000..b9095ee --- /dev/null +++ b/colorful-room/Assets.xcassets/colors/divider.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2B", + "green" : "0x29", + "red" : "0x27" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/colors/gold.colorset/Contents.json b/colorful-room/Assets.xcassets/colors/gold.colorset/Contents.json new file mode 100644 index 0000000..bd40d3c --- /dev/null +++ b/colorful-room/Assets.xcassets/colors/gold.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0xB8", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/colors/gray-dark.colorset/Contents.json b/colorful-room/Assets.xcassets/colors/gray-dark.colorset/Contents.json new file mode 100644 index 0000000..57ebf68 --- /dev/null +++ b/colorful-room/Assets.xcassets/colors/gray-dark.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.420", + "green" : "0.404", + "red" : "0.384" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/colors/gray-light.colorset/Contents.json b/colorful-room/Assets.xcassets/colors/gray-light.colorset/Contents.json new file mode 100644 index 0000000..7b0e7c8 --- /dev/null +++ b/colorful-room/Assets.xcassets/colors/gray-light.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE7", + "green" : "0xE3", + "red" : "0xDE" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/colors/gray.colorset/Contents.json b/colorful-room/Assets.xcassets/colors/gray.colorset/Contents.json new file mode 100644 index 0000000..bd0478b --- /dev/null +++ b/colorful-room/Assets.xcassets/colors/gray.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x9B", + "green" : "0x96", + "red" : "0x90" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/colors/panel.colorset/Contents.json b/colorful-room/Assets.xcassets/colors/panel.colorset/Contents.json new file mode 100644 index 0000000..ef6b208 --- /dev/null +++ b/colorful-room/Assets.xcassets/colors/panel.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x26", + "green" : "0x25", + "red" : "0x24" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/colors/primary.colorset/Contents.json b/colorful-room/Assets.xcassets/colors/primary.colorset/Contents.json new file mode 100644 index 0000000..b9dc9e3 --- /dev/null +++ b/colorful-room/Assets.xcassets/colors/primary.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0x54", + "red" : "0xF1" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/colors/sliver.colorset/Contents.json b/colorful-room/Assets.xcassets/colors/sliver.colorset/Contents.json new file mode 100644 index 0000000..d0f6a2a --- /dev/null +++ b/colorful-room/Assets.xcassets/colors/sliver.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.906", + "green" : "0.890", + "red" : "0.871" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/edit-buttons/Contents.json b/colorful-room/Assets.xcassets/edit-buttons/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/colorful-room/Assets.xcassets/edit-buttons/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/edit-buttons/brightness.imageset/Contents.json b/colorful-room/Assets.xcassets/edit-buttons/brightness.imageset/Contents.json new file mode 100644 index 0000000..0ad600a --- /dev/null +++ b/colorful-room/Assets.xcassets/edit-buttons/brightness.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "brightness.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/edit-buttons/brightness.imageset/brightness.png b/colorful-room/Assets.xcassets/edit-buttons/brightness.imageset/brightness.png new file mode 100644 index 0000000..1f14c05 Binary files /dev/null and b/colorful-room/Assets.xcassets/edit-buttons/brightness.imageset/brightness.png differ diff --git a/colorful-room/Assets.xcassets/edit-buttons/cil_blur.imageset/Contents.json b/colorful-room/Assets.xcassets/edit-buttons/cil_blur.imageset/Contents.json new file mode 100644 index 0000000..766741b --- /dev/null +++ b/colorful-room/Assets.xcassets/edit-buttons/cil_blur.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "cil_blur.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/edit-buttons/cil_blur.imageset/cil_blur.png b/colorful-room/Assets.xcassets/edit-buttons/cil_blur.imageset/cil_blur.png new file mode 100644 index 0000000..d3f1a76 Binary files /dev/null and b/colorful-room/Assets.xcassets/edit-buttons/cil_blur.imageset/cil_blur.png differ diff --git a/colorful-room/Assets.xcassets/edit-buttons/clarity.imageset/Contents.json b/colorful-room/Assets.xcassets/edit-buttons/clarity.imageset/Contents.json new file mode 100644 index 0000000..f1eb757 --- /dev/null +++ b/colorful-room/Assets.xcassets/edit-buttons/clarity.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "clarity.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/edit-buttons/clarity.imageset/clarity.png b/colorful-room/Assets.xcassets/edit-buttons/clarity.imageset/clarity.png new file mode 100644 index 0000000..7c55630 Binary files /dev/null and b/colorful-room/Assets.xcassets/edit-buttons/clarity.imageset/clarity.png differ diff --git a/colorful-room/Assets.xcassets/edit-buttons/contrast.imageset/Contents.json b/colorful-room/Assets.xcassets/edit-buttons/contrast.imageset/Contents.json new file mode 100644 index 0000000..f181140 --- /dev/null +++ b/colorful-room/Assets.xcassets/edit-buttons/contrast.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "contrast.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/edit-buttons/contrast.imageset/contrast.png b/colorful-room/Assets.xcassets/edit-buttons/contrast.imageset/contrast.png new file mode 100644 index 0000000..6d72299 Binary files /dev/null and b/colorful-room/Assets.xcassets/edit-buttons/contrast.imageset/contrast.png differ diff --git a/colorful-room/Assets.xcassets/edit-buttons/fade.imageset/Contents.json b/colorful-room/Assets.xcassets/edit-buttons/fade.imageset/Contents.json new file mode 100644 index 0000000..691bfae --- /dev/null +++ b/colorful-room/Assets.xcassets/edit-buttons/fade.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "fade.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/edit-buttons/fade.imageset/fade.png b/colorful-room/Assets.xcassets/edit-buttons/fade.imageset/fade.png new file mode 100644 index 0000000..8058a68 Binary files /dev/null and b/colorful-room/Assets.xcassets/edit-buttons/fade.imageset/fade.png differ diff --git a/colorful-room/Assets.xcassets/edit-buttons/hls.imageset/Contents.json b/colorful-room/Assets.xcassets/edit-buttons/hls.imageset/Contents.json new file mode 100644 index 0000000..b7d33be --- /dev/null +++ b/colorful-room/Assets.xcassets/edit-buttons/hls.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "hls.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/edit-buttons/hls.imageset/hls.png b/colorful-room/Assets.xcassets/edit-buttons/hls.imageset/hls.png new file mode 100644 index 0000000..e2483e6 Binary files /dev/null and b/colorful-room/Assets.xcassets/edit-buttons/hls.imageset/hls.png differ diff --git a/colorful-room/Assets.xcassets/edit-buttons/saturation.imageset/Contents.json b/colorful-room/Assets.xcassets/edit-buttons/saturation.imageset/Contents.json new file mode 100644 index 0000000..46d097c --- /dev/null +++ b/colorful-room/Assets.xcassets/edit-buttons/saturation.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "saturation.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/edit-buttons/saturation.imageset/saturation.png b/colorful-room/Assets.xcassets/edit-buttons/saturation.imageset/saturation.png new file mode 100644 index 0000000..c84ffd3 Binary files /dev/null and b/colorful-room/Assets.xcassets/edit-buttons/saturation.imageset/saturation.png differ diff --git a/colorful-room/Assets.xcassets/edit-buttons/sharpen.imageset/Contents.json b/colorful-room/Assets.xcassets/edit-buttons/sharpen.imageset/Contents.json new file mode 100644 index 0000000..1a3949d --- /dev/null +++ b/colorful-room/Assets.xcassets/edit-buttons/sharpen.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "sharpen.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/edit-buttons/sharpen.imageset/sharpen.png b/colorful-room/Assets.xcassets/edit-buttons/sharpen.imageset/sharpen.png new file mode 100644 index 0000000..fc1c640 Binary files /dev/null and b/colorful-room/Assets.xcassets/edit-buttons/sharpen.imageset/sharpen.png differ diff --git a/colorful-room/Assets.xcassets/edit-buttons/temperature.imageset/Contents.json b/colorful-room/Assets.xcassets/edit-buttons/temperature.imageset/Contents.json new file mode 100644 index 0000000..f0afc7f --- /dev/null +++ b/colorful-room/Assets.xcassets/edit-buttons/temperature.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "white-balance.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/edit-buttons/temperature.imageset/white-balance.png b/colorful-room/Assets.xcassets/edit-buttons/temperature.imageset/white-balance.png new file mode 100644 index 0000000..ad8b494 Binary files /dev/null and b/colorful-room/Assets.xcassets/edit-buttons/temperature.imageset/white-balance.png differ diff --git a/colorful-room/Assets.xcassets/edit-buttons/tone.imageset/Contents.json b/colorful-room/Assets.xcassets/edit-buttons/tone.imageset/Contents.json new file mode 100644 index 0000000..45f11af --- /dev/null +++ b/colorful-room/Assets.xcassets/edit-buttons/tone.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "tone.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/edit-buttons/tone.imageset/tone.png b/colorful-room/Assets.xcassets/edit-buttons/tone.imageset/tone.png new file mode 100644 index 0000000..349d386 Binary files /dev/null and b/colorful-room/Assets.xcassets/edit-buttons/tone.imageset/tone.png differ diff --git a/colorful-room/Assets.xcassets/edit-buttons/vignette.imageset/Contents.json b/colorful-room/Assets.xcassets/edit-buttons/vignette.imageset/Contents.json new file mode 100644 index 0000000..6b994af --- /dev/null +++ b/colorful-room/Assets.xcassets/edit-buttons/vignette.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "vignette.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/edit-buttons/vignette.imageset/vignette.png b/colorful-room/Assets.xcassets/edit-buttons/vignette.imageset/vignette.png new file mode 100644 index 0000000..a5cd4bf Binary files /dev/null and b/colorful-room/Assets.xcassets/edit-buttons/vignette.imageset/vignette.png differ diff --git a/colorful-room/Assets.xcassets/images/Contents.json b/colorful-room/Assets.xcassets/images/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/colorful-room/Assets.xcassets/images/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/images/carem.imageset/Contents.json b/colorful-room/Assets.xcassets/images/carem.imageset/Contents.json new file mode 100644 index 0000000..f082b45 --- /dev/null +++ b/colorful-room/Assets.xcassets/images/carem.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "carem.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/images/carem.imageset/carem.jpg b/colorful-room/Assets.xcassets/images/carem.imageset/carem.jpg new file mode 100644 index 0000000..51e44d5 Binary files /dev/null and b/colorful-room/Assets.xcassets/images/carem.imageset/carem.jpg differ diff --git a/colorful-room/Assets.xcassets/images/intro-image.imageset/Contents.json b/colorful-room/Assets.xcassets/images/intro-image.imageset/Contents.json new file mode 100644 index 0000000..cf24944 --- /dev/null +++ b/colorful-room/Assets.xcassets/images/intro-image.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "intro-image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/images/intro-image.imageset/intro-image.png b/colorful-room/Assets.xcassets/images/intro-image.imageset/intro-image.png new file mode 100644 index 0000000..ed7009a Binary files /dev/null and b/colorful-room/Assets.xcassets/images/intro-image.imageset/intro-image.png differ diff --git a/colorful-room/Assets.xcassets/images/sample-image.imageset/Contents.json b/colorful-room/Assets.xcassets/images/sample-image.imageset/Contents.json new file mode 100644 index 0000000..9d1c72a --- /dev/null +++ b/colorful-room/Assets.xcassets/images/sample-image.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "sample-image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/images/sample-image.imageset/sample-image.png b/colorful-room/Assets.xcassets/images/sample-image.imageset/sample-image.png new file mode 100644 index 0000000..4bee82a Binary files /dev/null and b/colorful-room/Assets.xcassets/images/sample-image.imageset/sample-image.png differ diff --git a/colorful-room/Assets.xcassets/luts/Contents.json b/colorful-room/Assets.xcassets/luts/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/cinematic/Contents.json b/colorful-room/Assets.xcassets/luts/cinematic/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/cinematic/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/cinematic/cinematic-1.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-1.imageset/Contents.json new file mode 100644 index 0000000..655b0ad --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "cinematic-1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/cinematic/cinematic-1.imageset/cinematic-1.png b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-1.imageset/cinematic-1.png new file mode 100644 index 0000000..6687caf Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-1.imageset/cinematic-1.png differ diff --git a/colorful-room/Assets.xcassets/luts/cinematic/cinematic-2.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-2.imageset/Contents.json new file mode 100644 index 0000000..6825eee --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "cinematic-2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/cinematic/cinematic-2.imageset/cinematic-2.png b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-2.imageset/cinematic-2.png new file mode 100644 index 0000000..b65f819 Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-2.imageset/cinematic-2.png differ diff --git a/colorful-room/Assets.xcassets/luts/cinematic/cinematic-3.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-3.imageset/Contents.json new file mode 100644 index 0000000..c9aa9eb --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "cinematic-3.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/cinematic/cinematic-3.imageset/cinematic-3.png b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-3.imageset/cinematic-3.png new file mode 100644 index 0000000..0e8711e Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-3.imageset/cinematic-3.png differ diff --git a/colorful-room/Assets.xcassets/luts/cinematic/cinematic-4.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-4.imageset/Contents.json new file mode 100644 index 0000000..535fa35 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "cinematic-4.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/cinematic/cinematic-4.imageset/cinematic-4.png b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-4.imageset/cinematic-4.png new file mode 100644 index 0000000..3395685 Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/cinematic/cinematic-4.imageset/cinematic-4.png differ diff --git a/colorful-room/Assets.xcassets/luts/film/Contents.json b/colorful-room/Assets.xcassets/luts/film/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/film/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/film/film-1.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/film/film-1.imageset/Contents.json new file mode 100644 index 0000000..2ffff6d --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/film/film-1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "film-1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/film/film-1.imageset/film-1.png b/colorful-room/Assets.xcassets/luts/film/film-1.imageset/film-1.png new file mode 100644 index 0000000..aea95d1 Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/film/film-1.imageset/film-1.png differ diff --git a/colorful-room/Assets.xcassets/luts/film/film-2.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/film/film-2.imageset/Contents.json new file mode 100644 index 0000000..1452623 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/film/film-2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "film-2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/film/film-2.imageset/film-2.png b/colorful-room/Assets.xcassets/luts/film/film-2.imageset/film-2.png new file mode 100644 index 0000000..440c81f Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/film/film-2.imageset/film-2.png differ diff --git a/colorful-room/Assets.xcassets/luts/film/film-3.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/film/film-3.imageset/Contents.json new file mode 100644 index 0000000..c30baa5 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/film/film-3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "film-3.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/film/film-3.imageset/film-3.png b/colorful-room/Assets.xcassets/luts/film/film-3.imageset/film-3.png new file mode 100644 index 0000000..60c2c42 Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/film/film-3.imageset/film-3.png differ diff --git a/colorful-room/Assets.xcassets/luts/lut-normal.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/lut-normal.imageset/Contents.json new file mode 100644 index 0000000..b402f85 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/lut-normal.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "lut.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/colorful-room/Assets.xcassets/luts/lut-normal.imageset/lut.jpg b/colorful-room/Assets.xcassets/luts/lut-normal.imageset/lut.jpg new file mode 100644 index 0000000..c16b4d0 Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/lut-normal.imageset/lut.jpg differ diff --git a/colorful-room/Assets.xcassets/luts/selfie good skin/Contents.json b/colorful-room/Assets.xcassets/luts/selfie good skin/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/selfie good skin/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-1.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-1.imageset/Contents.json new file mode 100644 index 0000000..5ee2f4c --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "selfie-1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-1.imageset/selfie-1.png b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-1.imageset/selfie-1.png new file mode 100644 index 0000000..88f367d Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-1.imageset/selfie-1.png differ diff --git a/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-2.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-2.imageset/Contents.json new file mode 100644 index 0000000..4d2ab06 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "selfie-2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-2.imageset/selfie-2.png b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-2.imageset/selfie-2.png new file mode 100644 index 0000000..724bd0b Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-2.imageset/selfie-2.png differ diff --git a/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-3.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-3.imageset/Contents.json new file mode 100644 index 0000000..934f56f --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "selfie-3.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-3.imageset/selfie-3.png b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-3.imageset/selfie-3.png new file mode 100644 index 0000000..d64ad70 Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-3.imageset/selfie-3.png differ diff --git a/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-4.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-4.imageset/Contents.json new file mode 100644 index 0000000..504a359 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "selfie-4.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-4.imageset/selfie-4.png b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-4.imageset/selfie-4.png new file mode 100644 index 0000000..4637506 Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-4.imageset/selfie-4.png differ diff --git a/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-5.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-5.imageset/Contents.json new file mode 100644 index 0000000..1ab5074 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-5.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "selfie-5.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-5.imageset/selfie-5.png b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-5.imageset/selfie-5.png new file mode 100644 index 0000000..2540193 Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/selfie good skin/selfie-5.imageset/selfie-5.png differ diff --git a/colorful-room/Assets.xcassets/luts/tan/Contents.json b/colorful-room/Assets.xcassets/luts/tan/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/tan/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-1.imageset/01.png b/colorful-room/Assets.xcassets/luts/tan/lut-1.imageset/01.png new file mode 100644 index 0000000..36098e4 Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/tan/lut-1.imageset/01.png differ diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-1.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/tan/lut-1.imageset/Contents.json new file mode 100644 index 0000000..7c0b348 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/tan/lut-1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "01.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-10.imageset/10.png b/colorful-room/Assets.xcassets/luts/tan/lut-10.imageset/10.png new file mode 100644 index 0000000..db4408d Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/tan/lut-10.imageset/10.png differ diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-10.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/tan/lut-10.imageset/Contents.json new file mode 100644 index 0000000..1d3ed0c --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/tan/lut-10.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "10.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-2.imageset/02.png b/colorful-room/Assets.xcassets/luts/tan/lut-2.imageset/02.png new file mode 100644 index 0000000..a619d9e Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/tan/lut-2.imageset/02.png differ diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-2.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/tan/lut-2.imageset/Contents.json new file mode 100644 index 0000000..cc1b0b7 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/tan/lut-2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "02.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-3.imageset/03.png b/colorful-room/Assets.xcassets/luts/tan/lut-3.imageset/03.png new file mode 100644 index 0000000..ebd97ab Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/tan/lut-3.imageset/03.png differ diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-3.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/tan/lut-3.imageset/Contents.json new file mode 100644 index 0000000..3d56aec --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/tan/lut-3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "03.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-4.imageset/04.png b/colorful-room/Assets.xcassets/luts/tan/lut-4.imageset/04.png new file mode 100644 index 0000000..95ba713 Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/tan/lut-4.imageset/04.png differ diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-4.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/tan/lut-4.imageset/Contents.json new file mode 100644 index 0000000..f3b3380 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/tan/lut-4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "04.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-5.imageset/05.png b/colorful-room/Assets.xcassets/luts/tan/lut-5.imageset/05.png new file mode 100644 index 0000000..b1b343c Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/tan/lut-5.imageset/05.png differ diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-5.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/tan/lut-5.imageset/Contents.json new file mode 100644 index 0000000..7887ea7 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/tan/lut-5.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "05.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-6.imageset/06.png b/colorful-room/Assets.xcassets/luts/tan/lut-6.imageset/06.png new file mode 100644 index 0000000..31bffb1 Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/tan/lut-6.imageset/06.png differ diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-6.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/tan/lut-6.imageset/Contents.json new file mode 100644 index 0000000..7770018 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/tan/lut-6.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "06.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-7.imageset/07.png b/colorful-room/Assets.xcassets/luts/tan/lut-7.imageset/07.png new file mode 100644 index 0000000..fc42ed3 Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/tan/lut-7.imageset/07.png differ diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-7.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/tan/lut-7.imageset/Contents.json new file mode 100644 index 0000000..5366057 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/tan/lut-7.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "07.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-8.imageset/08.png b/colorful-room/Assets.xcassets/luts/tan/lut-8.imageset/08.png new file mode 100644 index 0000000..c8c5f88 Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/tan/lut-8.imageset/08.png differ diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-8.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/tan/lut-8.imageset/Contents.json new file mode 100644 index 0000000..d83d964 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/tan/lut-8.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "08.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-9.imageset/09.png b/colorful-room/Assets.xcassets/luts/tan/lut-9.imageset/09.png new file mode 100644 index 0000000..e8e1374 Binary files /dev/null and b/colorful-room/Assets.xcassets/luts/tan/lut-9.imageset/09.png differ diff --git a/colorful-room/Assets.xcassets/luts/tan/lut-9.imageset/Contents.json b/colorful-room/Assets.xcassets/luts/tan/lut-9.imageset/Contents.json new file mode 100644 index 0000000..8bf0049 --- /dev/null +++ b/colorful-room/Assets.xcassets/luts/tan/lut-9.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "09.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/ui/Contents.json b/colorful-room/Assets.xcassets/ui/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/colorful-room/Assets.xcassets/ui/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/ui/emoji-support.imageset/Contents.json b/colorful-room/Assets.xcassets/ui/emoji-support.imageset/Contents.json new file mode 100644 index 0000000..e6e8aca --- /dev/null +++ b/colorful-room/Assets.xcassets/ui/emoji-support.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "emoji-support.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/ui/emoji-support.imageset/emoji-support.png b/colorful-room/Assets.xcassets/ui/emoji-support.imageset/emoji-support.png new file mode 100644 index 0000000..3436d1b Binary files /dev/null and b/colorful-room/Assets.xcassets/ui/emoji-support.imageset/emoji-support.png differ diff --git a/colorful-room/Assets.xcassets/ui/icon-bronze.imageset/Contents.json b/colorful-room/Assets.xcassets/ui/icon-bronze.imageset/Contents.json new file mode 100644 index 0000000..e058250 --- /dev/null +++ b/colorful-room/Assets.xcassets/ui/icon-bronze.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "icon-bronze.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/ui/icon-bronze.imageset/icon-bronze.png b/colorful-room/Assets.xcassets/ui/icon-bronze.imageset/icon-bronze.png new file mode 100644 index 0000000..6098a69 Binary files /dev/null and b/colorful-room/Assets.xcassets/ui/icon-bronze.imageset/icon-bronze.png differ diff --git a/colorful-room/Assets.xcassets/ui/icon-gold.imageset/Contents.json b/colorful-room/Assets.xcassets/ui/icon-gold.imageset/Contents.json new file mode 100644 index 0000000..1ccc926 --- /dev/null +++ b/colorful-room/Assets.xcassets/ui/icon-gold.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "icon-gold.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/ui/icon-gold.imageset/icon-gold.png b/colorful-room/Assets.xcassets/ui/icon-gold.imageset/icon-gold.png new file mode 100644 index 0000000..0746072 Binary files /dev/null and b/colorful-room/Assets.xcassets/ui/icon-gold.imageset/icon-gold.png differ diff --git a/colorful-room/Assets.xcassets/ui/icon-heart.imageset/Contents.json b/colorful-room/Assets.xcassets/ui/icon-heart.imageset/Contents.json new file mode 100644 index 0000000..d14a95c --- /dev/null +++ b/colorful-room/Assets.xcassets/ui/icon-heart.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "icon-heart.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/ui/icon-heart.imageset/icon-heart.png b/colorful-room/Assets.xcassets/ui/icon-heart.imageset/icon-heart.png new file mode 100644 index 0000000..20589ed Binary files /dev/null and b/colorful-room/Assets.xcassets/ui/icon-heart.imageset/icon-heart.png differ diff --git a/colorful-room/Assets.xcassets/ui/icon-silver.imageset/Contents.json b/colorful-room/Assets.xcassets/ui/icon-silver.imageset/Contents.json new file mode 100644 index 0000000..4254b15 --- /dev/null +++ b/colorful-room/Assets.xcassets/ui/icon-silver.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "icon-silver.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/ui/icon-silver.imageset/icon-silver.png b/colorful-room/Assets.xcassets/ui/icon-silver.imageset/icon-silver.png new file mode 100644 index 0000000..8ff5f80 Binary files /dev/null and b/colorful-room/Assets.xcassets/ui/icon-silver.imageset/icon-silver.png differ diff --git a/colorful-room/Assets.xcassets/ui/pattern-top.imageset/Contents.json b/colorful-room/Assets.xcassets/ui/pattern-top.imageset/Contents.json new file mode 100644 index 0000000..fbeca67 --- /dev/null +++ b/colorful-room/Assets.xcassets/ui/pattern-top.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "pattern-top.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/Assets.xcassets/ui/pattern-top.imageset/pattern-top.png b/colorful-room/Assets.xcassets/ui/pattern-top.imageset/pattern-top.png new file mode 100644 index 0000000..d07c543 Binary files /dev/null and b/colorful-room/Assets.xcassets/ui/pattern-top.imageset/pattern-top.png differ diff --git a/colorful-room/Base.lproj/LaunchScreen.storyboard b/colorful-room/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/colorful-room/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/colorful-room/ContentView.swift b/colorful-room/ContentView.swift new file mode 100644 index 0000000..022326c --- /dev/null +++ b/colorful-room/ContentView.swift @@ -0,0 +1,159 @@ +// +// ContentView.swift +// colorful-room +// +// Created by macOS on 7/3/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import UIKit +import PixelEngine + +struct ContentView: View { + + @State private var showImagePicker = false + + @State private var showImageEdit = false + // for pick view + @State private var pickImage: UIImage? + // for edit view + @State private var inputImage: UIImage? + + let imageHeight:Double = 355 + + var body: some View { + NavigationView{ + ZStack(alignment: .top){ + Color.myBackground + .edgesIgnoringSafeArea(.all) + Image("intro-image") + .resizable() + .aspectRatio(contentMode: .fill) + .frame(height: CGFloat(imageHeight)) + .edgesIgnoringSafeArea(.top) + + GeometryReader { geo in + VStack(alignment: .center, spacing: 24){ + Spacer() + HStack{ + Text("Create your\ncool filter") + .font(.system(size: 32, weight: .semibold)) + .fontWeight(.semibold) + .padding(.leading, 22) + Spacer() + } + VStack(spacing: 24){ + ListTitle( + title: "Free and Premium Filters", + supTitle: "A lot of filter you can use for your picture", + leadingImage: "edit-lut" + ) + ListTitle( + title: "Selective Color", + supTitle: "Freedom to custom your filter ", + leadingImage: "hls" + ) + ListTitle( + title: "Many Effects are available", + supTitle: "Freedom to custom your filter ", + leadingImage: "edit-effect" + ) + ListTitle( + title: "Get all your resources", + supTitle: "Export your picture, Lookup image, all effects, and more", + leadingImage: "icon-lut", + highlight: "AR filter" + ) + } + Spacer().frame(height: 0) + + ZStack{ + Rectangle() + .fill(Color.white) + .frame(width: geo.size.width - 60, height: 52) + HStack(alignment: .center, spacing: 10){ + + Image("icon-photo-add") + .resizable() + .scaledToFit() + .foregroundColor(Color.black) + .frame(width: 18, height: 18) + Text("CHOOSE YOUR PICTURE") + .font(.headline) + .foregroundColor(Color.black) + + } + .onTapGesture { + self.showImagePicker = true + self.showImageEdit = false + self.inputImage = nil + } + } + + NavigationLink(destination: SupportView() + .navigationBarTitle("") + .navigationBarHidden(true)){ + HStack(alignment: .center, spacing: 16){ + Spacer() + Image("emoji-support") + .renderingMode(.original) + .resizable() + .scaledToFit() + .frame(height: 17) + + Text("SUPPORT OUR TEAM") + Image(systemName: "chevron.right") + Spacer() + } + + .padding() + .foregroundColor(.white) + .font(.system(size: 13)) + } + NavigationLink(destination: PhotoEditView(image: self.inputImage) + .navigationBarTitle("") + .navigationBarHidden(true), isActive: self.$showImageEdit) { + EmptyView() + }.hidden() + + } + } + } + .navigationBarTitle("") + .navigationBarHidden(true) + + } + .navigationViewStyle(StackNavigationViewStyle()) + .sheet(isPresented: $showImagePicker, onDismiss: loadImage){ + ImagePicker(image: self.$pickImage) + }.onAppear(perform: { + print("onAppear") + // self.pickImage = UIImage(named: "carem") + // self.loadImage() + + }) + + } + + func loadImage(){ + print("loadImage: \(pickImage != nil)") + guard self.pickImage != nil else { + return + } + self.inputImage = self.pickImage + self.showImageEdit = true + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + Group { + ContentView() + .background(Color(UIColor.systemBackground)) + .environment(\.colorScheme, .dark) + .environmentObject(PECtl.shared) + .environmentObject(Data.shared) + } + } +} diff --git a/colorful-room/Info.plist b/colorful-room/Info.plist new file mode 100644 index 0000000..2120a03 --- /dev/null +++ b/colorful-room/Info.plist @@ -0,0 +1,66 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + NSPhotoLibraryAddUsageDescription + We need access your LIBRARY to save or pick your photo + NSPhotoLibraryUsageDescription + We need access your LIBRARY to save or pick your photo + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarStyle + UIStatusBarStyleDefault + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIUserInterfaceStyle + Dark + + diff --git a/colorful-room/PhotoEditor/Components/Controls/ColorCubeControl.swift b/colorful-room/PhotoEditor/Components/Controls/ColorCubeControl.swift new file mode 100644 index 0000000..5944565 --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/ColorCubeControl.swift @@ -0,0 +1,47 @@ +// +// ColorCubeControl.swift +// colorful-room +// +// Created by macOS on 7/20/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +struct ColorCubeControl: View { + + @State var filterIntensity:Double = 0 + + var body: some View { + + let intensity = Binding( + get: { + self.filterIntensity + }, + set: { + self.filterIntensity = $0 + self.valueChanged() + } + ) + return FilterSlider(value: intensity, range: (0, 1), defaultValue: 0, rangeDisplay: (0, 100)).onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + self.filterIntensity = edit.filters.colorCube?.amount ?? 1 + } + + func valueChanged() { + + guard let filter: FilterColorCube = PECtl.shared.edit.currentEdit.filters.colorCube else { + return + } + + let value = self.filterIntensity + let clone:FilterColorCube = FilterColorCube(name: filter.name, identifier: filter.identifier, filter: filter.filter, amount: value) + + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.colorCube = clone })) + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/ContrastControl.swift b/colorful-room/PhotoEditor/Components/Controls/ContrastControl.swift new file mode 100644 index 0000000..cc2dc06 --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/ContrastControl.swift @@ -0,0 +1,51 @@ +// +// ContrastControl.swift +// colorful-room +// +// Created by macOS on 7/13/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +struct ContrastControl: View { + @State var filterIntensity:Double = 0 + + var body: some View { + + let intensity = Binding( + get: { + self.filterIntensity + }, + set: { + self.filterIntensity = $0 + self.valueChanged() + } + ) + let min = FilterContrast.range.min + let max = FilterContrast.range.max + return FilterSlider(value: intensity, range: (min, max), defaultValue: 0) + .onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + self.filterIntensity = edit.filters.contrast?.value ?? 0 + } + + func valueChanged() { + + let value = self.filterIntensity + + guard value != 0 else { + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.contrast = nil })) + return + } + + var f = FilterContrast() + f.value = value + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.contrast = f })) + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/ExposureControl.swift b/colorful-room/PhotoEditor/Components/Controls/ExposureControl.swift new file mode 100644 index 0000000..3cb05a5 --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/ExposureControl.swift @@ -0,0 +1,51 @@ +// +// ExposureControl.swift +// colorful-room +// +// Created by macOS on 7/13/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +struct ExposureControl: View { + @State var filterIntensity:Double = 0 + + var body: some View { + + let intensity = Binding( + get: { + self.filterIntensity + }, + set: { + self.filterIntensity = $0 + self.valueChanged() + } + ) + let min = FilterExposure.range.min + let max = FilterExposure.range.max + return FilterSlider(value: intensity, range: (min, max), defaultValue: 0) + .onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + self.filterIntensity = edit.filters.exposure?.value ?? 0 + } + + func valueChanged() { + + let value = self.filterIntensity + + guard value != 0 else { + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.exposure = nil })) + return + } + + var f = FilterExposure() + f.value = value + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.exposure = f })) + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/FadeControl.swift b/colorful-room/PhotoEditor/Components/Controls/FadeControl.swift new file mode 100644 index 0000000..b82a3f4 --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/FadeControl.swift @@ -0,0 +1,50 @@ +// +// FadeControl.swift +// colorful-room +// +// Created by macOS on 7/8/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +struct FadeControl: View { + + @State var filterIntensity:Double = 0 + + var body: some View { + + let intensity = Binding( + get: { + self.filterIntensity + }, + set: { + self.filterIntensity = $0 + self.valueChanged() + } + ) + return FilterSlider(value: intensity, range: (0, 1), defaultValue: 0, rangeDisplay: (0, 100)).onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + self.filterIntensity = edit.filters.fade?.intensity ?? 0 + } + + func valueChanged() { + + let value = self.filterIntensity + + guard value != 0 else { + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.fade = nil })) + return + } + + + var f = FilterFade() + f.intensity = value + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.fade = f })) + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/GaussianBlurControl.swift b/colorful-room/PhotoEditor/Components/Controls/GaussianBlurControl.swift new file mode 100644 index 0000000..75ec56a --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/GaussianBlurControl.swift @@ -0,0 +1,51 @@ +// +// GaussianBlurControl.swift +// colorful-room +// +// Created by macOS on 7/13/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +struct GaussianBlurControl: View { + @State var filterIntensity:Double = 0 + + var body: some View { + + let intensity = Binding( + get: { + self.filterIntensity + }, + set: { + self.filterIntensity = $0 + self.valueChanged() + } + ) + let min = FilterGaussianBlur.range.min + let max = FilterGaussianBlur.range.max + return FilterSlider(value: intensity, range: (min, max), defaultValue: 0) + .onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + self.filterIntensity = edit.filters.gaussianBlur?.value ?? 0 + } + + func valueChanged() { + + let value = self.filterIntensity + + guard value != 0 else { + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.gaussianBlur = nil })) + return + } + + var f = FilterGaussianBlur() + f.value = value + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.gaussianBlur = f })) + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/Group/ColorControl.swift b/colorful-room/PhotoEditor/Components/Controls/Group/ColorControl.swift new file mode 100644 index 0000000..3ab071a --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/Group/ColorControl.swift @@ -0,0 +1,94 @@ +// +// BrightnessControl.swift +// colorful-room +// +// Created by macOS on 7/14/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + + +// Group of: +// Fade brightness +// Saturation +// Contrast +struct ColorControl: View { + @State var brightnessIntensity:Double = 0 + @State var saturationIntensity:Double = 0 + @State var contrastIntensity:Double = 0 + + + var body: some View { + + let brightness = Binding( + get: { + self.brightnessIntensity + }, + set: { + self.brightnessIntensity = $0 + self.valueChanged() + } + ) + let saturation = Binding( + get: { + self.saturationIntensity + }, + set: { + self.saturationIntensity = $0 + self.valueChanged() + } + ) + let contrast = Binding( + get: { + self.contrastIntensity + }, + set: { + self.contrastIntensity = $0 + self.valueChanged() + } + ) + + return VStack{ + FilterSlider( + value: brightness, + range: (FilterColor.rangeBrightness.min, FilterColor.rangeBrightness.max), + lable: "Brightness", + defaultValue: 0, + rangeDisplay: (-100, 100), + spacing: 8) + FilterSlider( + value: saturation, + range: (FilterColor.rangeSaturation.min, FilterColor.rangeSaturation.max), + lable: "Saturation", + defaultValue: 1, + rangeDisplay: (-100, 100), + spacing: 8) + FilterSlider( + value: contrast, + range: (FilterColor.rangeContrast.min, FilterColor.rangeContrast.max), + lable: "Contrast", + defaultValue: 1, + rangeDisplay: (-100, 100), + spacing: 8) + } + .onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + self.brightnessIntensity = edit.filters.color?.valueBrightness ?? 0 + self.saturationIntensity = edit.filters.color?.valueSaturation ?? 1 + self.contrastIntensity = edit.filters.color?.valueContrast ?? 1 + } + + func valueChanged() { + var f = FilterColor() + f.valueBrightness = self.brightnessIntensity + f.valueSaturation = self.saturationIntensity + f.valueContrast = self.contrastIntensity + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.color = f })) + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/Group/ToneControl.swift b/colorful-room/PhotoEditor/Components/Controls/Group/ToneControl.swift new file mode 100644 index 0000000..81b3680 --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/Group/ToneControl.swift @@ -0,0 +1,81 @@ +// +// ToneControl.swift +// colorful-room +// +// Created by macOS on 7/17/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +// Group of +// Highlihgt +// Shadow +struct ToneControl: View { + + @State var highlightIntensity:Double = 0 + + @State var shadowIntensity:Double = 0 + + var body: some View { + + let highlight = Binding( + get: { + self.highlightIntensity + }, + set: { + self.highlightIntensity = $0 + self.valueHighlightChanged() + } + ) + let shadow = Binding( + get: { + self.shadowIntensity + }, + set: { + self.shadowIntensity = $0 + self.valueShadowChanged() + } + ) + return VStack(spacing: 24){ + FilterSlider(value: highlight, range: (FilterHighlights.range.min, FilterHighlights.range.max), lable: "Highlights", defaultValue: 0, rangeDisplay: (0, 100), spacing: 8) + + FilterSlider(value: shadow, range: (FilterShadows.range.min, FilterShadows.range.max),lable: "Shadows", defaultValue: 0, spacing: 8) + } + .onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + self.highlightIntensity = edit.filters.highlights?.value ?? 0 + self.shadowIntensity = edit.filters.shadows?.value ?? 0 + + } + + func valueHighlightChanged() { + + let value = self.highlightIntensity + guard value != 0 else { + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.highlights = nil })) + return + } + + var f = FilterHighlights() + f.value = value + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.highlights = f })) + } + func valueShadowChanged() { + + let value = self.shadowIntensity + guard value != 0 else { + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.shadows = nil })) + return + } + + var f = FilterShadows() + f.value = value + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.shadows = f })) + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/Group/WhiteBalanceControl.swift b/colorful-room/PhotoEditor/Components/Controls/Group/WhiteBalanceControl.swift new file mode 100644 index 0000000..fb14338 --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/Group/WhiteBalanceControl.swift @@ -0,0 +1,71 @@ +// +// TemperatureControl.swift +// colorful-room +// +// Created by macOS on 7/8/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +// Group of +// Temperature +// Tint +struct WhiteBalanceControl: View { + + @State var temperatureIntensity:Double = 0 + @State var tintIntensity:Double = 0 + + var body: some View { + + let temperature = Binding( + get: { + self.temperatureIntensity + }, + set: { + self.temperatureIntensity = $0 + self.valueChanged() + } + ) + let tint = Binding( + get: { + self.tintIntensity + }, + set: { + self.tintIntensity = $0 + self.valueChanged() + } + ) + return VStack(spacing: 24){ + FilterSlider(value: temperature, range: (FilterWhiteBalance.range.min, FilterWhiteBalance.range.max), lable: "Temperature", defaultValue: 0, spacing: 8) + + FilterSlider(value: tint, range: (FilterWhiteBalance.range.min, FilterWhiteBalance.range.max),lable: "Tint", defaultValue: 0, spacing: 8) + } + .onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + self.temperatureIntensity = edit.filters.whiteBalance?.valueTemperature ?? 0 + self.tintIntensity = edit.filters.whiteBalance?.valueTint ?? 0 + + } + + func valueChanged() { + + let valueTemperature = self.temperatureIntensity + + let valueTint = self.tintIntensity + if (valueTemperature == 0 && valueTint == 0) { + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.whiteBalance = nil })) + return + } + + var f = FilterWhiteBalance() + f.valueTint = valueTint + f.valueTemperature = valueTemperature + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.whiteBalance = f })) + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/HLS/ColorSelectButton.swift b/colorful-room/PhotoEditor/Components/Controls/HLS/ColorSelectButton.swift new file mode 100644 index 0000000..e9ad54c --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/HLS/ColorSelectButton.swift @@ -0,0 +1,44 @@ +// +// ColorSelectButton.swift +// colorful-room +// +// Created by macOS on 7/8/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct ColorSelectButton: View { + + var index:Int + @Binding var current:Int + + var body: some View { + + let color = HLSControl.colors[index] + return ZStack{ + if(current == index){ + Button(action: action){ + Circle() + .fill(color) + .frame(width: 18, height: 18) + .padding(.all, 4) + .overlay( + Circle() + .stroke(color, lineWidth: 2) + ) + } + }else{ + Button(action: action){ + Circle() + .fill(color) + .frame(width: 24, height: 24) + } + } + } + + } + func action(){ + self.current = self.index + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/HLS/HLSControl.swift b/colorful-room/PhotoEditor/Components/Controls/HLS/HLSControl.swift new file mode 100644 index 0000000..ceb672e --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/HLS/HLSControl.swift @@ -0,0 +1,119 @@ +// +// HLSControl.swift +// colorful-room +// +// Created by macOS on 7/8/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +struct HLSControl: View { + + static var colors:[Color] = [ + Color(red: 0.8980392156862745, green: 0, blue: 0), + Color(red: 0.9764705882352941, green: 0.45098039215686275, blue: 0.023529411764705882), + Color(red: 1, green: 1, blue: 0.0784313725490196), + Color(red: 0.08235294117647059, green: 0.6901960784313725, blue: 0.10196078431372549), + Color(red: 0.07450980392156863, green: 0.9176470588235294, blue: 0.788235294117647), + Color(red: 0.011764705882352941, green: 0.2627450980392157, blue: 0.8745098039215686), + Color(red: 0.49411764705882355, green: 0.11764705882352941, blue: 0.611764705882353), + Color(red: 0.7607843137254902, green: 0, blue: 0.47058823529411764), + ] + + @State var current:Int = 0 + @State var hue:Double = 0.5 + @State var saturation:Double = 0.5 + @State var luminance:Double = 0.5 + + @State var inputShift:[CIVector]! + + var body: some View { + + let indexCurent = Binding( + get: { + self.current + }, + set: { + self.current = $0 + self.colorChange() + } + ) + + let intensityHeu = Binding( + get: { + self.hue + }, + set: { + self.hue = $0 + self.valueChanged() + } + ) + let intensitySaturation = Binding( + get: { + self.saturation + }, + set: { + self.saturation = $0 + self.valueChanged() + } + ) + let intensityLuminance = Binding( + get: { + self.luminance + }, + set: { + self.luminance = $0 + self.valueChanged() + } + ) + + return VStack(alignment: .leading){ + HStack{ + ForEach(0...HLSControl.colors.count - 1, id: \.self){index in + HStack{ + Spacer() + ColorSelectButton(index: index, current: indexCurent) + Spacer() + } + } + } + Spacer() + FilterSlider(value: intensityHeu, range: (-0.2, 0.2), lable: "Hue", defaultValue: 0) + FilterSlider(value: intensitySaturation, range: (0, 2), lable: "Saturation", defaultValue: 1) + FilterSlider(value: intensityLuminance, range: (0.5, 1.5), lable: "Luminance", defaultValue: 1) + Spacer() + } + .onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + guard let hsv:FilterHLS = edit.filters.hls else{ + self.inputShift = FilterHLS.defaultValue + print("hsv NULL") + colorChange() + return + } + self.inputShift = hsv.inputShift + colorChange() + + } + + func colorChange(){ + print("colorChange") + let currentShift:CIVector = self.inputShift[current] + hue = Double(currentShift.x) + saturation = Double(currentShift.y) + luminance = Double(currentShift.z) + } + + func valueChanged() { + self.inputShift[current] = CIVector(x: CGFloat(hue), y: CGFloat(saturation), z: CGFloat(luminance)) + + var hsv = FilterHLS() + hsv.inputShift = self.inputShift + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.hls = hsv })) + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/HighlightsControl.swift b/colorful-room/PhotoEditor/Components/Controls/HighlightsControl.swift new file mode 100644 index 0000000..e68db99 --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/HighlightsControl.swift @@ -0,0 +1,51 @@ +// +// HighlightsControl.swift +// colorful-room +// +// Created by macOS on 7/13/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +struct HighlightsControl: View { + @State var filterIntensity:Double = 0 + + var body: some View { + + let intensity = Binding( + get: { + self.filterIntensity + }, + set: { + self.filterIntensity = $0 + self.valueChanged() + } + ) + let min = FilterHighlights.range.min + let max = FilterHighlights.range.max + return FilterSlider(value: intensity, range: (min, max), defaultValue: 0, rangeDisplay: (0, 100)) + .onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + self.filterIntensity = edit.filters.highlights?.value ?? 0 + } + + func valueChanged() { + + let value = self.filterIntensity + + guard value != 0 else { + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.highlights = nil })) + return + } + + var f = FilterHighlights() + f.value = value + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.highlights = f })) + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/SaturationControl.swift b/colorful-room/PhotoEditor/Components/Controls/SaturationControl.swift new file mode 100644 index 0000000..aedbc2f --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/SaturationControl.swift @@ -0,0 +1,52 @@ +// +// SaturationControl.swift +// colorful-room +// +// Created by macOS on 7/14/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +struct SaturationControl: View { + @State var filterIntensity:Double = 0 + + var body: some View { + + let intensity = Binding( + get: { + self.filterIntensity + }, + set: { + self.filterIntensity = $0 + self.valueChanged() + } + ) + let min = FilterSaturation.range.min + let max = FilterSaturation.range.max + return FilterSlider(value: intensity, range: (min, max), lable: "Saturation", defaultValue: 0) + .onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + self.filterIntensity = edit.filters.saturation?.value ?? 0 + } + + func valueChanged() { + + let value = self.filterIntensity + + guard value != 0 else { + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.saturation = nil })) + return + } + + + var f = FilterSaturation() + f.value = value + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.saturation = f })) + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/ShadowsControl.swift b/colorful-room/PhotoEditor/Components/Controls/ShadowsControl.swift new file mode 100644 index 0000000..5a605aa --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/ShadowsControl.swift @@ -0,0 +1,51 @@ +// +// ShadowsControl.swift +// colorful-room +// +// Created by macOS on 7/13/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +struct ShadowsControl: View { + @State var filterIntensity:Double = 0 + + var body: some View { + + let intensity = Binding( + get: { + self.filterIntensity + }, + set: { + self.filterIntensity = $0 + self.valueChanged() + } + ) + let min = FilterShadows.range.min + let max = FilterShadows.range.max + return FilterSlider(value: intensity, range: (min, max), defaultValue: 0) + .onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + self.filterIntensity = edit.filters.shadows?.value ?? 0 + } + + func valueChanged() { + + let value = self.filterIntensity + + guard value != 0 else { + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.shadows = nil })) + return + } + + var f = FilterShadows() + f.value = value + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.shadows = f })) + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/SharpenControl.swift b/colorful-room/PhotoEditor/Components/Controls/SharpenControl.swift new file mode 100644 index 0000000..f2b1bc7 --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/SharpenControl.swift @@ -0,0 +1,74 @@ +// +// SharpenControl.swift +// colorful-room +// +// Created by macOS on 7/13/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +struct SharpenControl: View { + @State var sharpnessIntensity:Double = 0 + @State var radiusIntensity:Double = 0 + + + var body: some View { + + let sharpness = Binding( + get: { + self.sharpnessIntensity + }, + set: { + self.sharpnessIntensity = $0 + self.valueChanged() + } + ) + let radius = Binding( + get: { + self.radiusIntensity + }, + set: { + self.radiusIntensity = $0 + self.valueChanged() + } + ) + return VStack{ + FilterSlider( + value: sharpness, + range: (FilterSharpen.Params.sharpness.min, FilterSharpen.Params.sharpness.max), + lable: "Sharpness", + defaultValue: 0, + rangeDisplay: (0, 100), + spacing: 8) + FilterSlider( + value: radius, + range: (FilterSharpen.Params.radius.min, FilterSharpen.Params.radius.max), + lable: "Radius", + defaultValue: 0, + spacing: 8) + } + .onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + self.sharpnessIntensity = edit.filters.sharpen?.sharpness ?? 0 + self.radiusIntensity = edit.filters.sharpen?.radius ?? 10 + } + + func valueChanged() { + + guard self.sharpnessIntensity != 0 else { + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.sharpen = nil })) + return + } + + var f = FilterSharpen() + f.sharpness = self.sharpnessIntensity + f.radius = self.radiusIntensity + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.sharpen = f })) + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/TemperatureControl.swift b/colorful-room/PhotoEditor/Components/Controls/TemperatureControl.swift new file mode 100644 index 0000000..09d0267 --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/TemperatureControl.swift @@ -0,0 +1,52 @@ +// +// TemperatureControl.swift +// colorful-room +// +// Created by macOS on 7/8/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +struct TemperatureControl: View { + + @State var filterIntensity:Double = 0 + + var body: some View { + + let intensity = Binding( + get: { + self.filterIntensity + }, + set: { + self.filterIntensity = $0 + self.valueChanged() + } + ) + let min = FilterTemperature.range.min + let max = FilterTemperature.range.max + return FilterSlider(value: intensity, range: (min, max), defaultValue: 0) + .onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + self.filterIntensity = edit.filters.temperature?.value ?? 0 + + } + + func valueChanged() { + + let value = self.filterIntensity + guard value != 0 else { + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.temperature = nil })) + return + } + + var f = FilterTemperature() + f.value = value + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.temperature = f })) + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/UnsharpMaskControl.swift b/colorful-room/PhotoEditor/Components/Controls/UnsharpMaskControl.swift new file mode 100644 index 0000000..349f75d --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/UnsharpMaskControl.swift @@ -0,0 +1,74 @@ +// +// SharpenControl.swift +// colorful-room +// +// Created by macOS on 7/13/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +struct UnsharpMaskControl: View { + @State var valueIntensity:Double = 0 + @State var radiusIntensity:Double = 0 + + + var body: some View { + + let value = Binding( + get: { + self.valueIntensity + }, + set: { + self.valueIntensity = $0 + self.valueChanged() + } + ) + let radius = Binding( + get: { + self.radiusIntensity + }, + set: { + self.radiusIntensity = $0 + self.valueChanged() + } + ) + return VStack{ + FilterSlider( + value: value, + range: (FilterUnsharpMask.Params.intensity.min, FilterUnsharpMask.Params.intensity.max), + lable: "Intensity", + defaultValue: 0, + rangeDisplay: (0, 100), + spacing: 8) + FilterSlider( + value: radius, + range: (FilterUnsharpMask.Params.radius.min, FilterUnsharpMask.Params.radius.max), + lable: "Radius", + defaultValue: 0, + rangeDisplay: (0, 20), + spacing: 8) + } + .onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + self.valueIntensity = edit.filters.unsharpMask?.intensity ?? 0 + self.radiusIntensity = edit.filters.unsharpMask?.radius ?? 0.5 + } + + func valueChanged() { + + guard self.radiusIntensity != 0 || self.valueIntensity != 0 else { + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.sharpen = nil })) + return + } + + var f = FilterUnsharpMask() + f.intensity = self.valueIntensity + f.radius = self.radiusIntensity + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.unsharpMask = f })) + } +} diff --git a/colorful-room/PhotoEditor/Components/Controls/VignetteControl.swift b/colorful-room/PhotoEditor/Components/Controls/VignetteControl.swift new file mode 100644 index 0000000..927c5ac --- /dev/null +++ b/colorful-room/PhotoEditor/Components/Controls/VignetteControl.swift @@ -0,0 +1,50 @@ +// +// VignetteControl.swift +// colorful-room +// +// Created by macOS on 7/13/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +struct VignetteControl: View { + @State var filterIntensity:Double = 0 + + var body: some View { + + let intensity = Binding( + get: { + self.filterIntensity + }, + set: { + self.filterIntensity = $0 + self.valueChanged() + } + ) + let min = FilterVignette.range.min + let max = FilterVignette.range.max + return FilterSlider(value: intensity, range: (min, max), defaultValue: 0) + .onAppear(perform: didReceiveCurrentEdit) + } + + func didReceiveCurrentEdit() { + let edit: EditingStack.Edit = PECtl.shared.edit.currentEdit + self.filterIntensity = edit.filters.vignette?.value ?? 0 + } + + func valueChanged() { + + let value = self.filterIntensity + + guard value != 0 else { + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.vignette = nil })) + return + } + + var f = FilterVignette() + f.value = value + PECtl.shared.didReceive(action: PECtl.Action.setFilter({ $0.vignette = f })) + } +} diff --git a/colorful-room/PhotoEditor/Components/EditMenuView.swift b/colorful-room/PhotoEditor/Components/EditMenuView.swift new file mode 100644 index 0000000..2c2f811 --- /dev/null +++ b/colorful-room/PhotoEditor/Components/EditMenuView.swift @@ -0,0 +1,61 @@ +// +// EditMenuControlView.swift +// colorful-room +// +// Created by macOS on 7/8/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct EditMenuView: View { + + @EnvironmentObject var shared:PECtl + + @State var currentView:EditView = .lut + + var body: some View { + GeometryReader { geometry in + VStack{ + if((self.currentView == .filter && self.shared.index != .none) == false && self.shared.editingLut == false){ + HStack(spacing: 48){ + Button(action:{ + self.currentView = .lut + }){ + IconButton(self.currentView == .lut ? "edit-lut-highlight" : "edit-lut") + } + Button(action:{ + self.currentView = .filter + self.shared.didReceive(action: PECtl.Action.commit) + }){ + IconButton(self.currentView == .filter ? "edit-color-highlight" : "edit-color") + } + Button(action:{ + self.shared.didReceive(action: PECtl.Action.undo) + }){ + IconButton("icon-undo") + } + } + .frame(width: geometry.size.width, height: 50) + .background(Color.myPanel) + } + Spacer() + ZStack{ + if(self.currentView == .filter){ + FilterMenuUI() + } + if(self.currentView == .lut){ + LutMenuUI() + } + } + Spacer() + } + } + } + +} + +public enum EditView{ + case lut + case filter +} diff --git a/colorful-room/PhotoEditor/Components/FilterMenuUI.swift b/colorful-room/PhotoEditor/Components/FilterMenuUI.swift new file mode 100644 index 0000000..abcaaad --- /dev/null +++ b/colorful-room/PhotoEditor/Components/FilterMenuUI.swift @@ -0,0 +1,103 @@ +// +// FilterMenuView.swift +// colorful-room +// +// Created by macOS on 7/14/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct FilterMenuUI: View { + + @EnvironmentObject var shared:PECtl + + var body: some View { + ZStack{ + + VStack{ + Spacer() + ScrollView(.horizontal, showsIndicators: false){ + HStack(spacing: 16){ + Spacer().frame(width: 0) + ForEach(shared.filters, id: \.name) { filter in + ButtonView(action: filter) + } + Spacer().frame(width: 0) + } + } + Spacer() + Text("Edit Color") + .font(.system(size: 14, weight: .regular)) + .foregroundColor(Color.myGrayLight) + .padding(.bottom, 8) + } + if shared.currentFilter.edit != .none { + VStack{ + Spacer() + if shared.index == .color { + ColorControl() + }else if shared.index == .contrast { + ContrastControl() + }else if shared.index == .vignette { + VignetteControl() + }else if shared.index == .fade { + FadeControl() + }else if shared.index == .highlights { + HighlightsControl() + }else if shared.index == .hls { + HLSControl() + }else if shared.index == .exposure { + ExposureControl() + }else if shared.index == .saturation { + SaturationControl() + }else if shared.index == .shadows { + ShadowsControl() + }else if shared.index == .sharpen { + SharpenControl() + }else if shared.index == .temperature { + TemperatureControl() + }else if shared.index == .vignette { + VignetteControl() + }else if shared.index == .tone { + ToneControl() + }else if shared.index == .clarity { + UnsharpMaskControl() + }else if shared.index == .white_balance { + WhiteBalanceControl() + }else if shared.index == .gaussianBlur { + GaussianBlurControl() + }else{ + Text("Todo") + } + + Spacer() + HStack{ + Button(action: { + self.shared.didReceive(action: PECtl.Action.revert) + self.shared.currentFilter = noneFilterModel + }){ + Image(systemName: "xmark") + .foregroundColor(.white) + } + Spacer() + Text(shared.currentFilter.name) + .font(.system(size: 14, weight: .regular)) + .foregroundColor(Color.myGrayLight) + Spacer() + Button(action: { + self.shared.didReceive(action: PECtl.Action.commit) + self.shared.currentFilter = noneFilterModel + }){ + Image(systemName: "checkmark") + .foregroundColor(.white) + } + }.padding(.bottom, 8) + } + .padding(.horizontal) + .background(Color.myBackground) + } + + } + } +} diff --git a/colorful-room/PhotoEditor/Components/LutMenuUI.swift b/colorful-room/PhotoEditor/Components/LutMenuUI.swift new file mode 100644 index 0000000..0005af7 --- /dev/null +++ b/colorful-room/PhotoEditor/Components/LutMenuUI.swift @@ -0,0 +1,118 @@ +// +// LutMenuUI.swift +// colorful-room +// +// Created by macOS on 7/14/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct LutMenuUI: View { + + @EnvironmentObject var shared:PECtl + + var body: some View { + + let all:Bool = shared.currentCollection.isEmpty + return ZStack{ + VStack{ + Spacer() + ScrollView(.horizontal, showsIndicators: false){ + if(shared.loadingLut || shared.cubeSourceCG == nil){ + HStack(spacing: 12){ + Spacer().frame(width: 0) + LutLoadingButton(name: "Original", on: true) + Rectangle() + .fill(Color.myDivider) + .frame(width: 1, height: 92) + LutLoadingButton(name: "LUT 1", on: false) + LutLoadingButton(name: "LUT 2", on: false) + LutLoadingButton(name: "LUT 3", on: false) + LutLoadingButton(name: "LUT 4", on: false) + Spacer().frame(width: 0) + } + }else{ + HStack(spacing: 12){ + Spacer().frame(width: 0) + if(all){ + NeutralButton(image: UIImage(cgImage: shared.cubeSourceCG!)) + } + + if(all){ + ForEach(shared.collections, id: \.identifier) { collection in + HStack(spacing: 12){ + if(collection.cubePreviews.isEmpty == false){ + Rectangle() + .fill(Color.myDivider) + .frame(width: 1, height: 92) + } + ForEach(collection.cubePreviews, id: \.filter.identifier) { cube in + LUTButton(cube: cube) + } + } + } + }else{ + HStack(spacing: 12){ + //Rectangle().fill(Color.myDivider).frame(width: 1, height: 92) + ForEach(shared.cubes, id: \.filter.identifier) { cube in + LUTButton(cube: cube) + } + } + } + + Spacer().frame(width: 0) + } + } + } + Spacer() + ScrollView(.horizontal, showsIndicators: false){ + HStack(spacing: 16){ + Spacer().frame(width: 0) + CollectionButton(name: "All Luts", key: "") + + ForEach(shared.collections, id: \.identifier) { collection in + CollectionButton(name: collection.name, key: collection.identifier) + } + + Spacer().frame(width: 0) + } + }.padding(.bottom, 8) + } + if(self.shared.editingLut){ + VStack{ + Spacer() + ColorCubeControl() + .padding() + Spacer() + HStack{ + Button(action: { + self.shared.didReceive(action: PECtl.Action.revert) + self.shared.editingLut = false + }){ + Image(systemName: "xmark") + .foregroundColor(.white) + } + Spacer() + Text(self.shared.edit.currentEdit.filters.colorCube?.name ?? "Lut") + .font(.system(size: 14, weight: .regular)) + .foregroundColor(Color.myGrayLight) + Spacer() + Button(action: { + self.shared.didReceive(action: PECtl.Action.commit) + self.shared.editingLut = false + }){ + Image(systemName: "checkmark") + .foregroundColor(.white) + } + } + .padding(.horizontal) + .padding(.bottom, 8) + } + .background(Color.black) + } + } + } + + func scrollToSelectedItem(animated: Bool) {} +} diff --git a/colorful-room/PhotoEditor/Components/UI/ButtonView.swift b/colorful-room/PhotoEditor/Components/UI/ButtonView.swift new file mode 100644 index 0000000..341ace7 --- /dev/null +++ b/colorful-room/PhotoEditor/Components/UI/ButtonView.swift @@ -0,0 +1,31 @@ +// +// ButtonView.swift +// colorful-room +// +// Created by macOS on 7/8/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct ButtonView: View { + + var action:FilterModel + + @EnvironmentObject var shared:PECtl + + var body: some View { + Button(action: { + self.shared.currentFilter = self.action + }){ + VStack(spacing: 4){ + IconButton(self.action.image, size: 36) + Text(self.action.name) + .font(.system(size: 10, weight: .regular)) + .foregroundColor(Color.myGrayLight) + .padding(.top) + } + .frame(minWidth: 75) + } + } +} diff --git a/colorful-room/PhotoEditor/Components/UI/CollectionButton.swift b/colorful-room/PhotoEditor/Components/UI/CollectionButton.swift new file mode 100644 index 0000000..ef9c06d --- /dev/null +++ b/colorful-room/PhotoEditor/Components/UI/CollectionButton.swift @@ -0,0 +1,27 @@ +// +// CollectionButton.swift +// colorful-room +// +// Created by macOS on 7/16/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct CollectionButton: View { + var name:String + var key:String + + @EnvironmentObject var shared:PECtl + + var body: some View { + let on:Bool = shared.currentCollection == key + return Button(action:{ + self.shared.currentCollection = self.key + }){ + Text(name) + .font(.system(size: 14, weight: .regular)) + .foregroundColor(on ? Color.myGrayLight : Color.myGrayDark) + } + } +} diff --git a/colorful-room/PhotoEditor/Components/UI/CustomSlider.swift b/colorful-room/PhotoEditor/Components/UI/CustomSlider.swift new file mode 100644 index 0000000..2f8d6ea --- /dev/null +++ b/colorful-room/PhotoEditor/Components/UI/CustomSlider.swift @@ -0,0 +1,186 @@ +// +// CustomSlider.swift +// colorful-room +// +// Created by macOS on 7/15/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + + + +struct FilterSlider : View { + + var lable:String + var defaultValue:Double + @Binding var value:Double + var range: (Double, Double) + var rangeDisplay: (Double, Double) + var spacing:CGFloat + + init(value: Binding, range: (Double, Double) = (0, 1), lable:String = "", defaultValue:Double = 0, rangeDisplay: (Double, Double) = (-100, 100), spacing:CGFloat = 4) + { + _value = value + self.range = range + self.lable = lable + self.defaultValue = defaultValue + self.rangeDisplay = rangeDisplay + self.spacing = spacing + } + + var body: some View{ + + VStack(spacing: spacing){ + HStack{ + if(lable.isEmpty == false){ + Text(lable) + } + Spacer() + Text(displayValue(self.value)) + }.font(.system(size: 11, weight: .regular)) + CustomSlider(value: $value, defaultValue: defaultValue ,range: range) { modifiers in + ZStack { + Color.myGray.cornerRadius(1).frame(height: 1).modifier(modifiers.bar) + Circle() + .fill(Color.myGrayLight) + .frame(height: 5) + .modifier(modifiers.defaultDot) + ZStack { + Circle() + .fill(Color.myBackground) + Circle().stroke(Color.myGrayLight, lineWidth: 1) + + }.modifier(modifiers.knob) + } + }.frame(height: 20) + } + } + + func displayValue(_ value:Double) -> String{ + return String(format: "%.0f", value.convert(fromRange: range, toRange: rangeDisplay)) + } +} + +struct CustomSlider : View { + + @Binding var value: Double + var defaultValue:Double + var range: (Double, Double) + var knobWidth: CGFloat? + var dotWidth: CGFloat = 5 + let viewBuilder: (CustomSliderComponents) -> Component + + init(value: Binding, defaultValue:Double = 0,range: (Double, Double), knobWidth: CGFloat? = nil, + _ viewBuilder: @escaping (CustomSliderComponents) -> Component + ) { + _value = value + self.range = range + self.viewBuilder = viewBuilder + self.knobWidth = knobWidth + self.defaultValue = defaultValue + } + + var body: some View { + return GeometryReader { geometry in + self.view(geometry: geometry) // function below + } + } + + + private func view(geometry: GeometryProxy) -> some View { + + let frame = geometry.frame(in: .global) + let drag = DragGesture(minimumDistance: 0).onChanged({ drag in + self.onDragChange(drag, frame) } + ) + let offsetX = self.getOffsetX(frame: frame) + + let offsetDefault = getOffsetXDefaultDot(frame: frame) + + + let knobSize = CGSize(width: knobWidth ?? frame.height, height: frame.height) + let barSize = CGSize(width: frame.width, height: frame.height) +// let barLeftSize = CGSize(width: CGFloat(offsetX + knobSize.width * 0.5), height: frame.height) +// let barRightSize = CGSize(width: frame.width - barLeftSize.width, height: frame.height) + let dotSize = CGSize(width: dotWidth, height: frame.height) + + let modifiers = CustomSliderComponents( + bar: CustomSliderModifier(name: .bar, size: barSize, offset: 0), +// barLeft: CustomSliderModifier(name: .barLeft, size: barLeftSize, offset: 0), +// barRight: CustomSliderModifier(name: .barRight, size: barRightSize, offset: barLeftSize.width), + knob: CustomSliderModifier(name: .knob, size: knobSize, offset: offsetX), + defaultDot: CustomSliderModifier(name: .defaultDot, size: dotSize, offset: offsetDefault) + ) + + + return ZStack { viewBuilder(modifiers).gesture(drag) } + + } + + private func onDragChange(_ drag: DragGesture.Value,_ frame: CGRect) { + let width = (knob: Double(knobWidth ?? frame.size.height), view: Double(frame.size.width)) + let xrange = (min: Double(0), max: Double(width.view - width.knob)) + var value = Double(drag.startLocation.x + drag.translation.width) // knob center x + value -= 0.5*width.knob // offset from center to leading edge of knob + value = value > xrange.max ? xrange.max : value // limit to leading edge + value = value < xrange.min ? xrange.min : value // limit to trailing edge + value = value.convert(fromRange: (xrange.min, xrange.max), toRange: range) + self.value = value + } + + private func getOffsetX(frame: CGRect) -> CGFloat { + let width = (knob: knobWidth ?? frame.size.height, view: frame.size.width) + let xrange: (Double, Double) = (0, Double(width.view - width.knob)) + let result = self.value.convert(fromRange: range, toRange: xrange) + return CGFloat(result) + } + private func getOffsetXDefaultDot(frame: CGRect) -> CGFloat { + let width = (knob: dotWidth, view: frame.size.width) + let xrange: (Double, Double) = (0, Double(width.view - width.knob)) + let result = self.defaultValue.convert(fromRange: range, toRange: xrange) + return CGFloat(result) + } +} + +struct CustomSliderComponents { +// let barLeft: CustomSliderModifier +// let barRight: CustomSliderModifier + + let bar: CustomSliderModifier + let knob: CustomSliderModifier + let defaultDot: CustomSliderModifier +} + +struct CustomSliderModifier: ViewModifier { + enum Name { +// case barLeft +// case barRight + + case bar + case knob + case defaultDot + } + let name: Name + let size: CGSize + let offset: CGFloat + + func body(content: Content) -> some View { + content + .frame(width: size.width) + .position(x: size.width*0.5, y: size.height*0.5) + .offset(x: offset) + } +} + +extension Double { + func convert(fromRange: (Double, Double), toRange: (Double, Double)) -> Double { + // Example: if self = 1, fromRange = (0,2), toRange = (10,12) -> solution = 11 + var value = self + value -= fromRange.0 + value /= Double(fromRange.1 - fromRange.0) + value *= toRange.1 - toRange.0 + value += toRange.0 + return value + } +} diff --git a/colorful-room/PhotoEditor/Components/UI/IconButton.swift b/colorful-room/PhotoEditor/Components/UI/IconButton.swift new file mode 100644 index 0000000..9fe54c6 --- /dev/null +++ b/colorful-room/PhotoEditor/Components/UI/IconButton.swift @@ -0,0 +1,26 @@ +// +// IconButton.swift +// colorful-room +// +// Created by macOS on 7/15/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct IconButton: View { + var image:String + var size:CGFloat + + init(_ image:String, size:CGFloat = 32) { + self.image = image + self.size = size + } + var body: some View { + Image(self.image) + .renderingMode(.original) + .resizable() + .scaledToFit() + .frame(width: self.size, height: self.size) + } +} diff --git a/colorful-room/PhotoEditor/Components/UI/LUTButton.swift b/colorful-room/PhotoEditor/Components/UI/LUTButton.swift new file mode 100644 index 0000000..69526d4 --- /dev/null +++ b/colorful-room/PhotoEditor/Components/UI/LUTButton.swift @@ -0,0 +1,107 @@ +// +// LUTButton.swift +// colorful-room +// +// Created by macOS on 7/14/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI +import PixelEngine + +struct LUTButton: View { + + var cube:PreviewFilterColorCube + + @EnvironmentObject var shared:PECtl + + var body: some View { + let on = shared.currentCube == cube.filter.identifier + + return Button(action:{ + if(on){ + self.editAmong() + }else{ + self.valueChanged() + } + }){ + VStack(spacing: 0){ + Image(uiImage: UIImage(cgImage: cube.cgImage)) + .renderingMode(.original) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 68, height: 68) + .clipped() + Text(cube.filter.name) + .font(.system(size: 11, weight: .medium)) + .frame(width: 68, height: 24) + .background(on ? Color.myPrimary : Color.myButtonDark) + .foregroundColor(.white) + + } + .frame(width: 68) + } + } + + func valueChanged() { + shared.currentCube = cube.filter.identifier + shared.didReceive(action: PECtl.Action.setFilter({ $0.colorCube = self.cube.filter })) + } + func editAmong(){ + self.shared.editingLut = true + } +} + +struct NeutralButton: View { + + var image: UIImage + @EnvironmentObject var shared:PECtl + + var body: some View { + let on = shared.currentCube.isEmpty + + return Button(action:valueChanged){ + VStack(spacing: 0){ + Image(uiImage: image) + .renderingMode(.original) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 68, height: 68) + .clipped() + + Text("Original") + .font(.system(size: 11, weight: .medium)) + .frame(width: 68, height: 24) + .background(on ? Color.myPrimary : Color.myButtonDark) + .foregroundColor(.white) + } + } + } + func valueChanged() { + shared.currentCube = "" + shared.didReceive(action: PECtl.Action.setFilter({ $0.colorCube = nil })) + } +} + + +struct LutLoadingButton: View { + + var name:String + var on:Bool + + var body: some View { + return VStack(spacing: 0){ + Rectangle() + .fill(Color.myGrayDark) + .frame(width: 68, height: 68) + + Text(name) + .font(.system(size: 11, weight: .medium)) + .frame(width: 68, height: 24) + .background(on ? Color.myPrimary : Color.myButtonDark) + .foregroundColor(.white) + } + } + + +} diff --git a/colorful-room/PhotoEditor/EditMenu.swift b/colorful-room/PhotoEditor/EditMenu.swift new file mode 100644 index 0000000..a06d4b2 --- /dev/null +++ b/colorful-room/PhotoEditor/EditMenu.swift @@ -0,0 +1,49 @@ +// +// EditMenu.swift +// colorful-room +// +// Created by Ping9 on 8/13/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import Foundation + +public enum EditMenu { + case none + 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 + case color + case hls + case tone + case white_balance +} + +var noneFilterModel:FilterModel = FilterModel("", edit: EditMenu.none) + +class FilterModel{ + + var name:String + var image:String + var edit:EditMenu + + init(_ name:String, image:String = "", edit:EditMenu) { + self.name = name + if(image.isEmpty){ + self.image = name.lowercased() + }else{ + self.image = image + } + self.edit = edit + } +} diff --git a/colorful-room/PhotoEditor/PhotoEditorController.swift b/colorful-room/PhotoEditor/PhotoEditorController.swift new file mode 100644 index 0000000..0c4bb15 --- /dev/null +++ b/colorful-room/PhotoEditor/PhotoEditorController.swift @@ -0,0 +1,152 @@ +// +// PhotoEditorController.swift +// colorful-room +// +// Created by macOS on 7/8/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import Foundation +import Combine +import SwiftUI +import PixelEngine + +class PECtl : ObservableObject{ + public enum Action { + case setFilter((inout EditingStack.Edit.Filters) -> Void) + case commit + case revert + case undo + } + + static var shared = PECtl() + init() { + print("init PECtl") + collections = [] + } + + + var originUI:UIImage! + var originCI:CIImage! + + var filters:[FilterModel] = [ + FilterModel("Brightness", edit: EditMenu.exposure), + FilterModel("Contrast", edit: EditMenu.contrast), + FilterModel("Saturation", edit: EditMenu.saturation), + FilterModel("HSL",image: "hls", edit: EditMenu.hls), + FilterModel("White Blance",image:"temperature", edit: EditMenu.white_balance), + FilterModel("Tone",image: "tone", edit: EditMenu.tone), + FilterModel("Fade", edit: EditMenu.fade), + FilterModel("Vignette", edit: EditMenu.vignette), + FilterModel("Sharpen", edit: EditMenu.sharpen), + FilterModel("Unsharp", image: "clarity",edit: EditMenu.clarity), + FilterModel("Gaussian Blur", image: "cil_blur",edit: EditMenu.gaussianBlur), + ] + + @Published var image:UIImage? + @Published var currentFilter:FilterModel = noneFilterModel + @Published var editingLut:Bool = false + @Published var loadingLut:Bool = false + var index:EditMenu{ + get{ + return currentFilter.edit + } + } + + var edit: EditingStack! + + // call after pick image + func setImage(image:UIImage) { + /// resetUI + loadingLut = true + currentFilter = noneFilterModel + currentCollection = "" + currentCube = "" + self.image = nil + for collection in collections{ + collection.reset() + } + /// setImage + self.cubeSourceCG = nil + self.originUI = image + collections = Data.shared.collections + DispatchQueue.global(qos: .userInteractive).async { + self.originCI = convertUItoCI(from: image) + self.edit = EditingStack.init( + source: StaticImageSource(source: self.originCI!), + previewSize: CGSize(width: 512, height: 512 * self.originUI.size.width / self.originUI.size.height) + ) + self.apply() + self.initCube() + } + } + + var collections:[Collection] + var cubeSourceCI:CIImage? + var cubeSourceCG:CGImage? + @Published var currentCollection:String = "" + @Published var currentCube:String = "" + var cubes: [PreviewFilterColorCube]{ + get{ + if(currentCollection.isEmpty){ + return [] + } + for collection in collections{ + if(collection.identifier == currentCollection){ + return collection.cubePreviews + } + } + return [] + } + } + + private func initCube(){ + self.cubeSourceCI = resizedImage(at: self.originCI, scale: 128 / self.originUI.size.height, aspectRatio: 1) + self.cubeSourceCG = sharedContext.createCGImage(self.cubeSourceCI!, from: self.cubeSourceCI!.extent)! + for collection in collections { + collection.setImage(image: cubeSourceCI) + } + DispatchQueue.main.async { + self.loadingLut = false + } + } + + @Published var countEdit:Int = 1 + func didReceive(action: PECtl.Action) { + switch action { + case .setFilter(let closure): + edit.set(filters: closure) + case .commit: + if(edit != nil){ + edit.commit() + } + case .undo: + if(edit.canUndo){ + edit.undo() + let name = edit.currentEdit.filters.colorCube?.identifier ?? "" + currentCube = name + } + case .revert: + edit.revert() + } + apply() + } + + private func apply() { + DispatchQueue.main.async { + guard let preview:CIImage = self.edit.previewImage else{ + return + } + if let cgimg = sharedContext.createCGImage(preview, from: preview.extent) { + self.image = UIImage(cgImage: cgimg) + } + self.countEdit = self.edit.edits.count + + } + } + + func exportOrigin() { + let exportFile = self.edit.makeRenderer().render(resolution: .full) + ImageSaver().writeToPhotoAlbum(image: exportFile) + } +} diff --git a/colorful-room/PhotoEditor/PhotoEditorView.swift b/colorful-room/PhotoEditor/PhotoEditorView.swift new file mode 100644 index 0000000..b8176aa --- /dev/null +++ b/colorful-room/PhotoEditor/PhotoEditorView.swift @@ -0,0 +1,31 @@ +// +// PhotoEditorView.swift +// colorful-room +// +// Created by macOS on 7/8/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct PhotoEditorView: View { + + @EnvironmentObject var controller:PECtl + + var body: some View { + + VStack(spacing: 0){ + if(controller.image != nil){ + ImagePreviewView(image: controller.image!) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .clipped() + }else{ + Rectangle() + .fill(Color.myGrayDark) + } + EditMenuView() + .frame(height: 250) + } + + } +} diff --git a/colorful-room/PhotoEditor/Utility/Utility.swift b/colorful-room/PhotoEditor/Utility/Utility.swift new file mode 100644 index 0000000..0e5446a --- /dev/null +++ b/colorful-room/PhotoEditor/Utility/Utility.swift @@ -0,0 +1,56 @@ +// +// Utility.swift +// colorful-room +// +// Created by macOS on 7/16/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import Foundation + + +import UIKit +import CoreImage + +let sharedContext = CIContext(options: [.useSoftwareRenderer : false]) + + +func resizedImage(at image: CIImage, scale: CGFloat, aspectRatio: CGFloat) -> CIImage? { + + let filter = CIFilter(name: "CILanczosScaleTransform") + filter?.setValue(image, forKey: kCIInputImageKey) + filter?.setValue(scale, forKey: kCIInputScaleKey) + filter?.setValue(aspectRatio, forKey: kCIInputAspectRatioKey) + + return filter?.outputImage +} + + +func convertUItoCI(from:UIImage) -> CIImage{ + let image = CIImage(image: from)! + let fixedOriantationImage = image.oriented(forExifOrientation: imageOrientationToTiffOrientation(from.imageOrientation)) + return fixedOriantationImage +} + +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 + default: + return 1 + } +} diff --git a/colorful-room/PhotoEditor/image/ImagePreviewView.swift b/colorful-room/PhotoEditor/image/ImagePreviewView.swift new file mode 100644 index 0000000..958e4c8 --- /dev/null +++ b/colorful-room/PhotoEditor/image/ImagePreviewView.swift @@ -0,0 +1,28 @@ +// +// ImagePreviewView.swift +// colorful-room +// +// Created by macOS on 7/8/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct ImagePreviewView: View { + var image:UIImage + + @State var contentMode:ContentMode = .fit + + var body: some View { + GeometryReader { geo in + Image(uiImage: self.image) + .resizable() + .aspectRatio(contentMode: self.contentMode) + .onTapGesture(count: 2) { + withAnimation{ + self.contentMode = self.contentMode == .fit ? .fill : .fit + } + } + } + } +} diff --git a/colorful-room/Preview Content/Preview Assets.xcassets/Contents.json b/colorful-room/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/colorful-room/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/colorful-room/SceneDelegate.swift b/colorful-room/SceneDelegate.swift new file mode 100644 index 0000000..a4c2a88 --- /dev/null +++ b/colorful-room/SceneDelegate.swift @@ -0,0 +1,74 @@ +// +// SceneDelegate.swift +// colorful-room +// +// Created by macOS on 7/3/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import UIKit +import SwiftUI + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + + // Get the managed object context from the shared persistent container. + let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext + + // Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath. + // Add `@Environment(\.managedObjectContext)` in the views that will need the context. + let contentView = ContentView().environment(\.managedObjectContext, context) + + // Use a UIHostingController as window root view controller. + if let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + window.rootViewController = UIHostingController(rootView: contentView + .environmentObject(PECtl.shared) + .environmentObject(Data.shared) + ) + self.window = window + window.makeKeyAndVisible() + } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + + // Save changes in the application's managed object context when the application transitions to the background. + (UIApplication.shared.delegate as? AppDelegate)?.saveContext() + } + + +} + diff --git a/colorful-room/UI/ActivityIndicator.swift b/colorful-room/UI/ActivityIndicator.swift new file mode 100644 index 0000000..b929ac2 --- /dev/null +++ b/colorful-room/UI/ActivityIndicator.swift @@ -0,0 +1,40 @@ +// +// ActivityIndicator.swift +// colorful-room +// +// Created by macOS on 7/19/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct ActivityIndicator: View { + @State private var isAnimating: Bool = false + + var body: some View { + GeometryReader { (geometry: GeometryProxy) in + ForEach(0..<5) { index in + Group { + Circle() + .frame(width: geometry.size.width / 5, height: geometry.size.height / 5) + .scaleEffect(!self.isAnimating ? 1 - CGFloat(index) / 5 : 0.2 + CGFloat(index) / 5) + .offset(y: geometry.size.width / 10 - geometry.size.height / 2) + }.frame(width: geometry.size.width, height: geometry.size.height) + .rotationEffect(!self.isAnimating ? .degrees(0) : .degrees(360)) + .animation(Animation + .timingCurve(0.5, 0.15 + Double(index) / 5, 0.25, 1, duration: 1.5) + .repeatForever(autoreverses: false)) + } + } + .aspectRatio(1, contentMode: .fit) + .onAppear { + self.isAnimating = true + } + } +} + +struct ActivityIndicator_Previews: PreviewProvider { + static var previews: some View { + ActivityIndicator() + } +} diff --git a/colorful-room/UI/ListTitle.swift b/colorful-room/UI/ListTitle.swift new file mode 100644 index 0000000..db5ceba --- /dev/null +++ b/colorful-room/UI/ListTitle.swift @@ -0,0 +1,70 @@ +// +// ListTitle.swift +// colorful-room +// +// Created by macOS on 7/16/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct ListTitle: View { + + var title:String + var supTitle:String + var leadingImage:String + var highlight:String + + init(title:String, supTitle:String, leadingImage:String, highlight:String = "") { + self.title = title + self.supTitle = supTitle + self.leadingImage = leadingImage + self.highlight = highlight + } + + var body: some View { + HStack(alignment: .top, spacing: 0){ + Image(leadingImage) + .resizable() + .scaledToFit() + .frame(width: 32, height: 32) + .padding(.trailing, 20) + VStack(alignment: .leading, spacing: 4){ + HStack(spacing: 8){ + Text(title) + .font(.system(size: 15, weight: .medium)) + .multilineTextAlignment(.leading) + if(highlight.isEmpty == false){ + Text(highlight) + .font(.system(size: 12, weight: .medium)) + .padding(.horizontal, 8) + .padding(.vertical, 3) + .background(Color.myPrimary) + .cornerRadius(2) + } + Spacer() + } + + Text(supTitle) + .font(.system(size: 13, weight: .regular)) + .foregroundColor(Color.gray) + } + Spacer() + }.padding(.horizontal, 24) + } +} + +struct ListTitle_Previews: PreviewProvider { + static var previews: some View { + Group{ + ListTitle( + title: "Free and Premium Filters", + supTitle: "Export your picture, Lookup image, all effects, and more", + leadingImage: "edit-lut", + highlight: "AR filter" + ) + .background(Color(UIColor.systemBackground)) + .environment(\.colorScheme, .dark) + } + } +} diff --git a/colorful-room/UI/RaiseButton.swift b/colorful-room/UI/RaiseButton.swift new file mode 100644 index 0000000..32682bd --- /dev/null +++ b/colorful-room/UI/RaiseButton.swift @@ -0,0 +1,38 @@ +// +// RaiseButton.swift +// colorful-room +// +// Created by macOS on 7/23/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct RaiseButton: View { + + var title:String + var systemName:String + init(_ title:String, systemName:String = "arrow.down.to.line") { + self.title = title + self.systemName = systemName + } + + var body: some View { + HStack{ + Image(systemName: systemName) + Text(title) + } + .font(.system(size: 14, weight: .medium)) + .foregroundColor(.white) + .padding(.horizontal, 20) + .frame(minWidth: 160, minHeight: 48) + .background(Color.myDivider) + .cornerRadius(24) + } +} + +struct RaiseButton_Previews: PreviewProvider { + static var previews: some View { + RaiseButton("Save LUTs image") + } +} diff --git a/colorful-room/Utility/AppTheme.swift b/colorful-room/Utility/AppTheme.swift new file mode 100644 index 0000000..c8bed74 --- /dev/null +++ b/colorful-room/Utility/AppTheme.swift @@ -0,0 +1,58 @@ +// +// AppTheme.swift +// colorful-room +// +// Created by macOS on 7/15/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import Foundation +import SwiftUI + +extension Color{ + static var myGray:Color{ + get{ + return Color("gray") + } + } + static var myGrayDark:Color{ + get{ + return Color("gray-dark") + } + } + static var myGrayLight:Color{ + get{ + return Color("gray-light") + } + } + static var myPrimary:Color{ + get{ + return Color("primary") + } + } + static var myBackground:Color{ + get{ + return Color("background") + } + } + static var myAccent:Color{ + get{ + return Color("accent") + } + } + static var myButtonDark:Color{ + get{ + return Color("button-dark") + } + } + static var myPanel:Color{ + get{ + return Color("panel") + } + } + static var myDivider:Color{ + get{ + return Color("divider") + } + } +} diff --git a/colorful-room/colorful_room.xcdatamodeld/.xccurrentversion b/colorful-room/colorful_room.xcdatamodeld/.xccurrentversion new file mode 100644 index 0000000..042eba2 --- /dev/null +++ b/colorful-room/colorful_room.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + colorful_room.xcdatamodel + + diff --git a/colorful-room/colorful_room.xcdatamodeld/colorful_room.xcdatamodel/contents b/colorful-room/colorful_room.xcdatamodeld/colorful_room.xcdatamodel/contents new file mode 100644 index 0000000..50d2514 --- /dev/null +++ b/colorful-room/colorful_room.xcdatamodeld/colorful_room.xcdatamodel/contents @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/model/Collection.swift b/model/Collection.swift new file mode 100644 index 0000000..4d6d079 --- /dev/null +++ b/model/Collection.swift @@ -0,0 +1,63 @@ +// +// Collection.swift +// colorful-room +// +// Created by macOS on 7/15/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import Foundation +import PixelEngine +import SwiftUI + +public class Collection { + + public let name: String + public let identifier: String + public var cubeInfos:[FilterColorCubeInfo] + public var cubePreviews:[PreviewFilterColorCube] = [] + public func setImage(image:CIImage?){ + self.cubePreviews = [] + if let cubeSourceCI: CIImage = image + { + for item in cubeInfos { + let cube = FilterColorCube(name: item.name, identifier: item.identifier, lutImage: UIImage(named: item.lutImage)!, dimension: 64); + let preview = PreviewFilterColorCube(sourceImage: cubeSourceCI, filter: cube) + cubePreviews.append(preview) + + } + } + } + public func reset(){ + cubePreviews = [] + } + + public init( + name: String, + identifier: String, + cubeInfos: [FilterColorCubeInfo] = [] + ) { + self.name = name + self.identifier = identifier + self.cubeInfos = cubeInfos + } + +} + + +public struct FilterColorCubeInfo : Equatable { + public let name: String + public let identifier: String + public let lutImage:String + + public init( + name: String, + identifier: String, + lutImage: String + ) { + self.name = name + self.identifier = identifier + self.lutImage = lutImage + } + +} diff --git a/model/Data.swift b/model/Data.swift new file mode 100644 index 0000000..d59e84d --- /dev/null +++ b/model/Data.swift @@ -0,0 +1,64 @@ +// +// EditController.swift +// test +// +// Created by macOS on 7/2/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import Foundation +import SwiftUI +import PixelEngine + +class Data: ObservableObject { + + static var shared = Data(); + + init(){ + + // basic + let basic = Collection(name: "Basic", identifier: "Basic", cubeInfos: []) + for i in 1...10{ + let cube = FilterColorCubeInfo( + name: "A\(i)", + identifier: "basic-\(i)", + lutImage: "lut-\(i)" + ) + basic.cubeInfos.append(cube) + } + // Cinematic + let cinematic = Collection(name: "Cinematic", identifier: "Cinematic", cubeInfos: []) + for i in 1...4{ + let cube = FilterColorCubeInfo( + name: "C\(i)", + identifier: "Cinematic-\(i)", + lutImage: "cinematic-\(i)" + ) + cinematic.cubeInfos.append(cube) + } + // Film + let film = Collection(name: "Film", identifier: "Film", cubeInfos: []) + for i in 1...3{ + let cube = FilterColorCubeInfo( + name: "Film\(i)", + identifier: "Film-\(i)", + lutImage: "film-\(i)" + ) + film.cubeInfos.append(cube) + } + // Selfie Good Skin + let selfie = Collection(name: "Selfie", identifier: "Selfie", cubeInfos: []) + for i in 1...5{ + let cube = FilterColorCubeInfo( + name: "Selfie\(i)", + identifier: "Selfie-\(i)", + lutImage: "selfie-\(i)" + ) + selfie.cubeInfos.append(cube) + } + // init collections + self.collections = [basic, cinematic, film, selfie] + } + + var collections:[Collection] +} diff --git a/model/ImageSaver.swift b/model/ImageSaver.swift new file mode 100644 index 0000000..a656d0b --- /dev/null +++ b/model/ImageSaver.swift @@ -0,0 +1,30 @@ +// +// ImageSaver.swift +// test +// +// Created by macOS on 7/2/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import UIKit +import Combine + +class ImageSaver: NSObject { + + var successHandler: (() -> Void)? + var errorHandler: ((Error) -> Void)? + + func writeToPhotoAlbum(image: UIImage) { + UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveError), nil) + } + + @objc func saveError(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) { + if let error = error { + errorHandler?(error) + print("Oops: \(error.localizedDescription)") + } else { + successHandler?() + print("Success!") + } + } +} diff --git a/view/ExportView.swift b/view/ExportView.swift new file mode 100644 index 0000000..dab635e --- /dev/null +++ b/view/ExportView.swift @@ -0,0 +1,137 @@ +// +// ExportView.swift +// colorful-room +// +// Created by macOS on 7/23/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct ExportView: View { + + @EnvironmentObject var shared:PECtl + @State private var showSheet:Bool = false + @State private var showSuccessPopup = false + + + @Environment(\.presentationMode) var presentationMode: Binding + + var body: some View { + ZStack{ + Color.myBackground + .edgesIgnoringSafeArea(.all) + VStack{ + HStack{ + Spacer() + Button(action:{ + self.presentationMode.wrappedValue.dismiss() + }){ + Image(systemName: "xmark") + .foregroundColor(.white) + } + } + .padding() + .padding(.trailing) + + Text("Export your photo") + .font(.system(size: 26, weight: .semibold)) + .multilineTextAlignment(.center) + Text("You can download all that apply in your filter.\nAlways for FREE") + .font(.system(size: 14)) + .multilineTextAlignment(.center) + .foregroundColor(Color.myGray) + .padding() + + Spacer() + VStack{ + Image(uiImage: self.shared.image!) + .resizable() + .scaledToFit() + .border(Color.white, width: 1) + .padding(.horizontal) + + + Button(action:{ + self.shared.exportOrigin() + self.showSuccessPopup = true + }){ + RaiseButton("Save picture") + } + .padding(.top, 24) + } + .frame(height: 400) + .clipped() + Spacer() + NavigationLink(destination: SupportView() + .navigationBarTitle("") + .navigationBarHidden(true)){ + HStack(alignment: .center, spacing: 16){ + Spacer() + Image("emoji-support") + .renderingMode(.original) + .resizable() + .scaledToFit() + .frame(height: 17) + + Text("SUPPORT OUR TEAM") + Image(systemName: "chevron.right") + Spacer() + } + + .padding() + .foregroundColor(.white) + .font(.system(size: 13)) + } + Spacer().frame(height: 16) + Button(action: { + self.showSheet = true + }){ + VStack{ + HStack(alignment: .center){ + Spacer() + Image(systemName: "hand.point.right") + Text("PRODUCT ROAD MAP") + Image(systemName: "chevron.up") + Spacer() + } + .foregroundColor(Color.myGrayLight) + Text("We ​​are developing for the future") + .foregroundColor(Color.myGray) + } + .font(.system(size: 14)) + } + Spacer().frame(height: 16) + + } + } + .navigationBarTitle("", displayMode: .inline) + .navigationBarHidden(true) + .navigationViewStyle(StackNavigationViewStyle()) + + .sheet(isPresented: $showSheet){ + RoadMapView() + }.alert(isPresented: $showSuccessPopup) { + Alert( + title: Text("Success"), + message: Text("Your export success"), + dismissButton: .default(Text("Close"), action: {}) + ) + } + .onAppear(perform: { + print("Export view: onAppear") + PECtl.shared.didReceive(action: .commit) + }) + } +} + +struct ExportView_Previews: PreviewProvider { + static var previews: some View { + let shared = PECtl.shared + shared.originUI = UIImage(named: "carem") + return ExportView() + .background(Color(UIColor.systemBackground)) + .environment(\.colorScheme, .dark) + .environmentObject(shared) + } +} diff --git a/view/ImagePicker.swift b/view/ImagePicker.swift new file mode 100644 index 0000000..b506129 --- /dev/null +++ b/view/ImagePicker.swift @@ -0,0 +1,48 @@ +// +// ImagePicker.swift +// test +// +// Created by macOS on 7/2/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct ImagePicker: UIViewControllerRepresentable { + + @Environment(\.presentationMode) var presentationMode + @Binding var image:UIImage? + + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIImagePickerController { + let picker = UIImagePickerController() + picker.delegate = context.coordinator + return picker + } + + func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext) { + + } + + func makeCoordinator() -> ImagePickerCoordinator { + ImagePickerCoordinator(self) + } +} + +class ImagePickerCoordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate{ + let parent: ImagePicker + + init(_ parent:ImagePicker) { + self.parent = parent + } + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + if let uiImage = info[.originalImage] as? UIImage { + parent.image = uiImage + } + print("picker image dismiss") + parent.presentationMode.wrappedValue.dismiss() + } + + +} diff --git a/view/PhotoEditView.swift b/view/PhotoEditView.swift new file mode 100644 index 0000000..8795c1a --- /dev/null +++ b/view/PhotoEditView.swift @@ -0,0 +1,98 @@ +// +// PhotoEditView.swift +// colorful-room +// +// Created by macOS on 7/8/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + + +struct PhotoEditView: View { + init(image initImage:UIImage?){ + + print("Photo edit: init") + guard let image = initImage else { + return + } + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)){ + PECtl.shared.setImage(image: image) + } + } + + + @State private var showImagePicker = false + @State private var pickImage:UIImage? + @EnvironmentObject var shared:PECtl + @Environment(\.presentationMode) var presentationMode + + var body: some View { + NavigationView{ + ZStack{ + Color.myBackground + .edgesIgnoringSafeArea(.all) + VStack{ + HStack{ + Button(action:{ + self.showImagePicker = true + }){ + Text("Library") + .foregroundColor(.white) + .padding(.horizontal) + .padding(.top, 8) + } + Spacer() + if(shared.image != nil){ + NavigationLink(destination: ExportView()){ + Text("Export") + .foregroundColor(.white) + .padding(.horizontal) + .padding(.top, 8) + } + } + } + .zIndex(1) + PhotoEditorView().frame(maxWidth: .infinity, maxHeight: .infinity) + .zIndex(0) + } + } + .navigationBarTitle("") + .navigationBarHidden(true) + + } + .navigationViewStyle(StackNavigationViewStyle()) + .sheet(isPresented: $showImagePicker, onDismiss: self.loadImage){ + ZStack{ + ImagePicker(image: self.$pickImage) + } + + } + } + + + func loadImage(){ + print("Photo edit: pick image finish") + guard let image = self.pickImage else { + return + } + self.pickImage = nil + print("Photo edit: pick then setImage") + self.shared.setImage(image: image) + } +} + + + + +struct PhotoEditView_Previews: PreviewProvider { + static var previews: some View { + Group { + PhotoEditView(image: UIImage(named: "carem")) + .background(Color(UIColor.systemBackground)) + .environment(\.colorScheme, .dark) + .environmentObject(PECtl.shared) + } + } +} diff --git a/view/RoadMapView.swift b/view/RoadMapView.swift new file mode 100644 index 0000000..6c69958 --- /dev/null +++ b/view/RoadMapView.swift @@ -0,0 +1,109 @@ +// +// RoadMapView.swift +// colorful-room +// +// Created by Ping9 on 8/3/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct RoadMapView: View { + + @Environment(\.presentationMode) var presentationMode: Binding + + var body: some View { + ZStack(alignment: .top){ + Color.myBackground + .edgesIgnoringSafeArea(.all) + ScrollView(showsIndicators: false){ + VStack(alignment: .leading, spacing: 8){ + HStack{ + Spacer() + Button("Close", action: { + self.presentationMode.wrappedValue.dismiss() + }) + .foregroundColor(Color.myGrayLight) + .padding() + } + Text("Product\nRoad map") + .font(.system(size: 36, weight: .semibold )) + .foregroundColor(Color.myGrayLight) + .frame(height: 110) + + Text("Your great contribution will help our team a lot to continue developing better product.") + .font(.system(size: 15)) + .foregroundColor(Color.gray) + .padding(.vertical) + Text("What are we doing?") + .font(.system(size: 15, weight: .semibold )) + .foregroundColor(Color.myGrayLight) + .padding(.vertical) + + Group{ + RoadStepView(title: "Lauching MVP - Beta version", content: "Basic editor, include HLS editor, export your picture and LUTs image", date: "August 15, 2020", highLight: true) + RoadStepView(title: "Lauching Production v1.1", content: "You can crop image, import more LUTs templates", date: "September 10 ,2020") + RoadStepView(title: "Add Effect tool & export all effect", content: "You can add effect and export all effects image that you can use for AR filter", date: "December 1 ,2020") + RoadStepView(title: "Export Spark AR filter", content: "You can export Spark AR project file when done editing!", date: "2021") + } + + }.padding() + } + } + } +} + +struct RoadMapView_Previews: PreviewProvider { + static var previews: some View { + RoadMapView() + } +} + + +struct RoadStepView: View{ + + var title:String + var content:String + var date:String + + var highLight = false + + var body: some View{ + HStack{ + VStack{ + if(highLight){ + Circle() + .fill(Color.purple) + .frame(width: 10, height: 10).overlay( + RoundedRectangle(cornerRadius: 40) + .stroke(Color(red: 1, green: 0.33, blue: 1, opacity: 0.3), lineWidth: 8) + ) + }else{ + Circle() + .fill(Color.myGray) + .frame(width: 10, height: 10) + } + Rectangle() + .fill(Color.myGrayDark) + .frame(width: 1) + } + .padding(.horizontal) + .padding(.vertical, 4) + VStack(alignment: .leading){ + Text(self.title) + .font(.system(size: 15, weight: .semibold)) + .foregroundColor(highLight ? Color.myGrayLight : Color.myGray) + .padding(.bottom, 10) + + Text(self.content) + .font(.system(size: 14, weight: .regular)) + .foregroundColor(Color.myGray) + + Text(self.date.uppercased()) + .font(.system(size: 11, weight: .regular)) + .foregroundColor(Color.myGrayDark) + .padding(.vertical, 8) + } + } + } +} diff --git a/view/SupportView.swift b/view/SupportView.swift new file mode 100644 index 0000000..e1ced06 --- /dev/null +++ b/view/SupportView.swift @@ -0,0 +1,200 @@ +// +// SupportView.swift +// colorful-room +// +// Created by macOS on 7/19/20. +// Copyright © 2020 PingAK9. All rights reserved. +// + +import SwiftUI + +struct SupportView: View { + + let padding:CGFloat = 24 + + @Environment(\.presentationMode) var presentationMode: Binding + + @State var showSheet:Bool = false + + var body: some View { + NavigationView{ + ZStack(alignment: .top){ + Color.myBackground + .edgesIgnoringSafeArea(.all) + Image("pattern-top") + .resizable() + .scaledToFit() + .edgesIgnoringSafeArea(.top) + + ScrollView(showsIndicators: false){ + VStack(alignment: .leading, spacing: 0){ + HStack{ + Button(action:{ + self.presentationMode.wrappedValue.dismiss() + }){ + Text("BACK") + .font(.system(size: 14)) + .foregroundColor(.white) + .padding(.vertical) + .padding(.horizontal, padding) + } + Spacer() + } + Spacer().frame(height: 50) + Group{ + HStack{ + Text("We are") + .font(.system(size: 36, weight: .semibold)) + Image("icon-heart").resizable().scaledToFit().frame(width: 26) + } + .padding(.horizontal, padding) + Text("always free!") + .font(.system(size: 36, weight: .semibold)) + .padding(.horizontal, padding) + Text("& give you the best features") + .font(.system(size: 15, weight: .semibold)) + .padding(.horizontal, padding) + .padding(.vertical, 8) + Spacer().frame(height: 24) + Text("However, we also need your support to continue improving the app and serve you better. We are really appreciated for that!") + .font(.system(size: 14)) + .foregroundColor(Color.myGray) + .padding(.horizontal, padding) + } + + Spacer().frame(height: 32) + + VStack(alignment: .leading, spacing: 0){ + Group{ + + Text("BASIC SUPPORT") + .font(.system(size: 14, weight: .medium)) + .foregroundColor(Color.myGray) + .padding(.horizontal, padding) + .padding(.vertical, 16) + ScrollView(.horizontal, showsIndicators: false){ + HStack(spacing: 12){ + Spacer().frame(width: 8) + MinerDonate(value: 2.99) + MinerDonate(value: 3.99) + MinerDonate(value: 4.99) + + Spacer().frame(width: 8) + } + } + } + Spacer().frame(height: 32) + Group{ + Text("SUPER SUPPORT") + .font(.system(size: 14, weight: .medium)) + .foregroundColor(Color.myGray) + .padding(.horizontal, padding) + .padding(.vertical, 16) + ScrollView(.horizontal, showsIndicators: false){ + HStack(spacing: 16){ + Spacer().frame(width: 8) + MajorDonate(value: 39.99, image: "icon-gold", color: Color("gold")) + + MajorDonate(value: 29.99,image: "icon-silver", color: Color("sliver")) + + MajorDonate(value: 19.99, image: "icon-bronze", color: Color("bronze")) + + Spacer().frame(width: 8) + } + } + } + } + Spacer().frame(height: 15) + Button(action: { + self.showSheet = true + }){ + VStack{ + HStack(alignment: .center){ + Spacer() + Image(systemName: "hand.point.right") + Text("PRODUCT ROAD MAP") + Image(systemName: "chevron.up") + Spacer() + } + .foregroundColor(Color.myGrayLight) + Text("We ​​are developing for the future") + .foregroundColor(Color.myGray) + } + .font(.system(size: 14)) + } + Spacer().frame(height: 32) + + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + + } + .navigationBarTitle("") + .navigationBarHidden(true) + .navigationViewStyle(StackNavigationViewStyle()) + } + .sheet(isPresented: $showSheet, content: {RoadMapView()}) + } + + +} + +struct SupportView_Previews: PreviewProvider { + static var previews: some View { + SupportView() + .background(Color(UIColor.systemBackground)) + .environment(\.colorScheme, .dark) + } +} + +struct MinerDonate : View{ + var value:Double + + var body: some View{ + + Text("$\(value, specifier: "%.2f")") + .frame(width: 103, height: 50, alignment: .center) + .background(Color.myDivider) + .overlay( + RoundedRectangle(cornerRadius: 6) + .stroke(Color.myGray, lineWidth: 2)) + .cornerRadius(6) + .gesture( + TapGesture() + .onEnded { _ in + + + } + ) + } +} + +struct MajorDonate : View { + + var value:Double + var image:String + var color:Color + + var body: some View{ + VStack(spacing: 16){ + Image(image) + .resizable() + .scaledToFit() + .frame(height: 68) + Text("$\(value, specifier: "%.2f")") + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(color) + } + .frame(width: 140, height: 147, alignment: .center) + .background(Color.myDivider) + .overlay( + RoundedRectangle(cornerRadius: 6) + .stroke(color, lineWidth: 1.5)) + .gesture( + TapGesture() + .onEnded { _ in + + } + ) + } +}