Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/heestand-xyz/PixelKit
Live Graphics in Swift & Metal
https://github.com/heestand-xyz/PixelKit
graphics ios live macos metal swift
Last synced: about 1 month ago
JSON representation
Live Graphics in Swift & Metal
- Host: GitHub
- URL: https://github.com/heestand-xyz/PixelKit
- Owner: heestand-xyz
- License: mit
- Created: 2018-07-23T11:57:37.000Z (over 6 years ago)
- Default Branch: main
- Last Pushed: 2023-02-05T03:06:46.000Z (almost 2 years ago)
- Last Synced: 2024-07-21T04:42:58.642Z (5 months ago)
- Topics: graphics, ios, live, macos, metal, swift
- Language: Swift
- Homepage:
- Size: 61.8 MB
- Stars: 882
- Watchers: 26
- Forks: 65
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
*Check out [AsyncGraphics](https://github.com/heestand-xyz/AsyncGraphics).
Inspired by PixelKit & RenderKit.*# PixelKit
Live Graphics for iOS, macOS and tvOS
Runs on [RenderKit](https://github.com/heestand-xyz/RenderKit), powered by MetalPixelKit combines custom shaders, metal performance shaders, core image filters and vision to create tools for real-time rendering.
Examples:
[Camera Effects](#example-camera-effects) -
[Green Screen](#example-green-screen)
Info:
[Coordinate Space](#coordinate-space) -
[Blend Operators](#blend-operators) -
[Effect Convenience Funcs](#effect-convenience-funcs) -
[High Bit Mode](#high-bit-mode)| | | | | | | |
| --- | --- | --- | --- | --- | --- | --- |
| CameraPIX | DepthCameraPIX | ImagePIX | VideoPIX | ScreenCapturePIX | StreamInPIX | SlopePIX || | | | | | | | |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ColorPIX | CirclePIX | RectanglePIX | PolygonPIX | ArcPIX | LinePIX | GradientPIX | StackPIX || | | | | | | | |
| --- | --- | --- | --- | --- | --- | --- | --- |
| NoisePIX | TextPIX | MetalPIX | TwirlPIX | FeedbackPIX | DelayPIX | SharpenPIX | StreamOutPIX || | | | | | | |
| --- | --- | --- | --- | --- | --- | --- |
| LevelsPIX | BlurPIX | EdgePIX | ThresholdPIX | QuantizePIX | TransformPIX | KaleidoscopePIX || | | | | | | |
| --- | --- | --- | --- | --- | --- | --- |
| ChannelMixPIX | ChromaKeyPIX | CornerPinPIX | ColorShiftPIX | FlipFlopPIX | RangePIX | StarPIX || | | | | | | | |
| --- | --- | --- | --- | --- | --- | --- | --- |
| SepiaPIX | ConvertPIX | ReducePIX | ClampPIX | FreezePIX | FlarePIX | AirPlayPIX | RecordPIX || | | | | | | | |
| --- | --- | --- | --- | --- | --- | --- | --- |
| BlendPIX | CrossPIX | LookupPIX | DisplacePIX | RemapPIX | ReorderPIX | ResolutionPIX | CropPIX || | | | | | |
| --- | --- | --- | --- | --- | --- |
| BlendsPIX | LumaLevelsPIX | LumaBlurPIX | LumaTransformPIX | TimeMachinePIX | ArrayPIX |---
## Install
### Swift Package
~~~~swift
.package(url: "https://github.com/heestand-xyz/PixelKit", from: "3.0.0")
~~~~## Setup
### SwiftUI
~~~~swift
import SwiftUI
import PixelKitstruct ContentView: View {
@StateObject var circlePix = CirclePIX()
@StateObject var blurPix = BlurPIX()
var body: some View {
PixelView(pix: blurPix)
.onAppear {
blurPix.input = circlePix
blurPix.radius = 0.25
}
}
}
~~~~### UIKit
~~~~swift
import UIKit
import PixelKitclass ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()let circlePix = CirclePIX()
let blurPix = BlurPIX()
blurPix.input = circlePix
blurPix.radius = 0.25let finalPix: PIX = blurPix
finalPix.view.frame = view.bounds
view.addSubview(finalPix.view)
}
}
~~~~## Resolution
In PixelKit all PIXs have a resolution. Some PIXs have defined resolutions (default to `.auto`) and some PIXs have derived resolutions.
The `.auto` resolution will fill up the view and get the correct resolution based on the view size. If a view is 100x100 points, the resolution will be 200x200 pixels on macOS and 300x300 pixels on iPhone.
Import the resolution package to work with resolutions:
```swift
import Resolution
```You can multiply and divide resolutions with a `CGFloat` or `Int`.
There are predefined resolutions like `._1080p` & `._4K`.
## Rendered Image
~~~~swift
.renderedImage // UIImage or NSImage
.renderedTexture // MTLTexture
~~~~### Example: Camera Effects
| | | | | |
| --- | --- | --- | --- | --- |```swift
import SwiftUI
import PixelKitclass ViewModel: ObservableObject {
let camera: CameraPIX
let levels: LevelsPIX
let colorShift: ColorShiftPIX
let blur: BlurPIX
let circle: CirclePIX
let finalPix: PIX
init() {
camera = CameraPIX()
camera.cameraResolution = ._1080plevels = LevelsPIX()
levels.input = camera
levels.brightness = 1.5
levels.gamma = 0.5colorShift = ColorShiftPIX()
colorShift.input = levels
colorShift.saturation = 0.5blur = BlurPIX()
blur.input = colorShift
blur.radius = 0.25circle = CirclePIX(at: .square(1080))
circle.radius = 0.45
circle.backgroundColor = .clearfinalPix = blur & (camera * circle)
}
}struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
PixelView(pix: viewModel.finalPix)
}
}
```This can also be done with [Effect Convenience Funcs](#effect-convenience-funcs):
```swift
let pix = CameraPIX().pixBrightness(1.5).pixGamma(0.5).pixSaturation(0.5).pixBlur(0.25)
```Remeber to add `NSCameraUsageDescription` to your *Info.plist*
### Example: Green Screen
| | | | |
| --- | --- | --- | --- |`import RenderKit
import PixelKit`~~~~swift
let cityImage = ImagePIX()
cityImage.image = UIImage(named: "city")let supermanVideo = VideoPIX()
supermanVideo.load(fileNamed: "superman", withExtension: "mov")let supermanKeyed = ChromaKeyPIX()
supermanKeyed.input = supermanVideo
supermanKeyed.keyColor = .greenlet blendPix = BlendPIX()
blendPix.blendingMode = .over
blendPix.inputA = cityImage
blendPix.inputB = supermanKeyedlet finalPix: PIX = blendPix
finalPix.view.frame = view.bounds
view.addSubview(finalPix.view)
~~~~This can also be done with [Blend Operators](#blend-operators) and [Effect Convenience Funcs](#effect-convenience-funcs):
```swift
let pix = cityImage & supermanVideo.pixChromaKey(.green)
```### Example: Depth Camera
`
import RenderKit
import PixelKit`~~~~swift
let cameraPix = CameraPIX()
cameraPix.camera = .frontlet depthCameraPix = DepthCameraPIX.setup(with: cameraPix)
let levelsPix = LevelsPIX()
levelsPix.input = depthCameraPix
levelsPix.inverted = truelet lumaBlurPix = cameraPix.pixLumaBlur(pix: levelsPix, radius: 0.1)
let finalPix: PIX = lumaBlurPix
finalPix.view.frame = view.bounds
view.addSubview(finalPix.view)
~~~~The `DepthCameraPIX` was added in PixelKit `v0.8.4` and requires an iPhone X or newer.
Note to use the `setup(with:filter:)` method of `DepthCameraPIX`.
It will take care of orientation, color and enable depth on the `CameraPIX`.To gain access to depth values ouside of the 0.0 and 1.0 bounds,
enable `16 bit` mode like this: `PixelKit.main.render.bits = ._16`### Example: Multi Camera
~~~~swift
let cameraPix = CameraPIX()
cameraPix.camera = .backlet multiCameraPix = MultiCameraPIX.setup(with: cameraPix, camera: .front)
let movedMultiCameraPix = multiCameraPix.pixScale(by: 0.25).pixTranslate(x: 0.375 * (9 / 16), y: 0.375)
let finalPix: PIX = camearPix & movedMultiCameraPix
finalPix.view.frame = view.bounds
view.addSubview(finalPix.view)
~~~~Note `MultiCameraPIX` requires iOS 13.
## Coordinate Space
The PixelKit coordinate space is normailzed to the vertical axis (1.0 in height) with the origin (0.0, 0.0) in the center.
Note that compared to native UIKit and SwiftUI views the vertical axis is flipped and origin is moved, this is more convinent when working with graphics in PixelKit.
A full rotation is defined by 1.0Center: CGPoint(x: 0, y: 0)
Bottom Left: CGPoint(x: -0.5 * aspectRatio, y: -0.5)
Top Right: CGPoint(x: 0.5 * aspectRatio, y: 0.5)Tip: `Resolution` has an `.aspect` property:
`let aspectRatio: CGFloat = Resolution._1080p.aspect`## Blend Operators
A quick and convenient way to blend PIXs
These are the supported `BlendingMode` operators:| `&` | `!&` | `+` | `-` | `*` | `**` | `!**` | `%` | `~` | `°` |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| .over | .under | .add | .subtract | .multiply | .power | .gamma | .difference | .average | cosine || `<>` | `><` | `++` | `--` | `<->` | `>-<` | `+-+` |
| --- | --- | --- | --- | --- | --- | --- |
| .minimum | .maximum | .addWithAlpha | .subtractWithAlpha | inside | outside | exclusiveOr |```swift
let blendPix = (CameraPIX() !** NoisePIX(at: .fullHD(.portrait))) * CirclePIX(at: .fullHD(.portrait))
```The default global blend operator fill mode is `.fit`, change it like this:
`PIX.blendOperators.globalPlacement = .fill`## Effect Convenience Funcs
- .pixScaleResolution(to: ._1080p * 0.5) -> ResolutionPIX
- .pixScaleResolution(by: 0.5) -> ResolutionPIX
- .pixBrightness(0.5) -> LevelsPIX
- .pixDarkness(0.5) -> LevelsPIX
- .pixContrast(0.5) -> LevelsPIX
- .pixGamma(0.5) -> LevelsPIX
- .pixInvert() -> LevelsPIX
- .pixOpacity(0.5) -> LevelsPIX
- .pixBlur(0.5) -> BlurPIX
- .pixEdge() -> EdgePIX
- .pixThreshold(at: 0.5) -> ThresholdPIX
- .pixQuantize(by: 0.5) -> QuantizePIX
- .pixPosition(at: CGPoint(x: 0.5, y: 0.5)) -> TransformPIX
- .pixRotatate(by: 0.5) -> TransformPIX
- .pixRotatate(byRadians: .pi) -> TransformPIX
- .pixRotatate(byDegrees: 180) -> TransformPIX
- .pixScale(by: 0.5) -> TransformPIX
- .pixKaleidoscope() -> KaleidoscopePIX
- .pixTwirl(0.5) -> TwirlPIX
- .pixSwap(.red, .blue) -> ChannelMixPIX
- .pixChromaKey(.green) -> ChromaKeyPIX
- .pixHue(0.5) -> ColorShiftPIX
- .pixSaturation(0.5) -> ColorShiftPIX
- .pixCrop(CGRect(x: 0.25, y 0.25, width: 0.5, height: 0.5)) -> CropPIX
- .pixFlipX() -> FlipFlopPIX
- .pixFlipY() -> FlipFlopPIX
- .pixFlopLeft() -> FlipFlopPIX
- .pixFlopRight() -> FlipFlopPIX
- .pixRange(inLow: 0.0, inHigh: 0.5, outLow: 0.5, outHigh: 1.0) -> RangePIX
- .pixRange(inLow: .clear, inHigh: .gray, outLow: .gray, outHigh: .white) -> RangePIX
- .pixSharpen() -> SharpenPIX
- .pixSlope() - > SlopePIX
- .pixVignetting(radius: 0.5, inset: 0.25, gamma: 0.5) -> LumaLevelsPIX
- .pixLookup(pix: pixB, axis: .x) -> LookupPIX
- .pixLumaBlur(pix: pixB, radius: 0.5) -> LumaBlurPIX
- .pixLumaLevels(pix: pixB, brightness: 2.0) -> LumaLevelsPIX
- .pixDisplace(pix: pixB, distance: 0.5) -> DisplacePIX
- .pixRemap(pix: pixB) -> RemapPIXKeep in mind that these funcs will create new PIXs.
Be careful of overloading GPU memory, some funcs create several PIXs.## High Bit Mode
Some effects like DisplacePIX and SlopePIX can benefit from a higher bit depth.
The default is 8 bits. Change it like this:
`PixelKit.main.render.bits = ._16`Enable high bit mode before you create any PIXs.
Note resources do not support higher bits yet.
There is currently there is some gamma offset with resources.## MetalPIXs
~~~~swift
let metalPix = MetalPIX(at: ._1080p, code:
"""
pix = float4(u, v, 0.0, 1.0);
"""
)
~~~~~~~~swift
let metalEffectPix = MetalEffectPIX(code:
"""
float gamma = 0.25;
pix = pow(input, 1.0 / gamma);
"""
)
metalEffectPix.input = CameraPIX()
~~~~~~~~swift
let metalMergerEffectPix = MetalMergerEffectPIX(code:
"""
pix = pow(inputA, 1.0 / inputB);
"""
)
metalMergerEffectPix.inputA = CameraPIX()
metalMergerEffectPix.inputB = ImagePIX("img_name")
~~~~~~~~swift
let metalMultiEffectPix = MetalMultiEffectPIX(code:
"""
float4 inPixA = inTexs.sample(s, uv, 0);
float4 inPixB = inTexs.sample(s, uv, 1);
float4 inPixC = inTexs.sample(s, uv, 2);
pix = inPixA + inPixB + inPixC;
"""
)
metalMultiEffectPix.inputs = [ImagePIX("img_a"), ImagePIX("img_b"), ImagePIX("img_c")]
~~~~### Uniforms:
~~~~swift
var lumUniform = MetalUniform(name: "lum")
let metalPix = MetalPIX(at: ._1080p, code:
"""
pix = float4(in.lum, in.lum, in.lum, 1.0);
""",
uniforms: [lumUniform]
)
lumUniform.value = 0.5
~~~~### Notes:
- To gain camera access, on macOS, check Camera in the App Sandbox in your Xcode project settings under Capabilities.
---
inspired by [TouchDesigner](https://derivative.ca)
created by Anton [Heestand XYZ](http://heestand.xyz/)