diff --git a/Sources/Extensions/CGImage+Extensions.swift b/Sources/Extensions/CGImage+Extensions.swift index 6185e0d8..c65b3393 100644 --- a/Sources/Extensions/CGImage+Extensions.swift +++ b/Sources/Extensions/CGImage+Extensions.swift @@ -31,7 +31,7 @@ public extension CGImage { return nil } - /// Resize `CGImage` + /// Resize `CGImage` to fit, with aspect ration preserving func resizing(to size: CGSize) -> CGImage? { let size = self.size.fit(in: size) @@ -44,6 +44,17 @@ public extension CGImage { return context.makeImage() } + /// Scale `CGImage` to fill + func scaling(to size: CGSize) -> CGImage? { + guard let context = CGContext.make(self, width: Int(size.width), height: Int(size.height)) else { + return nil + } + + context.draw(self, in: CGRect(origin: .zero, size: size)) + + return context.makeImage() + } + /// Mirror `CGImage` func mirroring() -> CGImage? { return self.reflect(horizontally: true, vertically: false) @@ -255,6 +266,7 @@ internal extension CGImage { } // Apply operations in sorted order + var imageProcessor: ImageProcessor? for operation in operations.sorted() { switch operation { case let .rotate(value, fill): @@ -285,8 +297,8 @@ internal extension CGImage { cgImage = mirrored } } - /*case .imageProcessing(let function): - cgImage = function(cgImage, index)*/ + case .imageProcessing(let processor): + imageProcessor = processor } } @@ -299,6 +311,11 @@ internal extension CGImage { } } + // Apply custom image processor + if let imageProcessor = imageProcessor, let image = imageProcessor(nil, cgImage, orientation, index).cgImage { + cgImage = image + } + // Return modified image return cgImage } diff --git a/Sources/Extensions/CIImage+Extensions.swift b/Sources/Extensions/CIImage+Extensions.swift index 752f5b4b..743eca98 100644 --- a/Sources/Extensions/CIImage+Extensions.swift +++ b/Sources/Extensions/CIImage+Extensions.swift @@ -171,6 +171,7 @@ internal extension CIImage { } // Apply operations in sorted order + var imageProcessor: ImageProcessor? for operation in operations.sorted() { switch operation { case let .rotate(value, fill): @@ -195,8 +196,8 @@ internal extension CIImage { ciImage = ciImage .transformed(by: CGAffineTransform(scaleX: -1.0, y: 1.0)) } - /*case .imageProcessing(let function): - ciImage = function(ciImage, index)*/ + case .imageProcessing(let processor): + imageProcessor = processor } } @@ -213,6 +214,11 @@ internal extension CIImage { ciImage = ciImage.premultiplyingAlpha().composited(over: background) } + // Apply custom image processor + if let imageProcessor = imageProcessor, let image = imageProcessor(ciImage, nil, orientation, index).ciImage { + ciImage = image + } + // Return modified image return ciImage } diff --git a/Sources/Extensions/VImage+Extensions.swift b/Sources/Extensions/VImage+Extensions.swift index 7a4e52b7..71d29780 100644 --- a/Sources/Extensions/VImage+Extensions.swift +++ b/Sources/Extensions/VImage+Extensions.swift @@ -142,6 +142,7 @@ internal extension vImage { } // Apply operations in sorted order + var imageProcessor: ImageProcessor? let operations = operations.sorted() for operation in operations { switch operation { @@ -196,10 +197,9 @@ internal extension vImage { premultipliedAlpha = true }*/ } - /*case .imageProcessing(let function): + case .imageProcessing(let processor): // Custom image processing callback, executed after all the other image operations - break - */ + imageProcessor = processor } } @@ -222,6 +222,11 @@ internal extension vImage { } try error.check()*/ + // Apply custom image processor + if let imageProcessor = imageProcessor, let cgImage = imageProcessor(nil, image, orientation, index).cgImage { + image = cgImage + } + return image } } diff --git a/Sources/Types/Image/ImageOperation.swift b/Sources/Types/Image/ImageOperation.swift index 059f1af3..be4302a6 100644 --- a/Sources/Types/Image/ImageOperation.swift +++ b/Sources/Types/Image/ImageOperation.swift @@ -15,6 +15,12 @@ public enum RotationFill { case color(alpha: UInt8, red: UInt8, green: UInt8, blue: UInt8) } +/// Image processor type +/// Only one image passed in based on `preferredFramework` and internal framework support (`CIImage` doesn't support animations) +/// Return image of the same type to be written - when `CGImage` is not `nil`, modify and return `CGImage` while passing `nil` for `CIImage` +/// Index used as a frame number (starts with zero), `0` for static images +public typealias ImageProcessor = (_ ciImage: CIImage?, _ cgImage: CGImage?, _ orientation: CGImagePropertyOrientation?, _ index: Int) -> (ciImage: CIImage?, cgImage: CGImage?) + /// Image operations public enum ImageOperation: Equatable, Hashable, Comparable { /// Rotation @@ -29,8 +35,7 @@ public enum ImageOperation: Equatable, Hashable, Comparable { case mirror /// Custom image processing function, appplied after all the other image operations - /// Index used as a frame number, `0` for static images - // case imageProcessing((_ image: CGImage, _ index: Int) -> CGImage) // vImage, CIImage + case imageProcessing(ImageProcessor) /// Operation priority private var priority: Int { @@ -41,9 +46,9 @@ public enum ImageOperation: Equatable, Hashable, Comparable { return 2 case .mirror: return 3 - /*case .imageProcessing(_): + case .imageProcessing(_): // Should be executed after all the operations - return 100*/ + return 100 } } @@ -65,8 +70,8 @@ public enum ImageOperation: Equatable, Hashable, Comparable { hasher.combine("flip") case .mirror: hasher.combine("mirror") - /*case .imageProcessing(_): - hasher.combine("ImageProcessing")*/ + case .imageProcessing(_): + hasher.combine("ImageProcessing") } } @@ -79,6 +84,8 @@ public enum ImageOperation: Equatable, Hashable, Comparable { return true case (.mirror, .mirror): return true + case (.imageProcessing, .imageProcessing): + return true default: return false } diff --git a/Sources/Types/Image/ImageSize.swift b/Sources/Types/Image/ImageSize.swift index b25c5c6e..0a1d58d7 100644 --- a/Sources/Types/Image/ImageSize.swift +++ b/Sources/Types/Image/ImageSize.swift @@ -8,7 +8,7 @@ public enum ImageSize: Equatable { /// Size to fit in case fit(CGSize) - /// Scale (fill) - no aspect ration preserving + /// Scale (fill) - no aspect ratio preserving // case scale(CGSize) /// Cropping size and alignment, `fit` primarly used in video thumbnails