├── Assets └── Images │ ├── PixelUI-Example-1.png │ ├── PixelUI-Example-2.png │ └── PixelUI-Example-3.png ├── Tests └── PixelUITests │ └── PixelUITests.swift ├── .gitignore ├── PixelUI.xcworkspace ├── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── swiftpm │ │ └── Package.resolved └── contents.xcworkspacedata ├── Sources └── PixelUI │ ├── Pixel │ ├── Other │ │ ├── PixelBuilder.swift │ │ └── PixelTree.swift │ ├── Pixel.swift │ ├── Effects │ │ ├── Single │ │ │ ├── PixelOpticalFlow.swift │ │ │ ├── PixelFeedback.swift │ │ │ ├── PixelFreeze.swift │ │ │ ├── PixelTint.swift │ │ │ ├── PixelSepia.swift │ │ │ ├── PixelTwirl.swift │ │ │ ├── PixelDelay.swift │ │ │ ├── PixelFilter.swift │ │ │ ├── PixelSlope.swift │ │ │ ├── PixelSharpen.swift │ │ │ ├── PixelThreshold.swift │ │ │ ├── PixelConvert.swift │ │ │ ├── PixelEqualize.swift │ │ │ ├── PixelQuantize.swift │ │ │ ├── PixelFlipFlop.swift │ │ │ ├── PixelColorConvert.swift │ │ │ ├── PixelPixelate.swift │ │ │ ├── PixelMorph.swift │ │ │ ├── PixelClamp.swift │ │ │ ├── PixelColorShift.swift │ │ │ ├── PixelColorCorrect.swift │ │ │ ├── PixelChannelMix.swift │ │ │ ├── PixelKaleidoscope.swift │ │ │ ├── PixelWarp.swift │ │ │ ├── PixelMetalEffect.swift │ │ │ ├── PixelCrop.swift │ │ │ ├── PixelTransform.swift │ │ │ ├── PixelBlur.swift │ │ │ ├── PixelEdge.swift │ │ │ ├── PixelChromaKey.swift │ │ │ ├── PixelRainbowBlur.swift │ │ │ └── PixelCornerPin.swift │ │ ├── Merger │ │ │ ├── PixelRemap.swift │ │ │ ├── PixelCross.swift │ │ │ ├── PixelTimeMachine.swift │ │ │ ├── PixelLookup.swift │ │ │ ├── PixelDisplace.swift │ │ │ ├── PixelLumaColorShift.swift │ │ │ ├── PixelMetalMergerEffect.swift │ │ │ ├── PixelBlend.swift │ │ │ ├── PixelLumaBlur.swift │ │ │ └── PixelLumaTransform.swift │ │ └── Multi │ │ │ ├── PixelBlends.swift │ │ │ ├── PixelBlendsForEach.swift │ │ │ ├── PixelStack.swift │ │ │ └── PixelMetalMultiEffect.swift │ └── Content │ │ ├── Resource │ │ ├── PixelCamera.swift │ │ ├── PixelScreenCapture.swift │ │ ├── PixelView.swift │ │ ├── PixelImage.swift │ │ └── PixelEarth.swift │ │ └── Generator │ │ ├── PixelColor.swift │ │ ├── PixelMetal.swift │ │ ├── PixelRectangle.swift │ │ ├── PixelLine.swift │ │ ├── PixelCircle.swift │ │ ├── PixelPolygon.swift │ │ ├── PixelStar.swift │ │ └── PixelNoise.swift │ ├── Types │ ├── Future.swift │ └── PixelVariable.swift │ └── Pixels │ ├── Pixels.swift │ └── Extensions │ ├── Pixels+createPix.swift │ ├── Pixels+updateResolution.swift │ ├── Pixels+update.swift │ ├── Pixels+animation.swift │ └── Pixels+conversion.swift ├── Package.swift ├── Package.resolved └── README.md /Assets/Images/PixelUI-Example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/PixelUI/HEAD/Assets/Images/PixelUI-Example-1.png -------------------------------------------------------------------------------- /Assets/Images/PixelUI-Example-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/PixelUI/HEAD/Assets/Images/PixelUI-Example-2.png -------------------------------------------------------------------------------- /Assets/Images/PixelUI-Example-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/PixelUI/HEAD/Assets/Images/PixelUI-Example-3.png -------------------------------------------------------------------------------- /Tests/PixelUITests/PixelUITests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PixelUI 3 | 4 | final class PixelUITests: XCTestCase {} 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | -------------------------------------------------------------------------------- /PixelUI.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Other/PixelBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Resolution 6 | import RenderKit 7 | import PixelKit 8 | 9 | @resultBuilder 10 | public struct PixelBuilder { 11 | 12 | public static func buildBlock(_ pixels: Pixel...) -> [Pixel] { 13 | pixels 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/PixelUI/Types/Future.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-24. 3 | // 4 | 5 | import MultiViews 6 | 7 | protocol Future { 8 | associatedtype T: Any 9 | var call: () -> T? { get } 10 | } 11 | 12 | struct FutureImage: Future { 13 | var call: () -> MPImage? 14 | } 15 | 16 | struct FutureView: Future { 17 | var call: () -> MPView? 18 | } 19 | -------------------------------------------------------------------------------- /Sources/PixelUI/Types/PixelVariable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-23. 3 | // 4 | 5 | import CoreGraphics 6 | 7 | public struct PixelVariable { 8 | 9 | public var name: String 10 | public var value: CGFloat 11 | 12 | public init(name: String, value: CGFloat) { 13 | self.name = name 14 | self.value = value 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PixelUI.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 18 | 19 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixels/Pixels.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import SwiftUI 6 | import Resolution 7 | import PixelKit 8 | 9 | public struct Pixels: View { 10 | 11 | let resolution: Resolution? 12 | let pixel: () -> Pixel 13 | 14 | public init(resolution: Resolution? = nil, pixel: @escaping () -> Pixel) { 15 | self.resolution = resolution 16 | self.pixel = pixel 17 | } 18 | 19 | public var body: some View { 20 | GeometryReader { geometryProxy in 21 | PixelsView(resolution: resolution ?? (.size(geometryProxy.size) * 2), 22 | size: geometryProxy.size, 23 | pixel: pixel) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Pixel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import SwiftUI 6 | import PixelKit 7 | 8 | public protocol Pixel { 9 | 10 | var pixType: PIXType { get } 11 | var pixelTree: PixelTree { get } 12 | 13 | var metadata: [String: PixelMetadata] { get set } 14 | 15 | func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? 16 | func update(metadata: [String: PixelMetadata], pix: PIX, size: CGSize) 17 | } 18 | 19 | extension Pixel { 20 | 21 | var resizeContentResolution: Bool { 22 | [ 23 | .content(.resource(.camera)), 24 | .content(.resource(.image)), 25 | .content(.resource(.screenCapture)) 26 | ].contains(pixType) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Other/PixelTree.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import PixelKit 6 | import CoreGraphics 7 | 8 | public indirect enum PixelTree { 9 | 10 | case content 11 | 12 | case singleEffect(Pixel) 13 | case mergerEffect(Pixel, Pixel) 14 | case multiEffect([Pixel]) 15 | 16 | case feedback(Pixel, Pixel) 17 | 18 | var pixels: [Pixel] { 19 | switch self { 20 | case .content: 21 | return [] 22 | case .singleEffect(let pixel): 23 | return [pixel] 24 | case .mergerEffect(let pixelA, let pixelB): 25 | return [pixelA, pixelB] 26 | case .multiEffect(let pixels): 27 | return pixels 28 | case .feedback(let pixelA, let pixelB): 29 | return [pixelA, pixelB] 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /PixelUI.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CoreGraphicsExtensions", 6 | "repositoryURL": "https://github.com/heestand-xyz/CoreGraphicsExtensions", 7 | "state": { 8 | "branch": null, 9 | "revision": "a40dc230306daacb410f9a2f36f63a811ea2e88e", 10 | "version": "1.2.1" 11 | } 12 | }, 13 | { 14 | "package": "Logger", 15 | "repositoryURL": "https://github.com/heestand-xyz/Logger", 16 | "state": { 17 | "branch": null, 18 | "revision": "acaa6ef5116c201803682c8d0a8c09a642d8abe2", 19 | "version": "0.2.0" 20 | } 21 | }, 22 | { 23 | "package": "MultiViews", 24 | "repositoryURL": "https://github.com/heestand-xyz/MultiViews", 25 | "state": { 26 | "branch": null, 27 | "revision": "9cceb30e1af5fadf02558a970e744cec0136587b", 28 | "version": "1.4.1" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelOpticalFlow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelOpticalFlow: Pixel { 12 | 13 | typealias Pix = OpticalFlowPIX 14 | 15 | public let pixType: PIXType = .effect(.single(.opticalFlow)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | internal init(pixel: () -> Pixel) { 22 | 23 | pixelTree = .singleEffect(pixel()) 24 | } 25 | 26 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { nil } 27 | 28 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) {} 29 | } 30 | 31 | public extension Pixel { 32 | 33 | func pixelOpticalFlow() -> PixelOpticalFlow { 34 | PixelOpticalFlow(pixel: { self }) 35 | } 36 | } 37 | 38 | struct PixelOpticalFlow_Previews: PreviewProvider { 39 | static var previews: some View { 40 | Pixels { 41 | PixelCamera() 42 | .pixelOpticalFlow() 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "PixelUI", 7 | platforms: [ 8 | .iOS(.v14), 9 | .tvOS(.v14), 10 | .macOS(.v11), 11 | ], 12 | products: [ 13 | .library( 14 | name: "PixelUI", 15 | targets: ["PixelUI"]), 16 | ], 17 | dependencies: [ 18 | .package(url: "https://github.com/heestand-xyz/PixelKit", from: "3.0.1"), 19 | .package(url: "https://github.com/heestand-xyz/PixelColor", from: "1.3.0"), 20 | .package(url: "https://github.com/heestand-xyz/Resolution", from: "1.0.3"), 21 | .package(url: "https://github.com/heestand-xyz/MultiViews", from: "1.4.1"), 22 | .package(url: "https://github.com/heestand-xyz/CoreGraphicsExtensions", from: "1.2.0"), 23 | .package(url: "https://github.com/heestand-xyz/Logger", from: "0.2.0"), 24 | ], 25 | targets: [ 26 | .target( 27 | name: "PixelUI", 28 | dependencies: ["PixelKit", "PixelColor", "Resolution", "Logger", "CoreGraphicsExtensions", "MultiViews"]), 29 | .testTarget( 30 | name: "PixelUITests", 31 | dependencies: ["PixelUI"]), 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelFeedback.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// File.swift 3 | //// 4 | //// 5 | //// Created by Anton Heestand on 2021-11-23. 6 | //// 7 | // 8 | //import Foundation 9 | //import RenderKit 10 | //import PixelKit 11 | //import SwiftUI 12 | //import Resolution 13 | // 14 | //public struct PixelFeedback: Pixel { 15 | // 16 | // typealias Pix = FeedbackPIX 17 | // 18 | // public let pixType: PIXType = .effect(.single(.feedback)) 19 | // 20 | // public var pixelTree: PixelTree 21 | // 22 | // public var metadata: [String : PixelMetadata] = [:] 23 | // 24 | // public init(pixel: () -> Pixel, 25 | // feedbackPixel: () -> Pixel) { 26 | // 27 | // pixelTree = .feedback(pixel(), feedbackPixel()) 28 | // } 29 | // 30 | // public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { nil } 31 | // 32 | // public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) {} 33 | //} 34 | // 35 | //struct PixelFeedback_Previews: PreviewProvider { 36 | // static var previews: some View { 37 | // Pixels { 38 | // PixelFeedback { 39 | // PixelCircle(radius: 100) 40 | // } feedbackPixel: { 41 | // PixelStar(count: 5, radius: 100) 42 | // } 43 | // } 44 | // } 45 | //} 46 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Merger/PixelRemap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelRemap: Pixel { 12 | 13 | typealias Pix = RemapPIX 14 | 15 | public let pixType: PIXType = .effect(.merger(.remap)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | internal init(pixel leadingPixel: () -> Pixel, 22 | withPixel trailingPixel: () -> Pixel) { 23 | 24 | pixelTree = .mergerEffect(leadingPixel(), trailingPixel()) 25 | } 26 | 27 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { nil } 28 | 29 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) {} 30 | } 31 | 32 | public extension Pixel { 33 | 34 | func pixelRemap(pixel: () -> Pixel) -> PixelRemap { 35 | PixelRemap(pixel: { self }, withPixel: pixel) 36 | } 37 | } 38 | 39 | struct PixelRemap_Previews: PreviewProvider { 40 | static var previews: some View { 41 | Pixels { 42 | PixelCircle(radius: 100) 43 | .pixelRemap { 44 | PixelBlends(mode: .add) { 45 | PixelGradient(axis: .horizontal, colors: [.black, Color(red: 1.0, green: 0.0, blue: 0.0)]) 46 | PixelGradient(axis: .vertical, colors: [.black, Color(red: 0.0, green: 1.0, blue: 0.0)]) 47 | } 48 | .pixelDisplace(100) { 49 | PixelNoise() 50 | } 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Content/Resource/PixelCamera.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-22. 3 | // 4 | 5 | import Foundation 6 | import SwiftUI 7 | import RenderKit 8 | import PixelKit 9 | import PixelColor 10 | 11 | #warning("Camera Resolution won't work with Blends") 12 | 13 | public struct PixelCamera: Pixel { 14 | 15 | typealias Pix = CameraPIX 16 | 17 | public let pixType: PIXType = .content(.resource(.camera)) 18 | 19 | public let pixelTree: PixelTree = .content 20 | 21 | public var metadata: [String : PixelMetadata] = [:] 22 | 23 | enum Key: String, CaseIterable { 24 | case camera 25 | } 26 | 27 | public init(_ camera: CameraPIX.Camera = .default) { 28 | 29 | for key in Key.allCases { 30 | switch key { 31 | case .camera: 32 | metadata[key.rawValue] = camera.rawValue 33 | } 34 | } 35 | } 36 | 37 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 38 | 39 | guard let pix = pix as? Pix else { return nil } 40 | 41 | guard let key = Key(rawValue: key) else { return nil } 42 | 43 | switch key { 44 | case .camera: 45 | return pix.camera.rawValue 46 | } 47 | } 48 | 49 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 50 | 51 | guard var pix = pix as? Pix else { return } 52 | 53 | for (key, value) in metadata { 54 | 55 | guard let key = Key(rawValue: key) else { continue } 56 | 57 | switch key { 58 | case .camera: 59 | Pixels.updateRawValue(pix: &pix, value: value, at: \.camera) 60 | } 61 | } 62 | } 63 | } 64 | 65 | struct PixelCamera_Previews: PreviewProvider { 66 | static var previews: some View { 67 | Pixels { 68 | PixelCamera() 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Content/Resource/PixelScreenCapture.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-22. 3 | // 4 | 5 | #if os(macOS) && !targetEnvironment(macCatalyst) 6 | 7 | import Foundation 8 | import SwiftUI 9 | import RenderKit 10 | import PixelKit 11 | import PixelColor 12 | 13 | public struct PixelScreenCapture: Pixel { 14 | 15 | typealias Pix = ScreenCapturePIX 16 | 17 | public let pixType: PIXType = .content(.resource(.screenCapture)) 18 | 19 | public let pixelTree: PixelTree = .content 20 | 21 | public var metadata: [String : PixelMetadata] = [:] 22 | 23 | enum Key: String, CaseIterable { 24 | case screenIndex 25 | } 26 | 27 | public init() {} 28 | 29 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 30 | 31 | guard let pix = pix as? Pix else { return nil } 32 | 33 | guard let key = Key(rawValue: key) else { return nil } 34 | 35 | switch key { 36 | case .screenIndex: 37 | return pix.screenIndex 38 | } 39 | } 40 | 41 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 42 | 43 | guard var pix = pix as? Pix else { return } 44 | 45 | for (key, value) in metadata { 46 | 47 | guard let key = Key(rawValue: key) else { continue } 48 | 49 | switch key { 50 | case .screenIndex: 51 | Pixels.updateValue(pix: &pix, value: value, at: \.screenIndex) 52 | } 53 | } 54 | } 55 | } 56 | 57 | public extension PixelScreenCapture { 58 | 59 | func pixelScreenCaptureIndex(_ index: Int) -> Self { 60 | var pixel = self 61 | pixel.metadata[Key.screenIndex.rawValue] = index 62 | return pixel 63 | } 64 | } 65 | 66 | struct PixelScreenCapture_Previews: PreviewProvider { 67 | static var previews: some View { 68 | Pixels { 69 | PixelScreenCapture() 70 | } 71 | } 72 | } 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Multi/PixelBlends.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import SwiftUI 7 | import Resolution 8 | import RenderKit 9 | import PixelKit 10 | 11 | public struct PixelBlends: Pixel { 12 | 13 | typealias Pix = BlendsPIX 14 | 15 | public var pixType: PIXType = .effect(.multi(.blends)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case blendMode 23 | } 24 | 25 | public init(mode blendMode: RenderKit.BlendMode, 26 | @PixelBuilder pixels: () -> [Pixel]) { 27 | 28 | pixelTree = .multiEffect(pixels()) 29 | 30 | for key in Key.allCases { 31 | switch key { 32 | case .blendMode: 33 | metadata[key.rawValue] = blendMode.rawValue 34 | } 35 | } 36 | } 37 | 38 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 39 | 40 | guard let pix = pix as? Pix else { return nil } 41 | 42 | guard let key = Key(rawValue: key) else { return nil } 43 | 44 | switch key { 45 | case .blendMode: 46 | return pix.blendMode.rawValue 47 | } 48 | } 49 | 50 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 51 | 52 | guard var pix = pix as? Pix else { return } 53 | 54 | for (key, value) in metadata { 55 | 56 | guard let key = Key(rawValue: key) else { continue } 57 | 58 | switch key { 59 | case .blendMode: 60 | Pixels.updateRawValue(pix: &pix, value: value, at: \.blendMode) 61 | } 62 | } 63 | } 64 | } 65 | 66 | struct PixelBlends_Previews: PreviewProvider { 67 | static var previews: some View { 68 | Pixels { 69 | PixelBlends(mode: .average) { 70 | PixelCircle(radius: 100) 71 | PixelPolygon(count: 3, radius: 100) 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelFreeze.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelFreeze: Pixel { 12 | 13 | typealias Pix = FreezePIX 14 | 15 | public let pixType: PIXType = .effect(.single(.freeze)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case freeze 23 | } 24 | 25 | internal init(_ freeze: Bool, 26 | pixel: () -> Pixel) { 27 | 28 | pixelTree = .singleEffect(pixel()) 29 | 30 | for key in Key.allCases { 31 | switch key { 32 | case .freeze: 33 | metadata[key.rawValue] = freeze 34 | } 35 | } 36 | } 37 | 38 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 39 | 40 | guard let pix = pix as? Pix else { return nil } 41 | 42 | guard let key = Key(rawValue: key) else { return nil } 43 | 44 | switch key { 45 | case .freeze: 46 | return pix.freeze 47 | } 48 | } 49 | 50 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 51 | 52 | guard var pix = pix as? Pix else { return } 53 | 54 | for (key, value) in metadata { 55 | 56 | guard let key = Key(rawValue: key) else { continue } 57 | 58 | switch key { 59 | case .freeze: 60 | Pixels.updateValue(pix: &pix, value: value, at: \.freeze) 61 | } 62 | } 63 | } 64 | } 65 | 66 | public extension Pixel { 67 | 68 | func pixelFreeze(_ freeze: Bool) -> PixelFreeze { 69 | PixelFreeze(freeze, pixel: { self }) 70 | } 71 | } 72 | 73 | struct PixelFreeze_Previews: PreviewProvider { 74 | static var previews: some View { 75 | Pixels { 76 | PixelCamera() 77 | .pixelFreeze(true) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelTint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2022-01-06. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | public struct PixelTint: Pixel { 13 | 14 | typealias Pix = TintPIX 15 | 16 | public let pixType: PIXType = .effect(.single(.tint)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case color 24 | } 25 | 26 | internal init(color: PixelColor, 27 | pixel: () -> Pixel) { 28 | 29 | pixelTree = .singleEffect(pixel()) 30 | 31 | for key in Key.allCases { 32 | switch key { 33 | case .color: 34 | metadata[key.rawValue] = color 35 | } 36 | } 37 | } 38 | 39 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 40 | 41 | guard let pix = pix as? Pix else { return nil } 42 | 43 | guard let key = Key(rawValue: key) else { return nil } 44 | 45 | switch key { 46 | case .color: 47 | return pix.color 48 | } 49 | } 50 | 51 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 52 | 53 | guard var pix = pix as? Pix else { return } 54 | 55 | for (key, value) in metadata { 56 | 57 | guard let key = Key(rawValue: key) else { continue } 58 | 59 | switch key { 60 | case .color: 61 | Pixels.updateValue(pix: &pix, value: value, at: \.color) 62 | } 63 | } 64 | } 65 | } 66 | 67 | public extension Pixel { 68 | 69 | func pixelTint(_ color: Color) -> PixelTint { 70 | PixelTint(color: PixelColor(color), pixel: { self }) 71 | } 72 | } 73 | 74 | struct PixelTint_Previews: PreviewProvider { 75 | static var previews: some View { 76 | Pixels { 77 | PixelCamera() 78 | .pixelTint(.orange) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelSepia.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2022-01-06. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | public struct PixelSepia: Pixel { 13 | 14 | typealias Pix = SepiaPIX 15 | 16 | public let pixType: PIXType = .effect(.single(.sepia)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case color 24 | } 25 | 26 | internal init(color: PixelColor, 27 | pixel: () -> Pixel) { 28 | 29 | pixelTree = .singleEffect(pixel()) 30 | 31 | for key in Key.allCases { 32 | switch key { 33 | case .color: 34 | metadata[key.rawValue] = color 35 | } 36 | } 37 | } 38 | 39 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 40 | 41 | guard let pix = pix as? Pix else { return nil } 42 | 43 | guard let key = Key(rawValue: key) else { return nil } 44 | 45 | switch key { 46 | case .color: 47 | return pix.color 48 | } 49 | } 50 | 51 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 52 | 53 | guard var pix = pix as? Pix else { return } 54 | 55 | for (key, value) in metadata { 56 | 57 | guard let key = Key(rawValue: key) else { continue } 58 | 59 | switch key { 60 | case .color: 61 | Pixels.updateValue(pix: &pix, value: value, at: \.color) 62 | } 63 | } 64 | } 65 | } 66 | 67 | public extension Pixel { 68 | 69 | func pixelSepia(_ color: Color) -> PixelSepia { 70 | PixelSepia(color: PixelColor(color), pixel: { self }) 71 | } 72 | } 73 | 74 | struct PixelSepia_Previews: PreviewProvider { 75 | static var previews: some View { 76 | Pixels { 77 | PixelCamera() 78 | .pixelSepia(.orange) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelTwirl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2022-01-06. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | public struct PixelTwirl: Pixel { 13 | 14 | typealias Pix = TwirlPIX 15 | 16 | public let pixType: PIXType = .effect(.single(.twirl)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case strength 24 | } 25 | 26 | internal init(strength: CGFloat, 27 | pixel: () -> Pixel) { 28 | 29 | pixelTree = .singleEffect(pixel()) 30 | 31 | for key in Key.allCases { 32 | switch key { 33 | case .strength: 34 | metadata[key.rawValue] = strength 35 | } 36 | } 37 | } 38 | 39 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 40 | 41 | guard let pix = pix as? Pix else { return nil } 42 | 43 | guard let key = Key(rawValue: key) else { return nil } 44 | 45 | switch key { 46 | case .strength: 47 | return pix.strength 48 | } 49 | } 50 | 51 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 52 | 53 | guard var pix = pix as? Pix else { return } 54 | 55 | for (key, value) in metadata { 56 | 57 | guard let key = Key(rawValue: key) else { continue } 58 | 59 | switch key { 60 | case .strength: 61 | Pixels.updateValue(pix: &pix, value: value, at: \.strength) 62 | } 63 | } 64 | } 65 | } 66 | 67 | public extension Pixel { 68 | 69 | func pixelTwirl(_ strength: CGFloat) -> PixelTwirl { 70 | PixelTwirl(strength: strength, pixel: { self }) 71 | } 72 | } 73 | 74 | struct PixelTwirl_Previews: PreviewProvider { 75 | static var previews: some View { 76 | Pixels { 77 | PixelCamera() 78 | .pixelTwirl(2.0) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Merger/PixelCross.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelCross: Pixel { 12 | 13 | typealias Pix = CrossPIX 14 | 15 | public let pixType: PIXType = .effect(.merger(.cross)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case fraction 23 | } 24 | 25 | public init(at fraction: CGFloat, 26 | pixel leadingPixel: () -> Pixel, 27 | withPixel trailingPixel: () -> Pixel) { 28 | 29 | pixelTree = .mergerEffect(leadingPixel(), trailingPixel()) 30 | 31 | for key in Key.allCases { 32 | switch key { 33 | case .fraction: 34 | metadata[key.rawValue] = fraction 35 | } 36 | } 37 | } 38 | 39 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 40 | 41 | guard let pix = pix as? Pix else { return nil } 42 | 43 | guard let key = Key(rawValue: key) else { return nil } 44 | 45 | switch key { 46 | case .fraction: 47 | return pix.fraction 48 | } 49 | } 50 | 51 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 52 | 53 | guard var pix = pix as? Pix else { return } 54 | 55 | for (key, value) in metadata { 56 | 57 | guard let key = Key(rawValue: key) else { continue } 58 | 59 | switch key { 60 | case .fraction: 61 | Pixels.updateValue(pix: &pix, value: value, at: \.fraction) 62 | } 63 | } 64 | } 65 | } 66 | 67 | struct PixelCross_Previews: PreviewProvider { 68 | static var previews: some View { 69 | Pixels { 70 | PixelCross(at: 0.5) { 71 | PixelCircle(radius: 100) 72 | } withPixel: { 73 | PixelStar(count: 5, radius: 100) 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelDelay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelDelay: Pixel { 12 | 13 | typealias Pix = DelayPIX 14 | 15 | public let pixType: PIXType = .effect(.single(.delay)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case delayFrames 23 | } 24 | 25 | internal init(frameCount delayFrames: Int, 26 | pixel: () -> Pixel) { 27 | 28 | pixelTree = .singleEffect(pixel()) 29 | 30 | for key in Key.allCases { 31 | switch key { 32 | case .delayFrames: 33 | metadata[key.rawValue] = delayFrames 34 | } 35 | } 36 | } 37 | 38 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 39 | 40 | guard let pix = pix as? Pix else { return nil } 41 | 42 | guard let key = Key(rawValue: key) else { return nil } 43 | 44 | switch key { 45 | case .delayFrames: 46 | return pix.delayFrames 47 | } 48 | } 49 | 50 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 51 | 52 | guard var pix = pix as? Pix else { return } 53 | 54 | for (key, value) in metadata { 55 | 56 | guard let key = Key(rawValue: key) else { continue } 57 | 58 | switch key { 59 | case .delayFrames: 60 | Pixels.updateValue(pix: &pix, value: value, at: \.delayFrames) 61 | } 62 | } 63 | } 64 | } 65 | 66 | public extension Pixel { 67 | 68 | func pixelDelay(frameCount: Int) -> PixelDelay { 69 | PixelDelay(frameCount: frameCount, pixel: { self }) 70 | } 71 | } 72 | 73 | struct PixelDelay_Previews: PreviewProvider { 74 | static var previews: some View { 75 | Pixels { 76 | PixelCamera() 77 | .pixelDelay(frameCount: 100) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelFilter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | public struct PixelFilter: Pixel { 13 | 14 | typealias Pix = FilterPIX 15 | 16 | public let pixType: PIXType = .effect(.single(.filter)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case filter 24 | } 25 | 26 | internal init(_ filter: FilterPIX.Filter, 27 | pixel: () -> Pixel) { 28 | 29 | pixelTree = .singleEffect(pixel()) 30 | 31 | for key in Key.allCases { 32 | switch key { 33 | case .filter: 34 | metadata[key.rawValue] = filter.rawValue 35 | } 36 | } 37 | } 38 | 39 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 40 | 41 | guard let pix = pix as? Pix else { return nil } 42 | 43 | guard let key = Key(rawValue: key) else { return nil } 44 | 45 | switch key { 46 | case .filter: 47 | return pix.filter.rawValue 48 | } 49 | } 50 | 51 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 52 | 53 | guard var pix = pix as? Pix else { return } 54 | 55 | for (key, value) in metadata { 56 | 57 | guard let key = Key(rawValue: key) else { continue } 58 | 59 | switch key { 60 | case .filter: 61 | Pixels.updateRawValue(pix: &pix, value: value, at: \.filter) 62 | } 63 | } 64 | } 65 | } 66 | 67 | public extension Pixel { 68 | 69 | func pixelFilter(_ filter: FilterPIX.Filter) -> PixelFilter { 70 | PixelFilter(filter, pixel: { self }) 71 | } 72 | } 73 | 74 | struct PixelFilter_Previews: PreviewProvider { 75 | static var previews: some View { 76 | Pixels { 77 | PixelCamera() 78 | .pixelFilter(.noir) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelSlope.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2022-01-06. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | public struct PixelSlope: Pixel { 13 | 14 | typealias Pix = SlopePIX 15 | 16 | public let pixType: PIXType = .effect(.single(.slope)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case amplitude 24 | } 25 | 26 | internal init(amplitude: CGFloat, 27 | pixel: () -> Pixel) { 28 | 29 | pixelTree = .singleEffect(pixel()) 30 | 31 | for key in Key.allCases { 32 | switch key { 33 | case .amplitude: 34 | metadata[key.rawValue] = amplitude 35 | } 36 | } 37 | } 38 | 39 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 40 | 41 | guard let pix = pix as? Pix else { return nil } 42 | 43 | guard let key = Key(rawValue: key) else { return nil } 44 | 45 | switch key { 46 | case .amplitude: 47 | return pix.amplitude 48 | } 49 | } 50 | 51 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 52 | 53 | guard var pix = pix as? Pix else { return } 54 | 55 | for (key, value) in metadata { 56 | 57 | guard let key = Key(rawValue: key) else { continue } 58 | 59 | switch key { 60 | case .amplitude: 61 | Pixels.updateValue(pix: &pix, value: value, at: \.amplitude) 62 | } 63 | } 64 | } 65 | } 66 | 67 | public extension Pixel { 68 | 69 | func pixelSlope(amplitude: CGFloat) -> PixelSlope { 70 | PixelSlope(amplitude: amplitude, pixel: { self }) 71 | } 72 | } 73 | 74 | struct PixelSlope_Previews: PreviewProvider { 75 | static var previews: some View { 76 | Pixels { 77 | PixelCamera() 78 | .pixelSlope(amplitude: 100.0) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelSharpen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2022-01-06. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | public struct PixelSharpen: Pixel { 13 | 14 | typealias Pix = SharpenPIX 15 | 16 | public let pixType: PIXType = .effect(.single(.sharpen)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case contrast 24 | } 25 | 26 | internal init(contrast: CGFloat, 27 | pixel: () -> Pixel) { 28 | 29 | pixelTree = .singleEffect(pixel()) 30 | 31 | for key in Key.allCases { 32 | switch key { 33 | case .contrast: 34 | metadata[key.rawValue] = contrast 35 | } 36 | } 37 | } 38 | 39 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 40 | 41 | guard let pix = pix as? Pix else { return nil } 42 | 43 | guard let key = Key(rawValue: key) else { return nil } 44 | 45 | switch key { 46 | case .contrast: 47 | return pix.contrast 48 | } 49 | } 50 | 51 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 52 | 53 | guard var pix = pix as? Pix else { return } 54 | 55 | for (key, value) in metadata { 56 | 57 | guard let key = Key(rawValue: key) else { continue } 58 | 59 | switch key { 60 | case .contrast: 61 | Pixels.updateValue(pix: &pix, value: value, at: \.contrast) 62 | } 63 | } 64 | } 65 | } 66 | 67 | public extension Pixel { 68 | 69 | func pixelSharpen(contrast: CGFloat) -> PixelSharpen { 70 | PixelSharpen(contrast: contrast, pixel: { self }) 71 | } 72 | } 73 | 74 | struct PixelSharpen_Previews: PreviewProvider { 75 | static var previews: some View { 76 | Pixels { 77 | PixelCamera() 78 | .pixelSharpen(contrast: 10.0) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelThreshold.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelThreshold: Pixel { 12 | 13 | typealias Pix = ThresholdPIX 14 | 15 | public let pixType: PIXType = .effect(.single(.threshold)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case threshold 23 | } 24 | 25 | internal init(at threshold: CGFloat = 0.5, 26 | pixel: () -> Pixel) { 27 | 28 | pixelTree = .singleEffect(pixel()) 29 | 30 | for key in Key.allCases { 31 | switch key { 32 | case .threshold: 33 | metadata[key.rawValue] = threshold 34 | } 35 | } 36 | } 37 | 38 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 39 | 40 | guard let pix = pix as? Pix else { return nil } 41 | 42 | guard let key = Key(rawValue: key) else { return nil } 43 | 44 | switch key { 45 | case .threshold: 46 | return pix.threshold 47 | } 48 | } 49 | 50 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 51 | 52 | guard var pix = pix as? Pix else { return } 53 | 54 | for (key, value) in metadata { 55 | 56 | guard let key = Key(rawValue: key) else { continue } 57 | 58 | switch key { 59 | case .threshold: 60 | Pixels.updateValue(pix: &pix, value: value, at: \.threshold) 61 | } 62 | } 63 | } 64 | } 65 | 66 | public extension Pixel { 67 | 68 | func pixelThreshold(at threshold: CGFloat = 0.5) -> PixelThreshold { 69 | PixelThreshold(at: threshold, pixel: { self }) 70 | } 71 | } 72 | 73 | struct PixelThreshold_Previews: PreviewProvider { 74 | static var previews: some View { 75 | Pixels { 76 | PixelCamera() 77 | .pixelThreshold(at: 0.5) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelConvert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | public struct PixelConvert: Pixel { 13 | 14 | typealias Pix = ConvertPIX 15 | 16 | public let pixType: PIXType = .effect(.single(.convert)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case mode 24 | } 25 | 26 | internal init(_ mode: ConvertPIX.ConvertMode, 27 | pixel: () -> Pixel) { 28 | 29 | pixelTree = .singleEffect(pixel()) 30 | 31 | for key in Key.allCases { 32 | switch key { 33 | case .mode: 34 | metadata[key.rawValue] = mode.rawValue 35 | } 36 | } 37 | } 38 | 39 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 40 | 41 | guard let pix = pix as? Pix else { return nil } 42 | 43 | guard let key = Key(rawValue: key) else { return nil } 44 | 45 | switch key { 46 | case .mode: 47 | return pix.mode.rawValue 48 | } 49 | } 50 | 51 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 52 | 53 | guard var pix = pix as? Pix else { return } 54 | 55 | for (key, value) in metadata { 56 | 57 | guard let key = Key(rawValue: key) else { continue } 58 | 59 | switch key { 60 | case .mode: 61 | Pixels.updateRawValue(pix: &pix, value: value, at: \.mode) 62 | } 63 | } 64 | } 65 | } 66 | 67 | public extension Pixel { 68 | 69 | func pixelConvert(_ mode: ConvertPIX.ConvertMode) -> PixelConvert { 70 | PixelConvert(mode, pixel: { self }) 71 | } 72 | } 73 | 74 | struct PixelConvert_Previews: PreviewProvider { 75 | static var previews: some View { 76 | Pixels { 77 | PixelImage("kite") 78 | .pixelConvert(.squareToCircle) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Content/Resource/PixelView.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// Created by Anton Heestand on 2021-11-24. 3 | //// 4 | // 5 | //import Foundation 6 | //import SwiftUI 7 | //import RenderKit 8 | //import PixelKit 9 | //import PixelColor 10 | //import MultiViews 11 | // 12 | //public struct PixelView: Pixel { 13 | // 14 | // typealias Pix = ViewPIX 15 | // 16 | // public let pixType: PIXType = .content(.resource(.view)) 17 | // 18 | // public let pixelTree: PixelTree = .content 19 | // 20 | // public var metadata: [String : PixelMetadata] = [:] 21 | // 22 | // enum Key: String, CaseIterable { 23 | // case view 24 | // } 25 | // 26 | // public init(_ content: @escaping () -> Content) { 27 | // 28 | // for key in Key.allCases { 29 | // switch key { 30 | // case .view: 31 | // metadata[key.rawValue] = FutureView(call: { 32 | // MPHostingController(rootView: content()).view 33 | // }) 34 | // } 35 | // } 36 | // } 37 | // 38 | // public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 39 | // 40 | // guard let pix = pix as? Pix else { return nil } 41 | // 42 | // guard let key = Key(rawValue: key) else { return nil } 43 | // 44 | // switch key { 45 | // case .view: 46 | // return FutureView(call: { pix.view }) 47 | // } 48 | // } 49 | // 50 | // public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 51 | // 52 | // guard let pix = pix as? Pix else { return } 53 | // 54 | // for (key, value) in metadata { 55 | // 56 | // guard let key = Key(rawValue: key) else { continue } 57 | // 58 | // switch key { 59 | // case .view: 60 | // guard pix.renderView == nil else { return } 61 | // guard let futureView = value as? FutureView else { continue } 62 | // pix.renderView = futureView.call() 63 | // } 64 | // } 65 | // } 66 | //} 67 | // 68 | //struct PixelView_Previews: PreviewProvider { 69 | // static var previews: some View { 70 | // Pixels { 71 | // PixelView { 72 | // Text("Test") 73 | // } 74 | // } 75 | // } 76 | //} 77 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelEqualize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelEqualize: Pixel { 12 | 13 | typealias Pix = EqualizePIX 14 | 15 | public let pixType: PIXType = .effect(.single(.equalize)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case includeAlpha 23 | } 24 | 25 | internal init(includeAlpha: Bool = false, 26 | pixel: () -> Pixel) { 27 | 28 | pixelTree = .singleEffect(pixel()) 29 | 30 | for key in Key.allCases { 31 | switch key { 32 | case .includeAlpha: 33 | metadata[key.rawValue] = includeAlpha 34 | } 35 | } 36 | } 37 | 38 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 39 | 40 | guard let pix = pix as? Pix else { return nil } 41 | 42 | guard let key = Key(rawValue: key) else { return nil } 43 | 44 | switch key { 45 | case .includeAlpha: 46 | return pix.includeAlpha 47 | } 48 | } 49 | 50 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 51 | 52 | guard var pix = pix as? Pix else { return } 53 | 54 | for (key, value) in metadata { 55 | 56 | guard let key = Key(rawValue: key) else { continue } 57 | 58 | switch key { 59 | case .includeAlpha: 60 | Pixels.updateValue(pix: &pix, value: value, at: \.includeAlpha) 61 | } 62 | } 63 | } 64 | } 65 | 66 | public extension Pixel { 67 | 68 | func pixelEqualize(includeAlpha: Bool = false) -> PixelEqualize { 69 | PixelEqualize(includeAlpha: includeAlpha, pixel: { self }) 70 | } 71 | } 72 | 73 | struct PixelEqualize_Previews: PreviewProvider { 74 | static var previews: some View { 75 | Pixels { 76 | PixelCamera() 77 | .pixelEqualize() 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelQuantize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2022-01-06. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelQuantize: Pixel { 12 | 13 | typealias Pix = QuantizePIX 14 | 15 | public let pixType: PIXType = .effect(.single(.quantize)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case fraction 23 | } 24 | 25 | internal init(fraction: CGFloat, 26 | pixel: () -> Pixel) { 27 | 28 | pixelTree = .singleEffect(pixel()) 29 | 30 | for key in Key.allCases { 31 | switch key { 32 | case .fraction: 33 | metadata[key.rawValue] = fraction 34 | } 35 | } 36 | } 37 | 38 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 39 | 40 | guard let pix = pix as? Pix else { return nil } 41 | 42 | guard let key = Key(rawValue: key) else { return nil } 43 | 44 | switch key { 45 | case .fraction: 46 | return pix.fraction 47 | } 48 | } 49 | 50 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 51 | 52 | guard var pix = pix as? Pix else { return } 53 | 54 | for (key, value) in metadata { 55 | 56 | guard let key = Key(rawValue: key) else { continue } 57 | 58 | switch key { 59 | case .fraction: 60 | Pixels.updateValue(pix: &pix, value: value, at: \.fraction) 61 | } 62 | } 63 | } 64 | } 65 | 66 | public extension Pixel { 67 | 68 | /// Fraction is between `0.0` and `1.0` 69 | /// A higher value will result in lower quality image. 70 | func pixelQuantize(_ fraction: CGFloat) -> PixelQuantize { 71 | PixelQuantize(fraction: fraction, pixel: { self }) 72 | } 73 | } 74 | 75 | struct PixelQuantize_Previews: PreviewProvider { 76 | static var previews: some View { 77 | Pixels { 78 | PixelCamera() 79 | .pixelQuantize(0.125) 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixels/Extensions/Pixels+createPix.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-17. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import Resolution 9 | 10 | extension Pixels { 11 | 12 | static func createPix(for pixel: Pixel, at resolution: Resolution) -> PIX & NODEOut { 13 | createPix(for: pixel, at: resolution) ?? ColorPIX(at: ._128, color: .clear) 14 | } 15 | 16 | static func createPix(for pixel: Pixel, at resolution: Resolution) -> (PIX & NODEOut)? { 17 | 18 | guard var pix: PIX & NODEOut = pixel.pixType.pix(at: resolution) as? PIX & NODEOut else { return nil } 19 | 20 | switch pixel.pixelTree { 21 | case .content: 22 | 23 | if pixel.resizeContentResolution { 24 | let resolutionPix = ResolutionPIX(at: resolution) 25 | resolutionPix.placement = .fill 26 | resolutionPix.input = pix 27 | pix = resolutionPix 28 | } 29 | 30 | case .singleEffect(let pixel): 31 | 32 | guard let singleEffectPix = pix as? PIXSingleEffect else { return nil } 33 | 34 | singleEffectPix.input = Self.createPix(for: pixel, at: resolution) 35 | 36 | case .mergerEffect(let pixelA, let pixelB): 37 | 38 | guard let mergerEffectPix = pix as? PIXMergerEffect else { return nil } 39 | 40 | mergerEffectPix.inputA = Self.createPix(for: pixelA, at: resolution) 41 | mergerEffectPix.inputB = Self.createPix(for: pixelB, at: resolution) 42 | 43 | case .multiEffect(let pixels): 44 | 45 | guard let multiEffectPix = pix as? PIXMultiEffect else { return nil } 46 | 47 | multiEffectPix.inputs = pixels.map { pixel in 48 | Self.createPix(for: pixel, at: resolution) 49 | } 50 | 51 | case .feedback(let pixel, let feedbackPixel): 52 | 53 | guard let feedbackPix = pix as? FeedbackPIX else { return nil } 54 | 55 | feedbackPix.input = Self.createPix(for: pixel, at: resolution) 56 | feedbackPix.feedbackInput = Self.createPix(for: feedbackPixel, at: resolution) 57 | } 58 | 59 | return pix 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Merger/PixelTimeMachine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelTimeMachine: Pixel { 12 | 13 | typealias Pix = TimeMachinePIX 14 | 15 | public let pixType: PIXType = .effect(.merger(.timeMachine)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case seconds 23 | } 24 | 25 | internal init(seconds: CGFloat, 26 | pixel leadingPixel: () -> Pixel, 27 | withPixel trailingPixel: () -> Pixel) { 28 | 29 | pixelTree = .mergerEffect(leadingPixel(), trailingPixel()) 30 | 31 | for key in Key.allCases { 32 | switch key { 33 | case .seconds: 34 | metadata[key.rawValue] = seconds 35 | } 36 | } 37 | } 38 | 39 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 40 | 41 | guard let pix = pix as? Pix else { return nil } 42 | 43 | guard let key = Key(rawValue: key) else { return nil } 44 | 45 | switch key { 46 | case .seconds: 47 | return pix.seconds 48 | } 49 | } 50 | 51 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 52 | 53 | guard var pix = pix as? Pix else { return } 54 | 55 | for (key, value) in metadata { 56 | 57 | guard let key = Key(rawValue: key) else { continue } 58 | 59 | switch key { 60 | case .seconds: 61 | Pixels.updateValue(pix: &pix, value: value, at: \.seconds) 62 | } 63 | } 64 | } 65 | } 66 | 67 | public extension Pixel { 68 | 69 | func pixelTimeMachine(seconds: CGFloat, pixel: () -> Pixel) -> PixelTimeMachine { 70 | PixelTimeMachine(seconds: seconds, pixel: { self }, withPixel: pixel) 71 | } 72 | } 73 | 74 | struct PixelTimeMachine_Previews: PreviewProvider { 75 | static var previews: some View { 76 | Pixels { 77 | PixelCamera() 78 | .pixelTimeMachine(seconds: 2.0) { 79 | PixelGradient(axis: .vertical) 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CoreGraphicsExtensions", 6 | "repositoryURL": "https://github.com/heestand-xyz/CoreGraphicsExtensions", 7 | "state": { 8 | "branch": null, 9 | "revision": "82d4d803cebadae60bd7f4c6f86ae83ab076396c", 10 | "version": "1.2.0" 11 | } 12 | }, 13 | { 14 | "package": "Logger", 15 | "repositoryURL": "https://github.com/heestand-xyz/Logger", 16 | "state": { 17 | "branch": null, 18 | "revision": "acaa6ef5116c201803682c8d0a8c09a642d8abe2", 19 | "version": "0.2.0" 20 | } 21 | }, 22 | { 23 | "package": "MultiViews", 24 | "repositoryURL": "https://github.com/heestand-xyz/MultiViews", 25 | "state": { 26 | "branch": null, 27 | "revision": "9cceb30e1af5fadf02558a970e744cec0136587b", 28 | "version": "1.4.1" 29 | } 30 | }, 31 | { 32 | "package": "PixelColor", 33 | "repositoryURL": "https://github.com/heestand-xyz/PixelColor", 34 | "state": { 35 | "branch": null, 36 | "revision": "6c220285979cfd74f2b084ba75663bda33548949", 37 | "version": "1.3.1" 38 | } 39 | }, 40 | { 41 | "package": "PixelKit", 42 | "repositoryURL": "https://github.com/heestand-xyz/PixelKit", 43 | "state": { 44 | "branch": null, 45 | "revision": "e8e109c788cc3436a4d5f7f781eff5252fc372a3", 46 | "version": "2.2.0" 47 | } 48 | }, 49 | { 50 | "package": "RenderKit", 51 | "repositoryURL": "https://github.com/heestand-xyz/RenderKit", 52 | "state": { 53 | "branch": null, 54 | "revision": "c95535a42be16f69364ff016ae8da4a8c3ff14c6", 55 | "version": "1.2.0" 56 | } 57 | }, 58 | { 59 | "package": "Resolution", 60 | "repositoryURL": "https://github.com/heestand-xyz/Resolution", 61 | "state": { 62 | "branch": null, 63 | "revision": "6bfdb68f12165876ec7563b931e43d3dfbb3c953", 64 | "version": "1.0.3" 65 | } 66 | }, 67 | { 68 | "package": "TextureMap", 69 | "repositoryURL": "https://github.com/heestand-xyz/TextureMap", 70 | "state": { 71 | "branch": null, 72 | "revision": "def4664aba2002dd720301378fc77e7f734798e5", 73 | "version": "0.1.0" 74 | } 75 | } 76 | ] 77 | }, 78 | "version": 1 79 | } 80 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixels/Extensions/Pixels+updateResolution.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-17. 3 | // 4 | 5 | import Foundation 6 | import CoreGraphics 7 | import PixelKit 8 | import Resolution 9 | 10 | extension Pixels { 11 | 12 | static func update(resolution: Resolution, pixel: Pixel, pix: PIX) { 13 | 14 | pixel.pixType.set(resolution: resolution, pix: pix) 15 | 16 | switch pixel.pixelTree { 17 | case .content: 18 | 19 | if pixel.resizeContentResolution { 20 | guard let resolutionPix = pix as? ResolutionPIX else { return } 21 | resolutionPix.resolution = resolution 22 | } 23 | 24 | case .singleEffect(let pixel): 25 | 26 | guard let singleEffectPix = pix as? PIXSingleEffect else { return } 27 | 28 | guard let inputPix: PIX = singleEffectPix.input as? PIX else { return } 29 | 30 | Self.update(resolution: resolution, pixel: pixel, pix: inputPix) 31 | 32 | case .mergerEffect(let pixelA, let pixelB): 33 | 34 | guard let mergerEffectPix = pix as? PIXMergerEffect else { return } 35 | 36 | guard let inputPixA: PIX = mergerEffectPix.inputA as? PIX else { return } 37 | guard let inputPixB: PIX = mergerEffectPix.inputB as? PIX else { return } 38 | 39 | Self.update(resolution: resolution, pixel: pixelA, pix: inputPixA) 40 | Self.update(resolution: resolution, pixel: pixelB, pix: inputPixB) 41 | 42 | case .multiEffect(let pixels): 43 | 44 | guard let multiEffectPix = pix as? PIXMultiEffect else { return } 45 | 46 | for (pixel, inputNode) in zip(pixels, multiEffectPix.inputs) { 47 | guard let inputPix: PIX = inputNode as? PIX else { continue } 48 | Self.update(resolution: resolution, pixel: pixel, pix: inputPix) 49 | } 50 | 51 | case .feedback(let pixel, let feedbackPixel): 52 | 53 | guard let feedbackPix = pix as? FeedbackPIX else { return } 54 | 55 | guard let inputPix: PIX = feedbackPix.input as? PIX else { return } 56 | guard let feedbackInputPix: PIX = feedbackPix.feedbackInput else { return } 57 | #warning("Loop") 58 | 59 | Self.update(resolution: resolution, pixel: pixel, pix: inputPix) 60 | Self.update(resolution: resolution, pixel: feedbackPixel, pix: feedbackInputPix) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelFlipFlop.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | public struct PixelFlipFlop: Pixel { 13 | 14 | typealias Pix = FlipFlopPIX 15 | 16 | public let pixType: PIXType = .effect(.single(.flipFlop)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case flip 24 | case flop 25 | } 26 | 27 | internal init(flip: FlipFlopPIX.Flip = .none, 28 | flop: FlipFlopPIX.Flop = .none, 29 | pixel: () -> Pixel) { 30 | 31 | pixelTree = .singleEffect(pixel()) 32 | 33 | for key in Key.allCases { 34 | switch key { 35 | case .flip: 36 | metadata[key.rawValue] = flip.rawValue 37 | case .flop: 38 | metadata[key.rawValue] = flop.rawValue 39 | } 40 | } 41 | } 42 | 43 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 44 | 45 | guard let pix = pix as? Pix else { return nil } 46 | 47 | guard let key = Key(rawValue: key) else { return nil } 48 | 49 | switch key { 50 | case .flip: 51 | return pix.flip.rawValue 52 | case .flop: 53 | return pix.flop.rawValue 54 | } 55 | } 56 | 57 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 58 | 59 | guard var pix = pix as? Pix else { return } 60 | 61 | for (key, value) in metadata { 62 | 63 | guard let key = Key(rawValue: key) else { continue } 64 | 65 | switch key { 66 | case .flip: 67 | Pixels.updateRawValue(pix: &pix, value: value, at: \.flip) 68 | case .flop: 69 | Pixels.updateRawValue(pix: &pix, value: value, at: \.flop) 70 | } 71 | } 72 | } 73 | } 74 | 75 | public extension Pixel { 76 | 77 | func pixelFlip(_ flip: FlipFlopPIX.Flip) -> PixelFlipFlop { 78 | PixelFlipFlop(flip: flip, pixel: { self }) 79 | } 80 | 81 | func pixelFlop(_ flop: FlipFlopPIX.Flop) -> PixelFlipFlop { 82 | PixelFlipFlop(flop: flop, pixel: { self }) 83 | } 84 | } 85 | 86 | struct PixelFlipFlop_Previews: PreviewProvider { 87 | static var previews: some View { 88 | Pixels { 89 | PixelImage("kite") 90 | .pixelFlip(.y) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelColorConvert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelColorConvert: Pixel { 12 | 13 | typealias Pix = ColorConvertPIX 14 | 15 | public let pixType: PIXType = .effect(.single(.colorConvert)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case conversion 23 | case channel 24 | } 25 | 26 | internal init(conversion: ColorConvertPIX.Conversion, 27 | channel: ColorConvertPIX.Channel = .all, 28 | pixel: () -> Pixel) { 29 | 30 | pixelTree = .singleEffect(pixel()) 31 | 32 | for key in Key.allCases { 33 | switch key { 34 | case .conversion: 35 | metadata[key.rawValue] = conversion.rawValue 36 | case .channel: 37 | metadata[key.rawValue] = channel.rawValue 38 | } 39 | } 40 | } 41 | 42 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 43 | 44 | guard let pix = pix as? Pix else { return nil } 45 | 46 | guard let key = Key(rawValue: key) else { return nil } 47 | 48 | switch key { 49 | case .conversion: 50 | return pix.conversion.rawValue 51 | case .channel: 52 | return pix.channel.rawValue 53 | } 54 | } 55 | 56 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 57 | 58 | guard var pix = pix as? Pix else { return } 59 | 60 | for (key, value) in metadata { 61 | 62 | guard let key = Key(rawValue: key) else { continue } 63 | 64 | switch key { 65 | case .conversion: 66 | Pixels.updateRawValue(pix: &pix, value: value, at: \.conversion) 67 | case .channel: 68 | Pixels.updateRawValue(pix: &pix, value: value, at: \.channel) 69 | } 70 | } 71 | } 72 | } 73 | 74 | public extension Pixel { 75 | 76 | func pixelColorConvert(_ conversion: ColorConvertPIX.Conversion, 77 | channel: ColorConvertPIX.Channel = .all) -> PixelColorConvert { 78 | PixelColorConvert(conversion: conversion, channel: channel, pixel: { self }) 79 | } 80 | } 81 | 82 | struct PixelColorConvert_Previews: PreviewProvider { 83 | static var previews: some View { 84 | Pixels { 85 | PixelCircle(radius: 100) 86 | .pixelColor(.orange) 87 | .pixelColorConvert(.rgbToHsv) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Merger/PixelLookup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelLookup: Pixel { 12 | 13 | typealias Pix = LookupPIX 14 | 15 | public let pixType: PIXType = .effect(.merger(.lookup)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case axis 23 | case holdEdge 24 | } 25 | 26 | internal init(axis: LookupPIX.Axis, 27 | pixel leadingPixel: () -> Pixel, 28 | withPixel trailingPixel: () -> Pixel) { 29 | 30 | pixelTree = .mergerEffect(leadingPixel(), trailingPixel()) 31 | 32 | for key in Key.allCases { 33 | switch key { 34 | case .axis: 35 | metadata[key.rawValue] = axis.rawValue 36 | default: 37 | continue 38 | } 39 | } 40 | } 41 | 42 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 43 | 44 | guard let pix = pix as? Pix else { return nil } 45 | 46 | guard let key = Key(rawValue: key) else { return nil } 47 | 48 | switch key { 49 | case .axis: 50 | return pix.axis.rawValue 51 | case .holdEdge: 52 | return pix.holdEdge 53 | } 54 | } 55 | 56 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 57 | 58 | guard var pix = pix as? Pix else { return } 59 | 60 | for (key, value) in metadata { 61 | 62 | guard let key = Key(rawValue: key) else { continue } 63 | 64 | switch key { 65 | case .axis: 66 | Pixels.updateRawValue(pix: &pix, value: value, at: \.axis) 67 | case .holdEdge: 68 | Pixels.updateValue(pix: &pix, value: value, at: \.holdEdge) 69 | } 70 | } 71 | } 72 | } 73 | 74 | public extension Pixel { 75 | 76 | func pixelLookup(axis: LookupPIX.Axis, pixel: () -> Pixel) -> PixelLookup { 77 | PixelLookup(axis: axis, pixel: { self }, withPixel: pixel) 78 | } 79 | } 80 | 81 | struct PixelLookup_Previews: PreviewProvider { 82 | static var previews: some View { 83 | Pixels { 84 | PixelCircle(radius: 100) 85 | .pixelBlur(radius: 50) 86 | .pixelLookup(axis: .vertical) { 87 | PixelGradient(axis: .vertical, colorStops: [ 88 | PixelColorStop(at: 0.0, color: .red), 89 | PixelColorStop(at: 0.5, color: .orange), 90 | PixelColorStop(at: 1.0, color: .yellow), 91 | ]) 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Content/Resource/PixelImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-22. 3 | // 4 | 5 | import Foundation 6 | import SwiftUI 7 | import RenderKit 8 | import PixelKit 9 | import PixelColor 10 | import MultiViews 11 | 12 | public struct PixelImage: Pixel { 13 | 14 | typealias Pix = ImagePIX 15 | 16 | public let pixType: PIXType = .content(.resource(.image)) 17 | 18 | public let pixelTree: PixelTree = .content 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case name 24 | case image 25 | } 26 | 27 | var name: String? 28 | var imageId: UUID? 29 | 30 | public init(_ name: String) { 31 | 32 | for key in Key.allCases { 33 | switch key { 34 | case .name: 35 | metadata[key.rawValue] = name 36 | default: 37 | continue 38 | } 39 | } 40 | } 41 | 42 | public init(_ image: @autoclosure @escaping () -> MPImage) { 43 | 44 | for key in Key.allCases { 45 | switch key { 46 | case .image: 47 | metadata[key.rawValue] = FutureImage(call: image) 48 | default: 49 | continue 50 | } 51 | } 52 | } 53 | 54 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 55 | 56 | guard let pix = pix as? Pix else { return nil } 57 | 58 | guard let key = Key(rawValue: key) else { return nil } 59 | 60 | switch key { 61 | case .name: 62 | return name 63 | case .image: 64 | return FutureImage(call: { pix.image }) 65 | } 66 | } 67 | 68 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 69 | 70 | guard let pix = pix as? Pix else { return } 71 | 72 | for (key, value) in metadata { 73 | 74 | guard let key = Key(rawValue: key) else { continue } 75 | 76 | switch key { 77 | case .name: 78 | guard let name: String = value as? String else { continue } 79 | guard let image = MPImage(named: name) else { continue } 80 | pix.image = image 81 | DispatchQueue.main.async { 82 | pix.render() 83 | } 84 | case .image: 85 | guard pix.image == nil else { return } 86 | guard let futureImage = value as? FutureImage else { continue } 87 | pix.image = futureImage.call() 88 | } 89 | } 90 | } 91 | } 92 | 93 | struct PixelImage_Previews: PreviewProvider { 94 | static var previews: some View { 95 | Pixels { 96 | PixelImage("kite") 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelPixelate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2022-01-06. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelPixelate: Pixel { 12 | 13 | typealias Pix = PixelatePIX 14 | 15 | public let pixType: PIXType = .effect(.single(.pixelate)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case style 23 | case position 24 | case radius 25 | } 26 | 27 | internal init(style: PixelatePIX.Style, 28 | position: CGPoint, 29 | radius: CGFloat, 30 | pixel: () -> Pixel) { 31 | 32 | pixelTree = .singleEffect(pixel()) 33 | 34 | for key in Key.allCases { 35 | switch key { 36 | case .style: 37 | metadata[key.rawValue] = style.rawValue 38 | case .position: 39 | metadata[key.rawValue] = position 40 | case .radius: 41 | metadata[key.rawValue] = radius 42 | } 43 | } 44 | } 45 | 46 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 47 | 48 | guard let pix = pix as? Pix else { return nil } 49 | 50 | guard let key = Key(rawValue: key) else { return nil } 51 | 52 | switch key { 53 | case .style: 54 | return pix.style.rawValue 55 | case .position: 56 | return Pixels.inZeroViewSpace(pix.position, size: size) 57 | case .radius: 58 | return pix.radius 59 | } 60 | } 61 | 62 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 63 | 64 | guard var pix = pix as? Pix else { return } 65 | 66 | for (key, value) in metadata { 67 | 68 | guard let key = Key(rawValue: key) else { continue } 69 | 70 | switch key { 71 | case .style: 72 | Pixels.updateRawValue(pix: &pix, value: value, at: \.style) 73 | case .position: 74 | Pixels.updateValueInZeroPixelSpace(pix: &pix, value: value, size: size, at: \.position) 75 | case .radius: 76 | Pixels.updateValue(pix: &pix, value: value, at: \.radius) 77 | } 78 | } 79 | } 80 | } 81 | 82 | public extension Pixel { 83 | 84 | func pixelPixelate(style: PixelatePIX.Style = .pixel, position: CGPoint = .zero, radius: CGFloat = 10) -> PixelPixelate { 85 | PixelPixelate(style: style, position: position, radius: radius, pixel: { self }) 86 | } 87 | } 88 | 89 | struct PixelPixelate_Previews: PreviewProvider { 90 | static var previews: some View { 91 | Pixels { 92 | PixelCamera() 93 | .pixelPixelate() 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Multi/PixelBlendsForEach.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import SwiftUI 7 | import Resolution 8 | import RenderKit 9 | import PixelKit 10 | 11 | public struct PixelBlendsForEach: Pixel { 12 | 13 | typealias Pix = BlendsPIX 14 | 15 | public var pixType: PIXType = .effect(.multi(.blends)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case blendMode 23 | } 24 | 25 | public init(_ range: Range, 26 | mode blendMode: RenderKit.BlendMode, 27 | pixel: (Int) -> Pixel) { 28 | 29 | var pixels: [Pixel] = [] 30 | for index in range { 31 | let pixel = pixel(index) 32 | pixels.append(pixel) 33 | } 34 | 35 | pixelTree = .multiEffect(pixels) 36 | 37 | for key in Key.allCases { 38 | switch key { 39 | case .blendMode: 40 | metadata[key.rawValue] = blendMode.rawValue 41 | } 42 | } 43 | } 44 | 45 | public init(_ range: ClosedRange, 46 | mode blendMode: RenderKit.BlendMode, 47 | pixel: (Int) -> Pixel) { 48 | 49 | var pixels: [Pixel] = [] 50 | for index in range { 51 | let pixel = pixel(index) 52 | pixels.append(pixel) 53 | } 54 | 55 | pixelTree = .multiEffect(pixels) 56 | 57 | for key in Key.allCases { 58 | switch key { 59 | case .blendMode: 60 | metadata[key.rawValue] = blendMode.rawValue 61 | } 62 | } 63 | } 64 | 65 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 66 | 67 | guard let pix = pix as? Pix else { return nil } 68 | 69 | guard let key = Key(rawValue: key) else { return nil } 70 | 71 | switch key { 72 | case .blendMode: 73 | return pix.blendMode.rawValue 74 | } 75 | } 76 | 77 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 78 | 79 | guard var pix = pix as? Pix else { return } 80 | 81 | for (key, value) in metadata { 82 | 83 | guard let key = Key(rawValue: key) else { continue } 84 | 85 | switch key { 86 | case .blendMode: 87 | Pixels.updateRawValue(pix: &pix, value: value, at: \.blendMode) 88 | } 89 | } 90 | } 91 | } 92 | 93 | struct PixelBlendsForEach_Previews: PreviewProvider { 94 | static var previews: some View { 95 | Pixels { 96 | PixelBlendsForEach(0..<10, mode: .average) { index in 97 | PixelCircle(radius: 10 * CGFloat(index)) 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixels/Extensions/Pixels+update.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-17. 3 | // 4 | 5 | import Foundation 6 | import CoreGraphics 7 | import PixelKit 8 | import Resolution 9 | 10 | extension Pixels { 11 | 12 | static func update(metadata: [PixelsMetadata.Key: PixelMetadata], 13 | pixel: Pixel, 14 | pix: PIX, 15 | size: CGSize) { 16 | 17 | let pixId = pix.id 18 | 19 | var pix = pix 20 | if pixel.resizeContentResolution { 21 | guard let resolutionPix = pix as? ResolutionPIX else { return } 22 | guard let inputPix = resolutionPix.input as? PIX else { return } 23 | pix = inputPix 24 | } 25 | 26 | var localMetadata: [String: PixelMetadata] = [:] 27 | for (key, value) in metadata { 28 | guard key.pixId == pixId else { continue } 29 | localMetadata[key.variable] = value 30 | } 31 | pixel.update(metadata: localMetadata, pix: pix, size: size) 32 | 33 | switch pixel.pixelTree { 34 | case .content: 35 | break 36 | case .singleEffect(let pixel): 37 | 38 | guard let singleEffectPix = pix as? PIXSingleEffect else { return } 39 | 40 | guard let inputPix: PIX = singleEffectPix.input as? PIX else { return } 41 | 42 | Self.update(metadata: metadata, pixel: pixel, pix: inputPix, size: size) 43 | 44 | case .mergerEffect(let pixelA, let pixelB): 45 | 46 | guard let mergerEffectPix = pix as? PIXMergerEffect else { return } 47 | 48 | guard let inputPixA: PIX = mergerEffectPix.inputA as? PIX else { return } 49 | guard let inputPixB: PIX = mergerEffectPix.inputB as? PIX else { return } 50 | 51 | Self.update(metadata: metadata, pixel: pixelA, pix: inputPixA, size: size) 52 | Self.update(metadata: metadata, pixel: pixelB, pix: inputPixB, size: size) 53 | 54 | case .multiEffect(let pixels): 55 | 56 | guard let multiEffectPix = pix as? PIXMultiEffect else { return } 57 | 58 | for (pixel, inputNode) in zip(pixels, multiEffectPix.inputs) { 59 | guard let inputPix: PIX = inputNode as? PIX else { continue } 60 | Self.update(metadata: metadata, pixel: pixel, pix: inputPix, size: size) 61 | } 62 | 63 | case .feedback(let pixel, let feedbackPixel): 64 | 65 | guard let feedbackPix = pix as? FeedbackPIX else { return } 66 | 67 | guard let inputPix: PIX = feedbackPix.input as? PIX else { return } 68 | guard let feedbackInputPix: PIX = feedbackPix.feedbackInput else { return } 69 | #warning("Loop") 70 | 71 | Self.update(metadata: metadata, pixel: pixel, pix: inputPix, size: size) 72 | Self.update(metadata: metadata, pixel: feedbackPixel, pix: feedbackInputPix, size: size) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixels/Extensions/Pixels+animation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-20. 3 | // 4 | 5 | import Foundation 6 | import SwiftUI 7 | import PixelKit 8 | 9 | extension Pixels { 10 | 11 | static func animate(animation: Animation, 12 | timer: inout Timer?, 13 | loop: @escaping (CGFloat) -> ()) { 14 | 15 | func extractString(_ name: String, in text: String) -> String? { 16 | if text.contains("\(name): ") { 17 | if let subText: String.SubSequence = text.components(separatedBy: "\(name): ").last? 18 | .split(separator: ")").first? 19 | .split(separator: ",").first { 20 | return String(subText) 21 | } 22 | } 23 | return nil 24 | } 25 | 26 | func extractDouble(_ name: String, in text: String) -> Double? { 27 | if let valueText: String = extractString(name, in: text) { 28 | if let value: Double = Double(valueText) { 29 | return value 30 | } 31 | } 32 | return nil 33 | } 34 | 35 | let duration: Double = extractDouble("duration", in: animation.description) ?? 0.0 36 | let bx: Double = extractDouble("bx", in: animation.description) ?? 1.0 37 | 38 | let startTime: Date = .init() 39 | let interval: Double = 1.0 / Double(PixelKit.main.render.fpsMax) 40 | 41 | timer?.invalidate() 42 | 43 | enum Ease { 44 | case linear 45 | case easeIn 46 | case easeOut 47 | case easeInOut 48 | } 49 | var ease: Ease = .linear 50 | if bx == 3.0 { 51 | ease = .linear 52 | } else if bx < 0.0 { 53 | ease = .easeInOut 54 | } else if bx < 1.0 { 55 | ease = .easeIn 56 | } else if bx > 1.0 { 57 | ease = .easeOut 58 | } 59 | 60 | timer = Timer(timeInterval: interval, repeats: true) { timer in 61 | let rawFraction: CGFloat = CGFloat(-startTime.timeIntervalSinceNow / duration) 62 | var fraction: CGFloat = rawFraction 63 | if animation == .easeIn(duration: 0.0) { 64 | print("Aa") 65 | fraction = cos(-.pi + fraction * .pi / 2) 66 | } 67 | switch ease { 68 | case .easeIn: 69 | fraction = cos(-.pi + fraction * .pi / 2) + 1.0 70 | case .easeOut: 71 | fraction = cos(-.pi / 2 + fraction * .pi / 2) 72 | case .easeInOut: 73 | fraction = cos(.pi + fraction * .pi) / 2.0 + 0.5 74 | case .linear: 75 | break 76 | } 77 | if fraction > 1.0 { 78 | fraction = 1.0 79 | } 80 | loop(fraction) 81 | if rawFraction >= 1.0 { 82 | timer.invalidate() 83 | } 84 | } 85 | 86 | RunLoop.current.add(timer!, forMode: .common) 87 | 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelMorph.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelMorph: Pixel { 12 | 13 | typealias Pix = MorphPIX 14 | 15 | public let pixType: PIXType = .effect(.single(.morph)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case style 23 | case width 24 | case height 25 | } 26 | 27 | internal init(style: MorphPIX.Style = .maximum, 28 | width: Int = 1, 29 | height: Int = 1, 30 | pixel: () -> Pixel) { 31 | 32 | pixelTree = .singleEffect(pixel()) 33 | 34 | for key in Key.allCases { 35 | switch key { 36 | case .style: 37 | metadata[key.rawValue] = style.rawValue 38 | case .width: 39 | metadata[key.rawValue] = width 40 | case .height: 41 | metadata[key.rawValue] = height 42 | } 43 | } 44 | } 45 | 46 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 47 | 48 | guard let pix = pix as? Pix else { return nil } 49 | 50 | guard let key = Key(rawValue: key) else { return nil } 51 | 52 | switch key { 53 | case .style: 54 | return pix.style.rawValue 55 | case .width: 56 | return pix.width 57 | case .height: 58 | return pix.height 59 | } 60 | } 61 | 62 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 63 | 64 | guard var pix = pix as? Pix else { return } 65 | 66 | for (key, value) in metadata { 67 | 68 | guard let key = Key(rawValue: key) else { continue } 69 | 70 | switch key { 71 | case .style: 72 | Pixels.updateRawValue(pix: &pix, value: value, at: \.style) 73 | case .width: 74 | Pixels.updateValue(pix: &pix, value: value, at: \.width) 75 | case .height: 76 | Pixels.updateValue(pix: &pix, value: value, at: \.height) 77 | } 78 | } 79 | } 80 | } 81 | 82 | public extension Pixel { 83 | 84 | func pixelMorphMinimum(width: Int, height: Int) -> PixelMorph { 85 | PixelMorph(style: .minimum, width: width, height: height, pixel: { self }) 86 | } 87 | 88 | func pixelMorphMaximum(width: Int, height: Int) -> PixelMorph { 89 | PixelMorph(style: .maximum, width: width, height: height, pixel: { self }) 90 | } 91 | } 92 | 93 | struct PixelMorph_Previews: PreviewProvider { 94 | static var previews: some View { 95 | Pixels { 96 | PixelCamera() 97 | .pixelMorphMaximum(width: 10, height: 10) 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Content/Generator/PixelColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-23. 3 | // 4 | 5 | import Foundation 6 | import SwiftUI 7 | import CoreGraphics 8 | import RenderKit 9 | import PixelKit 10 | import PixelColor 11 | 12 | extension PixelColor: Pixel { 13 | 14 | typealias Pix = ColorPIX 15 | 16 | public var pixType: PIXType { .content(.generator(.color)) } 17 | 18 | public var pixelTree: PixelTree { .content } 19 | 20 | public var size: CGSize? { 21 | get { nil } 22 | set {} 23 | } 24 | 25 | public var metadata: [String : PixelMetadata] { 26 | get { [Key.color.rawValue: self] } 27 | set {} 28 | } 29 | 30 | enum Key: String, CaseIterable { 31 | case color 32 | } 33 | 34 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 35 | 36 | guard let pix = pix as? Pix else { return nil } 37 | 38 | guard let key = Key(rawValue: key) else { return nil } 39 | 40 | switch key { 41 | case .color: 42 | return pix.color 43 | } 44 | } 45 | 46 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 47 | 48 | guard let pix = pix as? Pix else { return } 49 | 50 | for (key, value) in metadata { 51 | 52 | guard let key = Key(rawValue: key) else { continue } 53 | 54 | switch key { 55 | case .color: 56 | guard let color = value as? PixelColor else { continue } 57 | pix.color = color 58 | } 59 | } 60 | } 61 | } 62 | 63 | extension Color: Pixel { 64 | 65 | typealias Pix = ColorPIX 66 | 67 | public var pixType: PIXType { .content(.generator(.color)) } 68 | 69 | public var pixelTree: PixelTree { .content } 70 | 71 | public var size: CGSize? { 72 | get { nil } 73 | set {} 74 | } 75 | 76 | public var metadata: [String : PixelMetadata] { 77 | get { [Key.color.rawValue: PixelColor(self)] } 78 | set {} 79 | } 80 | 81 | enum Key: String, CaseIterable { 82 | case color 83 | } 84 | 85 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 86 | 87 | guard let pix = pix as? Pix else { return nil } 88 | 89 | guard let key = Key(rawValue: key) else { return nil } 90 | 91 | switch key { 92 | case .color: 93 | return pix.color 94 | } 95 | } 96 | 97 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 98 | 99 | guard let pix = pix as? Pix else { return } 100 | 101 | for (key, value) in metadata { 102 | 103 | guard let key = Key(rawValue: key) else { continue } 104 | 105 | switch key { 106 | case .color: 107 | guard let color = value as? PixelColor else { continue } 108 | pix.color = color 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelClamp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelClamp: Pixel { 12 | 13 | typealias Pix = ClampPIX 14 | 15 | public let pixType: PIXType = .effect(.single(.clamp)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case low 23 | case high 24 | case clampAlpha 25 | } 26 | 27 | internal init(low: CGFloat = 0.0, 28 | high: CGFloat = 1.0, 29 | clampAlpha: Bool = false, 30 | pixel: () -> Pixel) { 31 | 32 | pixelTree = .singleEffect(pixel()) 33 | 34 | for key in Key.allCases { 35 | switch key { 36 | case .low: 37 | metadata[key.rawValue] = low 38 | case .high: 39 | metadata[key.rawValue] = high 40 | case .clampAlpha: 41 | metadata[key.rawValue] = clampAlpha 42 | } 43 | } 44 | } 45 | 46 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 47 | 48 | guard let pix = pix as? Pix else { return nil } 49 | 50 | guard let key = Key(rawValue: key) else { return nil } 51 | 52 | switch key { 53 | case .low: 54 | return pix.low 55 | case .high: 56 | return pix.high 57 | case .clampAlpha: 58 | return pix.clampAlpha 59 | } 60 | } 61 | 62 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 63 | 64 | guard var pix = pix as? Pix else { return } 65 | 66 | for (key, value) in metadata { 67 | 68 | guard let key = Key(rawValue: key) else { continue } 69 | 70 | switch key { 71 | case .low: 72 | Pixels.updateValue(pix: &pix, value: value, at: \.low) 73 | case .high: 74 | Pixels.updateValue(pix: &pix, value: value, at: \.high) 75 | case .clampAlpha: 76 | Pixels.updateValue(pix: &pix, value: value, at: \.clampAlpha) 77 | } 78 | } 79 | } 80 | } 81 | 82 | public extension Pixel { 83 | 84 | func pixelClamp(low: CGFloat = 0.0, 85 | high: CGFloat = 1.0) -> PixelClamp { 86 | PixelClamp(low: low, high: high, pixel: { self }) 87 | } 88 | 89 | func pixelClampWithAlpha(low: CGFloat = 0.0, 90 | high: CGFloat = 1.0) -> PixelClamp { 91 | PixelClamp(low: low, high: high, clampAlpha: true, pixel: { self }) 92 | } 93 | } 94 | 95 | struct PixelClamp_Previews: PreviewProvider { 96 | static var previews: some View { 97 | Pixels { 98 | PixelCircle(radius: 100) 99 | .pixelBlur(radius: 100) 100 | .pixelClamp(low: 0.25, high: 0.75) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelColorShift.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | public struct PixelColorShift: Pixel { 13 | 14 | typealias Pix = ColorShiftPIX 15 | 16 | public let pixType: PIXType = .effect(.single(.colorShift)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case hue 24 | case saturation 25 | case tintColor 26 | } 27 | 28 | internal init(hue: CGFloat = 0.0, 29 | saturation: CGFloat = 1.0, 30 | tintColor: Color = .white, 31 | pixel: () -> Pixel) { 32 | 33 | pixelTree = .singleEffect(pixel()) 34 | 35 | for key in Key.allCases { 36 | switch key { 37 | case .hue: 38 | metadata[key.rawValue] = hue 39 | case .saturation: 40 | metadata[key.rawValue] = saturation 41 | case .tintColor: 42 | metadata[key.rawValue] = PixelColor(tintColor) 43 | } 44 | } 45 | } 46 | 47 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 48 | 49 | guard let pix = pix as? Pix else { return nil } 50 | 51 | guard let key = Key(rawValue: key) else { return nil } 52 | 53 | switch key { 54 | case .hue: 55 | return pix.hue 56 | case .saturation: 57 | return pix.saturation 58 | case .tintColor: 59 | return pix.tintColor 60 | } 61 | } 62 | 63 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 64 | 65 | guard var pix = pix as? Pix else { return } 66 | 67 | for (key, value) in metadata { 68 | 69 | guard let key = Key(rawValue: key) else { continue } 70 | 71 | switch key { 72 | case .hue: 73 | Pixels.updateValue(pix: &pix, value: value, at: \.hue) 74 | case .saturation: 75 | Pixels.updateValue(pix: &pix, value: value, at: \.saturation) 76 | case .tintColor: 77 | Pixels.updateValue(pix: &pix, value: value, at: \.tintColor) 78 | } 79 | } 80 | } 81 | } 82 | 83 | public extension Pixel { 84 | 85 | func pixelHue(_ hue: CGFloat) -> PixelColorShift { 86 | PixelColorShift(hue: hue, pixel: { self }) 87 | } 88 | 89 | func pixelSaturation(_ saturation: CGFloat) -> PixelColorShift { 90 | PixelColorShift(saturation: saturation, pixel: { self }) 91 | } 92 | 93 | func pixelMonochrome() -> PixelColorShift { 94 | PixelColorShift(saturation: 0.0, pixel: { self }) 95 | } 96 | } 97 | 98 | struct PixelColorShift_Previews: PreviewProvider { 99 | static var previews: some View { 100 | Pixels { 101 | PixelImage("kite") 102 | .pixelMonochrome() 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Merger/PixelDisplace.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelDisplace: Pixel { 12 | 13 | typealias Pix = DisplacePIX 14 | 15 | public let pixType: PIXType = .effect(.merger(.displace)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case distance 23 | case origin 24 | case extend 25 | } 26 | 27 | internal init(distance: CGFloat, 28 | origin: CGFloat = 0.5, 29 | pixel leadingPixel: () -> Pixel, 30 | withPixel trailingPixel: () -> Pixel) { 31 | 32 | pixelTree = .mergerEffect(leadingPixel(), trailingPixel()) 33 | 34 | for key in Key.allCases { 35 | switch key { 36 | case .distance: 37 | metadata[key.rawValue] = distance 38 | case .origin: 39 | metadata[key.rawValue] = origin 40 | default: 41 | continue 42 | } 43 | } 44 | } 45 | 46 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 47 | 48 | guard let pix = pix as? Pix else { return nil } 49 | 50 | guard let key = Key(rawValue: key) else { return nil } 51 | 52 | switch key { 53 | case .distance: 54 | return Pixels.inViewSpace(pix.distance, size: size) 55 | case .origin: 56 | return pix.origin 57 | case .extend: 58 | return pix.extend.rawValue 59 | } 60 | } 61 | 62 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 63 | 64 | guard var pix = pix as? Pix else { return } 65 | 66 | for (key, value) in metadata { 67 | 68 | guard let key = Key(rawValue: key) else { continue } 69 | 70 | switch key { 71 | case .distance: 72 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.distance) 73 | case .origin: 74 | Pixels.updateValue(pix: &pix, value: value, at: \.origin) 75 | case .extend: 76 | Pixels.updateRawValue(pix: &pix, value: value, at: \.extend) 77 | } 78 | } 79 | } 80 | } 81 | 82 | public extension Pixel { 83 | 84 | func pixelDisplace(_ distance: CGFloat, origin: CGFloat = 0.5, pixel: () -> Pixel) -> PixelDisplace { 85 | PixelDisplace(distance: distance, origin: origin, pixel: { self }, withPixel: pixel) 86 | } 87 | } 88 | 89 | public extension PixelDisplace { 90 | 91 | func pixelExtend(_ extend: ExtendMode) -> Self { 92 | var pixel = self 93 | pixel.metadata[Key.extend.rawValue] = extend.rawValue 94 | return pixel 95 | } 96 | } 97 | 98 | struct PixelDisplace_Previews: PreviewProvider { 99 | static var previews: some View { 100 | Pixels { 101 | PixelCircle(radius: 100) 102 | .pixelDisplace(100) { 103 | PixelNoise() 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelColorCorrect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | public struct PixelColorCorrect: Pixel { 13 | 14 | typealias Pix = ColorCorrectPIX 15 | 16 | public let pixType: PIXType = .effect(.single(.colorCorrect)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case whitePoint 24 | case vibrance 25 | case temperature 26 | } 27 | 28 | internal init(whitePoint: Color = .white, 29 | vibrance: CGFloat = 0.0, 30 | temperature: CGFloat = 0.0, 31 | pixel: () -> Pixel) { 32 | 33 | pixelTree = .singleEffect(pixel()) 34 | 35 | for key in Key.allCases { 36 | switch key { 37 | case .whitePoint: 38 | metadata[key.rawValue] = PixelColor(whitePoint) 39 | case .vibrance: 40 | metadata[key.rawValue] = vibrance 41 | case .temperature: 42 | metadata[key.rawValue] = temperature 43 | } 44 | } 45 | } 46 | 47 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 48 | 49 | guard let pix = pix as? Pix else { return nil } 50 | 51 | guard let key = Key(rawValue: key) else { return nil } 52 | 53 | switch key { 54 | case .whitePoint: 55 | return pix.whitePoint 56 | case .vibrance: 57 | return pix.vibrance 58 | case .temperature: 59 | return pix.temperature 60 | } 61 | } 62 | 63 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 64 | 65 | guard var pix = pix as? Pix else { return } 66 | 67 | for (key, value) in metadata { 68 | 69 | guard let key = Key(rawValue: key) else { continue } 70 | 71 | switch key { 72 | case .whitePoint: 73 | Pixels.updateValue(pix: &pix, value: value, at: \.whitePoint) 74 | case .vibrance: 75 | Pixels.updateValue(pix: &pix, value: value, at: \.vibrance) 76 | case .temperature: 77 | Pixels.updateValue(pix: &pix, value: value, at: \.temperature) 78 | } 79 | } 80 | } 81 | } 82 | 83 | public extension Pixel { 84 | 85 | /// Pixel Color Correct 86 | /// - Parameters: 87 | /// - whitePoint: The color white should be. 88 | /// - vibrance: Saturation between `0.0` and `1.0`. 89 | /// - temperature: Cold is `-1.0`, neutral is `0.0` and warm is `1.0`, 90 | func pixelColorCorrect(whitePoint: Color = .white, 91 | vibrance: CGFloat = 0.0, 92 | temperature: CGFloat = 0.0) -> PixelColorCorrect { 93 | PixelColorCorrect(whitePoint: whitePoint, vibrance: vibrance, temperature: temperature, pixel: { self }) 94 | } 95 | } 96 | 97 | struct PixelColorCorrect_Previews: PreviewProvider { 98 | static var previews: some View { 99 | Pixels { 100 | PixelImage("kite") 101 | .pixelColorCorrect(vibrance: 0.5, temperature: 0.5) 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Content/Generator/PixelMetal.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-23. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | /// Pixel Metal 13 | /// 14 | /// **Variables:** pi, u, v, uv, var.resx, var.height, var.aspect, var.your-variable-name 15 | /// 16 | /// Example: 17 | /// ```metal 18 | /// return float4(u, v, 0.0, 1.0); 19 | /// ``` 20 | public struct PixelMetal: Pixel { 21 | 22 | typealias Pix = MetalPIX 23 | 24 | public let pixType: PIXType = .content(.generator(.metal)) 25 | 26 | public let pixelTree: PixelTree = .content 27 | 28 | public var metadata: [String : PixelMetadata] = [:] 29 | 30 | enum Key: String, CaseIterable { 31 | case code 32 | } 33 | 34 | public init(variables: [PixelVariable] = [], 35 | code: String) { 36 | 37 | for (index, variable) in variables.enumerated() { 38 | metadata["variable-\(index)"] = variable 39 | } 40 | 41 | for key in Key.allCases { 42 | switch key { 43 | case .code: 44 | metadata[key.rawValue] = code 45 | } 46 | } 47 | } 48 | 49 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 50 | 51 | guard let pix = pix as? Pix else { return nil } 52 | 53 | if key.starts(with: "variable") { 54 | guard let indexString = key.split(separator: "-").last else { return nil } 55 | guard let index = Int(indexString) else { return nil } 56 | guard pix.metalUniforms.indices.contains(index) else { return nil } 57 | let metalUniform: MetalUniform = pix.metalUniforms[index] 58 | return PixelVariable(name: metalUniform.name, value: metalUniform.value) 59 | } 60 | 61 | guard let key = Key(rawValue: key) else { return nil } 62 | 63 | switch key { 64 | case .code: 65 | return pix.code 66 | } 67 | } 68 | 69 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 70 | 71 | guard var pix = pix as? Pix else { return } 72 | 73 | for (key, value) in metadata { 74 | 75 | if key.starts(with: "variable") { 76 | guard let indexString = key.split(separator: "-").last else { continue } 77 | guard let index = Int(indexString) else { continue } 78 | guard let variable = value as? PixelVariable else { return } 79 | let metalUniform = MetalUniform(name: variable.name, value: variable.value) 80 | if pix.metalUniforms.indices.contains(index) { 81 | pix.metalUniforms[index] = metalUniform 82 | } else { 83 | pix.metalUniforms.append(metalUniform) 84 | } 85 | } 86 | 87 | guard let key = Key(rawValue: key) else { continue } 88 | 89 | switch key { 90 | case .code: 91 | Pixels.updateValue(pix: &pix, value: value, at: \.code) 92 | } 93 | } 94 | } 95 | } 96 | 97 | struct PixelMetal_Previews: PreviewProvider { 98 | static var previews: some View { 99 | Pixels { 100 | PixelMetal(code: "return float4(1.0, 0.5, 0.0, 1.0);") 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixels/Extensions/Pixels+conversion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-21. 3 | // 4 | 5 | import Foundation 6 | import SwiftUI 7 | 8 | extension Pixels { 9 | 10 | static func asAngle(_ value: CGFloat) -> Angle { 11 | Angle(degrees: value * 360) 12 | } 13 | 14 | static func asRotation(_ value: Angle) -> CGFloat { 15 | CGFloat(value.degrees) / 360 16 | } 17 | } 18 | 19 | extension Pixels { 20 | 21 | static func inViewSpace(_ value: CGFloat, size: CGSize) -> CGFloat { 22 | value * size.height 23 | } 24 | 25 | static func inZeroViewSpace(_ value: CGPoint, size: CGSize) -> CGPoint { 26 | CGPoint(x: value.x * size.height, 27 | y: -value.y * size.height) 28 | } 29 | 30 | static func inNormalizedViewSpace(_ value: CGPoint, size: CGSize) -> CGPoint { 31 | CGPoint(x: value.x * size.width, 32 | y: (1.0 - value.y) * size.height) 33 | } 34 | 35 | static func inNormalizedLeftViewSpace(_ value: CGFloat, size: CGSize) -> CGFloat { 36 | value * size.width 37 | } 38 | 39 | static func inNormalizedRightViewSpace(_ value: CGFloat, size: CGSize) -> CGFloat { 40 | (1.0 - value) * size.width 41 | } 42 | 43 | static func inNormalizedBottomViewSpace(_ value: CGFloat, size: CGSize) -> CGFloat { 44 | value * size.height 45 | } 46 | 47 | static func inNormalizedTopViewSpace(_ value: CGFloat, size: CGSize) -> CGFloat { 48 | (1.0 - value) * size.height 49 | } 50 | 51 | static func inViewSpace(_ value: CGPoint, size: CGSize) -> CGPoint { 52 | CGPoint(x: value.x * size.height + size.width / 2, 53 | y: -value.y * size.height + size.height / 2) 54 | } 55 | 56 | static func inViewSpace(_ value: CGSize, size: CGSize) -> CGSize { 57 | CGSize(width: value.width * size.height, 58 | height: value.height * size.height) 59 | } 60 | } 61 | 62 | extension Pixels { 63 | 64 | static func inPixelSpace(_ value: CGFloat, size: CGSize) -> CGFloat { 65 | value / size.height 66 | } 67 | 68 | static func inZeroPixelSpace(_ value: CGPoint, size: CGSize) -> CGPoint { 69 | CGPoint(x: value.x / size.height, 70 | y: -value.y / size.height) 71 | } 72 | 73 | static func inNormalizedPixelSpace(_ value: CGPoint, size: CGSize) -> CGPoint { 74 | CGPoint(x: value.x / size.width, 75 | y: 1.0 - (value.y / size.height)) 76 | } 77 | 78 | static func inNormalizedLeftPixelSpace(_ value: CGFloat, size: CGSize) -> CGFloat { 79 | value / size.width 80 | } 81 | 82 | static func inNormalizedRightPixelSpace(_ value: CGFloat, size: CGSize) -> CGFloat { 83 | 1.0 - (value / size.width) 84 | } 85 | 86 | static func inNormalizedBottomPixelSpace(_ value: CGFloat, size: CGSize) -> CGFloat { 87 | value / size.height 88 | } 89 | 90 | static func inNormalizedTopPixelSpace(_ value: CGFloat, size: CGSize) -> CGFloat { 91 | 1.0 - (value / size.height) 92 | } 93 | 94 | static func inPixelSpace(_ value: CGPoint, size: CGSize) -> CGPoint { 95 | CGPoint(x: (value.x - size.width / 2) / size.height, 96 | y: -(value.y - size.height / 2) / size.height) 97 | } 98 | 99 | static func inPixelSpace(_ value: CGSize, size: CGSize) -> CGSize { 100 | CGSize(width: value.width / size.height, 101 | height: value.height / size.height) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelChannelMix.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelChannelMix: Pixel { 12 | 13 | typealias Pix = ChannelMixPIX 14 | 15 | public let pixType: PIXType = .effect(.single(.channelMix)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case red 23 | case green 24 | case blue 25 | case alpha 26 | } 27 | 28 | internal init(red: ChannelMixPIX.Channel = .red, 29 | green: ChannelMixPIX.Channel = .green, 30 | blue: ChannelMixPIX.Channel = .blue, 31 | alpha: ChannelMixPIX.Channel = .alpha, 32 | pixel: () -> Pixel) { 33 | 34 | pixelTree = .singleEffect(pixel()) 35 | 36 | for key in Key.allCases { 37 | switch key { 38 | case .red: 39 | metadata[key.rawValue] = red.rawValue 40 | case .green: 41 | metadata[key.rawValue] = green.rawValue 42 | case .blue: 43 | metadata[key.rawValue] = blue.rawValue 44 | case .alpha: 45 | metadata[key.rawValue] = alpha.rawValue 46 | } 47 | } 48 | } 49 | 50 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 51 | 52 | guard let pix = pix as? Pix else { return nil } 53 | 54 | guard let key = Key(rawValue: key) else { return nil } 55 | 56 | switch key { 57 | case .red: 58 | return pix.red.rawValue 59 | case .green: 60 | return pix.green.rawValue 61 | case .blue: 62 | return pix.blue.rawValue 63 | case .alpha: 64 | return pix.alpha.rawValue 65 | } 66 | } 67 | 68 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 69 | 70 | guard var pix = pix as? Pix else { return } 71 | 72 | for (key, value) in metadata { 73 | 74 | guard let key = Key(rawValue: key) else { continue } 75 | 76 | switch key { 77 | case .red: 78 | Pixels.updateRawValue(pix: &pix, value: value, at: \.red) 79 | case .green: 80 | Pixels.updateRawValue(pix: &pix, value: value, at: \.green) 81 | case .blue: 82 | Pixels.updateRawValue(pix: &pix, value: value, at: \.blue) 83 | case .alpha: 84 | Pixels.updateRawValue(pix: &pix, value: value, at: \.alpha) 85 | } 86 | } 87 | } 88 | } 89 | 90 | public extension Pixel { 91 | 92 | func pixelChannelMix(red: ChannelMixPIX.Channel = .red, 93 | green: ChannelMixPIX.Channel = .green, 94 | blue: ChannelMixPIX.Channel = .blue, 95 | alpha: ChannelMixPIX.Channel = .alpha) -> PixelChannelMix { 96 | PixelChannelMix(red: red, green: green, blue: blue, alpha: alpha, pixel: { self }) 97 | } 98 | } 99 | 100 | struct PixelChannelMix_Previews: PreviewProvider { 101 | static var previews: some View { 102 | Pixels { 103 | PixelCircle(radius: 100) 104 | .pixelChannelMix(red: .blue, blue: .red) 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PixelUI 2 | 3 | ## Install 4 | 5 | ### Swift Package 6 | 7 | ~~~~swift 8 | .package(url: "https://github.com/heestand-xyz/PixelUI", from: "1.0.0") 9 | ~~~~ 10 | 11 | ## Camera Example 12 | 13 | ```swift 14 | import SwiftUI 15 | import PixelUI 16 | ``` 17 | 18 | ```swift 19 | struct ContentView: View { 20 | 21 | var body: some View { 22 | 23 | Pixels { 24 | PixelCamera() 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | ## Gradients Example 31 | 32 | 33 | 34 | ```swift 35 | import SwiftUI 36 | import PixelUI 37 | ``` 38 | 39 | ```swift 40 | struct GradientsView: View { 41 | 42 | var body: some View { 43 | 44 | Pixels { 45 | 46 | PixelBlends(mode: .add) { 47 | 48 | PixelGradient(axis: .vertical, colors: [.orange, .blue]) 49 | 50 | PixelNoise() 51 | } 52 | .pixelGamma(0.75) 53 | } 54 | } 55 | } 56 | ``` 57 | 58 | ## Effects Example 59 | 60 | 61 | 62 | ```swift 63 | import SwiftUI 64 | import PixelUI 65 | ``` 66 | 67 | ```swift 68 | struct EffectsView: View { 69 | 70 | var body: some View { 71 | 72 | Pixels { 73 | 74 | PixelImage("Kite") 75 | .pixelDisplace(300) { 76 | 77 | PixelBlends(mode: .over) { 78 | Color.gray 79 | PixelBlends(mode: .multiply) { 80 | PixelNoise() 81 | PixelGradient(axis: .radial, colors: [.clear, .white]) 82 | } 83 | } 84 | } 85 | .pixelLumaSaturation(0.0) { 86 | PixelGradient(axis: .radial) 87 | } 88 | } 89 | } 90 | } 91 | ``` 92 | 93 | ## Shapes Example 94 | 95 | 96 | 97 | ```swift 98 | import SwiftUI 99 | import PixelUI 100 | ``` 101 | 102 | ```swift 103 | struct ShapesView: View { 104 | 105 | var body: some View { 106 | 107 | GeometryReader { geo in 108 | 109 | let radius = geo.size.height / 2 110 | 111 | Pixels { 112 | 113 | PixelBlends(mode: .difference) { 114 | 115 | PixelPolygon(count: 3, radius: radius) 116 | .pixelCornerRadius(radius * 0.25) 117 | 118 | PixelPolygon(count: 3, radius: radius * 0.55) 119 | .pixelCornerRadius(radius * 0.125) 120 | .pixelFlip(.y) 121 | 122 | PixelCircle(radius: radius * 0.15) 123 | 124 | PixelBlendsForEach(0..<3, mode: .difference) { index in 125 | let radians = (CGFloat(index) / 3) * .pi * 2 - .pi / 2 126 | return PixelCircle(radius: radius * 0.15) 127 | .pixelOffset(x: cos(radians) * radius * 0.5, 128 | y: sin(radians) * radius * 0.5) 129 | } 130 | } 131 | } 132 | } 133 | } 134 | } 135 | ``` 136 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelKaleidoscope.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelKaleidoscope: Pixel { 12 | 13 | typealias Pix = KaleidoscopePIX 14 | 15 | public let pixType: PIXType = .effect(.single(.kaleidoscope)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case divisions 23 | case mirror 24 | case rotation 25 | case position 26 | } 27 | 28 | internal init(divisions: Int = 12, 29 | mirror: Bool = true, 30 | rotation: Angle = .zero, 31 | offset position: CGPoint = .zero, 32 | pixel: () -> Pixel) { 33 | 34 | pixelTree = .singleEffect(pixel()) 35 | 36 | for key in Key.allCases { 37 | switch key { 38 | case .divisions: 39 | metadata[key.rawValue] = divisions 40 | case .mirror: 41 | metadata[key.rawValue] = mirror 42 | case .rotation: 43 | metadata[key.rawValue] = rotation 44 | case .position: 45 | metadata[key.rawValue] = position 46 | } 47 | } 48 | } 49 | 50 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 51 | 52 | guard let pix = pix as? Pix else { return nil } 53 | 54 | guard let key = Key(rawValue: key) else { return nil } 55 | 56 | switch key { 57 | case .divisions: 58 | return pix.divisions 59 | case .mirror: 60 | return pix.mirror 61 | case .rotation: 62 | return Pixels.asAngle(pix.rotation) 63 | case .position: 64 | return Pixels.inZeroViewSpace(pix.position, size: size) 65 | } 66 | } 67 | 68 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 69 | 70 | guard var pix = pix as? Pix else { return } 71 | 72 | for (key, value) in metadata { 73 | 74 | guard let key = Key(rawValue: key) else { continue } 75 | 76 | switch key { 77 | case .divisions: 78 | Pixels.updateValue(pix: &pix, value: value, at: \.divisions) 79 | case .mirror: 80 | Pixels.updateValue(pix: &pix, value: value, at: \.mirror) 81 | case .rotation: 82 | Pixels.updateValueAngle(pix: &pix, value: value, at: \.rotation) 83 | case .position: 84 | Pixels.updateValueInZeroPixelSpace(pix: &pix, value: value, size: size, at: \.position) 85 | } 86 | } 87 | } 88 | } 89 | 90 | public extension Pixel { 91 | 92 | func pixelKaleidoscope(divisions: Int = 12, 93 | mirror: Bool = true, 94 | rotation: Angle = .zero, 95 | offset: CGPoint = .zero) -> PixelKaleidoscope { 96 | PixelKaleidoscope(divisions: divisions, 97 | mirror: mirror, 98 | rotation: rotation, 99 | offset: offset, 100 | pixel: { self }) 101 | } 102 | } 103 | 104 | struct PixelKaleidoscope_Previews: PreviewProvider { 105 | static var previews: some View { 106 | Pixels { 107 | PixelCamera() 108 | .pixelKaleidoscope() 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelWarp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2022-01-06. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | public struct PixelWarp: Pixel { 13 | 14 | typealias Pix = WarpPIX 15 | 16 | public let pixType: PIXType = .effect(.single(.warp)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case style 24 | case position 25 | case radius 26 | case scale 27 | case rotation 28 | } 29 | 30 | internal init(style: WarpPIX.Style, 31 | offset position: CGPoint, 32 | radius: CGFloat, 33 | scale: CGFloat, 34 | angle rotation: Angle, 35 | pixel: () -> Pixel) { 36 | 37 | pixelTree = .singleEffect(pixel()) 38 | 39 | for key in Key.allCases { 40 | switch key { 41 | case .style: 42 | metadata[key.rawValue] = style.rawValue 43 | case .position: 44 | metadata[key.rawValue] = position 45 | case .radius: 46 | metadata[key.rawValue] = radius 47 | case .scale: 48 | metadata[key.rawValue] = scale 49 | case .rotation: 50 | metadata[key.rawValue] = rotation 51 | } 52 | } 53 | } 54 | 55 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 56 | 57 | guard let pix = pix as? Pix else { return nil } 58 | 59 | guard let key = Key(rawValue: key) else { return nil } 60 | 61 | switch key { 62 | case .style: 63 | return pix.style.rawValue 64 | case .position: 65 | return Pixels.inZeroViewSpace(pix.position, size: size) 66 | case .radius: 67 | return Pixels.inViewSpace(pix.radius, size: size) 68 | case .scale: 69 | return pix.scale 70 | case .rotation: 71 | return Pixels.asAngle(pix.rotation) 72 | } 73 | } 74 | 75 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 76 | 77 | guard var pix = pix as? Pix else { return } 78 | 79 | for (key, value) in metadata { 80 | 81 | guard let key = Key(rawValue: key) else { continue } 82 | 83 | switch key { 84 | case .style: 85 | Pixels.updateRawValue(pix: &pix, value: value, at: \.style) 86 | case .position: 87 | Pixels.updateValueInZeroPixelSpace(pix: &pix, value: value, size: size, at: \.position) 88 | case .radius: 89 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.radius) 90 | case .scale: 91 | Pixels.updateValue(pix: &pix, value: value, at: \.scale) 92 | case .rotation: 93 | Pixels.updateValueAngle(pix: &pix, value: value, at: \.rotation) 94 | } 95 | } 96 | } 97 | } 98 | 99 | public extension Pixel { 100 | 101 | func pixelWarp(as style: WarpPIX.Style, offset: CGPoint = .zero, radius: CGFloat, scale: CGFloat = 1.0, angle: Angle = .zero) -> PixelWarp { 102 | PixelWarp(style: style, offset: offset, radius: radius, scale: scale, angle: angle, pixel: { self }) 103 | } 104 | } 105 | 106 | struct PixelWarp_Previews: PreviewProvider { 107 | static var previews: some View { 108 | Pixels { 109 | PixelCamera() 110 | .pixelWarp(as: .tunnel, radius: 100) 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Multi/PixelStack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2022-01-09. 3 | // 4 | 5 | import Foundation 6 | import SwiftUI 7 | import Resolution 8 | import RenderKit 9 | import PixelKit 10 | import PixelColor 11 | 12 | public struct PixelStack: Pixel { 13 | 14 | typealias Pix = StackPIX 15 | 16 | public var pixType: PIXType = .effect(.multi(.stack)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case axis 24 | case alignment 25 | case spacing 26 | case padding 27 | case backgroundColor 28 | } 29 | 30 | public init(axis: StackPIX.Axis, 31 | alignment: StackPIX.Alignment = .center, 32 | spacing: CGFloat = 0.0, 33 | padding: CGFloat = 0.0, 34 | @PixelBuilder pixels: () -> [Pixel]) { 35 | 36 | pixelTree = .multiEffect(pixels()) 37 | 38 | for key in Key.allCases { 39 | switch key { 40 | case .axis: 41 | metadata[key.rawValue] = axis.rawValue 42 | case .alignment: 43 | metadata[key.rawValue] = alignment.rawValue 44 | case .spacing: 45 | metadata[key.rawValue] = spacing 46 | case .padding: 47 | metadata[key.rawValue] = padding 48 | default: 49 | continue 50 | } 51 | } 52 | } 53 | 54 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 55 | 56 | guard let pix = pix as? Pix else { return nil } 57 | 58 | guard let key = Key(rawValue: key) else { return nil } 59 | 60 | switch key { 61 | case .axis: 62 | return pix.axis.rawValue 63 | case .alignment: 64 | return pix.alignment.rawValue 65 | case .spacing: 66 | return Pixels.inViewSpace(pix.spacing, size: size) 67 | case .padding: 68 | return Pixels.inViewSpace(pix.padding, size: size) 69 | case .backgroundColor: 70 | return pix.backgroundColor 71 | } 72 | } 73 | 74 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 75 | 76 | guard var pix = pix as? Pix else { return } 77 | 78 | for (key, value) in metadata { 79 | 80 | guard let key = Key(rawValue: key) else { continue } 81 | 82 | switch key { 83 | case .axis: 84 | Pixels.updateRawValue(pix: &pix, value: value, at: \.axis) 85 | case .alignment: 86 | Pixels.updateRawValue(pix: &pix, value: value, at: \.alignment) 87 | case .spacing: 88 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.spacing) 89 | case .padding: 90 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.padding) 91 | case .backgroundColor: 92 | Pixels.updateValue(pix: &pix, value: value, at: \.backgroundColor) 93 | } 94 | } 95 | } 96 | } 97 | 98 | public extension PixelStack { 99 | 100 | func pixelBackgroundColor(_ color: Color) -> Self { 101 | var pixel = self 102 | pixel.metadata[Key.backgroundColor.rawValue] = PixelColor(color) 103 | return pixel 104 | } 105 | } 106 | 107 | struct PixelStack_Previews: PreviewProvider { 108 | static var previews: some View { 109 | Pixels { 110 | PixelStack(axis: .vertical) { 111 | PixelCircle(radius: 100) 112 | PixelPolygon(count: 3, radius: 100) 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Content/Generator/PixelRectangle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import PixelKit 7 | import SwiftUI 8 | import Resolution 9 | import PixelColor 10 | 11 | public struct PixelRectangle: Pixel { 12 | 13 | typealias Pix = RectanglePIX 14 | 15 | public let pixType: PIXType = .content(.generator(.rectangle)) 16 | 17 | public let pixelTree: PixelTree = .content 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case size 23 | case position 24 | case cornerRadius 25 | case color 26 | case backgroundColor 27 | } 28 | 29 | public init(size: CGSize) { 30 | 31 | for key in Key.allCases { 32 | switch key { 33 | case .size: 34 | metadata[key.rawValue] = size 35 | default: 36 | continue 37 | } 38 | } 39 | } 40 | 41 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 42 | 43 | guard let pix = pix as? Pix else { return nil } 44 | 45 | guard let key = Key(rawValue: key) else { return nil } 46 | 47 | switch key { 48 | case .size: 49 | return Pixels.inViewSpace(pix.size, size: size) 50 | case .position: 51 | return Pixels.inZeroViewSpace(pix.position, size: size) 52 | case .cornerRadius: 53 | return Pixels.inViewSpace(pix.cornerRadius, size: size) 54 | case .color: 55 | return pix.color 56 | case .backgroundColor: 57 | return pix.backgroundColor 58 | } 59 | } 60 | 61 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 62 | 63 | guard var pix = pix as? Pix else { return } 64 | 65 | for (key, value) in metadata { 66 | 67 | guard let key = Key(rawValue: key) else { continue } 68 | 69 | switch key { 70 | case .size: 71 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.size) 72 | case .position: 73 | Pixels.updateValueInZeroPixelSpace(pix: &pix, value: value, size: size, at: \.position) 74 | case .cornerRadius: 75 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.cornerRadius) 76 | case .color: 77 | Pixels.updateValue(pix: &pix, value: value, at: \.color) 78 | case .backgroundColor: 79 | Pixels.updateValue(pix: &pix, value: value, at: \.backgroundColor) 80 | } 81 | } 82 | } 83 | } 84 | 85 | public extension PixelRectangle { 86 | 87 | func pixelOffset(x: CGFloat = 0.0, y: CGFloat = 0.0) -> Self { 88 | var pixel = self 89 | pixel.metadata[Key.position.rawValue] = CGPoint(x: x, y: y) 90 | return pixel 91 | } 92 | 93 | func pixelCornerRadius(_ value: CGFloat) -> Self { 94 | var pixel = self 95 | pixel.metadata[Key.cornerRadius.rawValue] = value 96 | return pixel 97 | } 98 | 99 | func pixelColor(_ color: Color) -> Self { 100 | var pixel = self 101 | pixel.metadata[Key.color.rawValue] = PixelColor(color) 102 | return pixel 103 | } 104 | 105 | func pixelBackgroundColor(_ color: Color) -> Self { 106 | var pixel = self 107 | pixel.metadata[Key.backgroundColor.rawValue] = PixelColor(color) 108 | return pixel 109 | } 110 | } 111 | 112 | struct PixelRectangle_Previews: PreviewProvider { 113 | static var previews: some View { 114 | Pixels { 115 | PixelRectangle(size: CGSize(width: 200, height: 200)) 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelMetalEffect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-23. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | /// Pixel Metal Effect 13 | /// 14 | /// **Variables:** pi, u, v, uv, w, h, wu, hv, tex, pix, var.width, var.height, var.aspect, var.your-variable-name 15 | /// 16 | /// Example: 17 | /// ```swift 18 | /// Pixels { 19 | /// PixelMetalEffect(code: 20 | /// """ 21 | /// float gamma = 0.25; 22 | /// return pow(pix, 1.0 / gamma); 23 | /// """ 24 | /// ) { 25 | /// PixelCamera() 26 | /// } 27 | /// } 28 | /// ``` 29 | public struct PixelMetalEffect: Pixel { 30 | 31 | typealias Pix = MetalEffectPIX 32 | 33 | public let pixType: PIXType = .effect(.single(.metalEffect)) 34 | 35 | public let pixelTree: PixelTree 36 | 37 | public var metadata: [String : PixelMetadata] = [:] 38 | 39 | enum Key: String, CaseIterable { 40 | case code 41 | } 42 | 43 | public init(variables: [PixelVariable] = [], 44 | code: String, 45 | pixel: () -> Pixel) { 46 | 47 | pixelTree = .singleEffect(pixel()) 48 | 49 | for (index, variable) in variables.enumerated() { 50 | metadata["variable-\(index)"] = variable 51 | } 52 | 53 | for key in Key.allCases { 54 | switch key { 55 | case .code: 56 | metadata[key.rawValue] = code 57 | } 58 | } 59 | } 60 | 61 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 62 | 63 | guard let pix = pix as? Pix else { return nil } 64 | 65 | if key.starts(with: "variable") { 66 | guard let indexString = key.split(separator: "-").last else { return nil } 67 | guard let index = Int(indexString) else { return nil } 68 | guard pix.metalUniforms.indices.contains(index) else { return nil } 69 | let metalUniform: MetalUniform = pix.metalUniforms[index] 70 | return PixelVariable(name: metalUniform.name, value: metalUniform.value) 71 | } 72 | 73 | guard let key = Key(rawValue: key) else { return nil } 74 | 75 | switch key { 76 | case .code: 77 | return pix.code 78 | } 79 | } 80 | 81 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 82 | 83 | guard var pix = pix as? Pix else { return } 84 | 85 | for (key, value) in metadata { 86 | 87 | if key.starts(with: "variable") { 88 | guard let indexString = key.split(separator: "-").last else { continue } 89 | guard let index = Int(indexString) else { continue } 90 | guard let variable = value as? PixelVariable else { return } 91 | let metalUniform = MetalUniform(name: variable.name, value: variable.value) 92 | if pix.metalUniforms.indices.contains(index) { 93 | pix.metalUniforms[index] = metalUniform 94 | } else { 95 | pix.metalUniforms.append(metalUniform) 96 | } 97 | } 98 | 99 | guard let key = Key(rawValue: key) else { continue } 100 | 101 | switch key { 102 | case .code: 103 | Pixels.updateValue(pix: &pix, value: value, at: \.code) 104 | } 105 | } 106 | } 107 | } 108 | 109 | struct PixelMetalEffect_Previews: PreviewProvider { 110 | static var previews: some View { 111 | Pixels { 112 | PixelMetalEffect(code: "return pix;") { 113 | PixelCircle(radius: 100) 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Content/Generator/PixelLine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import PixelKit 7 | import SwiftUI 8 | import Resolution 9 | import PixelColor 10 | 11 | public struct PixelLine: Pixel { 12 | 13 | typealias Pix = LinePIX 14 | 15 | public let pixType: PIXType = .content(.generator(.line)) 16 | 17 | public let pixelTree: PixelTree = .content 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case positionFrom 23 | case positionTo 24 | case lineWidth 25 | case color 26 | case backgroundColor 27 | } 28 | 29 | public init(from positionFrom: CGPoint, 30 | to positionTo: CGPoint, 31 | lineWidth: CGFloat = 1) { 32 | 33 | for key in Key.allCases { 34 | switch key { 35 | case .positionFrom: 36 | metadata[key.rawValue] = positionFrom 37 | case .positionTo: 38 | metadata[key.rawValue] = positionTo 39 | case .lineWidth: 40 | metadata[key.rawValue] = lineWidth 41 | default: 42 | continue 43 | } 44 | } 45 | } 46 | 47 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 48 | 49 | guard let pix = pix as? Pix else { return nil } 50 | 51 | guard let key = Key(rawValue: key) else { return nil } 52 | 53 | switch key { 54 | case .positionFrom: 55 | return Pixels.inViewSpace(pix.positionFrom, size: size) 56 | case .positionTo: 57 | return Pixels.inViewSpace(pix.positionTo, size: size) 58 | case .lineWidth: 59 | return Pixels.inViewSpace(pix.lineWidth, size: size) 60 | case .color: 61 | return pix.color 62 | case .backgroundColor: 63 | return pix.backgroundColor 64 | } 65 | } 66 | 67 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 68 | 69 | guard var pix = pix as? Pix else { return } 70 | 71 | for (key, value) in metadata { 72 | 73 | guard let key = Key(rawValue: key) else { continue } 74 | 75 | switch key { 76 | case .positionFrom: 77 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.positionFrom) 78 | case .positionTo: 79 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.positionTo) 80 | case .lineWidth: 81 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.lineWidth) 82 | case .color: 83 | Pixels.updateValue(pix: &pix, value: value, at: \.color) 84 | case .backgroundColor: 85 | Pixels.updateValue(pix: &pix, value: value, at: \.backgroundColor) 86 | } 87 | } 88 | } 89 | } 90 | 91 | public extension PixelLine { 92 | 93 | func pixelColor(_ color: Color) -> Self { 94 | var pixel = self 95 | pixel.metadata[Key.color.rawValue] = PixelColor(color) 96 | return pixel 97 | } 98 | 99 | func pixelBackgroundColor(_ color: Color) -> Self { 100 | var pixel = self 101 | pixel.metadata[Key.backgroundColor.rawValue] = PixelColor(color) 102 | return pixel 103 | } 104 | } 105 | 106 | struct PixelLine_Previews: PreviewProvider { 107 | static var previews: some View { 108 | GeometryReader { geometryProxy in 109 | Pixels { 110 | PixelLine(from: CGPoint(x: 0, y: geometryProxy.size.height / 2), 111 | to: CGPoint(x: geometryProxy.size.width, y: geometryProxy.size.height / 2)) 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelCrop.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | public struct PixelCrop: Pixel { 13 | 14 | typealias Pix = CropPIX 15 | 16 | public let pixType: PIXType = .effect(.single(.crop)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case cropLeft 24 | case cropRight 25 | case cropBottom 26 | case cropTop 27 | } 28 | 29 | internal init(left cropLeft: CGFloat = 0.0, 30 | right cropRight: CGFloat = 0.0, 31 | bottom cropBottom: CGFloat = 0.0, 32 | top cropTop: CGFloat = 0.0, 33 | pixel: () -> Pixel) { 34 | 35 | pixelTree = .singleEffect(pixel()) 36 | 37 | for key in Key.allCases { 38 | switch key { 39 | case .cropLeft: 40 | metadata[key.rawValue] = cropLeft 41 | case .cropRight: 42 | metadata[key.rawValue] = cropRight 43 | case .cropBottom: 44 | metadata[key.rawValue] = cropBottom 45 | case .cropTop: 46 | metadata[key.rawValue] = cropTop 47 | } 48 | } 49 | } 50 | 51 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 52 | 53 | guard let pix = pix as? Pix else { return nil } 54 | 55 | guard let key = Key(rawValue: key) else { return nil } 56 | 57 | switch key { 58 | case .cropLeft: 59 | return Pixels.inNormalizedLeftViewSpace(pix.cropLeft, size: size) 60 | case .cropRight: 61 | return Pixels.inNormalizedRightViewSpace(pix.cropRight, size: size) 62 | case .cropBottom: 63 | return Pixels.inNormalizedBottomViewSpace(pix.cropBottom, size: size) 64 | case .cropTop: 65 | return Pixels.inNormalizedTopViewSpace(pix.cropTop, size: size) 66 | } 67 | } 68 | 69 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 70 | 71 | guard var pix = pix as? Pix else { return } 72 | 73 | for (key, value) in metadata { 74 | 75 | guard let key = Key(rawValue: key) else { continue } 76 | 77 | switch key { 78 | case .cropLeft: 79 | Pixels.updateValueInNormalizedLeftPixelSpace(pix: &pix, value: value, size: size, at: \.cropLeft) 80 | case .cropRight: 81 | Pixels.updateValueInNormalizedRightPixelSpace(pix: &pix, value: value, size: size, at: \.cropRight) 82 | case .cropBottom: 83 | Pixels.updateValueInNormalizedBottomPixelSpace(pix: &pix, value: value, size: size, at: \.cropBottom) 84 | case .cropTop: 85 | Pixels.updateValueInNormalizedTopPixelSpace(pix: &pix, value: value, size: size, at: \.cropTop) 86 | } 87 | } 88 | } 89 | } 90 | 91 | public extension Pixel { 92 | 93 | /// Pixel Crop 94 | /// 95 | /// Crop values are relative to the side with positive values 96 | func pixelCrop(left: CGFloat = 0.0, 97 | right: CGFloat = 0.0, 98 | bottom: CGFloat = 0.0, 99 | top: CGFloat = 0.0) -> PixelCrop { 100 | PixelCrop(left: left, 101 | right: right, 102 | bottom: bottom, 103 | top: top, 104 | pixel: { self }) 105 | } 106 | } 107 | 108 | struct PixelCrop_Previews: PreviewProvider { 109 | static var previews: some View { 110 | Pixels { 111 | PixelImage("kite") 112 | .pixelCrop(left: 100) 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Merger/PixelLumaColorShift.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | public struct PixelLumaColorShift: Pixel { 13 | 14 | typealias Pix = LumaColorShiftPIX 15 | 16 | public let pixType: PIXType = .effect(.merger(.lumaColorShift)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case hue 24 | case saturation 25 | case tintColor 26 | case lumaGamma 27 | } 28 | 29 | internal init(hue: CGFloat = 0.0, 30 | saturation: CGFloat = 1.0, 31 | tintColor: Color = .white, 32 | lumaGamma: CGFloat = 1.0, 33 | pixel leadingPixel: () -> Pixel, 34 | withPixel trailingPixel: () -> Pixel) { 35 | 36 | pixelTree = .mergerEffect(leadingPixel(), trailingPixel()) 37 | 38 | for key in Key.allCases { 39 | switch key { 40 | case .hue: 41 | metadata[key.rawValue] = hue 42 | case .saturation: 43 | metadata[key.rawValue] = saturation 44 | case .tintColor: 45 | metadata[key.rawValue] = PixelColor(tintColor) 46 | case .lumaGamma: 47 | metadata[key.rawValue] = lumaGamma 48 | } 49 | } 50 | } 51 | 52 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 53 | 54 | guard let pix = pix as? Pix else { return nil } 55 | 56 | guard let key = Key(rawValue: key) else { return nil } 57 | 58 | switch key { 59 | case .hue: 60 | return pix.hue 61 | case .saturation: 62 | return pix.saturation 63 | case .tintColor: 64 | return pix.tintColor 65 | case .lumaGamma: 66 | return pix.lumaGamma 67 | } 68 | } 69 | 70 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 71 | 72 | guard var pix = pix as? Pix else { return } 73 | 74 | for (key, value) in metadata { 75 | 76 | guard let key = Key(rawValue: key) else { continue } 77 | 78 | switch key { 79 | case .hue: 80 | Pixels.updateValue(pix: &pix, value: value, at: \.hue) 81 | case .saturation: 82 | Pixels.updateValue(pix: &pix, value: value, at: \.saturation) 83 | case .tintColor: 84 | Pixels.updateValue(pix: &pix, value: value, at: \.tintColor) 85 | case .lumaGamma: 86 | Pixels.updateValue(pix: &pix, value: value, at: \.lumaGamma) 87 | } 88 | } 89 | } 90 | } 91 | 92 | public extension Pixel { 93 | 94 | func pixelLumaHue(_ hue: CGFloat, lumaGamma: CGFloat = 1.0, pixel: () -> Pixel) -> PixelLumaColorShift { 95 | PixelLumaColorShift(hue: hue, lumaGamma: lumaGamma, pixel: { self }, withPixel: pixel) 96 | } 97 | 98 | func pixelLumaSaturation(_ saturation: CGFloat, lumaGamma: CGFloat = 1.0, pixel: () -> Pixel) -> PixelLumaColorShift { 99 | PixelLumaColorShift(saturation: saturation, lumaGamma: lumaGamma, pixel: { self }, withPixel: pixel) 100 | } 101 | 102 | func pixelLumaMonochrome(lumaGamma: CGFloat = 1.0, pixel: () -> Pixel) -> PixelLumaColorShift { 103 | PixelLumaColorShift(saturation: 0.0, lumaGamma: lumaGamma, pixel: { self }, withPixel: pixel) 104 | } 105 | } 106 | 107 | struct PixelLumaColorShift_Previews: PreviewProvider { 108 | static var previews: some View { 109 | Pixels { 110 | PixelCamera() 111 | .pixelLumaHue(2.0) { 112 | PixelGradient(axis: .vertical) 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Content/Generator/PixelCircle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import PixelKit 7 | import SwiftUI 8 | import Resolution 9 | import PixelColor 10 | 11 | public struct PixelCircle: Pixel { 12 | 13 | typealias Pix = CirclePIX 14 | 15 | public let pixType: PIXType = .content(.generator(.circle)) 16 | 17 | public let pixelTree: PixelTree = .content 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case radius 23 | case position 24 | case color 25 | case backgroundColor 26 | case edgeRadius 27 | case edgeColor 28 | } 29 | 30 | public init(radius: CGFloat) { 31 | 32 | for key in Key.allCases { 33 | switch key { 34 | case .radius: 35 | metadata[key.rawValue] = radius 36 | default: 37 | continue 38 | } 39 | } 40 | } 41 | 42 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 43 | 44 | guard let pix = pix as? Pix else { return nil } 45 | 46 | guard let key = Key(rawValue: key) else { return nil } 47 | 48 | switch key { 49 | case .radius: 50 | return Pixels.inViewSpace(pix.radius, size: size) 51 | case .position: 52 | return Pixels.inZeroViewSpace(pix.position, size: size) 53 | case .color: 54 | return pix.color 55 | case .backgroundColor: 56 | return pix.backgroundColor 57 | case .edgeRadius: 58 | return Pixels.inViewSpace(pix.edgeRadius, size: size) 59 | case .edgeColor: 60 | return pix.edgeColor 61 | } 62 | } 63 | 64 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 65 | 66 | guard var pix = pix as? Pix else { return } 67 | 68 | for (key, value) in metadata { 69 | 70 | guard let key = Key(rawValue: key) else { continue } 71 | 72 | switch key { 73 | case .radius: 74 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.radius) 75 | case .position: 76 | Pixels.updateValueInZeroPixelSpace(pix: &pix, value: value, size: size, at: \.position) 77 | case .color: 78 | Pixels.updateValue(pix: &pix, value: value, at: \.color) 79 | case .backgroundColor: 80 | Pixels.updateValue(pix: &pix, value: value, at: \.backgroundColor) 81 | case .edgeRadius: 82 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.edgeRadius) 83 | case .edgeColor: 84 | Pixels.updateValue(pix: &pix, value: value, at: \.edgeColor) 85 | } 86 | } 87 | } 88 | } 89 | 90 | public extension PixelCircle { 91 | 92 | func pixelOffset(x: CGFloat = 0.0, y: CGFloat = 0.0) -> Self { 93 | var pixel = self 94 | pixel.metadata[Key.position.rawValue] = CGPoint(x: x, y: y) 95 | return pixel 96 | } 97 | 98 | func pixelColor(_ color: Color) -> Self { 99 | var pixel = self 100 | pixel.metadata[Key.color.rawValue] = PixelColor(color) 101 | return pixel 102 | } 103 | 104 | func pixelBackgroundColor(_ color: Color) -> Self { 105 | var pixel = self 106 | pixel.metadata[Key.backgroundColor.rawValue] = PixelColor(color) 107 | return pixel 108 | } 109 | 110 | func pixelCircleEdge(radius: CGFloat, color: Color) -> Self { 111 | var pixel = self 112 | pixel.metadata[Key.edgeRadius.rawValue] = radius 113 | pixel.metadata[Key.edgeColor.rawValue] = PixelColor(color) 114 | return pixel 115 | } 116 | } 117 | 118 | struct PixelCircle_Previews: PreviewProvider { 119 | static var previews: some View { 120 | Pixels { 121 | PixelCircle(radius: 100) 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Merger/PixelMetalMergerEffect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-23. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | /// Pixel Metal Merger Effect 13 | /// 14 | /// **Variables:** pi, u, v, uv, wA, hA, wuA, hvA, wB, hB, wuB, hvB, texA, texB, pixA, pixB, var.width, var.height, var.aspect, var.your-variable-name 15 | /// 16 | /// ```metal 17 | /// float4 pixA = pixA.sample(s, uv); 18 | /// ``` 19 | /// 20 | /// Example: 21 | /// ```swift 22 | /// Pixels { 23 | /// PixelMetalMergerEffect(code: 24 | /// """ 25 | /// return pow(pixA, 1.0 / pixB); 26 | /// """ 27 | /// ) { 28 | /// PixelCamera() 29 | /// } withPixel: { 30 | /// PixelCircle(radius: 100) 31 | /// } 32 | /// } 33 | /// ``` 34 | public struct PixelMetalMergerEffect: Pixel { 35 | 36 | typealias Pix = MetalMergerEffectPIX 37 | 38 | public let pixType: PIXType = .effect(.merger(.metalMergerEffect)) 39 | 40 | public let pixelTree: PixelTree 41 | 42 | public var metadata: [String : PixelMetadata] = [:] 43 | 44 | enum Key: String, CaseIterable { 45 | case code 46 | } 47 | 48 | public init(variables: [PixelVariable] = [], 49 | code: String, 50 | pixel leadingPixel: () -> Pixel, 51 | withPixel trailingPixel: () -> Pixel) { 52 | 53 | pixelTree = .mergerEffect(leadingPixel(), trailingPixel()) 54 | 55 | for (index, variable) in variables.enumerated() { 56 | metadata["variable-\(index)"] = variable 57 | } 58 | 59 | for key in Key.allCases { 60 | switch key { 61 | case .code: 62 | metadata[key.rawValue] = code 63 | } 64 | } 65 | } 66 | 67 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 68 | 69 | guard let pix = pix as? Pix else { return nil } 70 | 71 | if key.starts(with: "variable") { 72 | guard let indexString = key.split(separator: "-").last else { return nil } 73 | guard let index = Int(indexString) else { return nil } 74 | guard pix.metalUniforms.indices.contains(index) else { return nil } 75 | let metalUniform: MetalUniform = pix.metalUniforms[index] 76 | return PixelVariable(name: metalUniform.name, value: metalUniform.value) 77 | } 78 | 79 | guard let key = Key(rawValue: key) else { return nil } 80 | 81 | switch key { 82 | case .code: 83 | return pix.code 84 | } 85 | } 86 | 87 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 88 | 89 | guard var pix = pix as? Pix else { return } 90 | 91 | for (key, value) in metadata { 92 | 93 | if key.starts(with: "variable") { 94 | guard let indexString = key.split(separator: "-").last else { continue } 95 | guard let index = Int(indexString) else { continue } 96 | guard let variable = value as? PixelVariable else { return } 97 | let metalUniform = MetalUniform(name: variable.name, value: variable.value) 98 | if pix.metalUniforms.indices.contains(index) { 99 | pix.metalUniforms[index] = metalUniform 100 | } else { 101 | pix.metalUniforms.append(metalUniform) 102 | } 103 | } 104 | 105 | guard let key = Key(rawValue: key) else { continue } 106 | 107 | switch key { 108 | case .code: 109 | Pixels.updateValue(pix: &pix, value: value, at: \.code) 110 | } 111 | } 112 | } 113 | } 114 | 115 | struct PixelMetalMergerEffect_Previews: PreviewProvider { 116 | static var previews: some View { 117 | Pixels { 118 | PixelMetalMergerEffect(code: "return pixA + pixB;") { 119 | PixelPolygon(count: 3, radius: 100) 120 | } withPixel: { 121 | PixelStar(count: 5, radius: 100) 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelTransform: Pixel { 12 | 13 | typealias Pix = TransformPIX 14 | 15 | public let pixType: PIXType = .effect(.single(.transform)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case position 23 | case rotation 24 | case scale 25 | case size 26 | case extend 27 | } 28 | 29 | internal init(offset position: CGPoint = .zero, 30 | angle rotation: Angle = .zero, 31 | scale: CGFloat = 1.0, 32 | size: CGSize = CGSize(width: 1.0, height: 1.0), 33 | pixel: () -> Pixel) { 34 | 35 | pixelTree = .singleEffect(pixel()) 36 | 37 | for key in Key.allCases { 38 | switch key { 39 | case .position: 40 | metadata[key.rawValue] = position 41 | case .rotation: 42 | metadata[key.rawValue] = rotation 43 | case .scale: 44 | metadata[key.rawValue] = scale 45 | case .size: 46 | metadata[key.rawValue] = size 47 | default: 48 | continue 49 | } 50 | } 51 | } 52 | 53 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 54 | 55 | guard let pix = pix as? Pix else { return nil } 56 | 57 | guard let key = Key(rawValue: key) else { return nil } 58 | 59 | switch key { 60 | case .position: 61 | return Pixels.inZeroViewSpace(pix.position, size: size) 62 | case .rotation: 63 | return Pixels.asAngle(pix.rotation) 64 | case .scale: 65 | return pix.scale 66 | case .size: 67 | return pix.size 68 | case .extend: 69 | return pix.extend.rawValue 70 | } 71 | } 72 | 73 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 74 | 75 | guard var pix = pix as? Pix else { return } 76 | 77 | for (key, value) in metadata { 78 | 79 | guard let key = Key(rawValue: key) else { continue } 80 | 81 | switch key { 82 | case .position: 83 | Pixels.updateValueInZeroPixelSpace(pix: &pix, value: value, size: size, at: \.position) 84 | case .rotation: 85 | Pixels.updateValueAngle(pix: &pix, value: value, at: \.rotation) 86 | case .scale: 87 | Pixels.updateValue(pix: &pix, value: value, at: \.scale) 88 | case .size: 89 | Pixels.updateValue(pix: &pix, value: value, at: \.size) 90 | case .extend: 91 | Pixels.updateRawValue(pix: &pix, value: value, at: \.extend) 92 | } 93 | } 94 | } 95 | } 96 | 97 | public extension Pixel { 98 | 99 | func pixelOffset(x: CGFloat = 0.0, y: CGFloat = 0.0) -> PixelTransform { 100 | PixelTransform(offset: CGPoint(x: x, y: y), pixel: { self }) 101 | } 102 | 103 | func pixelRotate(_ angle: Angle) -> PixelTransform { 104 | PixelTransform(angle: angle, pixel: { self }) 105 | } 106 | 107 | func pixelScale(_ scale: CGFloat) -> PixelTransform { 108 | PixelTransform(scale: scale, pixel: { self }) 109 | } 110 | 111 | func pixelScale(x: CGFloat = 1.0, y: CGFloat = 1.0) -> PixelTransform { 112 | PixelTransform(size: CGSize(width: x, height: y), pixel: { self }) 113 | } 114 | } 115 | 116 | extension PixelTransform { 117 | 118 | func pixelExtend(_ extend: ExtendMode) -> Self { 119 | var pixel = self 120 | pixel.metadata[Key.extend.rawValue] = extend.rawValue 121 | return pixel 122 | } 123 | } 124 | 125 | struct PixelTransform_Previews: PreviewProvider { 126 | static var previews: some View { 127 | Pixels { 128 | PixelCamera() 129 | .pixelOffset(x: 100) 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelBlur.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelBlur: Pixel { 12 | 13 | typealias Pix = BlurPIX 14 | 15 | public let pixType: PIXType = .effect(.single(.blur)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case style 23 | case radius 24 | case angle 25 | case position 26 | case quality 27 | } 28 | 29 | internal init(style: Pix.BlurStyle = .default, 30 | radius: CGFloat, 31 | angle: Angle = .zero, 32 | offset position: CGPoint = .zero, 33 | quality: PIX.SampleQualityMode = .default, 34 | pixel: () -> Pixel) { 35 | 36 | pixelTree = .singleEffect(pixel()) 37 | 38 | for key in Key.allCases { 39 | switch key { 40 | case .style: 41 | metadata[key.rawValue] = style.rawValue 42 | case .radius: 43 | metadata[key.rawValue] = radius 44 | case .angle: 45 | metadata[key.rawValue] = angle 46 | case .position: 47 | metadata[key.rawValue] = position 48 | case .quality: 49 | metadata[key.rawValue] = quality.rawValue 50 | } 51 | } 52 | } 53 | 54 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 55 | 56 | guard let pix = pix as? Pix else { return nil } 57 | 58 | guard let key = Key(rawValue: key) else { return nil } 59 | 60 | switch key { 61 | case .style: 62 | return pix.style.rawValue 63 | case .radius: 64 | return Pixels.inViewSpace(pix.radius, size: size) 65 | case .angle: 66 | return Pixels.asAngle(pix.angle) 67 | case .position: 68 | return Pixels.inZeroViewSpace(pix.position, size: size) 69 | case .quality: 70 | return pix.quality.rawValue 71 | } 72 | } 73 | 74 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 75 | 76 | guard var pix = pix as? Pix else { return } 77 | 78 | for (key, value) in metadata { 79 | 80 | guard let key = Key(rawValue: key) else { continue } 81 | 82 | switch key { 83 | case .style: 84 | Pixels.updateRawValue(pix: &pix, value: value, at: \.style) 85 | case .radius: 86 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.radius) 87 | case .angle: 88 | Pixels.updateValueAngle(pix: &pix, value: value, at: \.angle) 89 | case .position: 90 | Pixels.updateValueInZeroPixelSpace(pix: &pix, value: value, size: size, at: \.position) 91 | case .quality: 92 | Pixels.updateRawValue(pix: &pix, value: value, at: \.quality) 93 | } 94 | } 95 | } 96 | } 97 | 98 | public extension Pixel { 99 | 100 | func pixelBlur(radius: CGFloat) -> PixelBlur { 101 | PixelBlur(style: .default, radius: radius, pixel: { self }) 102 | } 103 | 104 | func pixelBlurBox(radius: CGFloat) -> PixelBlur { 105 | PixelBlur(style: .box, radius: radius, pixel: { self }) 106 | } 107 | 108 | func pixelBlurAngle(radius: CGFloat, angle: Angle) -> PixelBlur { 109 | PixelBlur(style: .angle, radius: radius, angle: angle, pixel: { self }) 110 | } 111 | 112 | func pixelBlurZoom(radius: CGFloat, offset: CGPoint = .zero) -> PixelBlur { 113 | PixelBlur(style: .zoom, radius: radius, offset: offset, pixel: { self }) 114 | } 115 | 116 | func pixelBlurRandom(radius: CGFloat) -> PixelBlur { 117 | PixelBlur(style: .random, radius: radius, pixel: { self }) 118 | } 119 | } 120 | 121 | struct PixelBlur_Previews: PreviewProvider { 122 | static var previews: some View { 123 | Pixels { 124 | PixelCircle(radius: 100) 125 | .pixelBlur(radius: 0.1) 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Content/Resource/PixelEarth.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-22. 3 | // 4 | 5 | import Foundation 6 | import SwiftUI 7 | import RenderKit 8 | import PixelKit 9 | import PixelColor 10 | 11 | /// Pixel Earth 12 | /// 13 | /// The span is in degrees between 0.0 and 180.0. 14 | public struct PixelEarth: Pixel { 15 | 16 | typealias Pix = EarthPIX 17 | 18 | public let pixType: PIXType = .content(.resource(.maps)) 19 | 20 | public let pixelTree: PixelTree = .content 21 | 22 | public var metadata: [String : PixelMetadata] = [:] 23 | 24 | enum Key: String, CaseIterable { 25 | case mapType 26 | case coordinate 27 | case span 28 | case showsBuildings 29 | case showsPointsOfInterest 30 | case darkMode 31 | } 32 | 33 | public init(mapType: EarthPIX.MapType = .standard, 34 | latitude: CGFloat, 35 | longitude: CGFloat, 36 | span: CGFloat) { 37 | self.init(mapType: mapType, coordinate: CGPoint(x: longitude, y: latitude), span: span) 38 | } 39 | 40 | public init(mapType: EarthPIX.MapType = .standard, 41 | coordinate: CGPoint, 42 | span: CGFloat) { 43 | 44 | for key in Key.allCases { 45 | switch key { 46 | case .mapType: 47 | metadata[key.rawValue] = mapType.rawValue 48 | case .coordinate: 49 | metadata[key.rawValue] = coordinate 50 | case .span: 51 | metadata[key.rawValue] = span 52 | default: 53 | continue 54 | } 55 | } 56 | } 57 | 58 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 59 | 60 | guard let pix = pix as? Pix else { return nil } 61 | 62 | guard let key = Key(rawValue: key) else { return nil } 63 | 64 | switch key { 65 | case .mapType: 66 | return pix.mapType.rawValue 67 | case .coordinate: 68 | return pix.coordinate 69 | case .span: 70 | return pix.span 71 | case .showsBuildings: 72 | return pix.showsBuildings 73 | case .showsPointsOfInterest: 74 | return pix.showsPointsOfInterest 75 | case .darkMode: 76 | return pix.darkMode 77 | } 78 | } 79 | 80 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 81 | 82 | guard var pix = pix as? Pix else { return } 83 | 84 | for (key, value) in metadata { 85 | 86 | guard let key = Key(rawValue: key) else { continue } 87 | 88 | switch key { 89 | case .mapType: 90 | Pixels.updateRawValue(pix: &pix, value: value, at: \.mapType) 91 | case .coordinate: 92 | Pixels.updateValue(pix: &pix, value: value, at: \.coordinate) 93 | case .span: 94 | Pixels.updateValue(pix: &pix, value: value, at: \.span) 95 | case .showsBuildings: 96 | Pixels.updateValue(pix: &pix, value: value, at: \.showsBuildings) 97 | case .showsPointsOfInterest: 98 | Pixels.updateValue(pix: &pix, value: value, at: \.showsPointsOfInterest) 99 | case .darkMode: 100 | Pixels.updateValue(pix: &pix, value: value, at: \.darkMode) 101 | } 102 | } 103 | } 104 | } 105 | 106 | public extension PixelEarth { 107 | 108 | func pixelEarthShowBuildings() -> Self { 109 | var pixel = self 110 | pixel.metadata[Key.showsBuildings.rawValue] = true 111 | return pixel 112 | } 113 | 114 | func pixelEarthShowPointsOfInterest() -> Self { 115 | var pixel = self 116 | pixel.metadata[Key.showsPointsOfInterest.rawValue] = true 117 | return pixel 118 | } 119 | 120 | func pixelEarthDarkMode() -> Self { 121 | var pixel = self 122 | pixel.metadata[Key.darkMode.rawValue] = true 123 | return pixel 124 | } 125 | } 126 | 127 | struct PixelEarth_Previews: PreviewProvider { 128 | static var previews: some View { 129 | Pixels { 130 | PixelEarth(latitude: 59.3293, longitude: 18.0686, span: 1.0) 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelEdge.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelEdge: Pixel { 12 | 13 | typealias Pix = EdgePIX 14 | 15 | public let pixType: PIXType = .effect(.single(.edge)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case strength 23 | case distance 24 | case colored 25 | case transparent 26 | case includeAlpha 27 | case sobel 28 | } 29 | 30 | internal init(strength: CGFloat = 10.0, 31 | distance: CGFloat = 1.0, 32 | colored: Bool = false, 33 | transparent: Bool = false, 34 | includeAlpha: Bool = false, 35 | sobel: Bool = false, 36 | pixel: () -> Pixel) { 37 | 38 | pixelTree = .singleEffect(pixel()) 39 | 40 | for key in Key.allCases { 41 | switch key { 42 | case .strength: 43 | metadata[key.rawValue] = strength 44 | case .distance: 45 | metadata[key.rawValue] = distance 46 | case .colored: 47 | metadata[key.rawValue] = colored 48 | case .transparent: 49 | metadata[key.rawValue] = transparent 50 | case .includeAlpha: 51 | metadata[key.rawValue] = includeAlpha 52 | case .sobel: 53 | metadata[key.rawValue] = sobel 54 | } 55 | } 56 | } 57 | 58 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 59 | 60 | guard let pix = pix as? Pix else { return nil } 61 | 62 | guard let key = Key(rawValue: key) else { return nil } 63 | 64 | switch key { 65 | case .strength: 66 | return pix.strength 67 | case .distance: 68 | return pix.distance 69 | case .colored: 70 | return pix.colored 71 | case .transparent: 72 | return pix.transparent 73 | case .includeAlpha: 74 | return pix.includeAlpha 75 | case .sobel: 76 | return pix.sobel 77 | } 78 | } 79 | 80 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 81 | 82 | guard var pix = pix as? Pix else { return } 83 | 84 | for (key, value) in metadata { 85 | 86 | guard let key = Key(rawValue: key) else { continue } 87 | 88 | switch key { 89 | case .strength: 90 | Pixels.updateValue(pix: &pix, value: value, at: \.strength) 91 | case .distance: 92 | Pixels.updateValue(pix: &pix, value: value, at: \.distance) 93 | case .colored: 94 | Pixels.updateValue(pix: &pix, value: value, at: \.colored) 95 | case .transparent: 96 | Pixels.updateValue(pix: &pix, value: value, at: \.transparent) 97 | case .includeAlpha: 98 | Pixels.updateValue(pix: &pix, value: value, at: \.includeAlpha) 99 | case .sobel: 100 | Pixels.updateValue(pix: &pix, value: value, at: \.sobel) 101 | } 102 | } 103 | } 104 | } 105 | 106 | public extension Pixel { 107 | 108 | func pixelEdge(strength: CGFloat = 10.0, 109 | distance: CGFloat = 1.0, 110 | colored: Bool = false, 111 | transparent: Bool = false, 112 | includeAlpha: Bool = false, 113 | sobel: Bool = false) -> PixelEdge { 114 | PixelEdge(strength: strength, 115 | distance: distance, 116 | colored: colored, 117 | transparent: transparent, 118 | includeAlpha: includeAlpha, 119 | sobel: sobel, 120 | pixel: { self }) 121 | } 122 | } 123 | 124 | struct PixelEdge_Previews: PreviewProvider { 125 | static var previews: some View { 126 | Pixels { 127 | PixelCircle(radius: 100) 128 | .pixelEdge() 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelChromaKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | public struct PixelChromaKey: Pixel { 13 | 14 | typealias Pix = ChromaKeyPIX 15 | 16 | public let pixType: PIXType = .effect(.single(.chromaKey)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case keyColor 24 | case range 25 | case softness 26 | case edgeDesaturation 27 | case alphaCrop 28 | case premultiply 29 | } 30 | 31 | internal init(color keyColor: Color, 32 | range: CGFloat = 0.1, 33 | softness: CGFloat = 0.1, 34 | edgeDesaturation: CGFloat = 0.5, 35 | alphaCrop: CGFloat = 0.5, 36 | premultiply: Bool = true, 37 | pixel: () -> Pixel) { 38 | 39 | pixelTree = .singleEffect(pixel()) 40 | 41 | for key in Key.allCases { 42 | switch key { 43 | case .keyColor: 44 | metadata[key.rawValue] = PixelColor(keyColor) 45 | case .range: 46 | metadata[key.rawValue] = range 47 | case .softness: 48 | metadata[key.rawValue] = softness 49 | case .edgeDesaturation: 50 | metadata[key.rawValue] = edgeDesaturation 51 | case .alphaCrop: 52 | metadata[key.rawValue] = alphaCrop 53 | case .premultiply: 54 | metadata[key.rawValue] = premultiply 55 | } 56 | } 57 | } 58 | 59 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 60 | 61 | guard let pix = pix as? Pix else { return nil } 62 | 63 | guard let key = Key(rawValue: key) else { return nil } 64 | 65 | switch key { 66 | case .keyColor: 67 | return pix.keyColor 68 | case .range: 69 | return pix.range 70 | case .softness: 71 | return pix.softness 72 | case .edgeDesaturation: 73 | return pix.edgeDesaturation 74 | case .alphaCrop: 75 | return pix.alphaCrop 76 | case .premultiply: 77 | return pix.premultiply 78 | } 79 | } 80 | 81 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 82 | 83 | guard var pix = pix as? Pix else { return } 84 | 85 | for (key, value) in metadata { 86 | 87 | guard let key = Key(rawValue: key) else { continue } 88 | 89 | switch key { 90 | case .keyColor: 91 | Pixels.updateValue(pix: &pix, value: value, at: \.keyColor) 92 | case .range: 93 | Pixels.updateValue(pix: &pix, value: value, at: \.range) 94 | case .softness: 95 | Pixels.updateValue(pix: &pix, value: value, at: \.softness) 96 | case .edgeDesaturation: 97 | Pixels.updateValue(pix: &pix, value: value, at: \.edgeDesaturation) 98 | case .alphaCrop: 99 | Pixels.updateValue(pix: &pix, value: value, at: \.alphaCrop) 100 | case .premultiply: 101 | Pixels.updateValue(pix: &pix, value: value, at: \.premultiply) 102 | } 103 | } 104 | } 105 | } 106 | 107 | public extension Pixel { 108 | 109 | func pixelChromaKey(color: Color, 110 | range: CGFloat = 0.1, 111 | softness: CGFloat = 0.1, 112 | edgeDesaturation: CGFloat = 0.5, 113 | alphaCrop: CGFloat = 0.5, 114 | premultiply: Bool = true) -> PixelChromaKey { 115 | PixelChromaKey(color: color, range: range, softness: softness, edgeDesaturation: edgeDesaturation, alphaCrop: alphaCrop, premultiply: premultiply, pixel: { self }) 116 | } 117 | } 118 | 119 | struct PixelChromaKey_Previews: PreviewProvider { 120 | static var previews: some View { 121 | Pixels { 122 | PixelImage("kite") 123 | .pixelChromaKey(color: .green) 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Multi/PixelMetalMultiEffect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-23. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | /// Pixel Metal Multi Effect 13 | /// 14 | /// **Variables:** pi, u, v, uv, pixCount, texs, var.width, var.height, var.aspect, var.your-varaible-name 15 | /// 16 | /// Example: 17 | /// ```swift 18 | /// Pixels { 19 | /// PixelMetalMultiEffect(code: 20 | /// """ 21 | /// float4 pixA = texs.sample(s, uv, 0); 22 | /// float4 pixB = texs.sample(s, uv, 1); 23 | /// float4 pixC = texs.sample(s, uv, 2); 24 | /// return pixA + pixB + pixC; 25 | /// """ 26 | /// ) { 27 | /// PixelCircle(radius: 100) 28 | /// PixelStar(count: 5, radius: 100) 29 | /// PixelPolygon(count: 3, radius: 100) 30 | /// } 31 | /// } 32 | /// ``` 33 | public struct PixelMetalMultiEffect: Pixel { 34 | 35 | typealias Pix = MetalMultiEffectPIX 36 | 37 | public let pixType: PIXType = .effect(.multi(.metalMultiEffect)) 38 | 39 | public let pixelTree: PixelTree 40 | 41 | public var metadata: [String : PixelMetadata] = [:] 42 | 43 | enum Key: String, CaseIterable { 44 | case code 45 | } 46 | 47 | public init(variables: [PixelVariable] = [], 48 | code: String, 49 | @PixelBuilder pixels: () -> [Pixel]) { 50 | 51 | pixelTree = .multiEffect(pixels()) 52 | 53 | for (index, variable) in variables.enumerated() { 54 | metadata["variable-\(index)"] = variable 55 | } 56 | 57 | for key in Key.allCases { 58 | switch key { 59 | case .code: 60 | metadata[key.rawValue] = code 61 | } 62 | } 63 | } 64 | 65 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 66 | 67 | guard let pix = pix as? Pix else { return nil } 68 | 69 | if key.starts(with: "variable") { 70 | guard let indexString = key.split(separator: "-").last else { return nil } 71 | guard let index = Int(indexString) else { return nil } 72 | guard pix.metalUniforms.indices.contains(index) else { return nil } 73 | let metalUniform: MetalUniform = pix.metalUniforms[index] 74 | return PixelVariable(name: metalUniform.name, value: metalUniform.value) 75 | } 76 | 77 | guard let key = Key(rawValue: key) else { return nil } 78 | 79 | switch key { 80 | case .code: 81 | return pix.code 82 | } 83 | } 84 | 85 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 86 | 87 | guard var pix = pix as? Pix else { return } 88 | 89 | for (key, value) in metadata { 90 | 91 | if key.starts(with: "variable") { 92 | guard let indexString = key.split(separator: "-").last else { continue } 93 | guard let index = Int(indexString) else { continue } 94 | guard let variable = value as? PixelVariable else { return } 95 | let metalUniform = MetalUniform(name: variable.name, value: variable.value) 96 | if pix.metalUniforms.indices.contains(index) { 97 | pix.metalUniforms[index] = metalUniform 98 | } else { 99 | pix.metalUniforms.append(metalUniform) 100 | } 101 | } 102 | 103 | guard let key = Key(rawValue: key) else { continue } 104 | 105 | switch key { 106 | case .code: 107 | Pixels.updateValue(pix: &pix, value: value, at: \.code) 108 | } 109 | } 110 | } 111 | } 112 | 113 | struct PixelMetalMultiEffect_Previews: PreviewProvider { 114 | static var previews: some View { 115 | Pixels { 116 | PixelMetalMultiEffect(code: 117 | """ 118 | float4 pixA = texs.sample(s, uv, 0); 119 | float4 pixB = texs.sample(s, uv, 1); 120 | float4 pixC = texs.sample(s, uv, 2); 121 | return pixA + pixB + pixC; 122 | """ 123 | ) { 124 | PixelCircle(radius: 100) 125 | PixelStar(count: 5, radius: 100) 126 | PixelPolygon(count: 3, radius: 100) 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Content/Generator/PixelPolygon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import PixelKit 7 | import SwiftUI 8 | import Resolution 9 | import PixelColor 10 | 11 | public struct PixelPolygon: Pixel { 12 | 13 | typealias Pix = PolygonPIX 14 | 15 | public let pixType: PIXType = .content(.generator(.polygon)) 16 | 17 | public var metadata: [String : PixelMetadata] = [:] 18 | 19 | public let pixelTree: PixelTree = .content 20 | 21 | enum Key: String, CaseIterable { 22 | case count 23 | case radius 24 | case position 25 | case rotation 26 | case cornerRadius 27 | case color 28 | case backgroundColor 29 | } 30 | 31 | public init(count: Int, 32 | radius: CGFloat) { 33 | 34 | for key in Key.allCases { 35 | switch key { 36 | case .count: 37 | metadata[key.rawValue] = count 38 | case .radius: 39 | metadata[key.rawValue] = radius 40 | default: 41 | continue 42 | } 43 | } 44 | } 45 | 46 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 47 | 48 | guard let pix = pix as? Pix else { return nil } 49 | 50 | guard let key = Key(rawValue: key) else { return nil } 51 | 52 | switch key { 53 | case .count: 54 | return pix.count 55 | case .radius: 56 | return Pixels.inViewSpace(pix.radius, size: size) 57 | case .position: 58 | return Pixels.inZeroViewSpace(pix.position, size: size) 59 | case .rotation: 60 | return Pixels.asAngle(pix.rotation) 61 | case .cornerRadius: 62 | return Pixels.inViewSpace(pix.cornerRadius, size: size) 63 | case .color: 64 | return pix.color 65 | case .backgroundColor: 66 | return pix.backgroundColor 67 | } 68 | } 69 | 70 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 71 | 72 | guard var pix = pix as? Pix else { return } 73 | 74 | for (key, value) in metadata { 75 | 76 | guard let key = Key(rawValue: key) else { continue } 77 | 78 | switch key { 79 | case .count: 80 | Pixels.updateValue(pix: &pix, value: value, at: \.count) 81 | case .radius: 82 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.radius) 83 | case .position: 84 | Pixels.updateValueInZeroPixelSpace(pix: &pix, value: value, size: size, at: \.position) 85 | case .rotation: 86 | Pixels.updateValueAngle(pix: &pix, value: value, at: \.rotation) 87 | case .cornerRadius: 88 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.cornerRadius) 89 | case .color: 90 | Pixels.updateValue(pix: &pix, value: value, at: \.color) 91 | case .backgroundColor: 92 | Pixels.updateValue(pix: &pix, value: value, at: \.backgroundColor) 93 | } 94 | } 95 | } 96 | } 97 | 98 | public extension PixelPolygon { 99 | 100 | func pixelOffset(x: CGFloat = 0.0, y: CGFloat = 0.0) -> Self { 101 | var pixel = self 102 | pixel.metadata[Key.position.rawValue] = CGPoint(x: x, y: y) 103 | return pixel 104 | } 105 | 106 | func pixelRotation(_ angle: Angle) -> Self { 107 | var pixel = self 108 | pixel.metadata[Key.rotation.rawValue] = angle 109 | return pixel 110 | } 111 | 112 | func pixelCornerRadius(_ value: CGFloat) -> Self { 113 | var pixel = self 114 | pixel.metadata[Key.cornerRadius.rawValue] = value 115 | return pixel 116 | } 117 | 118 | func pixelColor(_ color: Color) -> Self { 119 | var pixel = self 120 | pixel.metadata[Key.color.rawValue] = PixelColor(color) 121 | return pixel 122 | } 123 | 124 | func pixelBackgroundColor(_ color: Color) -> Self { 125 | var pixel = self 126 | pixel.metadata[Key.backgroundColor.rawValue] = PixelColor(color) 127 | return pixel 128 | } 129 | } 130 | 131 | struct PixelPolygon_Previews: PreviewProvider { 132 | static var previews: some View { 133 | Pixels { 134 | PixelPolygon(count: 3, radius: 100) 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Merger/PixelBlend.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelBlend: Pixel { 12 | 13 | typealias Pix = BlendPIX 14 | 15 | public let pixType: PIXType = .effect(.merger(.blend)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case blendMode 23 | case bypassTransform 24 | case position 25 | case rotation 26 | case scale 27 | case size 28 | } 29 | 30 | public init(mode blendMode: RenderKit.BlendMode, 31 | pixel leadingPixel: () -> Pixel, 32 | withPixel trailingPixel: () -> Pixel) { 33 | 34 | pixelTree = .mergerEffect(leadingPixel(), trailingPixel()) 35 | 36 | for key in Key.allCases { 37 | switch key { 38 | case .blendMode: 39 | metadata[key.rawValue] = blendMode.rawValue 40 | case .bypassTransform: 41 | metadata[key.rawValue] = true 42 | default: 43 | continue 44 | } 45 | } 46 | } 47 | 48 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 49 | 50 | guard let pix = pix as? Pix else { return nil } 51 | 52 | guard let key = Key(rawValue: key) else { return nil } 53 | 54 | switch key { 55 | case .blendMode: 56 | return pix.blendMode.rawValue 57 | case .bypassTransform: 58 | return pix.bypassTransform 59 | case .position: 60 | return Pixels.inZeroViewSpace(pix.position, size: size) 61 | case .rotation: 62 | return Pixels.asAngle(pix.rotation) 63 | case .scale: 64 | return pix.scale 65 | case .size: 66 | return pix.size 67 | } 68 | } 69 | 70 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 71 | 72 | guard var pix = pix as? Pix else { return } 73 | 74 | for (key, value) in metadata { 75 | 76 | guard let key = Key(rawValue: key) else { continue } 77 | 78 | switch key { 79 | case .blendMode: 80 | Pixels.updateRawValue(pix: &pix, value: value, at: \.blendMode) 81 | case .bypassTransform: 82 | Pixels.updateValue(pix: &pix, value: value, at: \.bypassTransform) 83 | case .position: 84 | Pixels.updateValueInZeroPixelSpace(pix: &pix, value: value, size: size, at: \.position) 85 | case .rotation: 86 | Pixels.updateValueAngle(pix: &pix, value: value, at: \.rotation) 87 | case .scale: 88 | Pixels.updateValue(pix: &pix, value: value, at: \.scale) 89 | case .size: 90 | Pixels.updateValue(pix: &pix, value: value, at: \.size) 91 | } 92 | } 93 | } 94 | } 95 | 96 | public extension PixelBlend { 97 | 98 | func pixelBlendOffset(_ offset: CGPoint) -> Self { 99 | var pixel = self 100 | pixel.metadata[Key.bypassTransform.rawValue] = false 101 | pixel.metadata[Key.position.rawValue] = offset 102 | return pixel 103 | } 104 | 105 | func pixelBlendRotate(_ angle: Angle) -> Self { 106 | var pixel = self 107 | pixel.metadata[Key.bypassTransform.rawValue] = false 108 | pixel.metadata[Key.rotation.rawValue] = angle 109 | return pixel 110 | } 111 | 112 | func pixelBlendScale(_ scale: CGFloat) -> Self { 113 | var pixel = self 114 | pixel.metadata[Key.bypassTransform.rawValue] = false 115 | pixel.metadata[Key.scale.rawValue] = scale 116 | return pixel 117 | } 118 | 119 | func pixelBlendScale(x: CGFloat = 1.0, y: CGFloat = 1.0) -> Self { 120 | var pixel = self 121 | pixel.metadata[Key.bypassTransform.rawValue] = false 122 | pixel.metadata[Key.size.rawValue] = CGSize(width: x, height: y) 123 | return pixel 124 | } 125 | } 126 | 127 | struct PixelBlend_Previews: PreviewProvider { 128 | static var previews: some View { 129 | Pixels { 130 | PixelBlend(mode: .add) { 131 | PixelCircle(radius: 100) 132 | } withPixel: { 133 | PixelStar(count: 5, radius: 150) 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelRainbowBlur.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2022-01-06. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelRainbowBlur: Pixel { 12 | 13 | typealias Pix = RainbowBlurPIX 14 | 15 | public let pixType: PIXType = .effect(.single(.rainbowBlur)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case style 23 | case radius 24 | case quality 25 | case angle 26 | case position 27 | case light 28 | } 29 | 30 | internal init(style: RainbowBlurPIX.Style, 31 | radius: CGFloat, 32 | quality: PIX.SampleQualityMode, 33 | angle: CGFloat = 0.0, 34 | offset position: CGPoint = .zero, 35 | light: CGFloat = 1.0, 36 | pixel: () -> Pixel) { 37 | 38 | pixelTree = .singleEffect(pixel()) 39 | 40 | for key in Key.allCases { 41 | switch key { 42 | case .style: 43 | metadata[key.rawValue] = style.rawValue 44 | case .radius: 45 | metadata[key.rawValue] = radius 46 | case .quality: 47 | metadata[key.rawValue] = quality.rawValue 48 | case .angle: 49 | metadata[key.rawValue] = angle 50 | case .position: 51 | metadata[key.rawValue] = position 52 | case .light: 53 | metadata[key.rawValue] = light 54 | } 55 | } 56 | } 57 | 58 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 59 | 60 | guard let pix = pix as? Pix else { return nil } 61 | 62 | guard let key = Key(rawValue: key) else { return nil } 63 | 64 | switch key { 65 | case .style: 66 | return pix.style.rawValue 67 | case .radius: 68 | return Pixels.inViewSpace(pix.radius, size: size) 69 | case .quality: 70 | return pix.quality.rawValue 71 | case .angle: 72 | return Pixels.asAngle(pix.angle) 73 | case .position: 74 | return Pixels.inZeroViewSpace(pix.position, size: size) 75 | case .light: 76 | return pix.light 77 | } 78 | } 79 | 80 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 81 | 82 | guard var pix = pix as? Pix else { return } 83 | 84 | for (key, value) in metadata { 85 | 86 | guard let key = Key(rawValue: key) else { continue } 87 | 88 | switch key { 89 | case .style: 90 | Pixels.updateRawValue(pix: &pix, value: value, at: \.style) 91 | case .radius: 92 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.radius) 93 | case .quality: 94 | Pixels.updateRawValue(pix: &pix, value: value, at: \.quality) 95 | case .angle: 96 | Pixels.updateValue(pix: &pix, value: value, at: \.angle) 97 | case .position: 98 | Pixels.updateValueInZeroPixelSpace(pix: &pix, value: value, size: size, at: \.position) 99 | case .light: 100 | Pixels.updateValue(pix: &pix, value: value, at: \.light) 101 | } 102 | } 103 | } 104 | } 105 | 106 | public extension Pixel { 107 | 108 | func pixelRainbowBlurCircle(radius: CGFloat, quality: PIX.SampleQualityMode = .high, light: CGFloat = 1.0) -> PixelRainbowBlur { 109 | PixelRainbowBlur(style: .circle, radius: radius, quality: quality, light: light, pixel: { self }) 110 | } 111 | 112 | func pixelRainbowBlurAngle(radius: CGFloat, angle: CGFloat, quality: PIX.SampleQualityMode = .high, light: CGFloat = 1.0) -> PixelRainbowBlur { 113 | PixelRainbowBlur(style: .angle, radius: radius, quality: quality, angle: angle, light: light, pixel: { self }) 114 | } 115 | 116 | func pixelRainbowBlurZoom(radius: CGFloat, offset: CGPoint = .zero, quality: PIX.SampleQualityMode = .high, light: CGFloat = 1.0) -> PixelRainbowBlur { 117 | PixelRainbowBlur(style: .zoom, radius: radius, quality: quality, offset: offset, light: light, pixel: { self }) 118 | } 119 | } 120 | 121 | struct PixelRainbowBlur_Previews: PreviewProvider { 122 | static var previews: some View { 123 | Pixels { 124 | PixelCamera() 125 | .pixelRainbowBlurZoom(radius: 100) 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Content/Generator/PixelStar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import PixelKit 7 | import SwiftUI 8 | import Resolution 9 | import PixelColor 10 | 11 | public struct PixelStar: Pixel { 12 | 13 | typealias Pix = StarPIX 14 | 15 | public let pixType: PIXType = .content(.generator(.star)) 16 | 17 | public var metadata: [String : PixelMetadata] = [:] 18 | 19 | public let pixelTree: PixelTree = .content 20 | 21 | enum Key: String, CaseIterable { 22 | case count 23 | case leadingRadius 24 | case trailingRadius 25 | case position 26 | case rotation 27 | case cornerRadius 28 | case color 29 | case backgroundColor 30 | } 31 | 32 | public init(count: Int, 33 | radius leadingRadius: CGFloat, 34 | innerRadius trailingRadius: CGFloat? = nil) { 35 | 36 | for key in Key.allCases { 37 | switch key { 38 | case .count: 39 | metadata[key.rawValue] = count 40 | case .leadingRadius: 41 | metadata[key.rawValue] = leadingRadius 42 | case .trailingRadius: 43 | metadata[key.rawValue] = trailingRadius ?? leadingRadius / 2 44 | default: 45 | continue 46 | } 47 | } 48 | } 49 | 50 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 51 | 52 | guard let pix = pix as? Pix else { return nil } 53 | 54 | guard let key = Key(rawValue: key) else { return nil } 55 | 56 | switch key { 57 | case .count: 58 | return pix.count 59 | case .leadingRadius: 60 | return Pixels.inViewSpace(pix.leadingRadius, size: size) 61 | case .trailingRadius: 62 | return Pixels.inViewSpace(pix.trailingRadius, size: size) 63 | case .position: 64 | return Pixels.inZeroViewSpace(pix.position, size: size) 65 | case .rotation: 66 | return Pixels.asAngle(pix.rotation) 67 | case .cornerRadius: 68 | return Pixels.inViewSpace(pix.cornerRadius, size: size) 69 | case .color: 70 | return pix.color 71 | case .backgroundColor: 72 | return pix.backgroundColor 73 | } 74 | } 75 | 76 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 77 | 78 | guard var pix = pix as? Pix else { return } 79 | 80 | for (key, value) in metadata { 81 | 82 | guard let key = Key(rawValue: key) else { continue } 83 | 84 | switch key { 85 | case .count: 86 | Pixels.updateValue(pix: &pix, value: value, at: \.count) 87 | case .leadingRadius: 88 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.leadingRadius) 89 | case .trailingRadius: 90 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.trailingRadius) 91 | case .position: 92 | Pixels.updateValueInZeroPixelSpace(pix: &pix, value: value, size: size, at: \.position) 93 | case .rotation: 94 | Pixels.updateValueAngle(pix: &pix, value: value, at: \.rotation) 95 | case .cornerRadius: 96 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.cornerRadius) 97 | case .color: 98 | Pixels.updateValue(pix: &pix, value: value, at: \.color) 99 | case .backgroundColor: 100 | Pixels.updateValue(pix: &pix, value: value, at: \.backgroundColor) 101 | } 102 | } 103 | } 104 | } 105 | 106 | public extension PixelStar { 107 | 108 | func pixelOffset(x: CGFloat = 0.0, y: CGFloat = 0.0) -> Self { 109 | var pixel = self 110 | pixel.metadata[Key.position.rawValue] = CGPoint(x: x, y: y) 111 | return pixel 112 | } 113 | 114 | func pixelRotation(_ angle: Angle) -> Self { 115 | var pixel = self 116 | pixel.metadata[Key.rotation.rawValue] = angle 117 | return pixel 118 | } 119 | 120 | func pixelCornerRadius(_ value: CGFloat) -> Self { 121 | var pixel = self 122 | pixel.metadata[Key.cornerRadius.rawValue] = value 123 | return pixel 124 | } 125 | 126 | func pixelColor(_ color: Color) -> Self { 127 | var pixel = self 128 | pixel.metadata[Key.color.rawValue] = PixelColor(color) 129 | return pixel 130 | } 131 | 132 | func pixelBackgroundColor(_ color: Color) -> Self { 133 | var pixel = self 134 | pixel.metadata[Key.backgroundColor.rawValue] = PixelColor(color) 135 | return pixel 136 | } 137 | } 138 | 139 | struct PixelStar_Previews: PreviewProvider { 140 | static var previews: some View { 141 | Pixels { 142 | PixelStar(count: 5, radius: 100) 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Content/Generator/PixelNoise.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import PixelKit 7 | import SwiftUI 8 | import Resolution 9 | import PixelColor 10 | 11 | public struct PixelNoise: Pixel { 12 | 13 | typealias Pix = NoisePIX 14 | 15 | public let pixType: PIXType = .content(.generator(.noise)) 16 | 17 | public let pixelTree: PixelTree = .content 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case seed 23 | case octaves 24 | case position 25 | case motion 26 | case zoom 27 | case colored 28 | case random 29 | case includeAlpha 30 | } 31 | 32 | /// Pixel Noise 33 | /// - Parameter ocataves: The detail of noise. A lower value is smoother and a higher value is more detailed. The lowest value is 1 and the highest value is 10. 34 | public init(detail ocataves: Int = 1) { 35 | 36 | for key in Key.allCases { 37 | switch key { 38 | case .octaves: 39 | metadata[key.rawValue] = ocataves 40 | case .colored: 41 | metadata[key.rawValue] = true 42 | default: 43 | continue 44 | } 45 | } 46 | } 47 | 48 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 49 | 50 | guard let pix = pix as? Pix else { return nil } 51 | 52 | guard let key = Key(rawValue: key) else { return nil } 53 | 54 | switch key { 55 | case .seed: 56 | return pix.seed 57 | case .octaves: 58 | return pix.octaves 59 | case .position: 60 | return Pixels.inZeroViewSpace(pix.position, size: size) 61 | case .motion: 62 | return Pixels.inViewSpace(pix.motion, size: size) 63 | case .zoom: 64 | return pix.zoom 65 | case .colored: 66 | return pix.colored 67 | case .random: 68 | return pix.random 69 | case .includeAlpha: 70 | return pix.includeAlpha 71 | } 72 | } 73 | 74 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 75 | 76 | guard var pix = pix as? Pix else { return } 77 | 78 | for (key, value) in metadata { 79 | 80 | guard let key = Key(rawValue: key) else { continue } 81 | 82 | switch key { 83 | case .seed: 84 | Pixels.updateValue(pix: &pix, value: value, at: \.seed) 85 | case .octaves: 86 | Pixels.updateValue(pix: &pix, value: value, at: \.octaves) 87 | case .position: 88 | Pixels.updateValueInZeroPixelSpace(pix: &pix, value: value, size: size, at: \.position) 89 | case .motion: 90 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.motion) 91 | case .zoom: 92 | Pixels.updateValue(pix: &pix, value: value, at: \.zoom) 93 | case .colored: 94 | Pixels.updateValue(pix: &pix, value: value, at: \.colored) 95 | case .random: 96 | Pixels.updateValue(pix: &pix, value: value, at: \.random) 97 | case .includeAlpha: 98 | Pixels.updateValue(pix: &pix, value: value, at: \.includeAlpha) 99 | } 100 | } 101 | } 102 | } 103 | 104 | public extension PixelNoise { 105 | 106 | func pixelOffset(x: CGFloat = 0.0, y: CGFloat = 0.0) -> Self { 107 | var pixel = self 108 | pixel.metadata[Key.position.rawValue] = CGPoint(x: x, y: y) 109 | return pixel 110 | } 111 | 112 | func pixelNoiseMonochrome() -> Self { 113 | var pixel = self 114 | pixel.metadata[Key.colored.rawValue] = false 115 | return pixel 116 | } 117 | 118 | func pixelNoiseSeed(_ seed: Int) -> Self { 119 | var pixel = self 120 | pixel.metadata[Key.seed.rawValue] = seed 121 | return pixel 122 | } 123 | 124 | func pixelNoiseMotion(_ motion: CGFloat) -> Self { 125 | var pixel = self 126 | pixel.metadata[Key.motion.rawValue] = motion 127 | return pixel 128 | } 129 | 130 | func pixelNoiseZoom(_ zoom: CGFloat) -> Self { 131 | var pixel = self 132 | pixel.metadata[Key.zoom.rawValue] = zoom 133 | return pixel 134 | } 135 | 136 | func pixelNoiseRandom() -> Self { 137 | var pixel = self 138 | pixel.metadata[Key.random.rawValue] = true 139 | return pixel 140 | } 141 | 142 | func pixelNoiseIncludeAlpha() -> Self { 143 | var pixel = self 144 | pixel.metadata[Key.includeAlpha.rawValue] = true 145 | return pixel 146 | } 147 | } 148 | 149 | struct PixelNoise_Previews: PreviewProvider { 150 | static var previews: some View { 151 | Pixels { 152 | PixelNoise() 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Single/PixelCornerPin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | import PixelColor 11 | 12 | public struct PixelCornerPin: Pixel { 13 | 14 | typealias Pix = CornerPinPIX 15 | 16 | public let pixType: PIXType = .effect(.single(.cornerPin)) 17 | 18 | public var pixelTree: PixelTree 19 | 20 | public var metadata: [String : PixelMetadata] = [:] 21 | 22 | enum Key: String, CaseIterable { 23 | case topLeft 24 | case topRight 25 | case bottomLeft 26 | case bottomRight 27 | case perspective 28 | case subdivisions 29 | } 30 | 31 | internal init(topLeft: CGPoint, 32 | topRight: CGPoint, 33 | bottomLeft: CGPoint, 34 | bottomRight: CGPoint, 35 | perspective: Bool = false, 36 | subdivisions: Int = 16, 37 | pixel: () -> Pixel) { 38 | 39 | pixelTree = .singleEffect(pixel()) 40 | 41 | for key in Key.allCases { 42 | switch key { 43 | case .topLeft: 44 | metadata[key.rawValue] = topLeft 45 | case .topRight: 46 | metadata[key.rawValue] = topRight 47 | case .bottomLeft: 48 | metadata[key.rawValue] = bottomLeft 49 | case .bottomRight: 50 | metadata[key.rawValue] = bottomRight 51 | case .perspective: 52 | metadata[key.rawValue] = perspective 53 | case .subdivisions: 54 | metadata[key.rawValue] = subdivisions 55 | } 56 | } 57 | } 58 | 59 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 60 | 61 | guard let pix = pix as? Pix else { return nil } 62 | 63 | guard let key = Key(rawValue: key) else { return nil } 64 | 65 | switch key { 66 | case .topLeft: 67 | return Pixels.inNormalizedViewSpace(pix.topLeft, size: size) 68 | case .topRight: 69 | return Pixels.inNormalizedViewSpace(pix.topRight, size: size) 70 | case .bottomLeft: 71 | return Pixels.inNormalizedViewSpace(pix.bottomLeft, size: size) 72 | case .bottomRight: 73 | return Pixels.inNormalizedViewSpace(pix.bottomRight, size: size) 74 | case .perspective: 75 | return pix.perspective 76 | case .subdivisions: 77 | return pix.subdivisions 78 | } 79 | } 80 | 81 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 82 | 83 | guard var pix = pix as? Pix else { return } 84 | 85 | for (key, value) in metadata { 86 | 87 | guard let key = Key(rawValue: key) else { continue } 88 | 89 | switch key { 90 | case .topLeft: 91 | Pixels.updateValueInNormalizedPixelSpace(pix: &pix, value: value, size: size, at: \.topLeft) 92 | case .topRight: 93 | Pixels.updateValueInNormalizedPixelSpace(pix: &pix, value: value, size: size, at: \.topRight) 94 | case .bottomLeft: 95 | Pixels.updateValueInNormalizedPixelSpace(pix: &pix, value: value, size: size, at: \.bottomLeft) 96 | case .bottomRight: 97 | Pixels.updateValueInNormalizedPixelSpace(pix: &pix, value: value, size: size, at: \.bottomRight) 98 | case .perspective: 99 | Pixels.updateValue(pix: &pix, value: value, at: \.perspective) 100 | case .subdivisions: 101 | Pixels.updateValue(pix: &pix, value: value, at: \.subdivisions) 102 | } 103 | } 104 | } 105 | } 106 | 107 | public extension Pixel { 108 | 109 | func pixelCornerPin(topLeft: CGPoint, 110 | topRight: CGPoint, 111 | bottomLeft: CGPoint, 112 | bottomRight: CGPoint, 113 | perspective: Bool = false, 114 | subdivisions: Int = 16) -> PixelCornerPin { 115 | PixelCornerPin(topLeft: topLeft, 116 | topRight: topRight, 117 | bottomLeft: bottomLeft, 118 | bottomRight: bottomRight, 119 | perspective: perspective, 120 | subdivisions: subdivisions, 121 | pixel: { self }) 122 | } 123 | } 124 | 125 | struct PixelCornerPin_Previews: PreviewProvider { 126 | static var previews: some View { 127 | GeometryReader { geo in 128 | Pixels { 129 | PixelImage("kite") 130 | .pixelCornerPin(topLeft: CGPoint(x: 100, y: 100), topRight: CGPoint(x: geo.size.width, y: 0.0), bottomLeft: CGPoint(x: 0.0, y: geo.size.height), bottomRight: CGPoint(x: geo.size.width, y: geo.size.height)) 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Merger/PixelLumaBlur.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelLumaBlur: Pixel { 12 | 13 | typealias Pix = LumaBlurPIX 14 | 15 | public let pixType: PIXType = .effect(.merger(.lumaBlur)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case style 23 | case radius 24 | case quality 25 | case angle 26 | case position 27 | case lumaGamma 28 | } 29 | 30 | internal init(style: LumaBlurPIX.Style, 31 | radius: CGFloat, 32 | quality: PIX.SampleQualityMode = .high, 33 | angle: Angle = .zero, 34 | offset position: CGPoint = .zero, 35 | lumaGamma: CGFloat = 1.0, 36 | pixel leadingPixel: () -> Pixel, 37 | withPixel trailingPixel: () -> Pixel) { 38 | 39 | pixelTree = .mergerEffect(leadingPixel(), trailingPixel()) 40 | 41 | for key in Key.allCases { 42 | switch key { 43 | case .style: 44 | metadata[key.rawValue] = style.rawValue 45 | case .radius: 46 | metadata[key.rawValue] = radius 47 | case .quality: 48 | metadata[key.rawValue] = quality.rawValue 49 | case .angle: 50 | metadata[key.rawValue] = angle 51 | case .position: 52 | metadata[key.rawValue] = position 53 | case .lumaGamma: 54 | metadata[key.rawValue] = lumaGamma 55 | } 56 | } 57 | } 58 | 59 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 60 | 61 | guard let pix = pix as? Pix else { return nil } 62 | 63 | guard let key = Key(rawValue: key) else { return nil } 64 | 65 | switch key { 66 | case .style: 67 | return pix.style.rawValue 68 | case .radius: 69 | return Pixels.inViewSpace(pix.radius, size: size) 70 | case .quality: 71 | return pix.quality.rawValue 72 | case .angle: 73 | return Pixels.asAngle(pix.angle) 74 | case .position: 75 | return Pixels.inZeroViewSpace(pix.position, size: size) 76 | case .lumaGamma: 77 | return pix.lumaGamma 78 | } 79 | } 80 | 81 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 82 | 83 | guard var pix = pix as? Pix else { return } 84 | 85 | for (key, value) in metadata { 86 | 87 | guard let key = Key(rawValue: key) else { continue } 88 | 89 | switch key { 90 | case .style: 91 | Pixels.updateRawValue(pix: &pix, value: value, at: \.style) 92 | case .radius: 93 | Pixels.updateValueInPixelSpace(pix: &pix, value: value, size: size, at: \.radius) 94 | case .quality: 95 | Pixels.updateRawValue(pix: &pix, value: value, at: \.quality) 96 | case .angle: 97 | Pixels.updateValueAngle(pix: &pix, value: value, at: \.angle) 98 | case .position: 99 | Pixels.updateValueInZeroPixelSpace(pix: &pix, value: value, size: size, at: \.position) 100 | case .lumaGamma: 101 | Pixels.updateValue(pix: &pix, value: value, at: \.lumaGamma) 102 | } 103 | } 104 | } 105 | } 106 | 107 | public extension Pixel { 108 | 109 | func pixelLumaBlurBox(radius: CGFloat, lumaGamma: CGFloat = 1.0, pixel: () -> Pixel) -> PixelLumaBlur { 110 | PixelLumaBlur(style: .box, radius: radius, lumaGamma: lumaGamma, pixel: { self }, withPixel: pixel) 111 | } 112 | 113 | func pixelLumaBlurAngle(radius: CGFloat, angle: Angle, lumaGamma: CGFloat = 1.0, pixel: () -> Pixel) -> PixelLumaBlur { 114 | PixelLumaBlur(style: .angle, radius: radius, angle: angle, lumaGamma: lumaGamma, pixel: { self }, withPixel: pixel) 115 | } 116 | 117 | func pixelLumaBlurZoom(radius: CGFloat, offset: CGPoint = .zero, lumaGamma: CGFloat = 1.0, pixel: () -> Pixel) -> PixelLumaBlur { 118 | PixelLumaBlur(style: .zoom, radius: radius, offset: offset, lumaGamma: lumaGamma, pixel: { self }, withPixel: pixel) 119 | } 120 | 121 | func pixelLumaBlurRandom(radius: CGFloat, lumaGamma: CGFloat = 1.0, pixel: () -> Pixel) -> PixelLumaBlur { 122 | PixelLumaBlur(style: .random, radius: radius, lumaGamma: lumaGamma, pixel: { self }, withPixel: pixel) 123 | } 124 | } 125 | 126 | struct PixelLumaBlur_Previews: PreviewProvider { 127 | static var previews: some View { 128 | Pixels { 129 | PixelCircle(radius: 100) 130 | .pixelLumaBlurBox(radius: 100) { 131 | PixelGradient(axis: .vertical) 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Sources/PixelUI/Pixel/Effects/Merger/PixelLumaTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anton Heestand on 2021-11-16. 3 | // 4 | 5 | import Foundation 6 | import RenderKit 7 | import PixelKit 8 | import SwiftUI 9 | import Resolution 10 | 11 | public struct PixelLumaTransform: Pixel { 12 | 13 | typealias Pix = LumaTransformPIX 14 | 15 | public let pixType: PIXType = .effect(.merger(.lumaTransform)) 16 | 17 | public var pixelTree: PixelTree 18 | 19 | public var metadata: [String : PixelMetadata] = [:] 20 | 21 | enum Key: String, CaseIterable { 22 | case position 23 | case rotation 24 | case scale 25 | case size 26 | case lumaGamma 27 | case extend 28 | } 29 | 30 | internal init(offset position: CGPoint = .zero, 31 | angle rotation: Angle = .zero, 32 | scale: CGFloat = 1.0, 33 | size: CGSize = CGSize(width: 1.0, height: 1.0), 34 | lumaGamma: CGFloat = 1.0, 35 | pixel leadingPixel: () -> Pixel, 36 | withPixel trailingPixel: () -> Pixel) { 37 | 38 | pixelTree = .mergerEffect(leadingPixel(), trailingPixel()) 39 | 40 | for key in Key.allCases { 41 | switch key { 42 | case .position: 43 | metadata[key.rawValue] = position 44 | case .rotation: 45 | metadata[key.rawValue] = rotation 46 | case .scale: 47 | metadata[key.rawValue] = scale 48 | case .size: 49 | metadata[key.rawValue] = size 50 | case .lumaGamma: 51 | metadata[key.rawValue] = lumaGamma 52 | default: 53 | continue 54 | } 55 | } 56 | } 57 | 58 | public func value(at key: String, pix: PIX, size: CGSize) -> PixelMetadata? { 59 | 60 | guard let pix = pix as? Pix else { return nil } 61 | 62 | guard let key = Key(rawValue: key) else { return nil } 63 | 64 | switch key { 65 | case .position: 66 | return Pixels.inZeroViewSpace(pix.position, size: size) 67 | case .rotation: 68 | return Pixels.asAngle(pix.rotation) 69 | case .scale: 70 | return pix.scale 71 | case .size: 72 | return pix.size 73 | case .lumaGamma: 74 | return pix.lumaGamma 75 | case .extend: 76 | return pix.extend.rawValue 77 | } 78 | } 79 | 80 | public func update(metadata: [String : PixelMetadata], pix: PIX, size: CGSize) { 81 | 82 | guard var pix = pix as? Pix else { return } 83 | 84 | for (key, value) in metadata { 85 | 86 | guard let key = Key(rawValue: key) else { continue } 87 | 88 | switch key { 89 | case .position: 90 | Pixels.updateValueInZeroPixelSpace(pix: &pix, value: value, size: size, at: \.position) 91 | case .rotation: 92 | Pixels.updateValueAngle(pix: &pix, value: value, at: \.rotation) 93 | case .scale: 94 | Pixels.updateValue(pix: &pix, value: value, at: \.scale) 95 | case .size: 96 | Pixels.updateValue(pix: &pix, value: value, at: \.size) 97 | case .lumaGamma: 98 | Pixels.updateValue(pix: &pix, value: value, at: \.lumaGamma) 99 | case .extend: 100 | Pixels.updateRawValue(pix: &pix, value: value, at: \.extend) 101 | } 102 | } 103 | } 104 | } 105 | 106 | public extension Pixel { 107 | 108 | func pixelLumaOffset(x: CGFloat = 0.0, y: CGFloat = 0.0, lumaGamma: CGFloat = 1.0, pixel: () -> Pixel) -> PixelLumaTransform { 109 | PixelLumaTransform(offset: CGPoint(x: x, y: y), lumaGamma: lumaGamma, pixel: { self }, withPixel: pixel) 110 | } 111 | 112 | func pixelLumaRotate(_ angle: Angle, lumaGamma: CGFloat = 1.0, pixel: () -> Pixel) -> PixelLumaTransform { 113 | PixelLumaTransform(angle: angle, lumaGamma: lumaGamma, pixel: { self }, withPixel: pixel) 114 | } 115 | 116 | func pixelLumaScale(_ scale: CGFloat, lumaGamma: CGFloat = 1.0, pixel: () -> Pixel) -> PixelLumaTransform { 117 | PixelLumaTransform(scale: scale, lumaGamma: lumaGamma, pixel: { self }, withPixel: pixel) 118 | } 119 | 120 | func pixelLumaScale(x: CGFloat = 1.0, y: CGFloat = 1.0, lumaGamma: CGFloat = 1.0, pixel: () -> Pixel) -> PixelLumaTransform { 121 | PixelLumaTransform(size: CGSize(width: x, height: y), lumaGamma: lumaGamma, pixel: { self }, withPixel: pixel) 122 | } 123 | } 124 | 125 | extension PixelLumaTransform { 126 | 127 | func pixelExtend(_ extend: ExtendMode) -> Self { 128 | var pixel = self 129 | pixel.metadata[Key.extend.rawValue] = extend.rawValue 130 | return pixel 131 | } 132 | } 133 | 134 | struct PixelLumaTransform_Previews: PreviewProvider { 135 | static var previews: some View { 136 | Pixels { 137 | PixelCircle(radius: 500) 138 | .pixelLumaOffset(x: 500) { 139 | PixelGradient(axis: .vertical) 140 | } 141 | } 142 | } 143 | } 144 | --------------------------------------------------------------------------------