Skip to content

Commit

Permalink
Add Upscaling CIFilter + CIImageProcessorKernel
Browse files Browse the repository at this point in the history
  • Loading branch information
finnvoor committed Jun 5, 2024
1 parent 32c3739 commit 7f44147
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 1 deletion.
6 changes: 5 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ let package = Package(
]
),
.target(name: "Upscaling"),
.testTarget(name: "UpscalingTests", dependencies: ["Upscaling"], resources: [.process("Resources")])
.testTarget(
name: "UpscalingTests",
dependencies: ["Upscaling"],
resources: [.process("Resources")]
)
]
)
48 changes: 48 additions & 0 deletions Sources/Upscaling/CoreImage/UpscalingFilter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import CoreImage
import Foundation
#if canImport(MetalFX)
import MetalFX
#endif

// MARK: - UpscalingFilter

public class UpscalingFilter: CIFilter {
// MARK: Public

public var inputImage: CIImage?
public var outputSize: CGSize?

override public var outputImage: CIImage? {
#if canImport(MetalFX)
guard let device, let inputImage, let outputSize else { return nil }

if spatialScaler?.inputSize != inputImage.extent.size || spatialScaler?.outputSize != outputSize {
let spatialScalerDescriptor = MTLFXSpatialScalerDescriptor()
spatialScalerDescriptor.inputSize = inputImage.extent.size
spatialScalerDescriptor.outputSize = outputSize
spatialScalerDescriptor.colorTextureFormat = .bgra8Unorm
spatialScalerDescriptor.outputTextureFormat = .bgra8Unorm
spatialScalerDescriptor.colorProcessingMode = .perceptual
spatialScaler = spatialScalerDescriptor.makeSpatialScaler(device: device)
}

guard let spatialScaler else { return nil }

return try? UpscalingImageProcessorKernel.apply(
withExtent: CGRect(origin: .zero, size: spatialScaler.outputSize),
inputs: [inputImage],
arguments: ["spatialScaler": spatialScaler]
)
#else
return inputImage
#endif
}

// MARK: Private

private let device = MTLCreateSystemDefaultDevice()

#if canImport(MetalFX)
private var spatialScaler: MTLFXSpatialScaler?
#endif
}
47 changes: 47 additions & 0 deletions Sources/Upscaling/CoreImage/UpscalingImageProcessorKernel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import CoreImage
import Foundation
#if canImport(MetalFX)
import MetalFX
#endif

// MARK: - UpscalingImageProcessorKernel

public class UpscalingImageProcessorKernel: CIImageProcessorKernel {
override public class var synchronizeInputs: Bool { false }
override public class var outputFormat: CIFormat { .BGRA8 }

override public class func formatForInput(at _: Int32) -> CIFormat { .BGRA8 }

override public class func process(
with inputs: [any CIImageProcessorInput]?,
arguments: [String: Any]?,
output: any CIImageProcessorOutput
) throws {
#if canImport(MetalFX)
guard let spatialScaler = arguments?["spatialScaler"] as? MTLFXSpatialScaler,
let inputTexture = inputs?.first?.metalTexture,
let outputTexture = output.metalTexture,
let commandBuffer = output.metalCommandBuffer else {
return
}
spatialScaler.colorTexture = inputTexture
spatialScaler.outputTexture = outputTexture
spatialScaler.encode(commandBuffer: commandBuffer)
#endif
}

override public class func roi(
forInput _: Int32,
arguments: [String: Any]?,
outputRect: CGRect
) -> CGRect {
#if canImport(MetalFX)
guard let spatialScaler = arguments?["spatialScaler"] as? MTLFXSpatialScaler else {
return .null
}
return CGRect(origin: .zero, size: spatialScaler.inputSize)
#else
return outputRect
#endif
}
}
13 changes: 13 additions & 0 deletions Sources/Upscaling/Extensions/MTLFXSpatialScaler+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#if canImport(MetalFX)
import MetalFX

extension MTLFXSpatialScaler {
var inputSize: CGSize {
CGSize(width: inputWidth, height: inputHeight)
}

var outputSize: CGSize {
CGSize(width: outputWidth, height: outputHeight)
}
}
#endif
Binary file added Tests/UpscalingTests/Resources/ladybird.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions Tests/UpscalingTests/UpscalingTests.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import CoreImage
import MetalFX
@testable import Upscaling
import XCTest

Expand Down Expand Up @@ -42,4 +44,21 @@ final class UpscalingTests: XCTestCase {
// ensure progress works
throw XCTSkip("unimplemented")
}

func testFilter() async throws {
let inputImage = CIImage(
contentsOf: Bundle.module.url(forResource: "ladybird", withExtension: "jpg")!
)!
let outputSize = CGSize(
width: inputImage.extent.width * 8,
height: inputImage.extent.height * 8
)

let filter = UpscalingFilter()
filter.inputImage = inputImage
filter.outputSize = outputSize
let outputImage = filter.outputImage!
// TODO: - diff images
XCTAssertEqual(outputImage.extent.size, outputSize)
}
}

0 comments on commit 7f44147

Please sign in to comment.