├── Assets ├── in.jpg ├── banner.png └── fx │ ├── out_hue.jpg │ ├── out_blur.jpg │ ├── out_clamp.jpg │ ├── out_edge.jpg │ ├── out_gamma.jpg │ ├── out_range.jpg │ ├── out_sepia.jpg │ ├── out_slope.jpg │ ├── out_twirl.jpg │ ├── out_contrast.jpg │ ├── out_inverted.jpg │ ├── out_opacity.jpg │ ├── out_quantize.jpg │ ├── out_sharpen.jpg │ ├── out_brightness.jpg │ ├── out_saturation.jpg │ ├── out_threshold.jpg │ └── out_kaleidoscope.jpg ├── .gitignore ├── .swiftpm └── xcode │ ├── package.xcworkspace │ └── contents.xcworkspacedata │ └── xcshareddata │ └── xcschemes │ └── ImageFX.xcscheme ├── Package.swift ├── LICENSE ├── Package.resolved ├── Sources └── ImageFX │ └── ImageFX.swift └── README.md /Assets/in.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/in.jpg -------------------------------------------------------------------------------- /Assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/banner.png -------------------------------------------------------------------------------- /Assets/fx/out_hue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_hue.jpg -------------------------------------------------------------------------------- /Assets/fx/out_blur.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_blur.jpg -------------------------------------------------------------------------------- /Assets/fx/out_clamp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_clamp.jpg -------------------------------------------------------------------------------- /Assets/fx/out_edge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_edge.jpg -------------------------------------------------------------------------------- /Assets/fx/out_gamma.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_gamma.jpg -------------------------------------------------------------------------------- /Assets/fx/out_range.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_range.jpg -------------------------------------------------------------------------------- /Assets/fx/out_sepia.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_sepia.jpg -------------------------------------------------------------------------------- /Assets/fx/out_slope.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_slope.jpg -------------------------------------------------------------------------------- /Assets/fx/out_twirl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_twirl.jpg -------------------------------------------------------------------------------- /Assets/fx/out_contrast.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_contrast.jpg -------------------------------------------------------------------------------- /Assets/fx/out_inverted.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_inverted.jpg -------------------------------------------------------------------------------- /Assets/fx/out_opacity.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_opacity.jpg -------------------------------------------------------------------------------- /Assets/fx/out_quantize.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_quantize.jpg -------------------------------------------------------------------------------- /Assets/fx/out_sharpen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_sharpen.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | 7 | Pods/* 8 | *.xcworkspace -------------------------------------------------------------------------------- /Assets/fx/out_brightness.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_brightness.jpg -------------------------------------------------------------------------------- /Assets/fx/out_saturation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_saturation.jpg -------------------------------------------------------------------------------- /Assets/fx/out_threshold.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_threshold.jpg -------------------------------------------------------------------------------- /Assets/fx/out_kaleidoscope.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heestand-xyz/ImageFX/HEAD/Assets/fx/out_kaleidoscope.jpg -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "ImageFX", 7 | platforms: [ 8 | .iOS(.v16), 9 | .macOS(.v13), 10 | ], 11 | products: [ 12 | .library( 13 | name: "ImageFX", 14 | targets: ["ImageFX"] 15 | ), 16 | ], 17 | dependencies: [ 18 | .package( 19 | url: "http://github.com/heestand-xyz/AsyncGraphics", 20 | from: "2.0.5" 21 | ), 22 | .package( 23 | url: "https://github.com/heestand-xyz/PixelColor", 24 | from: "2.2.2" 25 | ), 26 | ], 27 | targets: [ 28 | .target( 29 | name: "ImageFX", 30 | dependencies: [ 31 | "AsyncGraphics", 32 | "PixelColor", 33 | ] 34 | ), 35 | ] 36 | ) 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Anton Heestand 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "asyncgraphics", 5 | "kind" : "remoteSourceControl", 6 | "location" : "http://github.com/heestand-xyz/AsyncGraphics", 7 | "state" : { 8 | "revision" : "cfb9dfbc0ec094084e4d9bd52a368c0fd8738994", 9 | "version" : "2.0.5" 10 | } 11 | }, 12 | { 13 | "identity" : "coregraphicsextensions", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/heestand-xyz/CoreGraphicsExtensions", 16 | "state" : { 17 | "revision" : "10a0403455eff7b8217054960d6391c98c0dea37", 18 | "version" : "1.7.2" 19 | } 20 | }, 21 | { 22 | "identity" : "pixelcolor", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/heestand-xyz/PixelColor", 25 | "state" : { 26 | "revision" : "985d12c8339bc0ae08803a0abde32c2e4696c4ce", 27 | "version" : "2.2.2" 28 | } 29 | }, 30 | { 31 | "identity" : "spatialextensions", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/heestand-xyz/SpatialExtensions", 34 | "state" : { 35 | "revision" : "f2b83a6928847f8cc9fa792af5f7d003a34b33c9", 36 | "version" : "0.1.2" 37 | } 38 | }, 39 | { 40 | "identity" : "swift-syntax", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/apple/swift-syntax", 43 | "state" : { 44 | "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", 45 | "version" : "509.1.1" 46 | } 47 | }, 48 | { 49 | "identity" : "texturemap", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/heestand-xyz/TextureMap", 52 | "state" : { 53 | "revision" : "f07a0127f59217933b9b89c2eda4dd26a2da701f", 54 | "version" : "1.0.2" 55 | } 56 | }, 57 | { 58 | "identity" : "videoframes", 59 | "kind" : "remoteSourceControl", 60 | "location" : "https://github.com/heestand-xyz/VideoFrames", 61 | "state" : { 62 | "revision" : "3de476e958b765699eb2f98ca42336acfc262f49", 63 | "version" : "1.1.1" 64 | } 65 | } 66 | ], 67 | "version" : 2 68 | } 69 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/ImageFX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Sources/ImageFX/ImageFX.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | #if os(macOS) 3 | import Cocoa 4 | #else 5 | import UIKit 6 | #endif 7 | import AsyncGraphics 8 | import PixelColor 9 | 10 | #if os(macOS) 11 | public typealias FXImage = NSImage 12 | public typealias FXColor = NSColor 13 | #else 14 | public typealias FXImage = UIImage 15 | public typealias FXColor = UIColor 16 | #endif 17 | 18 | 19 | public extension FXImage { 20 | 21 | func fx( 22 | edit: (Graphic) async throws -> Graphic 23 | ) async throws -> FXImage { 24 | try await edit(Graphic.image(self)).image 25 | } 26 | } 27 | 28 | public extension FXImage { 29 | 30 | func fxBlur(radius: CGFloat) async throws -> FXImage { 31 | try await fx { try await $0.blurred(radius: radius) } 32 | } 33 | 34 | func fxRainbowBlur(radius: CGFloat) async throws -> FXImage { 35 | try await fx { try await $0.rainbowBlurredCircle(radius: radius) } 36 | } 37 | 38 | func fxEdge(amplitude: CGFloat = 1.0, distance: CGFloat = 1.0) async throws -> FXImage { 39 | try await fx { try await $0.edge(amplitude: amplitude, distance: distance) } 40 | } 41 | 42 | func fxClamp(low: CGFloat = 0.0, high: CGFloat = 1.0) async throws -> FXImage { 43 | try await fx { try await $0.clamp(low: low, high: high) } 44 | } 45 | 46 | func fxKaleidoscope(divisions: Int = 12, mirror: Bool = true) async throws -> FXImage { 47 | try await fx { try await $0.kaleidoscope(count: divisions, mirror: mirror) } 48 | } 49 | 50 | func fxBrightness(_ value: CGFloat) async throws -> FXImage { 51 | try await fx { try await $0.brightness(value) } 52 | } 53 | 54 | func fxDarkness(_ value: CGFloat) async throws -> FXImage { 55 | try await fx { try await $0.darkness(value) } 56 | } 57 | 58 | func fxContrast(_ value: CGFloat) async throws -> FXImage { 59 | try await fx { try await $0.contrast(value) } 60 | } 61 | 62 | func fxGamma(_ value: CGFloat) async throws -> FXImage { 63 | try await fx { try await $0.gamma(value) } 64 | } 65 | 66 | func fxInvert() async throws -> FXImage { 67 | try await fx { try await $0.inverted() } 68 | } 69 | 70 | func fxOpacity(_ value: CGFloat) async throws -> FXImage { 71 | try await fx { try await $0.opacity(value) } 72 | } 73 | 74 | func fxQuantize(fraction: CGFloat = 0.1) async throws -> FXImage { 75 | try await fx { try await $0.quantize(fraction) } 76 | } 77 | 78 | func fxSharpen(_ value: CGFloat = 2.0) async throws -> FXImage { 79 | try await fx { try await $0.sharpen(value) } 80 | } 81 | 82 | func fxSlope(_ value: CGFloat = 1.0) async throws -> FXImage { 83 | try await fx { try await $0.slope(amplitude: value) } 84 | } 85 | 86 | func fxThreshold(_ value: CGFloat = 0.5) async throws -> FXImage { 87 | try await fx { try await $0.threshold(value) } 88 | } 89 | 90 | func fxRange( 91 | inLow: CGFloat = 0.0, 92 | inHigh: CGFloat = 1.0, 93 | outLow: CGFloat = 0.0, 94 | outHigh: CGFloat = 1.0 95 | ) async throws -> FXImage { 96 | try await fx { 97 | try await $0.range( 98 | referenceLow: inLow, 99 | referenceHigh: inHigh, 100 | targetLow: outLow, 101 | targetHigh: outHigh 102 | ) 103 | } 104 | } 105 | 106 | func fxSaturation(_ value: CGFloat) async throws -> FXImage { 107 | try await fx { try await $0.saturated(value) } 108 | } 109 | 110 | func fxMonochrome() async throws -> FXImage { 111 | try await fx { try await $0.monochrome() } 112 | } 113 | 114 | func fxHue(_ value: Angle) async throws -> FXImage { 115 | try await fx { try await $0.hue(value) } 116 | } 117 | 118 | func fxSepia(color: FXColor) async throws -> FXImage { 119 | try await fx { try await $0.sepia(color: PixelColor(color)) } 120 | } 121 | 122 | func fxFlipX() async throws -> FXImage { 123 | try await fx { try await $0.mirroredHorizontally() } 124 | } 125 | 126 | func fxFlipY() async throws -> FXImage { 127 | try await fx { try await $0.mirroredVertically() } 128 | } 129 | 130 | func fxFlopLeft() async throws -> FXImage { 131 | try await fx { try await $0.rotatedLeft() } 132 | } 133 | 134 | func fxFlopRight() async throws -> FXImage { 135 | try await fx { try await $0.rotatedRight() } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # ImageFX 4 | 5 | Powered by Metal with [AsyncGraphics](https://github.com/heestand-xyz/AsyncGraphics) 6 | 7 | ## Install 8 | 9 | ### Swift Package 10 | 11 | ```swift 12 | .package(url: "https://github.com/heestand-xyz/ImageFX", from: "2.0.0") 13 | ``` 14 | 15 | > In version `2.0.0` spatial values are not relative anymore, they are absolute in pixel space 16 | 17 | ## Effects 18 | 19 | > All examples work with `UIImage` and `NSImage` 20 | 21 | 22 | 23 | ```swift 24 | let image: UIImage = UIImage(named: "Kite")! 25 | ``` 26 | 27 | ### Blur 28 | 29 | 30 | ```swift 31 | let fxImage: UIImage = try await image.fxBlur(100) 32 | ``` 33 | 34 | ### Edge 35 | 36 | 37 | ```swift 38 | let fxImage: UIImage = try await image.fxEdge() 39 | ``` 40 | 41 | ### Clamp 42 | 43 | 44 | ```swift 45 | let fxImage: UIImage = try await image.fxClamp(low: 0.25, high: 0.75) 46 | ``` 47 | 48 | ### Kaleidoscope 49 | 50 | 51 | ```swift 52 | let fxImage: UIImage = try await image.fxKaleidoscope() 53 | ``` 54 | 55 | ### Levels: Brightness 56 | 57 | 58 | ```swift 59 | let fxImage: UIImage = try await image.fxBrightness(2.0) 60 | ``` 61 | 62 | ### Levels: Gamma 63 | 64 | 65 | ```swift 66 | let fxImage: UIImage = try await image.fxGamma(0.5) 67 | ``` 68 | 69 | ### Levels: Invert 70 | 71 | 72 | ```swift 73 | let fxImage: UIImage = try await image.fxInvert() 74 | ``` 75 | 76 | ### Levels: Opacity 77 | 78 | 79 | ```swift 80 | let fxImage: UIImage = try await image.fxOpacity(0.5) 81 | ``` 82 | 83 | 84 | ### Levels: Contrast 85 | 86 | 87 | ```swift 88 | let fxImage: UIImage = try await image.fxContrast(2.0) 89 | ``` 90 | 91 | ### Quantize 92 | 93 | 94 | ```swift 95 | let fxImage: UIImage = try await image.fxQuantize(0.125) 96 | ``` 97 | 98 | ### Sharpen 99 | 100 | 101 | ```swift 102 | let fxImage: UIImage = try await image.fxSharpen(2.0) 103 | ``` 104 | 105 | ### Slope 106 | 107 | 108 | ```swift 109 | let fxImage: UIImage = try await image.fxSlope() 110 | ``` 111 | 112 | ### Threshold 113 | 114 | 115 | ```swift 116 | let fxImage: UIImage = try await image.fxThreshold() 117 | ``` 118 | 119 | ### Sepia 120 | 121 | 122 | ```swift 123 | let fxImage: UIImage = try await image.fxSepia(color: .orange) 124 | ``` 125 | 126 | ### Range 127 | 128 | 129 | ```swift 130 | let fxImageA: UIImage = try await image.fxRange(inLow: 0.0, inHigh: 1.0, outLow: 0.0, outHigh: 0.5) 131 | ``` 132 | 133 | ### Saturation 134 | 135 | 136 | ```swift 137 | let fxImageA: UIImage = try await image.fxSaturation(0.5) 138 | let fxImageB: UIImage = try await image.fxMonochrome() 139 | ``` 140 | 141 | ### Hue 142 | 143 | 144 | ```swift 145 | let fxImage: UIImage = try await image.fxHue(.degrees(180)) 146 | ``` 147 | 148 | ## Custom Effects 149 | 150 | ```swift 151 | import AsyncGraphics 152 | ``` 153 | 154 | ```swift 155 | let fxImage: UIImage = try await image 156 | .fx { graphic in 157 | let noise: Graphic = try await .coloredNoise(resolution: graphic.resolution) 158 | return try await graphic.displaced(with: noise, offset: 100) 159 | } 160 | ``` 161 | --------------------------------------------------------------------------------