├── 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 |
--------------------------------------------------------------------------------