From e5d8a8d27e9efb7403570ce1ca6afc1bb628528e Mon Sep 17 00:00:00 2001 From: Tobias Haeberle Date: Fri, 14 Nov 2025 12:19:23 +0100 Subject: [PATCH 01/14] some refactoring --- .github/workflows/build-and-test.yaml | 2 +- .gitignore | 1 + .swift-version | 1 - .vscode/launch.json | 10 +- .vscode/settings.json | 6 + Package.swift | 12 +- Sources/CvipsShim/CvipsShim.c | 9 + Sources/CvipsShim/include/CvipsShim.h | 4 + Sources/VIPS/Core/GConnectFlags.swift | 15 + Sources/VIPS/Core/VIPSImage+Export.swift | 2 - Sources/VIPS/Core/VIPSImage.swift | 200 +- Sources/VIPS/Core/VIPSInterpolate.swift | 10 +- Sources/VIPS/Core/VIPSObject.swift | 173 +- Sources/VIPS/Core/VIPSSource.swift | 260 +- Sources/VIPS/Core/VIPSTarget.swift | 307 ++- .../VIPSTests/ArithmeticGeneratedTests.swift | 429 ---- .../VIPSTests/ArithmeticOperationsTests.swift | 2261 +++++++++-------- Tests/VIPSTests/ArithmeticTests.swift | 432 ++++ .../VIPSTests/ConversionGeneratedTests.swift | 463 ---- Tests/VIPSTests/ConversionTests.swift | 467 ++++ .../VIPSTests/ConvolutionGeneratedTests.swift | 589 ----- Tests/VIPSTests/ConvolutionTests.swift | 627 +++++ Tests/VIPSTests/CoreTests.swift | 60 + ...GeneratedTests.swift => CreateTests.swift} | 7 +- Tests/VIPSTests/ForeignTests.swift | 1078 ++++---- Tests/VIPSTests/HistogramTests.swift | 646 ++--- Tests/VIPSTests/ResampleTests.swift | 838 +++--- Tests/VIPSTests/TestSetup.swift | 5 +- Tests/VIPSTests/VIPSBlobTests.swift | 100 +- Tests/VIPSTests/VIPSSourceCustomTests.swift | 38 + Tests/VIPSTests/VIPSTargetCustomTests.swift | 62 + Tests/VIPSTests/VIPSTests.swift | 568 ++--- docs/VIPS.md | 1650 ++++++++++++ 33 files changed, 6808 insertions(+), 4524 deletions(-) delete mode 100644 .swift-version create mode 100644 .vscode/settings.json create mode 100644 Sources/VIPS/Core/GConnectFlags.swift delete mode 100644 Tests/VIPSTests/ArithmeticGeneratedTests.swift create mode 100644 Tests/VIPSTests/ArithmeticTests.swift delete mode 100644 Tests/VIPSTests/ConversionGeneratedTests.swift create mode 100644 Tests/VIPSTests/ConversionTests.swift delete mode 100644 Tests/VIPSTests/ConvolutionGeneratedTests.swift create mode 100644 Tests/VIPSTests/ConvolutionTests.swift create mode 100644 Tests/VIPSTests/CoreTests.swift rename Tests/VIPSTests/{CreateGeneratedTests.swift => CreateTests.swift} (99%) create mode 100644 Tests/VIPSTests/VIPSSourceCustomTests.swift create mode 100644 Tests/VIPSTests/VIPSTargetCustomTests.swift create mode 100644 docs/VIPS.md diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index b7ed5af..bd0b097 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - swift-version: ["6.2.0", "6.1.3"] + swift-version: ["6.2.1"] vips-version: ["8.15.5", "8.16.1", "8.17.2"] container: image: ghcr.io/t089/swift-vips-builder:swift-${{ matrix.swift-version }}-vips-${{ matrix.vips-version }} diff --git a/.gitignore b/.gitignore index fc80199..d6da5db 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ .swiftpm swiftly swiftly-*.tar.gz +.swift-version diff --git a/.swift-version b/.swift-version deleted file mode 100644 index 92f2ea2..0000000 --- a/.swift-version +++ /dev/null @@ -1 +0,0 @@ -6.1.2 \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 391ce93..4ff0ea3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,19 +4,21 @@ "type": "swift", "request": "launch", "name": "Debug vips-tool", - "program": "${workspaceFolder:swift-vips}/.build/debug/vips-tool", "args": [], "cwd": "${workspaceFolder:swift-vips}", - "preLaunchTask": "swift: Build Debug vips-tool" + "preLaunchTask": "swift: Build Debug vips-tool", + "target": "vips-tool", + "configuration": "debug" }, { "type": "swift", "request": "launch", "name": "Release vips-tool", - "program": "${workspaceFolder:swift-vips}/.build/release/vips-tool", "args": [], "cwd": "${workspaceFolder:swift-vips}", - "preLaunchTask": "swift: Build Release vips-tool" + "preLaunchTask": "swift: Build Release vips-tool", + "target": "vips-tool", + "configuration": "release" } ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..bd03017 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.associations": { + "*.swift": "swift", + "cvipsshim.h": "c" + } +} \ No newline at end of file diff --git a/Package.swift b/Package.swift index 5b1f857..34fe01a 100644 --- a/Package.swift +++ b/Package.swift @@ -1,10 +1,11 @@ -// swift-tools-version:6.1 +// swift-tools-version:6.2 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "swift-vips", + platforms: [ .macOS(.v14)], products: [ // Products define the executables and libraries produced by a package, and make them visible to other packages. .library(name: "VIPS", targets: ["VIPS"]), @@ -13,13 +14,9 @@ let package = Package( "FoundationSupport", ], dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.6.0") ], targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages which this package depends on. .systemLibrary(name: "Cvips", pkgConfig: "vips"), .target( @@ -33,6 +30,11 @@ let package = Package( "Cvips", "CvipsShim", .product(name: "Logging", package: "swift-log") + ], + swiftSettings: [ + .enableUpcomingFeature("NonisolatedNonsendingByDefault"), + .enableUpcomingFeature("InferIsolatedConformances"), + .enableExperimentalFeature("Lifetimes") ]), .executableTarget(name: "vips-tool", dependencies: ["VIPS", "Cvips"] diff --git a/Sources/CvipsShim/CvipsShim.c b/Sources/CvipsShim/CvipsShim.c index 1164533..0fcdacf 100644 --- a/Sources/CvipsShim/CvipsShim.c +++ b/Sources/CvipsShim/CvipsShim.c @@ -6,8 +6,17 @@ // #include "CvipsShim.h" +#include "glib.h" +gboolean shim_g_is_object(const void * p) { + return G_IS_OBJECT(p); +} + +gint shim_g_object_get_ref_count(GObject* object) { + return g_atomic_int_get(&object->ref_count); +} + GObject* shim_g_object(const void * p) { return G_OBJECT(p); } diff --git a/Sources/CvipsShim/include/CvipsShim.h b/Sources/CvipsShim/include/CvipsShim.h index 46ae9df..31655f5 100644 --- a/Sources/CvipsShim/include/CvipsShim.h +++ b/Sources/CvipsShim/include/CvipsShim.h @@ -14,6 +14,10 @@ VipsImage* shim_vips_image_new_from_source(VipsSource *source, const char* options); +gint shim_g_object_get_ref_count(GObject* object); + +gboolean shim_g_is_object(const void * p); + GObject* shim_g_object(const void * p); GType shim_g_object_type(const void * p); diff --git a/Sources/VIPS/Core/GConnectFlags.swift b/Sources/VIPS/Core/GConnectFlags.swift new file mode 100644 index 0000000..8d4fc7a --- /dev/null +++ b/Sources/VIPS/Core/GConnectFlags.swift @@ -0,0 +1,15 @@ +import Cvips + +extension GConnectFlags: @retroactive OptionSet { + public static var `default`: Self { + GConnectFlags(0) + } + + public static var after: Self { + G_CONNECT_AFTER + } + + public static var swapped: Self { + G_CONNECT_SWAPPED + } +} \ No newline at end of file diff --git a/Sources/VIPS/Core/VIPSImage+Export.swift b/Sources/VIPS/Core/VIPSImage+Export.swift index 6d8885c..f96431c 100644 --- a/Sources/VIPS/Core/VIPSImage+Export.swift +++ b/Sources/VIPS/Core/VIPSImage+Export.swift @@ -46,8 +46,6 @@ extension VIPSImage { let blob = outBuf.pointee - defer { vips_area_unref(shim_vips_area(blob)) } - return VIPSBlob(blob) } diff --git a/Sources/VIPS/Core/VIPSImage.swift b/Sources/VIPS/Core/VIPSImage.swift index b305ca8..191c059 100644 --- a/Sources/VIPS/Core/VIPSImage.swift +++ b/Sources/VIPS/Core/VIPSImage.swift @@ -1,12 +1,23 @@ import Cvips import CvipsShim -open class VIPSImage { +public typealias VIPSProgress = Cvips.VipsProgress + +open class VIPSImage: VIPSObject, VIPSImageProtocol { internal var other: Any? = nil - @usableFromInline - private(set) var image: UnsafeMutablePointer + public var image: UnsafeMutablePointer! + + public required init(_ ptr: UnsafeMutableRawPointer) { + self.image = ptr.assumingMemoryBound(to: VipsImage.self) + super.init(ptr) + } + + public init(_ image: UnsafeMutablePointer!) { + self.image = image + super.init(shim_vips_object(image)) + } func withVipsImage(_ body: (UnsafeMutablePointer) -> R) -> R { return body(self.image) @@ -48,11 +59,14 @@ open class VIPSImage { if let maybe = maybe { self.image = maybe + super.init(shim_vips_object(maybe)) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.image = image + super.init(shim_vips_object(image)) } } @@ -98,11 +112,14 @@ open class VIPSImage { if let maybe = maybe { self.image = maybe + super.init(shim_vips_object(maybe)) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.image = image + super.init(shim_vips_object(image)) } } @@ -148,11 +165,14 @@ open class VIPSImage { if let maybe = maybe { self.image = maybe + super.init(shim_vips_object(maybe)) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.image = image + super.init(shim_vips_object(image)) } } @@ -198,11 +218,14 @@ open class VIPSImage { if let maybe = maybe { self.image = maybe + super.init(shim_vips_object(maybe)) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.image = image + super.init(shim_vips_object(image)) } } @@ -248,11 +271,14 @@ open class VIPSImage { if let maybe = maybe { self.image = maybe + super.init(shim_vips_object(maybe)) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.image = image + super.init(shim_vips_object(image)) } } @@ -298,11 +324,14 @@ open class VIPSImage { if let maybe = maybe { self.image = maybe + super.init(shim_vips_object(maybe)) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.image = image + super.init(shim_vips_object(image)) } } @@ -348,11 +377,14 @@ open class VIPSImage { if let maybe = maybe { self.image = maybe + super.init(shim_vips_object(maybe)) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.image = image + super.init(shim_vips_object(image)) } } @@ -398,11 +430,14 @@ open class VIPSImage { if let maybe = maybe { self.image = maybe + super.init(shim_vips_object(maybe)) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.image = image + super.init(shim_vips_object(image)) } } @@ -441,6 +476,7 @@ open class VIPSImage { } self.image = image + super.init(shim_vips_object(image)) } /// Creates a VIPSImage from a memory area containing signed 8-bit integer data. @@ -478,6 +514,7 @@ open class VIPSImage { } self.image = image + super.init(shim_vips_object(image)) } /// Creates a VIPSImage from a memory area containing unsigned 16-bit integer data. @@ -515,6 +552,7 @@ open class VIPSImage { } self.image = image + super.init(shim_vips_object(image)) } /// Creates a VIPSImage from a memory area containing signed 16-bit integer data. @@ -552,6 +590,7 @@ open class VIPSImage { } self.image = image + super.init(shim_vips_object(image)) } /// Creates a VIPSImage from a memory area containing unsigned 32-bit integer data. @@ -589,6 +628,7 @@ open class VIPSImage { } self.image = image + super.init(shim_vips_object(image)) } /// Creates a VIPSImage from a memory area containing signed 32-bit integer data. @@ -626,6 +666,7 @@ open class VIPSImage { } self.image = image + super.init(shim_vips_object(image)) } /// Creates a VIPSImage from a memory area containing 32-bit floating point data. @@ -663,6 +704,7 @@ open class VIPSImage { } self.image = image + super.init(shim_vips_object(image)) } /// Creates a VIPSImage from a memory area containing 64-bit floating point data. @@ -700,6 +742,7 @@ open class VIPSImage { } self.image = image + super.init(shim_vips_object(image)) } /// Creates a new image by loading the given data. @@ -804,9 +847,9 @@ open class VIPSImage { let loader = loader ?? vips_foreign_find_load_buffer(unsafeData.baseAddress, unsafeData.count) .flatMap(String.init(cString:)) - else { - throw VIPSError() - } + else { + throw VIPSError() + } let blob = vips_blob_new(nil, unsafeData.baseAddress, unsafeData.count) defer { @@ -840,7 +883,7 @@ open class VIPSImage { else { throw VIPSError() } - + try self.init(blob) { out in var option = VIPSOption() try blob.withVipsBlob { blob in @@ -879,13 +922,13 @@ open class VIPSImage { /// ```swift /// // Basic usage /// let image = try VIPSImage(fromFilePath: "fred.tif") - /// + /// /// // With access pattern hint /// let image = try VIPSImage(fromFilePath: "large.tif", access: .sequential) - /// + /// /// // Force memory loading /// let image = try VIPSImage(fromFilePath: "image.png", inMemory: true) - /// + /// /// // Load options can be embedded in filename /// let image = try VIPSImage(fromFilePath: "fred.jpg[shrink=2]") /// ``` @@ -895,12 +938,16 @@ open class VIPSImage { /// - access: Access pattern hint (`.random` or `.sequential`) /// - inMemory: Force loading via memory instead of temporary files /// - Throws: VIPSError if the file cannot be opened or read - public init(fromFilePath path: String, access: VipsAccess = .random, inMemory: Bool = false) throws { - guard let image = shim_vips_image_new_from_file(path, access, inMemory ? .true : .false) else { + public init(fromFilePath path: String, access: VipsAccess = .random, inMemory: Bool = false) + throws + { + guard let image = shim_vips_image_new_from_file(path, access, inMemory ? .true : .false) + else { throw VIPSError(vips_error_buffer()) } self.image = image + super.init(shim_vips_object(image)) } public convenience init( @@ -919,11 +966,6 @@ open class VIPSImage { } } - /// Wraps a VipsImage pointer taking ownership of the pointer - public init(_ image: UnsafeMutablePointer) { - self.image = image - } - @usableFromInline init(_ other: Any?, _ block: (inout UnsafeMutablePointer?) throws -> Void) rethrows { let image: UnsafeMutablePointer?> = .allocate(capacity: 1) @@ -935,6 +977,7 @@ open class VIPSImage { precondition(image.pointee != nil, "Image pointer cannot be nil after init.") self.image = image.pointee! self.other = other + super.init(shim_vips_object(self.image)) } func withUnsafeMutablePointer(_ block: (inout UnsafeMutablePointer) throws -> (T)) @@ -943,10 +986,6 @@ open class VIPSImage { return try block(&self.image) } - deinit { - g_object_unref(self.image) - } - @usableFromInline static func call( _ name: UnsafePointer!, @@ -1040,4 +1079,107 @@ extension VIPSImage { if out == nil { throw VIPSError() } } } + +} + +public protocol VIPSImageProtocol: VIPSObjectProtocol { + var image: UnsafeMutablePointer! { get } +} + +extension VIPSImageProtocol { + @inlinable + public var width: Int { + return Int(vips_image_get_width(self.image)) + } + + @inlinable + public var height: Int { + return Int(vips_image_get_height(self.image)) + } + + @inlinable + public var bands: Int { + return Int(vips_image_get_bands(self.image)) + } + + var kill: Bool { + get { + return vips_image_iskilled(self.image) != 0 + } + set { + vips_image_set_kill(self.image, newValue ? .true : .false) + } + } + + func setProgressReportingEnabled(_ enabled: Bool) { + vips_image_set_progress(self.image, enabled ? .true : .false) + } + + @discardableResult + public func onPreeval(_ handler: @escaping (VIPSImageRef, VIPSProgress) -> Void) -> Int { + self.onProgress(signal: "preeval", handler: handler) + } + + @discardableResult + public func onEval(_ handler: @escaping (VIPSImageRef, VIPSProgress) -> Void) -> Int { + self.onProgress(signal: "eval", handler: handler) + } + + @discardableResult + public func onPosteval(_ handler: @escaping (VIPSImageRef, VIPSProgress) -> Void) -> Int { + self.onProgress(signal: "posteval", handler: handler) + } + + private func onProgress(signal: String, handler: @escaping (VIPSImageRef, VIPSProgress) -> Void) + -> Int + { + let cHandler: + @convention(c) ( + UnsafeMutablePointer?, UnsafeMutablePointer?, + UnsafeMutableRawPointer? + ) -> Void = { imagePtr, progressPtr, userData in + guard + let imagePtr, + let progressPtr, + let userData + else { + return + } + + let holder = Unmanaged< + ClosureHolder<(VIPSImageRef, VIPSProgress), Void> + > + .fromOpaque(userData).takeUnretainedValue() + holder.closure((VIPSImageRef(imagePtr), progressPtr.pointee)) + } + let closureHolder = ClosureHolder<(VIPSImageRef, VIPSProgress), Void>(handler) + let userData = Unmanaged.passRetained(closureHolder).toOpaque() + + return self.connect( + signal: signal, + callback: unsafeBitCast(cHandler, to: GCallback.self), + userData: userData, + destroyData: { userData, _ in + if let userData { + Unmanaged< + ClosureHolder<(VIPSImageRef, VIPSProgress), Void> + > + .fromOpaque(userData) + .release() + } + } + ) + } +} + +public struct VIPSImageRef: VIPSImageProtocol { + public var ptr: UnsafeMutableRawPointer! + + public init(_ ptr: UnsafeMutableRawPointer) { + self.ptr = ptr + } + + public var image: UnsafeMutablePointer! { + return ptr.assumingMemoryBound(to: VipsImage.self) + } } diff --git a/Sources/VIPS/Core/VIPSInterpolate.swift b/Sources/VIPS/Core/VIPSInterpolate.swift index f57a034..53c7cd3 100644 --- a/Sources/VIPS/Core/VIPSInterpolate.swift +++ b/Sources/VIPS/Core/VIPSInterpolate.swift @@ -3,11 +3,16 @@ import CvipsShim open class VIPSInterpolate: VIPSObject { - private(set) var interpolate: UnsafeMutablePointer! + var interpolate: UnsafeMutablePointer! { + return self.ptr.assumingMemoryBound(to: VipsInterpolate.self) + } + + public required init(_ ptr: UnsafeMutableRawPointer) { + super.init(ptr) + } public init(_ interpolate: UnsafeMutablePointer!) { super.init(shim_vips_object(interpolate)) - self.interpolate = interpolate } public init(_ name: String) throws { @@ -15,7 +20,6 @@ open class VIPSInterpolate: VIPSObject { throw VIPSError() } super.init(shim_vips_object(ptr)) - self.interpolate = ptr } public var bilinear: VIPSInterpolate { diff --git a/Sources/VIPS/Core/VIPSObject.swift b/Sources/VIPS/Core/VIPSObject.swift index a9da666..a891ddd 100644 --- a/Sources/VIPS/Core/VIPSObject.swift +++ b/Sources/VIPS/Core/VIPSObject.swift @@ -1,25 +1,182 @@ import Cvips import CvipsShim -open class VIPSObject { - private(set) var object: UnsafeMutablePointer! +final class ClosureHolder where Input: ~Copyable, Input: ~Escapable, Output: ~Copyable, Output: ~Escapable { + let closure: (borrowing Input) -> Output - init(_ object: UnsafeMutablePointer!) { - self.object = object + init(_ closure: @escaping (borrowing Input) -> Output) { + self.closure = closure + } +} + +open class VIPSObject: VIPSObjectProtocol { + public func unref() { + g_object_unref(self.ptr) + } + + public required init(_ ptr: UnsafeMutableRawPointer) { + self.ptr = ptr + } + + public var ptr: UnsafeMutableRawPointer! + + @usableFromInline + init(_ object: UnsafeMutablePointer) { + self.ptr = UnsafeMutableRawPointer(object) + } + + public var type: GType { + return shim_g_object_type(object) + } + + public func withVipsObject(_ body: (UnsafeMutablePointer) throws -> R) rethrows + -> R + { + return try body(self.object) + } + + + + deinit { + guard let ptr = self.ptr else { return } + g_object_unref(ptr) + self.ptr = nil + } +} + +public protocol PointerWrapper: ~Copyable { + init(_ ptr: UnsafeMutableRawPointer) + var ptr: UnsafeMutableRawPointer! { get } +} + +public protocol VIPSObjectProtocol: PointerWrapper, ~Copyable { + var object: UnsafeMutablePointer! { get } + + func ref() -> Self + consuming func unref() +} + +extension VIPSObjectProtocol where Self : ~Copyable { + public var object: UnsafeMutablePointer! { + return self.ptr.assumingMemoryBound(to: VipsObject.self) } public var type: GType { return shim_g_object_type(object) } - public func withVipsObject(_ body: (UnsafeMutablePointer) throws -> R) rethrows -> R { + public func withVipsObject(_ body: (UnsafeMutablePointer) throws -> R) rethrows + -> R + { return try body(self.object) } + @discardableResult + public func onPreClose(_ handler: @escaping (UnownedVIPSObjectRef) -> Void) -> Int { + let closureHolder = ClosureHolder(handler) + let gpointer = Unmanaged.passRetained(closureHolder).toOpaque() + + let callback: @convention(c) (UnsafeMutablePointer?, gpointer?) -> Void = { + ( + objectPtr: UnsafeMutablePointer?, + userData: gpointer? + ) -> Void in + guard + let objectPtr = objectPtr, + let userData + else { + return + } + + let closureHolder = Unmanaged< + ClosureHolder<(UnownedVIPSObjectRef), Void> + > + .fromOpaque(userData).takeUnretainedValue() + + closureHolder.closure((UnownedVIPSObjectRef(objectPtr))) + } + + return self.connect( + signal: "preclose", + callback: unsafeBitCast(callback, to: GCallback.self), + userData: gpointer, + destroyData: { userData, _ in + if let userData { + Unmanaged< + ClosureHolder<(VIPSObjectRef), Void> + > + .fromOpaque(userData).release() + print("ClosureHolder released") + } + }, + flags: .default + ) + } + + @discardableResult + public func connect( + signal: String, + callback: GCallback, + userData: gpointer?, + destroyData: GClosureNotify?, + flags: GConnectFlags = .default + ) -> Int { + return Int( + g_signal_connect_data( + self.object, + signal, + callback, + userData, + destroyData, + flags + ) + ) + } + + public func disconnect(signalHandler: Int) { + g_signal_handler_disconnect(self.ptr, gulong(signalHandler)) + } + + public func ref() -> Self { + g_object_ref(self.ptr) + return Self(self.ptr) + } + + public func unref() { + g_object_unref(self.ptr) + } +} + +public struct VIPSObjectRef: VIPSObjectProtocol, ~Copyable { + public let ptr: UnsafeMutableRawPointer! + + public init(_ ptr: UnsafeMutableRawPointer) { + self.ptr = ptr + } + + public consuming func unref() { + g_object_unref(self.ptr) + discard self + } + deinit { - guard let object = self.object else { return } - g_object_unref(object) + g_object_unref(self.ptr) + } +} + +public struct UnownedVIPSObjectRef: VIPSObjectProtocol { + public let ptr: UnsafeMutableRawPointer! + + public init(_ ptr: UnsafeMutableRawPointer) { + self.ptr = ptr + } + + public func ref() -> UnownedVIPSObjectRef { + g_object_ref(self.ptr) + return self + } - self.object = nil + public func unref() { + g_object_unref(self.ptr) } } \ No newline at end of file diff --git a/Sources/VIPS/Core/VIPSSource.swift b/Sources/VIPS/Core/VIPSSource.swift index ed58ca1..8a363fe 100644 --- a/Sources/VIPS/Core/VIPSSource.swift +++ b/Sources/VIPS/Core/VIPSSource.swift @@ -40,7 +40,9 @@ import CvipsShim /// VIPSSource instances are not thread-safe. Each source should be used /// from a single thread, or access should be synchronized externally. public class VIPSSource: VIPSObject { - var source: UnsafeMutablePointer! + var source: UnsafeMutablePointer! { + return self.ptr.assumingMemoryBound(to: VipsSource.self) + } /// Creates a VIPSSource from an existing VipsSource pointer. /// @@ -50,7 +52,10 @@ public class VIPSSource: VIPSObject { /// - Parameter source: A pointer to an existing VipsSource public init(_ source: UnsafeMutablePointer!) { super.init(shim_vips_object(source)) - self.source = source + } + + public required init(_ ptr: UnsafeMutableRawPointer) { + super.init(ptr) } /// Creates a source that will read from the named file. @@ -65,8 +70,6 @@ public class VIPSSource: VIPSObject { guard let source = vips_source_new_from_file(path) else { throw VIPSError() } - - self.source = source super.init(shim_vips_object(source)) } @@ -81,8 +84,6 @@ public class VIPSSource: VIPSObject { guard let source = vips_source_new_from_descriptor(descriptor) else { throw VIPSError() } - - self.source = source super.init(shim_vips_object(source)) } @@ -99,8 +100,6 @@ public class VIPSSource: VIPSObject { guard let source = vips_source_new_from_memory(data, length) else { throw VIPSError() } - - self.source = source super.init(shim_vips_object(source)) } @@ -114,8 +113,6 @@ public class VIPSSource: VIPSObject { guard let source = blob.withVipsBlob({ vips_source_new_from_blob($0) }) else { throw VIPSError() } - - self.source = source super.init(shim_vips_object(source)) } @@ -130,8 +127,6 @@ public class VIPSSource: VIPSObject { guard let source = vips_source_new_from_options(options) else { throw VIPSError() } - - self.source = source super.init(shim_vips_object(source)) } @@ -182,13 +177,30 @@ public class VIPSSource: VIPSObject { /// - Throws: VIPSError if the source cannot be mapped, or any error from the closure public func withMappedMemory(_ work: (UnsafeRawBufferPointer) throws -> T) throws -> T { var length: Int = 0 - let ptr = vips_source_map(self.source, &length) - if ptr == nil { + guard let ptr = vips_source_map(self.source, &length) else { throw VIPSError() } return try work(UnsafeRawBufferPointer(start: ptr, count: length)) } + /// Maps the entire source into memory and provides safe access. + /// + /// This operation can take a long time. Use `isMappable` to + /// check if a source can be mapped efficiently. + /// + /// - Returns: The mapped memory buffer + /// - Throws: VIPSError if the source cannot be mapped + public var mappedMemory: RawSpan { + @_lifetime(borrow self) get throws { + var length: Int = 0 + guard let ptr = vips_source_map(self.source, &length) else { + throw VIPSError() + } + let buffer = UnsafeRawBufferPointer(start: ptr, count: length) + return _overrideLifetime(buffer.bytes, borrowing: self) + } + } + // MARK: - Stream Operations /// Reads data from the source into the provided buffer. @@ -199,17 +211,41 @@ public class VIPSSource: VIPSObject { /// /// - Parameters: /// - buffer: Buffer to read data into - /// - length: Maximum number of bytes to read - /// - Returns: Number of bytes actually read, or -1 on error + /// - Returns: Number of bytes actually read /// - Throws: VIPSError if the read operation fails - public func read(into buffer: UnsafeMutableRawPointer, length: Int) throws -> Int { - let result = vips_source_read(self.source, buffer, length) + public func unsafeRead(into buffer: UnsafeMutableRawBufferPointer) throws -> Int { + let result = vips_source_read(self.source, buffer.baseAddress, buffer.count) if result == -1 { throw VIPSError() } return Int(result) } + /// Reads data from the source into the provided buffer. + /// + /// This is the fundamental read operation for sources. For seekable sources, + /// this reads from the current read position. For pipe sources, this + /// reads the next available data. + /// + /// - Parameters: + /// - outputSpan: Span to read data into + /// - Returns: Number of bytes actually read + /// - Throws: VIPSError if the read operation fails + public func read(into span: inout OutputRawSpan) throws -> Int { + return try span.withUnsafeMutableBytes { buffer, initializedCount in + precondition(initializedCount <= buffer.count, "Buffer overflow") + + let dest = UnsafeMutableRawBufferPointer( + start: buffer.baseAddress!.advanced(by: initializedCount), + count: buffer.count - initializedCount + ) + + let bytesRead = try unsafeRead(into: dest) + initializedCount += bytesRead + return bytesRead + } + } + /// Reads data from the source into a byte array. /// /// This is a convenience wrapper that allocates a byte array @@ -219,28 +255,25 @@ public class VIPSSource: VIPSObject { /// - Returns: Byte array containing the read bytes /// - Throws: VIPSError if the read operation fails public func read(length: Int) throws -> [UInt8] { - try withUnsafeTemporaryAllocation( - byteCount: length, - alignment: MemoryLayout.alignment - ) { buffer in - let bytesRead = try read(into: buffer.baseAddress!, length: length) - return Array(buffer.prefix(bytesRead)) - } + try Array(unsafeUninitializedCapacity: length, initializingWith: { buffer, initializedCount in + let bytesRead = try unsafeRead(into: UnsafeMutableRawBufferPointer(buffer)) + initializedCount = bytesRead + }) } /// Seeks to a specific position in the source. /// /// This changes the current read position. The whence parameter /// determines how the offset is interpreted: - /// - SEEK_SET (0): Absolute position from start - /// - SEEK_CUR (1): Relative to current position - /// - SEEK_END (2): Relative to end of source + /// - .set: Absolute position from start + /// - .current: Relative to current position + /// - .end: Relative to end of source /// /// - Parameters: /// - offset: Byte offset for the seek operation - /// - whence: How to interpret the offset (SEEK_SET, SEEK_CUR, SEEK_END) + /// - whence: How to interpret the offset (`.set`, `.current`, `.end`) /// - Returns: New absolute position in the source - /// - Throws: VIPSError if the seek operation fails or is not supported + /// - Throws: `VIPSError` if the seek operation fails or is not supported public func seek(offset: Int64, whence: Whence) throws -> Int64 { let result = vips_source_seek(self.source, gint64(offset), whence.rawValue) if result == -1 { @@ -266,14 +299,17 @@ public class VIPSSource: VIPSObject { /// and returns it for format detection. The source position is not /// permanently changed by this operation. /// - /// - Parameter length: Maximum number of bytes to sniff - /// - Returns: Byte array from the beginning of the source + /// - Parameter length: Number of bytes to sniff + /// - Returns: Byte array from the beginning of the source or `null` if source is too short /// - Throws: VIPSError if the sniff operation fails - public func sniff(length: Int) throws -> [UInt8] { + @_lifetime(borrow self) + public func sniff(length: Int) throws -> RawSpan? { guard let ptr = vips_source_sniff(self.source, length) else { - throw VIPSError() + return nil } - return Array(UnsafeRawBufferPointer(start: ptr, count: length)) + let buffer = UnsafeRawBufferPointer(start: ptr, count: length) + let span = buffer.bytes + return _overrideLifetime(span, borrowing: self) } /// Sniffs at most the specified number of bytes from the source. @@ -282,15 +318,18 @@ public class VIPSSource: VIPSObject { /// if the source is shorter than requested. /// /// - Parameter maxLength: Maximum number of bytes to sniff - /// - Returns: Tuple of (data pointer, actual length) + /// - Returns: Read data as a byte array /// - Throws: VIPSError if the sniff operation fails - public func sniffAtMost(maxLength: Int) throws -> [UInt8] { + @_lifetime(borrow self) + public func sniffAtMost(maxLength: Int) throws -> RawSpan { var dataPtr: UnsafeMutablePointer? let actualLength = vips_source_sniff_at_most(self.source, &dataPtr, maxLength) if actualLength == -1 || dataPtr == nil { throw VIPSError() } - return Array(UnsafeRawBufferPointer(start: dataPtr!, count: Int(actualLength))) + let buffer = UnsafeRawBufferPointer(start: dataPtr!, count: Int(actualLength)) + let span = buffer.bytes + return _overrideLifetime(span, borrowing: self) } /// Creates a VIPSBlob by mapping the entire source. @@ -480,12 +519,9 @@ public class VIPSSource: VIPSObject { /// Custom sources and their callbacks are not inherently thread-safe. /// Ensure proper synchronization if the source will be accessed from multiple threads. public final class VIPSSourceCustom: VIPSSource { - private var customSource: UnsafeMutablePointer! - - var reader: (Int, inout [UInt8]) -> Void = { _, _ in } - var unsafeReader: (UnsafeMutableRawBufferPointer) -> Int = { _ in 0 } - - var _onDeinit: () -> Void = {} + private var customSource: UnsafeMutablePointer! { + return self.ptr.assumingMemoryBound(to: VipsSourceCustom.self) + } /// Creates a new custom source. /// @@ -495,26 +531,20 @@ public final class VIPSSourceCustom: VIPSSource { public init() { let source = vips_source_custom_new() super.init(shim_VIPS_SOURCE(source)) - self.customSource = source } - typealias ReadHandle = @convention(c) ( - UnsafeMutablePointer?, UnsafeMutableRawPointer, Int64, gpointer - ) -> Int64 - - private func _onRead(_ handle: @escaping ReadHandle, userInfo: UnsafeMutableRawPointer? = nil) { - shim_g_signal_connect( - self.source, - "read", - shim_G_CALLBACK(unsafeBitCast(handle, to: UnsafeMutableRawPointer.self)), - userInfo - ) + public required init(_ ptr: UnsafeMutableRawPointer) { + super.init(ptr) } - /// Sets a high-performance read callback that works with raw memory buffers. + typealias ReadHandle = + @convention(c) ( + UnsafeMutablePointer?, UnsafeMutableRawPointer, Int64, gpointer + ) -> Int64 + + /// Sets a read callback that works with raw memory buffers. /// - /// This is the more efficient callback option for custom sources. Your callback - /// should read data into the provided buffer and return the number of bytes + /// Your callback should read data into the provided buffer and return the number of bytes /// actually read. Return 0 to indicate end-of-stream. /// /// - Parameter handle: Callback that fills a buffer with data and returns bytes read @@ -540,89 +570,51 @@ public final class VIPSSourceCustom: VIPSSource { /// return bytesToRead /// } /// ``` - public func onUnsafeRead(_ handle: @escaping (UnsafeMutableRawBufferPointer) -> Int) { - self.unsafeReader = handle - let selfptr = Unmanaged.passUnretained(self).toOpaque() - - self._onRead( - { _, buf, length, obj in - let me = Unmanaged.fromOpaque(obj).takeUnretainedValue() - - let buffer = UnsafeMutableRawBufferPointer.init(start: buf, count: Int(length)) - return Int64(me.unsafeReader(buffer)) - }, - userInfo: selfptr - ) - } - - /// Sets a simple read callback that works with Swift arrays. - /// - /// This is the simpler but less efficient callback option. Your callback - /// receives the requested number of bytes to read and should populate - /// the provided array with the actual data read. - /// - /// For better performance with large data, use onUnsafeRead(_:) instead. - /// - /// - Parameter handle: Callback that receives byte count and fills an array - /// - /// # Example - /// - /// ```swift - /// var data: [UInt8] = // your image data - /// var position = 0 - /// - /// customSource.onRead { requestedBytes, buffer in - /// let remainingBytes = data.count - position - /// let bytesToRead = min(requestedBytes, remainingBytes) - /// - /// if bytesToRead > 0 { - /// let range = position..<(position + bytesToRead) - /// buffer = Array(data[range]) - /// position += bytesToRead - /// } - /// } - /// ``` - public func onRead(_ handle: @escaping (Int, inout [UInt8]) -> Void) { - self.reader = handle - - let selfptr = Unmanaged.passUnretained(self).toOpaque() - - _onRead( - { _, buf, length, obj in - var buffer = [UInt8]() - - let me = Unmanaged.fromOpaque(obj).takeUnretainedValue() - - me.reader(Int(length), &buffer) - - guard buffer.count <= length else { - fatalError("Trying to copy too much data") - } + @discardableResult + public func onUnsafeRead(_ handle: @escaping (UnsafeMutableRawBufferPointer) -> Int) -> Int { + let holder = ClosureHolder(handle) + let data = Unmanaged.passRetained(holder).toOpaque() + + let cCallback: ReadHandle = { + _, + buf, + len, + data in + let me = Unmanaged>.fromOpaque(data) + .takeUnretainedValue() + let buffer = UnsafeMutableRawBufferPointer(start: buf, count: Int(len)) + let bytesRead = me.closure(buffer) + return Int64(bytesRead) + } - buf.copyMemory(from: buffer, byteCount: buffer.count) - return Int64(buffer.count) - }, - userInfo: selfptr + return self.connect( + signal: "read", + callback: unsafeBitCast(cCallback, to: GCallback.self), + userData: data, + destroyData: { data, _ in + data.flatMap { Unmanaged.fromOpaque($0).release() } + } ) } - /// Sets a cleanup callback that will be called when the source is destroyed. + /// Sets a read callback that works with raw memory buffers. /// - /// Use this to perform any necessary cleanup when the source is no longer needed. - /// This might include closing files, releasing resources, or notifying other - /// components that the source is being destroyed. + /// Your callback should read data into the provided output span. Do not write anything to + /// indicate end-of-stream. /// - /// - Parameter work: Cleanup callback to execute during deinitialization - public func onDeinit(_ work: @escaping () -> Void) { - self._onDeinit = work - } - - deinit { - self._onDeinit() + /// - Parameter handle: Callback that fills an output span with data + /// + @discardableResult + public func onRead(_ handler: @escaping (inout OutputRawSpan) -> ()) -> Int { + return self.onUnsafeRead { buffer in + var span = OutputRawSpan(buffer: buffer, initializedCount: 0) + handler(&span) + let bytesInitialized = span.finalize(for: buffer) + return bytesInitialized + } } } - public struct Whence: RawRepresentable, Equatable, Hashable, Sendable { public var rawValue: Int32 @@ -638,4 +630,4 @@ public struct Whence: RawRepresentable, Equatable, Hashable, Sendable { public static var set: Whence { Whence(rawValue: SEEK_SET)! } public static var current: Whence { Whence(rawValue: SEEK_CUR)! } public static var end: Whence { Whence(rawValue: SEEK_END)! } -} \ No newline at end of file +} diff --git a/Sources/VIPS/Core/VIPSTarget.swift b/Sources/VIPS/Core/VIPSTarget.swift index 76e9ba4..78d8b5c 100644 --- a/Sources/VIPS/Core/VIPSTarget.swift +++ b/Sources/VIPS/Core/VIPSTarget.swift @@ -41,7 +41,13 @@ import CvipsShim /// VIPSTarget instances are not thread-safe. Each target should be used /// from a single thread, or access should be synchronized externally. open class VIPSTarget: VIPSObject { - private(set) var target: UnsafeMutablePointer! + public required init(_ ptr: UnsafeMutableRawPointer) { + super.init(ptr) + } + + var target: UnsafeMutablePointer! { + return self.ptr.assumingMemoryBound(to: VipsTarget.self) + } /// Creates a VIPSTarget from an existing VipsTarget pointer. /// @@ -51,7 +57,6 @@ open class VIPSTarget: VIPSObject { /// - Parameter target: A pointer to an existing VipsTarget public init(_ target: UnsafeMutablePointer!) { super.init(shim_vips_object(target)) - self.target = target } /// Creates a target that will write to the named file. @@ -66,7 +71,6 @@ open class VIPSTarget: VIPSObject { throw VIPSError() } - self.target = target super.init(shim_vips_object(target)) } @@ -82,7 +86,6 @@ open class VIPSTarget: VIPSObject { throw VIPSError() } - self.target = target super.init(shim_vips_object(target)) } @@ -112,7 +115,6 @@ open class VIPSTarget: VIPSObject { throw VIPSError() } - self.target = target super.init(shim_vips_object(target)) } @@ -205,19 +207,6 @@ open class VIPSTarget: VIPSObject { } } - /// Flushes any buffered data to the underlying destination. - /// - /// This forces any data that has been written to the target but not yet - /// sent to the underlying destination (file, descriptor, etc.) to be written. - /// - /// - Throws: VIPSError if the flush operation fails - public func flush() throws { - // vips_target_flush is not exposed in the public API, but is called internally - // by other operations. We can trigger a flush by calling end() which includes flush. - // However, end() also marks the target as ended, so we avoid calling it here. - // Instead, we rely on libvips' internal buffering and flushing. - } - /// Ends the target and flushes all remaining data. /// /// This finalizes the target, ensuring all buffered data is written @@ -251,7 +240,10 @@ open class VIPSTarget: VIPSObject { defer { g_free(data) } let buffer = UnsafeBufferPointer(start: data, count: length) - return withUnsafeTemporaryAllocation(byteCount: length, alignment: MemoryLayout.alignment) { tempBuffer in + return withUnsafeTemporaryAllocation( + byteCount: length, + alignment: MemoryLayout.alignment + ) { tempBuffer in tempBuffer.copyBytes(from: buffer) return Array(tempBuffer) } @@ -317,6 +309,18 @@ open class VIPSTarget: VIPSObject { return Int(result) } + public func read(into span: inout OutputRawSpan) throws { + try span.withUnsafeMutableBytes { buffer, initialized in + let destBuffer = UnsafeMutableRawBufferPointer( + start: buffer.baseAddress!.advanced(by: initialized), + count: buffer.count - initialized + ) + + let bytesRead = try self.unsafeRead(into: destBuffer) + initialized += bytesRead + } + } + /// Reads data from the target into the provided buffer. /// /// This operation is only supported on seekable targets (like files). @@ -327,7 +331,7 @@ open class VIPSTarget: VIPSObject { /// - Returns: Number of bytes actually read /// - Throws: VIPSError if the read operation fails or is not supported @discardableResult - public func read(into buffer: UnsafeMutableRawBufferPointer) throws -> Int { + public func unsafeRead(into buffer: UnsafeMutableRawBufferPointer) throws -> Int { let result = vips_target_read(self.target, buffer.baseAddress, buffer.count) guard result >= 0 else { throw VIPSError() @@ -434,23 +438,10 @@ open class VIPSTarget: VIPSObject { /// - **Seek callback**: Should change position and return new absolute position (-1 for error) /// - **Read callback**: Should read up to the requested bytes and return actual data read public final class VIPSTargetCustom: VIPSTarget { - private var customTarget: UnsafeMutablePointer! - - /// The write handler that processes data written to this target - var writer: (UnsafeRawBufferPointer) -> Int = { _ in return 0 } - - /// The finish handler called when the target is finalized (deprecated, use ender instead) - var finisher: () -> () = { } - - /// The end handler called when the target is ended (replaces finish) - var ender: () -> Int = { return 0 } - - /// The seek handler for seekable custom targets - var seeker: (Int64, Int32) -> Int64 = { _, _ in return -1 } - - /// The read handler for readable custom targets - var reader: (Int) -> [UInt8] = { _ in return [] } - + var customTarget: UnsafeMutablePointer! { + self.ptr.assumingMemoryBound(to: VipsTargetCustom.self) + } + /// Creates a new custom target with default (no-op) implementations. /// /// After creation, set up the appropriate callback handlers using @@ -458,35 +449,12 @@ public final class VIPSTargetCustom: VIPSTarget { public init() { let customTarget = vips_target_custom_new() super.init(shim_VIPS_TARGET(customTarget)) - self.customTarget = customTarget - } - - typealias WriteHandle = @convention(c) (UnsafeMutablePointer?, gpointer, Int64, gpointer ) -> Int64 - typealias FinishHandle = @convention(c) (UnsafeMutablePointer?, gpointer) -> Void - typealias EndHandle = @convention(c) (UnsafeMutablePointer?, gpointer) -> Int32 - typealias SeekHandle = @convention(c) (UnsafeMutablePointer?, Int64, Int32, gpointer) -> Int64 - typealias ReadHandle = @convention(c) (UnsafeMutablePointer?, gpointer, Int64, gpointer) -> Int64 - - private func _onWrite(_ handle: @escaping WriteHandle, userInfo: UnsafeMutableRawPointer? = nil) { - shim_g_signal_connect(self.customTarget, "write", shim_G_CALLBACK(unsafeBitCast(handle, to: UnsafeMutableRawPointer.self)), userInfo); - } - - private func _onFinish(_ handle: @escaping FinishHandle, userInfo: UnsafeMutableRawPointer? = nil) { - shim_g_signal_connect(self.customTarget, "finish", shim_G_CALLBACK(unsafeBitCast(handle, to: UnsafeMutableRawPointer.self)), userInfo); - } - - private func _onEnd(_ handle: @escaping EndHandle, userInfo: UnsafeMutableRawPointer? = nil) { - shim_g_signal_connect(self.customTarget, "end", shim_G_CALLBACK(unsafeBitCast(handle, to: UnsafeMutableRawPointer.self)), userInfo); - } - - private func _onSeek(_ handle: @escaping SeekHandle, userInfo: UnsafeMutableRawPointer? = nil) { - shim_g_signal_connect(self.customTarget, "seek", shim_G_CALLBACK(unsafeBitCast(handle, to: UnsafeMutableRawPointer.self)), userInfo); - } - - private func _onRead(_ handle: @escaping ReadHandle, userInfo: UnsafeMutableRawPointer? = nil) { - shim_g_signal_connect(self.customTarget, "read", shim_G_CALLBACK(unsafeBitCast(handle, to: UnsafeMutableRawPointer.self)), userInfo); - } - + } + + public required init(_ ptr: UnsafeMutableRawPointer) { + super.init(ptr) + } + /// Sets the write handler for this custom target. /// /// The write handler will be called whenever data needs to be written @@ -496,37 +464,41 @@ public final class VIPSTargetCustom: VIPSTarget { /// - Parameter handler: A closure that receives data and returns bytes written /// - Parameter data: Buffer to write /// - Returns: Number of bytes actually written (should be <= data.count) - public func onWrite(_ handler: @escaping (UnsafeRawBufferPointer) -> Int) { - self.writer = handler - - let data = Unmanaged.passUnretained(self).toOpaque() - - self._onWrite({ _, buf, len, data in - let me = Unmanaged.fromOpaque(data).takeUnretainedValue() - let buffer = UnsafeRawBufferPointer(start: buf, count: Int(len)) - return Int64(me.writer(buffer)) - - }, userInfo: data) - } - - /// Sets the finish handler for this custom target. - /// - /// ⚠️ **Deprecated**: This method is deprecated in favor of onEnd(). - /// The finish handler will be called when the target is being finalized. - /// Use this for cleanup operations like closing files or network connections. - /// - /// - Parameter handler: A closure called when the target is finished - @available(*, deprecated, message: "Use onEnd(_:) instead") - public func onFinish(_ handler: @escaping () -> ()) { - self.finisher = handler - let data = Unmanaged.passUnretained(self).toOpaque() - - self._onFinish({ _, data in - let me = Unmanaged.fromOpaque(data).takeUnretainedValue() - me.finisher() - }, userInfo: data) - } - + @discardableResult + public func onUnsafeWrite(_ handler: @escaping (UnsafeRawBufferPointer) -> Int) -> Int { + let holder = ClosureHolder(handler) + + let data = Unmanaged.passRetained(holder).toOpaque() + let cCallback: + @convention(c) ( + UnsafeMutablePointer?, UnsafeRawPointer?, Int64, gpointer? + ) -> Int64 = { _, buf, len, data in + let holder = Unmanaged> + .fromOpaque(data!) + .takeUnretainedValue() + + return Int64(holder.closure(UnsafeRawBufferPointer(start: buf, count: Int(len)))) + } + + return self.connect( + signal: "write", + callback: unsafeBitCast(cCallback, to: GCallback.self), + userData: data, + destroyData: { data, _ in + guard let data else { return } + Unmanaged>.fromOpaque(data).release() + } + ) + } + + @discardableResult + public func onWrite(_ handler: @escaping (RawSpan) -> Int) -> Int { + return self.onUnsafeWrite { buffer in + let span = buffer.bytes + return handler(span) + } + } + /// Sets the end handler for this custom target. /// /// The end handler will be called when the target is being ended. @@ -535,16 +507,30 @@ public final class VIPSTargetCustom: VIPSTarget { /// /// - Parameter handler: A closure called when the target is ended /// - Returns: 0 for success, non-zero for error - public func onEnd(_ handler: @escaping () -> Int) { - self.ender = handler - let data = Unmanaged.passUnretained(self).toOpaque() - - self._onEnd({ _, data in - let me = Unmanaged.fromOpaque(data).takeUnretainedValue() - return Int32(me.ender()) - }, userInfo: data) - } - + @discardableResult + public func onEnd(_ handler: @escaping () -> Int) -> Int { + let holder = ClosureHolder(handler) + let data = Unmanaged.passRetained(holder).toOpaque() + + let cCallback: + @convention(c) (UnsafeMutablePointer?, gpointer?) -> Int32 = { + _, + data in + let me = Unmanaged>.fromOpaque(data!).takeUnretainedValue() + return Int32(me.closure(())) + } + + return self.connect( + signal: "end", + callback: unsafeBitCast(cCallback, to: GCallback.self), + userData: data, + destroyData: { data, _ in + guard let data else { return } + Unmanaged>.fromOpaque(data).release() + } + ) + } + /// Sets the seek handler for this custom target. /// /// The seek handler enables random access within the target, which is @@ -555,44 +541,83 @@ public final class VIPSTargetCustom: VIPSTarget { /// - Parameter offset: Byte offset for the seek operation /// - Parameter whence: How to interpret offset (SEEK_SET=0, SEEK_CUR=1, SEEK_END=2) /// - Returns: New absolute position, or -1 for error - public func onSeek(_ handler: @escaping (Int64, Int32) -> Int64) { - self.seeker = handler - let data = Unmanaged.passUnretained(self).toOpaque() - - self._onSeek({ _, offset, whence, data in - let me = Unmanaged.fromOpaque(data).takeUnretainedValue() - return me.seeker(offset, whence) - }, userInfo: data) - } - - /// Sets the read handler for this custom target. + @discardableResult + public func onSeek(_ handler: @escaping (Int64, Whence) -> Int64) -> Int { + let holder = ClosureHolder(handler) + let data = Unmanaged.passRetained(holder).toOpaque() + + let cCallback: + @convention(c) ( + UnsafeMutablePointer?, Int64, Int32, gpointer? + ) -> Int64 = { _, offset, whence, data in + let me = Unmanaged>.fromOpaque(data!) + .takeUnretainedValue() + return me.closure((offset, Whence(rawValue: whence)!)) + } + + return self.connect( + signal: "seek", + callback: unsafeBitCast(cCallback, to: GCallback.self), + userData: data, + destroyData: { data, _ in + guard let data else { return } + Unmanaged>.fromOpaque(data).release() + } + ) + } + + /// Adds a read handler to this custom target. /// /// The read handler enables reading back data that was previously written, /// which is required by some image formats (like TIFF). If your custom /// target doesn't support reading, don't set this handler. /// /// - Parameter handler: A closure that handles read operations - /// - Parameter length: Maximum number of bytes to read - /// - Returns: Array of bytes actually read (can be shorter than requested) - public func onRead(_ handler: @escaping (Int) -> [UInt8]) { - self.reader = handler - let data = Unmanaged.passUnretained(self).toOpaque() - - self._onRead({ _, buffer, length, data in - let me = Unmanaged.fromOpaque(data).takeUnretainedValue() - - let readData = me.reader(Int(length)) - let bytesToCopy = min(readData.count, Int(length)) - - if bytesToCopy > 0 { - let bufferPtr = buffer.assumingMemoryBound(to: UInt8.self) - readData.withUnsafeBufferPointer { readBuffer in - bufferPtr.update(from: readBuffer.baseAddress!, count: bytesToCopy) - } + /// - Parameter buffer: The destination buffer to read bytes into. + /// - Returns: The actual number of bytes written to buffer. + @discardableResult + public func onUnsafeRead(_ handler: @escaping (UnsafeMutableRawBufferPointer) -> (Int)) -> Int { + let holder = ClosureHolder(handler) + let data = Unmanaged.passRetained(holder).toOpaque() + + let cCallback: + @convention(c) ( + UnsafeMutablePointer?, gpointer?, Int64, gpointer? + ) -> Int64 = { _, buf, len, data in + let me = Unmanaged> + .fromOpaque(data!) + .takeUnretainedValue() + let destBuf = UnsafeMutableRawBufferPointer(start: buf, count: Int(len)) + return Int64(me.closure(destBuf)) } - - return Int64(bytesToCopy) - }, userInfo: data) + + return self.connect( + signal: "read", + callback: unsafeBitCast(cCallback, to: GCallback.self), + userData: data, + destroyData: { data, _ in + guard let data else { return } + Unmanaged.fromOpaque(data).release() + } + ) + } + + /// Adds a read handler to this custom target. + /// + /// The read handler enables reading back data that was previously written, + /// which is required by some image formats (like TIFF). If your custom + /// target doesn't support reading, don't set this handler. + /// + /// - Parameter handler: A closure that handles read operations + /// - Parameter outputSpan: The destination span to read bytes into. + @discardableResult + public func onRead(_ handler: @escaping (inout OutputRawSpan) -> Void) -> Int { + return self.onUnsafeRead { buffer in + var span = OutputRawSpan(buffer: buffer, initializedCount: 0) + handler(&span) + let bytesInitialized = span.finalize(for: buffer) + return bytesInitialized + } } /// Provides safe access to the underlying VipsTargetCustom pointer. @@ -603,7 +628,9 @@ public final class VIPSTargetCustom: VIPSTarget { /// - Parameter body: Closure that receives the VipsTargetCustom pointer /// - Returns: The result of the closure /// - Throws: Any error thrown by the closure - func withVipsTargetCustom(_ body: (UnsafeMutablePointer) throws -> R) rethrows -> R { + func withVipsTargetCustom(_ body: (UnsafeMutablePointer) throws -> R) + rethrows -> R + { return try body(self.customTarget) } -} \ No newline at end of file +} diff --git a/Tests/VIPSTests/ArithmeticGeneratedTests.swift b/Tests/VIPSTests/ArithmeticGeneratedTests.swift deleted file mode 100644 index 725cfa8..0000000 --- a/Tests/VIPSTests/ArithmeticGeneratedTests.swift +++ /dev/null @@ -1,429 +0,0 @@ -@testable import VIPS -import Cvips -import Testing -import Foundation - -@Suite(.vips) -struct ArithmeticGeneratedTests { - - // MARK: - Basic Arithmetic Operations - - @Test - func testAddOperations() throws { - let image1 = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 50.0) - let image2 = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 25.0) - - // Test image + image - let added = try image1.add(image2) - #expect(try added.avg() == 75.0) - - // Test with operator - let addedOp = try image1 + image2 - #expect(try addedOp.avg() == 75.0) - - // Test image + constant - let addedConst = try image1 + 10.0 - #expect(try addedConst.avg() == 60.0) - } - - @Test - func testSubtractOperations() throws { - let image1 = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 100.0) - let image2 = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 30.0) - - // Test image - image - let subtracted = try image1.subtract(image2) - #expect(try subtracted.avg() == 70.0) - - // Test with operator - let subtractedOp = try image1 - image2 - #expect(try subtractedOp.avg() == 70.0) - - // Test image - constant (using linear for subtraction: y = x*1 - 20) - let subtractedConst = try image1.linear(1.0, -20.0) - #expect(try subtractedConst.avg() == 80.0) - } - - @Test - func testMultiplyOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 5.0) - let multiplier = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 2.0) - - // Test image * image - let multiplied = try image.multiply(multiplier) - #expect(try multiplied.avg() == 10.0) - - // Test with operator - let multipliedOp = try image * multiplier - #expect(try multipliedOp.avg() == 10.0) - - // Test image * constant - let multipliedConst = try image * 3.0 - #expect(try multipliedConst.avg() == 15.0) - } - - @Test - func testDivideOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 100.0) - let divisor = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 4.0) - - // Test image / image - let divided = try image.divide(divisor) - #expect(try divided.avg() == 25.0) - - // Test with operator - let dividedOp = try image / divisor - #expect(try dividedOp.avg() == 25.0) - - // Test image / constant (using linear for division: y = x*0.5 + 0) - let dividedConst = try image.linear(0.5, 0.0) - #expect(try dividedConst.avg() == 50.0) - } - - @Test - func testRemainderOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 10.0) - let divisor = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 3.0) - - // Test image % image - let remainder = try image.remainder(divisor) - #expect(try remainder.avg() == 1.0) - - // Test with operator - % operator not defined for VIPSImage - // let remainderOp = try image % divisor - // #expect(try remainderOp.avg() == 1.0) - - // Test image % constant - let remainderConst = try image.remainder(4.0) - #expect(try remainderConst.avg() == 2.0) - } - - @Test - func testPowerOperations() throws { - let base = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 2.0) - let exponent = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 3.0) - - // Test pow(image, image) - let powered = try base.pow(exponent) - #expect(try powered.avg() == 8.0) - - // Test pow(image, constant) - let poweredConst = try base.pow(4.0) - #expect(try poweredConst.avg() == 16.0) - } - - // MARK: - Mathematical Functions - - @Test - func testMathOperations() throws { - // Test exp - let zeros = try VIPSImage.black(width: 10, height: 10) - let expResult = try zeros.math(.exp) - #expect(abs(try expResult.avg() - 1.0) < 0.001) - - // Test log - let ones = try VIPSImage.black(width: 10, height: 10) - .linear(0.0, exp(1.0)) - let logResult = try ones.math(.log) - #expect(abs(try logResult.avg() - 1.0) < 0.001) - - // Test log10 - let tens = try VIPSImage.black(width: 10, height: 10) - .linear(0.0, 10.0) - let log10Result = try tens.math(.log10) - #expect(abs(try log10Result.avg() - 1.0) < 0.001) - } - - @Test - func testMath2Operations() throws { - let image1 = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 3.0) - let image2 = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 4.0) - - // Test wop (weighted operation) - let wop = try image1.math2(image2, math2: .wop) - #expect(try wop.avg() > 0) - } - - // MARK: - Complex Operations - - @Test - func testComplexOperations() throws { - // Create a complex image (real + imaginary parts) - let real = try VIPSImage.black(width: 10, height: 10, bands: 1) - .linear(1.0, 1.0) - let imag = try VIPSImage.black(width: 10, height: 10, bands: 1) - .linear(1.0, 0.0) - // Note: For complex operations to work properly, the image needs to be in complex format - // However, complexget still returns a 2-band image when extracting parts - let complexImage = try real.bandjoin([imag]) - - // Test complex operations - let conjugate = try complexImage.complex(cmplx: .conj) - #expect(conjugate.bands == 2) - - // Test getting real part - complexget returns a 2-band image with the real part data - let realPart = try complexImage.complexget(get: .real) - #expect(realPart.bands == 2) - // The average of a 2-band image where band 0 is 1.0 and band 1 is 0.0 is 0.5 - #expect(abs(try realPart.avg() - 0.5) < 0.001) - - // Test getting imaginary part - let imagPart = try complexImage.complexget(get: .imag) - #expect(imagPart.bands == 2) - #expect(abs(try imagPart.avg() - 0.0) < 0.001) - } - - // MARK: - Boolean Operations - - @Test - func testBooleanOperations() throws { - let image1 = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 170.0) // Binary: 10101010 - let image2 = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 204.0) // Binary: 11001100 - - // Test AND - let andResult = try image1.boolean(image2, boolean: .and) - #expect(try andResult.avg() == 136.0) // 10001000 - - // Test OR - let orResult = try image1.boolean(image2, boolean: .or) - #expect(try orResult.avg() == 238.0) // 11101110 - - // Test XOR - let xorResult = try image1.boolean(image2, boolean: .eor) - #expect(try xorResult.avg() == 102.0) // 01100110 - } - - @Test - func testBooleanConstOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 170.0) // Binary: 10101010 - - // Test AND with constant - let andConst = try image.booleanConst(boolean: .and, c: [204.0]) // 11001100 - #expect(try andConst.avg() == 136.0) // 10001000 - - // Test OR with constant - let orConst = try image.booleanConst(boolean: .or, c: [204.0]) - #expect(try orConst.avg() == 238.0) // 11101110 - - // Test XOR with constant - let xorConst = try image.booleanConst(boolean: .eor, c: [204.0]) - #expect(try xorConst.avg() == 102.0) // 01100110 - } - - @Test - func testBitwiseOperators() throws { - let image1 = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 170.0) - let image2 = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 204.0) - - // Test bitwise AND operator - let andOp = try image1 & image2 - #expect(try andOp.avg() == 136.0) - - // Test bitwise OR operator - let orOp = try image1 | image2 - #expect(try orOp.avg() == 238.0) - - // Test bitwise XOR operator - let xorOp = try image1 ^ image2 - #expect(try xorOp.avg() == 102.0) - - // Test with constants - bitwise ops with constants not supported - // let andConstOp = try image1 & 204.0 - // #expect(try andConstOp.avg() == 136.0) - } - - @Test - func testShiftOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 4.0) - - // Test left shift - let leftShift = try image << 2 - #expect(try leftShift.avg() == 16.0) - - // Test right shift - let rightShift = try image >> 1 - #expect(try rightShift.avg() == 2.0) - } - - // MARK: - Statistical Operations - - @Test - func testStatisticalOperations() throws { - // Create an image with varying values - let image = try VIPSImage.gaussnoise(width: 100, height: 100, sigma: 10.0, mean: 100.0) - - // Test avg - let avg = try image.avg() - #expect(abs(avg - 100.0) < 5.0) // Allow some variance - - // Test deviate (standard deviation) - let deviate = try image.deviate() - #expect(abs(deviate - 10.0) < 2.0) // Allow some variance - - // Test min - let minVal = try image.min() - #expect(minVal < 100.0) - - // Test max - let maxVal = try image.max() - #expect(maxVal > 100.0) - - // Test sum - sum is a static method that sums multiple images - // For summing pixels in an image, we need to use a different approach - // let sum = try image.sum() - // #expect(abs(sum - 1000000.0) < 50000.0) - - // Test stats (returns array: min, max, sum, sum_sq, mean, deviation) - let stats = try image.stats() - #expect(stats.bands == 1) - } - - @Test - func testHistogramOperations() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - .cast(format: .uchar) // Cast to uchar for proper histogram generation - - // Test histogram generation - for uchar images, histogram should be 256 wide - let hist = try image.histFind() - #expect(hist.width == 256) - #expect(hist.height == 1) - - // Test histogram operations - let cumulative = try hist.histCum() - #expect(cumulative.width > 0) - - let normalized = try hist.histNorm() - #expect(normalized.width > 0) - } - - // MARK: - Miscellaneous Arithmetic - - @Test - func testLinearOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 50.0) - - // Test linear transformation: output = a * input + b - let transformed = try image.linear(2.0, 10.0) - #expect(try transformed.avg() == 110.0) // 2 * 50 + 10 - - // Test linear with arrays for multi-band - let rgbImage = try image.bandjoin([image, image]) - let rgbTransformed = try rgbImage.linear([1.0, 2.0, 3.0], [10.0, 20.0, 30.0]) - #expect(rgbTransformed.bands == 3) - } - - @Test - func testInvertOperations() throws { - // Test standard invert - for uchar images, invert does 255-x - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 200.0) - .cast(format: .uchar) // Cast to uchar for proper inversion behavior - - let inverted = try image.invert() - #expect(try inverted.avg() == 55.0) // 255 - 200 - } - - @Test - func testAbsoluteValue() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, -50.0) - - let absImage = try image.abs() - #expect(try absImage.avg() == 50.0) - #expect(try absImage.min() >= 0) - } - - @Test - func testSignOperation() throws { - let positive = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 50.0) - let negative = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, -50.0) - let zero = try VIPSImage.black(width: 10, height: 10) - - #expect(try positive.sign().avg() == 1.0) - #expect(try negative.sign().avg() == -1.0) - #expect(try zero.sign().avg() == 0.0) - } - - @Test - func testRoundingOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 10.7) - - // Test different rounding modes - let rounded = try image.round(.rint) - #expect(try rounded.avg() == 11.0) - - let floored = try image.round(.floor) - #expect(try floored.avg() == 10.0) - - let ceiled = try image.round(.ceil) - #expect(try ceiled.avg() == 11.0) - } - - @Test - func testRelationalOperations() throws { - let image1 = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 50.0) - let image2 = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 100.0) - - // Test relational operations - let equal = try image1.relational(image1, relational: .equal) - #expect(try equal.avg() == 255.0) // All pixels equal - - let notEqual = try image1.relational(image2, relational: .noteq) - #expect(try notEqual.avg() == 255.0) // All pixels not equal - - let less = try image1.relational(image2, relational: .less) - #expect(try less.avg() == 255.0) // All pixels less - - let lessEq = try image1.relational(image2, relational: .lesseq) - #expect(try lessEq.avg() == 255.0) // All pixels less or equal - - let more = try image2.relational(image1, relational: .more) - #expect(try more.avg() == 255.0) // All pixels more - - let moreEq = try image2.relational(image1, relational: .moreeq) - #expect(try moreEq.avg() == 255.0) // All pixels more or equal - } - - @Test - func testRelationalConstOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 128.0) - - // Test relational operations with constants - let equal = try image.relationalConst(relational: .equal, c: [128.0]) - #expect(try equal.avg() == 255.0) - - let less = try image.relationalConst(relational: .less, c: [200.0]) - #expect(try less.avg() == 255.0) - - let more = try image.relationalConst(relational: .more, c: [100.0]) - #expect(try more.avg() == 255.0) - } -} \ No newline at end of file diff --git a/Tests/VIPSTests/ArithmeticOperationsTests.swift b/Tests/VIPSTests/ArithmeticOperationsTests.swift index 70304eb..9ff0efa 100644 --- a/Tests/VIPSTests/ArithmeticOperationsTests.swift +++ b/Tests/VIPSTests/ArithmeticOperationsTests.swift @@ -1,1120 +1,1167 @@ -@testable import VIPS import Cvips -import Testing import Foundation +import Testing + +@testable import VIPS + +extension VIPSTests { + @Suite(.vips, .serialized) + struct ArithmeticOperationsTests { -@Suite(.vips) -struct ArithmeticOperationsTests { - - // MARK: - Basic Arithmetic with Different Formats - - @Test - func testAdditionAcrossFormats() throws { - for format in nonComplexFormats { - let a = try makeTestImage(value: 10).cast(format) - let b = try makeTestImage(value: 20).cast(format) - - // Image + Image - let result1 = try a + b - assertAlmostEqual(try result1.avg(), 30.0, threshold: 1.0) + // MARK: - Basic Arithmetic with Different Formats + + @Test + func testAdditionAcrossFormats() throws { + for format in nonComplexFormats { + let a = try makeTestImage(value: 10).cast(format) + let b = try makeTestImage(value: 20).cast(format) + + // Image + Image + let result1 = try a + b + assertAlmostEqual(try result1.avg(), 30.0, threshold: 1.0) + } } - } - - // MARK: - Trigonometric Operations Tests - - @Test - func testSin() throws { - // Create a simple test image with known values - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(1.0, 0.0) // Create values [0, 0, 0, 0, 0, 0, 0, 0, 0] - .linear(0.0, 90.0) // Create values [90, 90, 90, 90, 90, 90, 90, 90, 90] - - let result = try image.sin() - - // sin(90 degrees) = 1.0 - let avg = try result.avg() - #expect(abs(avg - 1.0) < 0.01) - } - - @Test - func testCos() throws { - // Create a test image with 0 degrees - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - - let result = try image.cos() - - // cos(0 degrees) = 1.0 - let avg = try result.avg() - #expect(abs(avg - 1.0) < 0.01) - } - - @Test - func testTan() throws { - // Create a test image with 45 degrees - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 45.0) - - let result = try image.tan() - - // tan(45 degrees) = 1.0 - let avg = try result.avg() - #expect(abs(avg - 1.0) < 0.01) - } - - // MARK: - Exponential and Logarithmic Operations Tests - - @Test - func testExp() throws { - // Create image with value 1 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 1.0) - - let result = try image.exp() - - // e^1 ≈ 2.71828 - let avg = try result.avg() - #expect(abs(avg - 2.71828) < 0.01) - } - - @Test - func testLog() throws { - // Create image with value e - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 2.71828) - - let result = try image.log() - - // ln(e) = 1.0 - let avg = try result.avg() - #expect(abs(avg - 1.0) < 0.01) - } - - @Test - func testLog10() throws { - // Create image with value 100 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 100.0) - - let result = try image.log10() - - // log10(100) = 2.0 - let avg = try result.avg() - #expect(abs(avg - 2.0) < 0.01) - } - - // MARK: - Math2 Operations Tests - - @Test - func testPowImage() throws { - // Create base image with value 2 - let base = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 2.0) - - // Create exponent image with value 3 - let exponent = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 3.0) - - let result = try base.pow(exponent) - - // 2^3 = 8 - let avg = try result.avg() - #expect(abs(avg - 8.0) < 0.01) - } - - @Test - func testPowConstant() throws { - // Create base image with value 3 - let base = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 3.0) - - let result = try base.pow(2.0) - - // 3^2 = 9 - let avg = try result.avg() - #expect(abs(avg - 9.0) < 0.01) - } - - @Test - func testAtan2() throws { - // Create y image with value 1 - let y = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 1.0) - - // Create x image with value 1 - let x = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 1.0) - - let result = try y.atan2(x) - - // atan2(1, 1) = 45 degrees - let avg = try result.avg() - #expect(abs(avg - 45.0) < 0.01) - } - - // MARK: - Bitwise Operations Tests - - @Test - func testAndImage() throws { - // Create image with value 12 (binary: 1100) - let left = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 12.0) - .cast(.uchar) - - // Create image with value 10 (binary: 1010) - let right = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 10.0) - .cast(.uchar) - - let result = try left.andimage(right) - - // 12 & 10 = 8 (binary: 1000) - let avg = try result.avg() - #expect(abs(avg - 8.0) < 0.01) - } - - @Test - func testAndImageOperator() throws { - // Create image with value 15 (binary: 1111) - let left = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 15.0) - .cast(.uchar) - - // Create image with value 7 (binary: 0111) - let right = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 7.0) - .cast(.uchar) - - let result = try left & right - - // 15 & 7 = 7 (binary: 0111) - let avg = try result.avg() - #expect(abs(avg - 7.0) < 0.01) - } - - @Test - func testOrImage() throws { - // Create image with value 12 (binary: 1100) - let left = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 12.0) - .cast(.uchar) - - // Create image with value 10 (binary: 1010) - let right = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 10.0) - .cast(.uchar) - - let result = try left.orimage(right) - - // 12 | 10 = 14 (binary: 1110) - let avg = try result.avg() - #expect(abs(avg - 14.0) < 0.01) - } - - @Test - func testOrImageOperator() throws { - // Create image with value 8 (binary: 1000) - let left = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 8.0) - .cast(.uchar) - - // Create image with value 4 (binary: 0100) - let right = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 4.0) - .cast(.uchar) - - let result = try left | right - - // 8 | 4 = 12 (binary: 1100) - let avg = try result.avg() - #expect(abs(avg - 12.0) < 0.01) - } - - @Test - func testAndImageConst() throws { - // Create image with value 15 (binary: 1111) - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 15.0) - .cast(.uchar) - - let result = try image.andimage(3.0) - - // 15 & 3 = 3 (binary: 0011) - let avg = try result.avg() - #expect(abs(avg - 3.0) < 0.01) - } - - @Test - func testOrImageConst() throws { - // Create image with value 8 (binary: 1000) - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 8.0) - .cast(.uchar) - - let result = try image.orimage(3.0) - - // 8 | 3 = 11 (binary: 1011) - let avg = try result.avg() - #expect(abs(avg - 11.0) < 0.01) - } - - // MARK: - Bit Shift Operations Tests - - @Test - func testLShiftConst() throws { - // Create image with value 3 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 3.0) - .cast(.uchar) - - let result = try image.lshift(2) - - // 3 << 2 = 12 - let avg = try result.avg() - #expect(abs(avg - 12.0) < 0.01) - } - - @Test - func testLShiftOperator() throws { - // Create image with value 5 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 5.0) - .cast(.uchar) - - let result = try image << 1 - - // 5 << 1 = 10 - let avg = try result.avg() - #expect(abs(avg - 10.0) < 0.01) - } - - @Test - func testRShiftConst() throws { - // Create image with value 12 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 12.0) - .cast(.uchar) - - let result = try image.rshift(2) - - // 12 >> 2 = 3 - let avg = try result.avg() - #expect(abs(avg - 3.0) < 0.01) - } - - @Test - func testRShiftOperator() throws { - // Create image with value 20 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 20.0) - .cast(.uchar) - - let result = try image >> 2 - - // 20 >> 2 = 5 - let avg = try result.avg() - #expect(abs(avg - 5.0) < 0.01) - } - - @Test - func testLShiftImage() throws { - // Create image with value 3 - let left = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 3.0) - .cast(.uchar) - - // Create shift amount image with value 3 - let right = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 3.0) - .cast(.uchar) - - let result = try left.lshift(right) - - // 3 << 3 = 24 - let avg = try result.avg() - #expect(abs(avg - 24.0) < 0.01) - } - - @Test - func testRShiftImage() throws { - // Create image with value 32 - let left = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 32.0) - .cast(.uchar) - - // Create shift amount image with value 3 - let right = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 3.0) - .cast(.uchar) - - let result = try left.rshift(right) - - // 32 >> 3 = 4 - let avg = try result.avg() - #expect(abs(avg - 4.0) < 0.01) - } - - // MARK: - Linear Operation Tests - - @Test - func testLinearBasic() throws { - // Create a test image with value 2 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 2.0) - - // Apply linear transform: out = in * 3 + 5 - let result = try image.linear(3.0, 5.0) - - // 2 * 3 + 5 = 11 - let avg = try result.avg() - #expect(abs(avg - 11.0) < 0.01) - } - - @Test - func testLinearInteger() throws { - // Create a test image with value 4 - let image = try VIPSImage.black(width: 2, height: 2, bands: 1) - .linear(0, 4) - - // Apply linear transform with integers: out = in * 2 + 3 - let result = try image.linear(2, 3) - - // 4 * 2 + 3 = 11 - let avg = try result.avg() - #expect(abs(avg - 11.0) < 0.01) - } - - @Test - func testLinearArrays() throws { - // Create a 3-band image with values [1, 2, 3] - let r = try VIPSImage.black(width: 2, height: 2, bands: 1).linear(0.0, 1.0) - let g = try VIPSImage.black(width: 2, height: 2, bands: 1).linear(0.0, 2.0) - let b = try VIPSImage.black(width: 2, height: 2, bands: 1).linear(0.0, 3.0) - let image = try r.bandjoin([g, b]) - - // Apply different linear transforms per band - let a = [2.0, 3.0, 4.0] // multipliers - let b_vals = [1.0, 2.0, 3.0] // addends - let result = try image.linear(a, b_vals) - - // Expected: [1*2+1, 2*3+2, 3*4+3] = [3, 8, 15] - - // Check each band average - let band0 = try result.extractBand(0) - let band1 = try result.extractBand(1) - let band2 = try result.extractBand(2) - - #expect(abs(try band0.avg() - 3.0) < 0.01) - #expect(abs(try band1.avg() - 8.0) < 0.01) - #expect(abs(try band2.avg() - 15.0) < 0.01) - } - - @Test - func testLinearDefaults() throws { - // Create a test image with value 5 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 5.0) - - // Apply linear with default parameters (a=1.0, b=0) - let result = try image.linear(1.0, 0.0) - - // 5 * 1 + 0 = 5 (should be unchanged) - let avg = try result.avg() - #expect(abs(avg - 5.0) < 0.01) - } - - @Test - func testLinearMultiplyOnly() throws { - // Create a test image with value 7 - let image = try VIPSImage.black(width: 2, height: 2, bands: 1) - .linear(0.0, 7.0) - - // Apply linear with only multiplication (b=0 by default) - let result = try image.linear(0.5) - - // 7 * 0.5 + 0 = 3.5 - let avg = try result.avg() - #expect(abs(avg - 3.5) < 0.01) - } - - @Test - func testLinearAddOnly() throws { - // Create a test image with value 10 - let image = try VIPSImage.black(width: 2, height: 2, bands: 1) - .linear(0.0, 10.0) - - // Apply linear with only addition (a=1.0 by default) - let result = try image.linear(1.0, -3.0) - - // 10 * 1 + (-3) = 7 - let avg = try result.avg() - #expect(abs(avg - 7.0) < 0.01) - } - - @Test - func testLinearUchar() throws { - // Create a test image that would overflow uchar without the uchar parameter - let image = try VIPSImage.black(width: 2, height: 2, bands: 1) - .linear(0.0, 100.0) - - // Apply linear transform that would create value > 255 - let result = try image.linear(3.0, 0.0, uchar: true) - - // 100 * 3 = 300, but should be clamped to 255 with uchar: true - let max = try result.max() - #expect(max <= 255.0) - - // Verify the type is actually uchar - let format = result.format - #expect(format == .uchar) - } - - // MARK: - Inverse Trigonometric Operations Tests - - @Test - func testAsin() throws { - // Create a test image with value 1.0 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 1.0) - - let result = try image.asin() - - // asin(1.0) = 90 degrees - let avg = try result.avg() - #expect(abs(avg - 90.0) < 0.01) - } - - @Test - func testAcos() throws { - // Create a test image with value 0.0 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - - let result = try image.acos() - - // acos(0.0) = 90 degrees - let avg = try result.avg() - #expect(abs(avg - 90.0) < 0.01) - } - - @Test - func testAtan() throws { - // Create a test image with value 1.0 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 1.0) - - let result = try image.atan() - - // atan(1.0) = 45 degrees - let avg = try result.avg() - #expect(abs(avg - 45.0) < 0.01) - } - - // MARK: - Hyperbolic Functions Tests - - @Test - func testSinh() throws { - // Create a test image with value 0 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - - let result = try image.sinh() - - // sinh(0) = 0 - let avg = try result.avg() - #expect(abs(avg) < 0.01) - } - - @Test - func testCosh() throws { - // Create a test image with value 0 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - - let result = try image.cosh() - - // cosh(0) = 1 - let avg = try result.avg() - #expect(abs(avg - 1.0) < 0.01) - } - - @Test - func testTanh() throws { - // Create a test image with value 0 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - - let result = try image.tanh() - - // tanh(0) = 0 - let avg = try result.avg() - #expect(abs(avg) < 0.01) - } - - // MARK: - Inverse Hyperbolic Functions Tests - - @Test - func testAsinh() throws { - // Create a test image with value 0 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - - let result = try image.asinh() - - // asinh(0) = 0 - let avg = try result.avg() - #expect(abs(avg) < 0.01) - } - - @Test - func testAcosh() throws { - // Create a test image with value 1.0 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 1.0) - - let result = try image.acosh() - - // acosh(1) = 0 - let avg = try result.avg() - #expect(abs(avg) < 0.01) - } - - @Test - func testAtanh() throws { - // Create a test image with value 0 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - - let result = try image.atanh() - - // atanh(0) = 0 - let avg = try result.avg() - #expect(abs(avg) < 0.01) - } - - // MARK: - Additional Exponential Operations Tests - - @Test - func testExp10() throws { - // Create a test image with value 2 - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 2.0) - - let result = try image.exp10() - - // 10^2 = 100 - let avg = try result.avg() - #expect(abs(avg - 100.0) < 0.01) - } - - // MARK: - Math2 Operations Tests - - @Test - func testWopImage() throws { - // Create exponent image with value 2 - let exponent = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 2.0) - - // Create base image with value 3 - let base = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 3.0) - - let result = try exponent.wop(base) - - // 3^2 = 9 (wop swaps the arguments) - let avg = try result.avg() - #expect(abs(avg - 9.0) < 0.01) - } - - @Test - func testWopConst() throws { - // Create exponent image with value 3 - let exponent = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 3.0) - - let result = try exponent.wop(2.0) - - // 2^3 = 8 - let avg = try result.avg() - #expect(abs(avg - 8.0) < 0.01) - } - - @Test - func testRemainderImage() throws { - // Create dividend image with value 13 - let dividend = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 13.0) - - // Create divisor image with value 5 - let divisor = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 5.0) - - let result = try dividend.remainder(divisor) - - // 13 % 5 = 3 - let avg = try result.avg() - #expect(abs(avg - 3.0) < 0.01) - } - - @Test - func testRemainderConst() throws { - // Create dividend image with value 17 - let dividend = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 17.0) - - let result = try dividend.remainder(7.0) - - // 17 % 7 = 3 - let avg = try result.avg() - #expect(abs(avg - 3.0) < 0.01) - } - - @Test - func testRemainderConstInt() throws { - // Create dividend image with value 20 - let dividend = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 20.0) - - let result = try dividend.remainder(6) - - // 20 % 6 = 2 - let avg = try result.avg() - #expect(abs(avg - 2.0) < 0.01) - } - - // MARK: - Bitwise XOR Operations Tests - - @Test - func testEorImage() throws { - // Create image with value 12 (binary: 1100) - let left = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 12.0) - .cast(.uchar) - - // Create image with value 5 (binary: 0101) - let right = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 5.0) - .cast(.uchar) - - let result = try left.eorimage(right) - - // 12 ^ 5 = 9 (binary: 1001) - let avg = try result.avg() - #expect(abs(avg - 9.0) < 0.01) - } - - @Test - func testEorImageConst() throws { - // Create image with value 15 (binary: 1111) - let image = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 15.0) - .cast(.uchar) - - let result = try image.eorimage(10.0) - - // 15 ^ 10 = 5 (binary: 0101) - let avg = try result.avg() - #expect(abs(avg - 5.0) < 0.01) - } - - @Test - func testXorOperator() throws { - // Create image with value 7 (binary: 0111) - let left = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 7.0) - .cast(.uchar) - - // Create image with value 3 (binary: 0011) - let right = try VIPSImage.black(width: 3, height: 3, bands: 1) - .linear(0.0, 3.0) - .cast(.uchar) - - let result = try left ^ right - - // 7 ^ 3 = 4 (binary: 0100) - let avg = try result.avg() - #expect(abs(avg - 4.0) < 0.01) - } - - // MARK: - Complex Number Operations Tests - - @Test - func testComplexForm() throws { - // Create real and imaginary parts - let real = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 3.0) // real = 3 - let imag = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 4.0) // imag = 4 - - // Combine into complex image - let complex = try real.complex(imag) - - // Complex image should have format COMPLEX - #expect(complex.bands == 1) - - // Alternative method - let complex2 = try real.complexform(imag) - #expect(complex2.bands == 1) - } - - @Test - func testPolarAndRect() throws { - // Create a complex number 3 + 4i - let real = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 3.0) - let imag = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 4.0) - let complex = try real.complexform(imag) - - // Convert to polar form - let polar = try complex.polar() - - // Magnitude should be 5 (sqrt(3² + 4²)) - // Phase should be atan(4/3) ≈ 53.13 degrees - - // Convert back to rectangular - let rect = try polar.rect() - - // Should get back approximately 3 + 4i - let realPart = try rect.real() - let imagPart = try rect.imag() - - let realAvg = try realPart.avg() - let imagAvg = try imagPart.avg() - - #expect(abs(realAvg - 3.0) < 0.1) - #expect(abs(imagAvg - 4.0) < 0.1) - } - - @Test - func testComplexConjugate() throws { - // Create complex number 2 + 3i - let real = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 2.0) - let imag = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 3.0) - let complex = try real.complexform(imag) - - // Get conjugate (2 - 3i) - let conjugate = try complex.conj() - - // Extract parts - let conjReal = try conjugate.real() - let conjImag = try conjugate.imag() - - // Real part should remain 2 - #expect(abs(try conjReal.avg() - 2.0) < 0.01) - // Imaginary part should be -3 - #expect(abs(try conjImag.avg() - (-3.0)) < 0.01) - } - - @Test - func testRealAndImag() throws { - // Create complex number 5 + 7i - let real = try VIPSImage.black(width: 2, height: 2, bands: 1).linear(0.0, 5.0) - let imag = try VIPSImage.black(width: 2, height: 2, bands: 1).linear(0.0, 7.0) - let complex = try real.complexform(imag) - - // Extract real and imaginary parts - let extractedReal = try complex.real() - let extractedImag = try complex.imag() - - #expect(abs(try extractedReal.avg() - 5.0) < 0.01) - #expect(abs(try extractedImag.avg() - 7.0) < 0.01) - } - - // MARK: - Statistical Operations Tests - - @Test - func testSum() throws { - // Create three images with different values - let img1 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 10.0) - let img2 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 20.0) - let img3 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 30.0) - - // Sum them - let sum = try VIPSImage.sum([img1, img2, img3]) - - // Should be 10 + 20 + 30 = 60 per pixel - let avg = try sum.avg() - #expect(abs(avg - 60.0) < 0.01) - } - - @Test - func testSumSingleImage() throws { - let img = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 42.0) - - // Sum of single image should return the image itself - let sum = try VIPSImage.sum([img]) - - let avg = try sum.avg() - #expect(abs(avg - 42.0) < 0.01) - } - - @Test - func testStats() throws { - // Create a simple test image with uniform values for predictable statistics - // Using a 4x4 image with value 5.0 everywhere for simple verification - let testImg = try VIPSImage.black(width: 4, height: 4, bands: 1).linear(0.0, 5.0) - - // Get statistics - let stats = try testImg.stats() - - // Verify the stats image dimensions - // Stats image format: - // - Width = 10 (number of statistics columns) - // - Height = n+1 where n is number of bands - // For a 1-band image: height = 2 (row 0: all bands together, row 1: band 0) - #expect(stats.width == 10, "Stats should have 10 columns for 10 statistics") - #expect(stats.height == 2, "Stats should have 2 rows for 1-band image") - - // The 10 statistics columns are documented as follows: - // Column 0: minimum - // Column 1: maximum - // Column 2: sum - // Column 3: sum of squares - // Column 4: mean - // Column 5: standard deviation - // Column 6: x coordinate of minimum - // Column 7: y coordinate of minimum - // Column 8: x coordinate of maximum - // Column 9: y coordinate of maximum - - // Read the actual statistics from row 0 (all bands together) - // For a 4x4 image with all values = 5.0: - let row0Stats = try (0..<10).map { x in - try stats.getpoint(x: x, y: 0)[0] + + // MARK: - Trigonometric Operations Tests + + @Test + func testSin() throws { + // Create a simple test image with known values + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(1.0, 0.0) // Create values [0, 0, 0, 0, 0, 0, 0, 0, 0] + .linear(0.0, 90.0) // Create values [90, 90, 90, 90, 90, 90, 90, 90, 90] + + let result = try image.sin() + + // sin(90 degrees) = 1.0 + let avg = try result.avg() + #expect(abs(avg - 1.0) < 0.01) } - - // Verify each statistic: - #expect(row0Stats[0] == 5.0, "Column 0 (minimum) should be 5.0, got \(row0Stats[0])") - #expect(row0Stats[1] == 5.0, "Column 1 (maximum) should be 5.0, got \(row0Stats[1])") - #expect(row0Stats[2] == 80.0, "Column 2 (sum) should be 80.0 (5.0 * 16), got \(row0Stats[2])") - #expect(row0Stats[3] == 400.0, "Column 3 (sum of squares) should be 400.0 (25 * 16), got \(row0Stats[3])") - #expect(row0Stats[4] == 5.0, "Column 4 (mean) should be 5.0, got \(row0Stats[4])") - #expect(row0Stats[5] == 0.0, "Column 5 (std dev) should be 0.0, got \(row0Stats[5])") - // Coordinates can be any pixel since all values are the same - #expect(row0Stats[6] >= 0 && row0Stats[6] < 4, "Column 6 (x of min) should be valid coordinate") - #expect(row0Stats[7] >= 0 && row0Stats[7] < 4, "Column 7 (y of min) should be valid coordinate") - #expect(row0Stats[8] >= 0 && row0Stats[8] < 4, "Column 8 (x of max) should be valid coordinate") - #expect(row0Stats[9] >= 0 && row0Stats[9] < 4, "Column 9 (y of max) should be valid coordinate") - - // Test with a more complex image with varied values - // For testing varied values, let's use the identity LUT - // Identity creates a 256x1 image with values 0-255 - let identityImg = try VIPSImage.identity(bands: 1, size: 256) - .cast(.double) - - // Extract just the first 9 pixels using crop - let variedImg = try identityImg.crop(left: 0, top: 0, width: 9, height: 1) - .linear(1.0, 1.0) // Add 1 to get values 1-9 - - let variedStats = try variedImg.stats() - #expect(variedStats.width == 10, "Varied stats should have 10 columns") - - // Read statistics for cropped identity image (values 1-9) - let variedRow0 = try (0..<10).map { x in - try variedStats.getpoint(x: x, y: 0)[0] + + @Test + func testCos() throws { + // Create a test image with 0 degrees + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + + let result = try image.cos() + + // cos(0 degrees) = 1.0 + let avg = try result.avg() + #expect(abs(avg - 1.0) < 0.01) } - - // Verify statistics for values 1-9: - #expect(variedRow0[0] == 1.0, "Minimum should be 1.0, got \(variedRow0[0])") - #expect(variedRow0[1] == 9.0, "Maximum should be 9.0, got \(variedRow0[1])") - // Sum: 1+2+3+4+5+6+7+8+9 = 45 - #expect(variedRow0[2] == 45.0, "Sum should be 45.0, got \(variedRow0[2])") - // Sum of squares: 1+4+9+16+25+36+49+64+81 = 285 - #expect(variedRow0[3] == 285.0, "Sum of squares should be 285.0, got \(variedRow0[3])") - // Mean: 45/9 = 5 - #expect(variedRow0[4] == 5.0, "Mean should be 5.0, got \(variedRow0[4])") - // Std dev: For values 1-9, the population std dev is ~2.738 - // The sample std dev would be slightly higher - #expect(abs(variedRow0[5] - 2.7386128) < 0.001, "Std dev should be ~2.738, got \(variedRow0[5])") - - // Additional test: multi-band image statistics - let band1 = try VIPSImage.black(width: 2, height: 2, bands: 1).linear(0.0, 2.0) // All 2s - let band2 = try VIPSImage.black(width: 2, height: 2, bands: 1).linear(0.0, 4.0) // All 4s - let multiBand = try band1.bandjoin([band2]) - - let multiBandStats = try multiBand.stats() - - // For a 2-band image: height should be 3 (row 0: all bands, row 1: band 0, row 2: band 1) - #expect(multiBandStats.width == 10, "Multi-band stats should have 10 columns") - #expect(multiBandStats.height == 3, "Stats should have 3 rows for 2-band image") - - // Verify statistics for band 0 (row 1) from the stats image - let band0Stats = try (0..<10).map { x in - try multiBandStats.getpoint(x: x, y: 1)[0] + + @Test + func testTan() throws { + // Create a test image with 45 degrees + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 45.0) + + let result = try image.tan() + + // tan(45 degrees) = 1.0 + let avg = try result.avg() + #expect(abs(avg - 1.0) < 0.01) } - #expect(band0Stats[0] == 2.0, "Band 0 minimum should be 2.0, got \(band0Stats[0])") - #expect(band0Stats[1] == 2.0, "Band 0 maximum should be 2.0, got \(band0Stats[1])") - #expect(band0Stats[2] == 8.0, "Band 0 sum should be 8.0 (2.0 * 4), got \(band0Stats[2])") - #expect(band0Stats[3] == 16.0, "Band 0 sum of squares should be 16.0 (4 * 4), got \(band0Stats[3])") - #expect(band0Stats[4] == 2.0, "Band 0 mean should be 2.0, got \(band0Stats[4])") - #expect(band0Stats[5] == 0.0, "Band 0 std dev should be 0.0, got \(band0Stats[5])") - - // Verify statistics for band 1 (row 2) from the stats image - let band1Stats = try (0..<10).map { x in - try multiBandStats.getpoint(x: x, y: 2)[0] + + // MARK: - Exponential and Logarithmic Operations Tests + + @Test + func testExp() throws { + // Create image with value 1 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 1.0) + + let result = try image.exp() + + // e^1 ≈ 2.71828 + let avg = try result.avg() + #expect(abs(avg - 2.71828) < 0.01) } - #expect(band1Stats[0] == 4.0, "Band 1 minimum should be 4.0, got \(band1Stats[0])") - #expect(band1Stats[1] == 4.0, "Band 1 maximum should be 4.0, got \(band1Stats[1])") - #expect(band1Stats[2] == 16.0, "Band 1 sum should be 16.0 (4.0 * 4), got \(band1Stats[2])") - #expect(band1Stats[3] == 64.0, "Band 1 sum of squares should be 64.0 (16 * 4), got \(band1Stats[3])") - #expect(band1Stats[4] == 4.0, "Band 1 mean should be 4.0, got \(band1Stats[4])") - #expect(band1Stats[5] == 0.0, "Band 1 std dev should be 0.0, got \(band1Stats[5])") - } - - @Test - func testProfile() throws { - // Create a simple 3x3 image with known values - let img = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(1.0, 1.0) - - // Get profiles (averages across rows and columns) - let profiles = try img.profile() - - // Column profile should be 3x1 (average of each column) - // Row profile should be 1x3 (average of each row) - let colWidth = profiles.columns.width - let colHeight = profiles.columns.height - let rowWidth = profiles.rows.width - let rowHeight = profiles.rows.height - - #expect(colWidth == 3) - #expect(colHeight == 1) - #expect(rowWidth == 1) - #expect(rowHeight == 3) - } - - @Test - func testProject() throws { - // Create a simple 3x3 image with known values - let img = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(1.0, 0.0) - - // Project to get row and column sums - let projection = try img.project() - - // Each row should have same sum (all pixels are 0) - // Each column should have same sum (all pixels are 0) - let rowWidth = projection.rows.width - let rowHeight = projection.rows.height - let colWidth = projection.columns.width - let colHeight = projection.columns.height - - #expect(rowWidth == 1) - #expect(rowHeight == 3) - #expect(colWidth == 3) - #expect(colHeight == 1) - } - - // MARK: - Band Operations Tests - - @Test - func testBandAnd() throws { - // Create multi-band image - let band1 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 7.0).cast(.uchar) // 0b0111 - let band2 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 5.0).cast(.uchar) // 0b0101 - let img = try band1.bandjoin([band2]) - - // AND across bands: 7 & 5 = 5 - let result = try img.bandand() - - #expect(result.bands == 1) - let avg = try result.avg() - #expect(abs(avg - 5.0) < 0.01) - } - - @Test - func testBandOr() throws { - // Create multi-band image - let band1 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 4.0).cast(.uchar) // 0b0100 - let band2 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 2.0).cast(.uchar) // 0b0010 - let img = try band1.bandjoin([band2]) - - // OR across bands: 4 | 2 = 6 - let result = try img.bandor() - - #expect(result.bands == 1) - let avg = try result.avg() - #expect(abs(avg - 6.0) < 0.01) - } - - @Test - func testBandEor() throws { - // Create multi-band image - let band1 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 7.0).cast(.uchar) // 0b0111 - let band2 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 3.0).cast(.uchar) // 0b0011 - let img = try band1.bandjoin([band2]) - - // XOR across bands: 7 ^ 3 = 4 - let result = try img.bandeor() - - #expect(result.bands == 1) - let avg = try result.avg() - #expect(abs(avg - 4.0) < 0.01) - } - - @Test - func testREADMEArithmeticExamples() throws { - let tempDir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) - .appendingPathComponent("vips-arithmetic-test-\(UUID().uuidString)") - - try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) - - defer { - try? FileManager.default.removeItem(at: tempDir) + + @Test + func testLog() throws { + // Create image with value e + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 2.71828) + + let result = try image.log() + + // ln(e) = 1.0 + let avg = try result.avg() + #expect(abs(avg - 1.0) < 0.01) + } + + @Test + func testLog10() throws { + // Create image with value 100 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 100.0) + + let result = try image.log10() + + // log10(100) = 2.0 + let avg = try result.avg() + #expect(abs(avg - 2.0) < 0.01) + } + + // MARK: - Math2 Operations Tests + + @Test + func testPowImage() throws { + // Create base image with value 2 + let base = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 2.0) + + // Create exponent image with value 3 + let exponent = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 3.0) + + let result = try base.pow(exponent) + + // 2^3 = 8 + let avg = try result.avg() + #expect(abs(avg - 8.0) < 0.01) + } + + @Test + func testPowConstant() throws { + // Create base image with value 3 + let base = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 3.0) + + let result = try base.pow(2.0) + + // 3^2 = 9 + let avg = try result.avg() + #expect(abs(avg - 9.0) < 0.01) + } + + @Test + func testAtan2() throws { + // Create y image with value 1 + let y = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 1.0) + + // Create x image with value 1 + let x = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 1.0) + + let result = try y.atan2(x) + + // atan2(1, 1) = 45 degrees + let avg = try result.avg() + #expect(abs(avg - 45.0) < 0.01) + } + + // MARK: - Bitwise Operations Tests + + @Test + func testAndImage() throws { + // Create image with value 12 (binary: 1100) + let left = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 12.0) + .cast(.uchar) + + // Create image with value 10 (binary: 1010) + let right = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 10.0) + .cast(.uchar) + + let result = try left.andimage(right) + + // 12 & 10 = 8 (binary: 1000) + let avg = try result.avg() + #expect(abs(avg - 8.0) < 0.01) + } + + @Test + func testAndImageOperator() throws { + // Create image with value 15 (binary: 1111) + let left = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 15.0) + .cast(.uchar) + + // Create image with value 7 (binary: 0111) + let right = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 7.0) + .cast(.uchar) + + let result = try left & right + + // 15 & 7 = 7 (binary: 0111) + let avg = try result.avg() + #expect(abs(avg - 7.0) < 0.01) + } + + @Test + func testOrImage() throws { + // Create image with value 12 (binary: 1100) + let left = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 12.0) + .cast(.uchar) + + // Create image with value 10 (binary: 1010) + let right = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 10.0) + .cast(.uchar) + + let result = try left.orimage(right) + + // 12 | 10 = 14 (binary: 1110) + let avg = try result.avg() + #expect(abs(avg - 14.0) < 0.01) + } + + @Test + func testOrImageOperator() throws { + // Create image with value 8 (binary: 1000) + let left = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 8.0) + .cast(.uchar) + + // Create image with value 4 (binary: 0100) + let right = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 4.0) + .cast(.uchar) + + let result = try left | right + + // 8 | 4 = 12 (binary: 1100) + let avg = try result.avg() + #expect(abs(avg - 12.0) < 0.01) + } + + @Test + func testAndImageConst() throws { + // Create image with value 15 (binary: 1111) + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 15.0) + .cast(.uchar) + + let result = try image.andimage(3.0) + + // 15 & 3 = 3 (binary: 0011) + let avg = try result.avg() + #expect(abs(avg - 3.0) < 0.01) + } + + @Test + func testOrImageConst() throws { + // Create image with value 8 (binary: 1000) + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 8.0) + .cast(.uchar) + + let result = try image.orimage(3.0) + + // 8 | 3 = 11 (binary: 1011) + let avg = try result.avg() + #expect(abs(avg - 11.0) < 0.01) + } + + // MARK: - Bit Shift Operations Tests + + @Test + func testLShiftConst() throws { + // Create image with value 3 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 3.0) + .cast(.uchar) + + let result = try image.lshift(2) + + // 3 << 2 = 12 + let avg = try result.avg() + #expect(abs(avg - 12.0) < 0.01) + } + + @Test + func testLShiftOperator() throws { + // Create image with value 5 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 5.0) + .cast(.uchar) + + let result = try image << 1 + + // 5 << 1 = 10 + let avg = try result.avg() + #expect(abs(avg - 10.0) < 0.01) } - // Create two simple test images instead of loading from files to avoid compatibility issues - let image1 = try VIPSImage.black(width: 100, height: 100, bands: 3) - .linear([1.0, 1.0, 1.0], [100.0, 150.0, 200.0]) // RGB values: (100, 150, 200) - - let image2 = try VIPSImage.black(width: 100, height: 100, bands: 3) - .linear([1.0, 1.0, 1.0], [80.0, 120.0, 160.0]) // RGB values: (80, 120, 160) - - // Image arithmetic operations from README - let sum = try image1 + image2 - let difference = try image1 - image2 - let product = try image1 * image2 - let quotient = try image1 / (image2 + 1) // Add 1 to avoid division by zero - - // Scalar operations from README - let brightened = try image1 + 50 // Add 50 to all pixels - let dimmed = try image1 * 0.8 // Scale all pixels by 0.8 - let enhanced = try image1 * [1.2, 1.0, 0.9] // Per-channel scaling - - // Linear transformation: a * image + b - let linearized = try image1.linear([1.1, 1.0, 0.9], [10, 0, -5]) - - // Save all results to temp directory for inspection - let results: [(VIPSImage, String)] = [ - (image1, "image1.jpg"), - (image2, "image2.jpg"), - (sum, "sum.jpg"), - (difference, "difference.jpg"), - (product, "product.jpg"), - (quotient, "quotient.jpg"), - (brightened, "brightened.jpg"), - (dimmed, "dimmed.jpg"), - (enhanced, "enhanced.jpg"), - (linearized, "linearized.jpg") - ] - - for (resultImage, filename) in results { - let outputPath = tempDir.appendingPathComponent(filename).path - try resultImage.writeToFile(outputPath) - - // Verify the file was created and has content - let fileSize = try FileManager.default.attributesOfItem(atPath: outputPath)[.size] as! Int64 - #expect(fileSize > 0, "Output file \(filename) should not be empty") - - // Verify we can read it back - let verifyImage = try VIPSImage(fromFilePath: outputPath) - #expect(verifyImage.width > 0) - #expect(verifyImage.height > 0) + @Test + func testRShiftConst() throws { + // Create image with value 12 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 12.0) + .cast(.uchar) + + let result = try image.rshift(2) + + // 12 >> 2 = 3 + let avg = try result.avg() + #expect(abs(avg - 3.0) < 0.01) } - - // Verify some arithmetic results are as expected - let sumAvg = try sum.avg() - let expectedSumAvg = (100 + 80 + 150 + 120 + 200 + 160) / 3.0 // Average of all channels - assertAlmostEqual(sumAvg, expectedSumAvg, threshold: 1.0) - - let brightenedAvg = try brightened.avg() - let expectedBrightenedAvg = (100 + 50 + 150 + 50 + 200 + 50) / 3.0 - assertAlmostEqual(brightenedAvg, expectedBrightenedAvg, threshold: 1.0) - - print("All arithmetic operations completed successfully!") - print("Results written to: \(tempDir.path)") - print("Files created:") - for (_, filename) in results { - print(" - \(filename)") + + @Test + func testRShiftOperator() throws { + // Create image with value 20 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 20.0) + .cast(.uchar) + + let result = try image >> 2 + + // 20 >> 2 = 5 + let avg = try result.avg() + #expect(abs(avg - 5.0) < 0.01) } + + @Test + func testLShiftImage() throws { + // Create image with value 3 + let left = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 3.0) + .cast(.uchar) + + // Create shift amount image with value 3 + let right = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 3.0) + .cast(.uchar) + + let result = try left.lshift(right) + + // 3 << 3 = 24 + let avg = try result.avg() + #expect(abs(avg - 24.0) < 0.01) + } + + @Test + func testRShiftImage() throws { + // Create image with value 32 + let left = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 32.0) + .cast(.uchar) + + // Create shift amount image with value 3 + let right = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 3.0) + .cast(.uchar) + + let result = try left.rshift(right) + + // 32 >> 3 = 4 + let avg = try result.avg() + #expect(abs(avg - 4.0) < 0.01) + } + + // MARK: - Linear Operation Tests + + @Test + func testLinearBasic() throws { + // Create a test image with value 2 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 2.0) + + // Apply linear transform: out = in * 3 + 5 + let result = try image.linear(3.0, 5.0) + + // 2 * 3 + 5 = 11 + let avg = try result.avg() + #expect(abs(avg - 11.0) < 0.01) + } + + @Test + func testLinearInteger() throws { + // Create a test image with value 4 + let image = try VIPSImage.black(width: 2, height: 2, bands: 1) + .linear(0, 4) + + // Apply linear transform with integers: out = in * 2 + 3 + let result = try image.linear(2, 3) + + // 4 * 2 + 3 = 11 + let avg = try result.avg() + #expect(abs(avg - 11.0) < 0.01) + } + + @Test + func testLinearArrays() throws { + // Create a 3-band image with values [1, 2, 3] + let r = try VIPSImage.black(width: 2, height: 2, bands: 1).linear(0.0, 1.0) + let g = try VIPSImage.black(width: 2, height: 2, bands: 1).linear(0.0, 2.0) + let b = try VIPSImage.black(width: 2, height: 2, bands: 1).linear(0.0, 3.0) + let image = try r.bandjoin([g, b]) + + // Apply different linear transforms per band + let a = [2.0, 3.0, 4.0] // multipliers + let b_vals = [1.0, 2.0, 3.0] // addends + let result = try image.linear(a, b_vals) + + // Expected: [1*2+1, 2*3+2, 3*4+3] = [3, 8, 15] + + // Check each band average + let band0 = try result.extractBand(0) + let band1 = try result.extractBand(1) + let band2 = try result.extractBand(2) + + #expect(abs(try band0.avg() - 3.0) < 0.01) + #expect(abs(try band1.avg() - 8.0) < 0.01) + #expect(abs(try band2.avg() - 15.0) < 0.01) + } + + @Test + func testLinearDefaults() throws { + // Create a test image with value 5 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 5.0) + + // Apply linear with default parameters (a=1.0, b=0) + let result = try image.linear(1.0, 0.0) + + // 5 * 1 + 0 = 5 (should be unchanged) + let avg = try result.avg() + #expect(abs(avg - 5.0) < 0.01) + } + + @Test + func testLinearMultiplyOnly() throws { + // Create a test image with value 7 + let image = try VIPSImage.black(width: 2, height: 2, bands: 1) + .linear(0.0, 7.0) + + // Apply linear with only multiplication (b=0 by default) + let result = try image.linear(0.5) + + // 7 * 0.5 + 0 = 3.5 + let avg = try result.avg() + #expect(abs(avg - 3.5) < 0.01) + } + + @Test + func testLinearAddOnly() throws { + // Create a test image with value 10 + let image = try VIPSImage.black(width: 2, height: 2, bands: 1) + .linear(0.0, 10.0) + + // Apply linear with only addition (a=1.0 by default) + let result = try image.linear(1.0, -3.0) + + // 10 * 1 + (-3) = 7 + let avg = try result.avg() + #expect(abs(avg - 7.0) < 0.01) + } + + @Test + func testLinearUchar() throws { + // Create a test image that would overflow uchar without the uchar parameter + let image = try VIPSImage.black(width: 2, height: 2, bands: 1) + .linear(0.0, 100.0) + + // Apply linear transform that would create value > 255 + let result = try image.linear(3.0, 0.0, uchar: true) + + // 100 * 3 = 300, but should be clamped to 255 with uchar: true + let max = try result.max() + #expect(max <= 255.0) + + // Verify the type is actually uchar + let format = result.format + #expect(format == .uchar) + } + + // MARK: - Inverse Trigonometric Operations Tests + + @Test + func testAsin() throws { + // Create a test image with value 1.0 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 1.0) + + let result = try image.asin() + + // asin(1.0) = 90 degrees + let avg = try result.avg() + #expect(abs(avg - 90.0) < 0.01) + } + + @Test + func testAcos() throws { + // Create a test image with value 0.0 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + + let result = try image.acos() + + // acos(0.0) = 90 degrees + let avg = try result.avg() + #expect(abs(avg - 90.0) < 0.01) + } + + @Test + func testAtan() throws { + // Create a test image with value 1.0 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 1.0) + + let result = try image.atan() + + // atan(1.0) = 45 degrees + let avg = try result.avg() + #expect(abs(avg - 45.0) < 0.01) + } + + // MARK: - Hyperbolic Functions Tests + + @Test + func testSinh() throws { + // Create a test image with value 0 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + + let result = try image.sinh() + + // sinh(0) = 0 + let avg = try result.avg() + #expect(abs(avg) < 0.01) + } + + @Test + func testCosh() throws { + // Create a test image with value 0 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + + let result = try image.cosh() + + // cosh(0) = 1 + let avg = try result.avg() + #expect(abs(avg - 1.0) < 0.01) + } + + @Test + func testTanh() throws { + // Create a test image with value 0 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + + let result = try image.tanh() + + // tanh(0) = 0 + let avg = try result.avg() + #expect(abs(avg) < 0.01) + } + + // MARK: - Inverse Hyperbolic Functions Tests + + @Test + func testAsinh() throws { + // Create a test image with value 0 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + + let result = try image.asinh() + + // asinh(0) = 0 + let avg = try result.avg() + #expect(abs(avg) < 0.01) + } + + @Test + func testAcosh() throws { + // Create a test image with value 1.0 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 1.0) + + let result = try image.acosh() + + // acosh(1) = 0 + let avg = try result.avg() + #expect(abs(avg) < 0.01) + } + + @Test + func testAtanh() throws { + // Create a test image with value 0 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + + let result = try image.atanh() + + // atanh(0) = 0 + let avg = try result.avg() + #expect(abs(avg) < 0.01) + } + + // MARK: - Additional Exponential Operations Tests + + @Test + func testExp10() throws { + // Create a test image with value 2 + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 2.0) + + let result = try image.exp10() + + // 10^2 = 100 + let avg = try result.avg() + #expect(abs(avg - 100.0) < 0.01) + } + + // MARK: - Math2 Operations Tests + + @Test + func testWopImage() throws { + // Create exponent image with value 2 + let exponent = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 2.0) + + // Create base image with value 3 + let base = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 3.0) + + let result = try exponent.wop(base) + + // 3^2 = 9 (wop swaps the arguments) + let avg = try result.avg() + #expect(abs(avg - 9.0) < 0.01) + } + + @Test + func testWopConst() throws { + // Create exponent image with value 3 + let exponent = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 3.0) + + let result = try exponent.wop(2.0) + + // 2^3 = 8 + let avg = try result.avg() + #expect(abs(avg - 8.0) < 0.01) + } + + @Test + func testRemainderImage() throws { + // Create dividend image with value 13 + let dividend = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 13.0) + + // Create divisor image with value 5 + let divisor = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 5.0) + + let result = try dividend.remainder(divisor) + + // 13 % 5 = 3 + let avg = try result.avg() + #expect(abs(avg - 3.0) < 0.01) + } + + @Test + func testRemainderConst() throws { + // Create dividend image with value 17 + let dividend = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 17.0) + + let result = try dividend.remainder(7.0) + + // 17 % 7 = 3 + let avg = try result.avg() + #expect(abs(avg - 3.0) < 0.01) + } + + @Test + func testRemainderConstInt() throws { + // Create dividend image with value 20 + let dividend = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 20.0) + + let result = try dividend.remainder(6) + + // 20 % 6 = 2 + let avg = try result.avg() + #expect(abs(avg - 2.0) < 0.01) + } + + // MARK: - Bitwise XOR Operations Tests + + @Test + func testEorImage() throws { + // Create image with value 12 (binary: 1100) + let left = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 12.0) + .cast(.uchar) + + // Create image with value 5 (binary: 0101) + let right = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 5.0) + .cast(.uchar) + + let result = try left.eorimage(right) + + // 12 ^ 5 = 9 (binary: 1001) + let avg = try result.avg() + #expect(abs(avg - 9.0) < 0.01) + } + + @Test + func testEorImageConst() throws { + // Create image with value 15 (binary: 1111) + let image = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 15.0) + .cast(.uchar) + + let result = try image.eorimage(10.0) + + // 15 ^ 10 = 5 (binary: 0101) + let avg = try result.avg() + #expect(abs(avg - 5.0) < 0.01) + } + + @Test + func testXorOperator() throws { + // Create image with value 7 (binary: 0111) + let left = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 7.0) + .cast(.uchar) + + // Create image with value 3 (binary: 0011) + let right = try VIPSImage.black(width: 3, height: 3, bands: 1) + .linear(0.0, 3.0) + .cast(.uchar) + + let result = try left ^ right + + // 7 ^ 3 = 4 (binary: 0100) + let avg = try result.avg() + #expect(abs(avg - 4.0) < 0.01) + } + + // MARK: - Complex Number Operations Tests + + @Test + func testComplexForm() throws { + // Create real and imaginary parts + let real = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 3.0) // real = 3 + let imag = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 4.0) // imag = 4 + + // Combine into complex image + let complex = try real.complex(imag) + + // Complex image should have format COMPLEX + #expect(complex.bands == 1) + + // Alternative method + let complex2 = try real.complexform(imag) + #expect(complex2.bands == 1) + } + + @Test + func testPolarAndRect() throws { + // Create a complex number 3 + 4i + let real = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 3.0) + let imag = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 4.0) + let complex = try real.complexform(imag) + + // Convert to polar form + let polar = try complex.polar() + + // Magnitude should be 5 (sqrt(3² + 4²)) + // Phase should be atan(4/3) ≈ 53.13 degrees + + // Convert back to rectangular + let rect = try polar.rect() + + // Should get back approximately 3 + 4i + let realPart = try rect.real() + let imagPart = try rect.imag() + + let realAvg = try realPart.avg() + let imagAvg = try imagPart.avg() + + #expect(abs(realAvg - 3.0) < 0.1) + #expect(abs(imagAvg - 4.0) < 0.1) + } + + @Test + func testComplexConjugate() throws { + // Create complex number 2 + 3i + let real = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 2.0) + let imag = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 3.0) + let complex = try real.complexform(imag) + + // Get conjugate (2 - 3i) + let conjugate = try complex.conj() + + // Extract parts + let conjReal = try conjugate.real() + let conjImag = try conjugate.imag() + + // Real part should remain 2 + #expect(abs(try conjReal.avg() - 2.0) < 0.01) + // Imaginary part should be -3 + #expect(abs(try conjImag.avg() - (-3.0)) < 0.01) + } + + @Test + func testRealAndImag() throws { + // Create complex number 5 + 7i + let real = try VIPSImage.black(width: 2, height: 2, bands: 1).linear(0.0, 5.0) + let imag = try VIPSImage.black(width: 2, height: 2, bands: 1).linear(0.0, 7.0) + let complex = try real.complexform(imag) + + // Extract real and imaginary parts + let extractedReal = try complex.real() + let extractedImag = try complex.imag() + + #expect(abs(try extractedReal.avg() - 5.0) < 0.01) + #expect(abs(try extractedImag.avg() - 7.0) < 0.01) + } + + // MARK: - Statistical Operations Tests + + @Test + func testSum() throws { + // Create three images with different values + let img1 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 10.0) + let img2 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 20.0) + let img3 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 30.0) + + // Sum them + let sum = try VIPSImage.sum([img1, img2, img3]) + + // Should be 10 + 20 + 30 = 60 per pixel + let avg = try sum.avg() + #expect(abs(avg - 60.0) < 0.01) + } + + @Test + func testSumSingleImage() throws { + let img = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 42.0) + + // Sum of single image should return the image itself + let sum = try VIPSImage.sum([img]) + + let avg = try sum.avg() + #expect(abs(avg - 42.0) < 0.01) + } + + @Test + func testStats() throws { + // Create a simple test image with uniform values for predictable statistics + // Using a 4x4 image with value 5.0 everywhere for simple verification + let testImg = try VIPSImage.black(width: 4, height: 4, bands: 1).linear(0.0, 5.0) + + // Get statistics + let stats = try testImg.stats() + + // Verify the stats image dimensions + // Stats image format: + // - Width = 10 (number of statistics columns) + // - Height = n+1 where n is number of bands + // For a 1-band image: height = 2 (row 0: all bands together, row 1: band 0) + #expect(stats.width == 10, "Stats should have 10 columns for 10 statistics") + #expect(stats.height == 2, "Stats should have 2 rows for 1-band image") + + // The 10 statistics columns are documented as follows: + // Column 0: minimum + // Column 1: maximum + // Column 2: sum + // Column 3: sum of squares + // Column 4: mean + // Column 5: standard deviation + // Column 6: x coordinate of minimum + // Column 7: y coordinate of minimum + // Column 8: x coordinate of maximum + // Column 9: y coordinate of maximum + + // Read the actual statistics from row 0 (all bands together) + // For a 4x4 image with all values = 5.0: + let row0Stats = try (0..<10) + .map { x in + try stats.getpoint(x: x, y: 0)[0] + } + + // Verify each statistic: + #expect(row0Stats[0] == 5.0, "Column 0 (minimum) should be 5.0, got \(row0Stats[0])") + #expect(row0Stats[1] == 5.0, "Column 1 (maximum) should be 5.0, got \(row0Stats[1])") + #expect( + row0Stats[2] == 80.0, + "Column 2 (sum) should be 80.0 (5.0 * 16), got \(row0Stats[2])" + ) + #expect( + row0Stats[3] == 400.0, + "Column 3 (sum of squares) should be 400.0 (25 * 16), got \(row0Stats[3])" + ) + #expect(row0Stats[4] == 5.0, "Column 4 (mean) should be 5.0, got \(row0Stats[4])") + #expect(row0Stats[5] == 0.0, "Column 5 (std dev) should be 0.0, got \(row0Stats[5])") + // Coordinates can be any pixel since all values are the same + #expect( + row0Stats[6] >= 0 && row0Stats[6] < 4, + "Column 6 (x of min) should be valid coordinate" + ) + #expect( + row0Stats[7] >= 0 && row0Stats[7] < 4, + "Column 7 (y of min) should be valid coordinate" + ) + #expect( + row0Stats[8] >= 0 && row0Stats[8] < 4, + "Column 8 (x of max) should be valid coordinate" + ) + #expect( + row0Stats[9] >= 0 && row0Stats[9] < 4, + "Column 9 (y of max) should be valid coordinate" + ) + + // Test with a more complex image with varied values + // For testing varied values, let's use the identity LUT + // Identity creates a 256x1 image with values 0-255 + let identityImg = try VIPSImage.identity(bands: 1, size: 256) + .cast(.double) + + // Extract just the first 9 pixels using crop + let variedImg = try identityImg.crop(left: 0, top: 0, width: 9, height: 1) + .linear(1.0, 1.0) // Add 1 to get values 1-9 + + let variedStats = try variedImg.stats() + #expect(variedStats.width == 10, "Varied stats should have 10 columns") + + // Read statistics for cropped identity image (values 1-9) + let variedRow0 = try (0..<10) + .map { x in + try variedStats.getpoint(x: x, y: 0)[0] + } + + // Verify statistics for values 1-9: + #expect(variedRow0[0] == 1.0, "Minimum should be 1.0, got \(variedRow0[0])") + #expect(variedRow0[1] == 9.0, "Maximum should be 9.0, got \(variedRow0[1])") + // Sum: 1+2+3+4+5+6+7+8+9 = 45 + #expect(variedRow0[2] == 45.0, "Sum should be 45.0, got \(variedRow0[2])") + // Sum of squares: 1+4+9+16+25+36+49+64+81 = 285 + #expect(variedRow0[3] == 285.0, "Sum of squares should be 285.0, got \(variedRow0[3])") + // Mean: 45/9 = 5 + #expect(variedRow0[4] == 5.0, "Mean should be 5.0, got \(variedRow0[4])") + // Std dev: For values 1-9, the population std dev is ~2.738 + // The sample std dev would be slightly higher + #expect( + abs(variedRow0[5] - 2.7386128) < 0.001, + "Std dev should be ~2.738, got \(variedRow0[5])" + ) + + // Additional test: multi-band image statistics + let band1 = try VIPSImage.black(width: 2, height: 2, bands: 1).linear(0.0, 2.0) // All 2s + let band2 = try VIPSImage.black(width: 2, height: 2, bands: 1).linear(0.0, 4.0) // All 4s + let multiBand = try band1.bandjoin([band2]) + + let multiBandStats = try multiBand.stats() + + // For a 2-band image: height should be 3 (row 0: all bands, row 1: band 0, row 2: band 1) + #expect(multiBandStats.width == 10, "Multi-band stats should have 10 columns") + #expect(multiBandStats.height == 3, "Stats should have 3 rows for 2-band image") + + // Verify statistics for band 0 (row 1) from the stats image + let band0Stats = try (0..<10) + .map { x in + try multiBandStats.getpoint(x: x, y: 1)[0] + } + #expect(band0Stats[0] == 2.0, "Band 0 minimum should be 2.0, got \(band0Stats[0])") + #expect(band0Stats[1] == 2.0, "Band 0 maximum should be 2.0, got \(band0Stats[1])") + #expect( + band0Stats[2] == 8.0, + "Band 0 sum should be 8.0 (2.0 * 4), got \(band0Stats[2])" + ) + #expect( + band0Stats[3] == 16.0, + "Band 0 sum of squares should be 16.0 (4 * 4), got \(band0Stats[3])" + ) + #expect(band0Stats[4] == 2.0, "Band 0 mean should be 2.0, got \(band0Stats[4])") + #expect(band0Stats[5] == 0.0, "Band 0 std dev should be 0.0, got \(band0Stats[5])") + + // Verify statistics for band 1 (row 2) from the stats image + let band1Stats = try (0..<10) + .map { x in + try multiBandStats.getpoint(x: x, y: 2)[0] + } + #expect(band1Stats[0] == 4.0, "Band 1 minimum should be 4.0, got \(band1Stats[0])") + #expect(band1Stats[1] == 4.0, "Band 1 maximum should be 4.0, got \(band1Stats[1])") + #expect( + band1Stats[2] == 16.0, + "Band 1 sum should be 16.0 (4.0 * 4), got \(band1Stats[2])" + ) + #expect( + band1Stats[3] == 64.0, + "Band 1 sum of squares should be 64.0 (16 * 4), got \(band1Stats[3])" + ) + #expect(band1Stats[4] == 4.0, "Band 1 mean should be 4.0, got \(band1Stats[4])") + #expect(band1Stats[5] == 0.0, "Band 1 std dev should be 0.0, got \(band1Stats[5])") + } + + @Test + func testProfile() throws { + // Create a simple 3x3 image with known values + let img = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(1.0, 1.0) + + // Get profiles (averages across rows and columns) + let profiles = try img.profile() + + // Column profile should be 3x1 (average of each column) + // Row profile should be 1x3 (average of each row) + let colWidth = profiles.columns.width + let colHeight = profiles.columns.height + let rowWidth = profiles.rows.width + let rowHeight = profiles.rows.height + + #expect(colWidth == 3) + #expect(colHeight == 1) + #expect(rowWidth == 1) + #expect(rowHeight == 3) + } + + @Test + func testProject() throws { + // Create a simple 3x3 image with known values + let img = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(1.0, 0.0) + + // Project to get row and column sums + let projection = try img.project() + + // Each row should have same sum (all pixels are 0) + // Each column should have same sum (all pixels are 0) + let rowWidth = projection.rows.width + let rowHeight = projection.rows.height + let colWidth = projection.columns.width + let colHeight = projection.columns.height + + #expect(rowWidth == 1) + #expect(rowHeight == 3) + #expect(colWidth == 3) + #expect(colHeight == 1) + } + + // MARK: - Band Operations Tests + + @Test + func testBandAnd() throws { + // Create multi-band image + let band1 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 7.0) + .cast(.uchar) // 0b0111 + let band2 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 5.0) + .cast(.uchar) // 0b0101 + let img = try band1.bandjoin([band2]) + + // AND across bands: 7 & 5 = 5 + let result = try img.bandand() + + #expect(result.bands == 1) + let avg = try result.avg() + #expect(abs(avg - 5.0) < 0.01) + } + + @Test + func testBandOr() throws { + // Create multi-band image + let band1 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 4.0) + .cast(.uchar) // 0b0100 + let band2 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 2.0) + .cast(.uchar) // 0b0010 + let img = try band1.bandjoin([band2]) + + // OR across bands: 4 | 2 = 6 + let result = try img.bandor() + + #expect(result.bands == 1) + let avg = try result.avg() + #expect(abs(avg - 6.0) < 0.01) + } + + @Test + func testBandEor() throws { + // Create multi-band image + let band1 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 7.0) + .cast(.uchar) // 0b0111 + let band2 = try VIPSImage.black(width: 3, height: 3, bands: 1).linear(0.0, 3.0) + .cast(.uchar) // 0b0011 + let img = try band1.bandjoin([band2]) + + // XOR across bands: 7 ^ 3 = 4 + let result = try img.bandeor() + + #expect(result.bands == 1) + let avg = try result.avg() + #expect(abs(avg - 4.0) < 0.01) + } + + @Test(.disabled()) + func testREADMEArithmeticExamples() throws { + let tempDir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) + .appendingPathComponent("vips-arithmetic-test-\(UUID().uuidString)") + + try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) + + defer { + try? FileManager.default.removeItem(at: tempDir) + } + + // Create two simple test images instead of loading from files to avoid compatibility issues + let image1 = try VIPSImage.black(width: 100, height: 100, bands: 3) + .linear([1.0, 1.0, 1.0], [100.0, 150.0, 200.0]) // RGB values: (100, 150, 200) + + let image2 = try VIPSImage.black(width: 100, height: 100, bands: 3) + .linear([1.0, 1.0, 1.0], [80.0, 120.0, 160.0]) // RGB values: (80, 120, 160) + + // Image arithmetic operations from README + let sum = try image1 + image2 + let difference = try image1 - image2 + let product = try image1 * image2 + let quotient = try image1 / (image2 + 1) // Add 1 to avoid division by zero + + // Scalar operations from README + let brightened = try image1 + 50 // Add 50 to all pixels + let dimmed = try image1 * 0.8 // Scale all pixels by 0.8 + let enhanced = try image1 * [1.2, 1.0, 0.9] // Per-channel scaling + + // Linear transformation: a * image + b + let linearized = try image1.linear([1.1, 1.0, 0.9], [10, 0, -5]) + + // Save all results to temp directory for inspection + let results: [(VIPSImage, String)] = [ + (image1, "image1.jpg"), + (image2, "image2.jpg"), + (sum, "sum.jpg"), + (difference, "difference.jpg"), + (product, "product.jpg"), + (quotient, "quotient.jpg"), + (brightened, "brightened.jpg"), + (dimmed, "dimmed.jpg"), + (enhanced, "enhanced.jpg"), + (linearized, "linearized.jpg"), + ] + + for (resultImage, filename) in results { + let outputPath = tempDir.appendingPathComponent(filename).path + try resultImage.writeToFile(outputPath) + + // Verify the file was created and has content + let fileSize = + try FileManager.default.attributesOfItem(atPath: outputPath)[.size] as! Int64 + #expect(fileSize > 0, "Output file \(filename) should not be empty") + + // Verify we can read it back + let verifyImage = try VIPSImage(fromFilePath: outputPath) + #expect(verifyImage.width > 0) + #expect(verifyImage.height > 0) + } + + // Verify some arithmetic results are as expected + let sumAvg = try sum.avg() + let expectedSumAvg = (100 + 80 + 150 + 120 + 200 + 160) / 3.0 // Average of all channels + assertAlmostEqual(sumAvg, expectedSumAvg, threshold: 1.0) + + let brightenedAvg = try brightened.avg() + let expectedBrightenedAvg = (100 + 50 + 150 + 50 + 200 + 50) / 3.0 + assertAlmostEqual(brightenedAvg, expectedBrightenedAvg, threshold: 1.0) + + print("All arithmetic operations completed successfully!") + print("Results written to: \(tempDir.path)") + print("Files created:") + for (_, filename) in results { + print(" - \(filename)") + } + } + } - -} \ No newline at end of file +} diff --git a/Tests/VIPSTests/ArithmeticTests.swift b/Tests/VIPSTests/ArithmeticTests.swift new file mode 100644 index 0000000..5807cd1 --- /dev/null +++ b/Tests/VIPSTests/ArithmeticTests.swift @@ -0,0 +1,432 @@ +import Cvips +import Foundation +import Testing + +@testable import VIPS + +extension VIPSTests { + @Suite(.vips, .serialized) + struct ArithmeticTests { + + // MARK: - Basic Arithmetic Operations + + @Test + func testAddOperations() throws { + let image1 = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 50.0) + let image2 = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 25.0) + + // Test image + image + let added = try image1.add(image2) + #expect(try added.avg() == 75.0) + + // Test with operator + let addedOp = try image1 + image2 + #expect(try addedOp.avg() == 75.0) + + // Test image + constant + let addedConst = try image1 + 10.0 + #expect(try addedConst.avg() == 60.0) + } + + @Test + func testSubtractOperations() throws { + let image1 = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 100.0) + let image2 = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 30.0) + + // Test image - image + let subtracted = try image1.subtract(image2) + #expect(try subtracted.avg() == 70.0) + + // Test with operator + let subtractedOp = try image1 - image2 + #expect(try subtractedOp.avg() == 70.0) + + // Test image - constant (using linear for subtraction: y = x*1 - 20) + let subtractedConst = try image1.linear(1.0, -20.0) + #expect(try subtractedConst.avg() == 80.0) + } + + @Test + func testMultiplyOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 5.0) + let multiplier = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 2.0) + + // Test image * image + let multiplied = try image.multiply(multiplier) + #expect(try multiplied.avg() == 10.0) + + // Test with operator + let multipliedOp = try image * multiplier + #expect(try multipliedOp.avg() == 10.0) + + // Test image * constant + let multipliedConst = try image * 3.0 + #expect(try multipliedConst.avg() == 15.0) + } + + @Test + func testDivideOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 100.0) + let divisor = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 4.0) + + // Test image / image + let divided = try image.divide(divisor) + #expect(try divided.avg() == 25.0) + + // Test with operator + let dividedOp = try image / divisor + #expect(try dividedOp.avg() == 25.0) + + // Test image / constant (using linear for division: y = x*0.5 + 0) + let dividedConst = try image.linear(0.5, 0.0) + #expect(try dividedConst.avg() == 50.0) + } + + @Test + func testRemainderOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 10.0) + let divisor = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 3.0) + + // Test image % image + let remainder = try image.remainder(divisor) + #expect(try remainder.avg() == 1.0) + + // Test with operator - % operator not defined for VIPSImage + // let remainderOp = try image % divisor + // #expect(try remainderOp.avg() == 1.0) + + // Test image % constant + let remainderConst = try image.remainder(4.0) + #expect(try remainderConst.avg() == 2.0) + } + + @Test + func testPowerOperations() throws { + let base = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 2.0) + let exponent = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 3.0) + + // Test pow(image, image) + let powered = try base.pow(exponent) + #expect(try powered.avg() == 8.0) + + // Test pow(image, constant) + let poweredConst = try base.pow(4.0) + #expect(try poweredConst.avg() == 16.0) + } + + // MARK: - Mathematical Functions + + @Test + func testMathOperations() throws { + // Test exp + let zeros = try VIPSImage.black(width: 10, height: 10) + let expResult = try zeros.math(.exp) + #expect(abs(try expResult.avg() - 1.0) < 0.001) + + // Test log + let ones = try VIPSImage.black(width: 10, height: 10) + .linear(0.0, exp(1.0)) + let logResult = try ones.math(.log) + #expect(abs(try logResult.avg() - 1.0) < 0.001) + + // Test log10 + let tens = try VIPSImage.black(width: 10, height: 10) + .linear(0.0, 10.0) + let log10Result = try tens.math(.log10) + #expect(abs(try log10Result.avg() - 1.0) < 0.001) + } + + @Test + func testMath2Operations() throws { + let image1 = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 3.0) + let image2 = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 4.0) + + // Test wop (weighted operation) + let wop = try image1.math2(image2, math2: .wop) + #expect(try wop.avg() > 0) + } + + // MARK: - Complex Operations + + @Test + func testComplexOperations() throws { + // Create a complex image (real + imaginary parts) + let real = try VIPSImage.black(width: 10, height: 10, bands: 1) + .linear(1.0, 1.0) + let imag = try VIPSImage.black(width: 10, height: 10, bands: 1) + .linear(1.0, 0.0) + // Note: For complex operations to work properly, the image needs to be in complex format + // However, complexget still returns a 2-band image when extracting parts + let complexImage = try real.bandjoin([imag]) + + // Test complex operations + let conjugate = try complexImage.complex(cmplx: .conj) + #expect(conjugate.bands == 2) + + // Test getting real part - complexget returns a 2-band image with the real part data + let realPart = try complexImage.complexget(get: .real) + #expect(realPart.bands == 2) + // The average of a 2-band image where band 0 is 1.0 and band 1 is 0.0 is 0.5 + #expect(abs(try realPart.avg() - 0.5) < 0.001) + + // Test getting imaginary part + let imagPart = try complexImage.complexget(get: .imag) + #expect(imagPart.bands == 2) + #expect(abs(try imagPart.avg() - 0.0) < 0.001) + } + + // MARK: - Boolean Operations + + @Test + func testBooleanOperations() throws { + let image1 = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 170.0) // Binary: 10101010 + let image2 = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 204.0) // Binary: 11001100 + + // Test AND + let andResult = try image1.boolean(image2, boolean: .and) + #expect(try andResult.avg() == 136.0) // 10001000 + + // Test OR + let orResult = try image1.boolean(image2, boolean: .or) + #expect(try orResult.avg() == 238.0) // 11101110 + + // Test XOR + let xorResult = try image1.boolean(image2, boolean: .eor) + #expect(try xorResult.avg() == 102.0) // 01100110 + } + + @Test + func testBooleanConstOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 170.0) // Binary: 10101010 + + // Test AND with constant + let andConst = try image.booleanConst(boolean: .and, c: [204.0]) // 11001100 + #expect(try andConst.avg() == 136.0) // 10001000 + + // Test OR with constant + let orConst = try image.booleanConst(boolean: .or, c: [204.0]) + #expect(try orConst.avg() == 238.0) // 11101110 + + // Test XOR with constant + let xorConst = try image.booleanConst(boolean: .eor, c: [204.0]) + #expect(try xorConst.avg() == 102.0) // 01100110 + } + + @Test + func testBitwiseOperators() throws { + let image1 = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 170.0) + let image2 = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 204.0) + + // Test bitwise AND operator + let andOp = try image1 & image2 + #expect(try andOp.avg() == 136.0) + + // Test bitwise OR operator + let orOp = try image1 | image2 + #expect(try orOp.avg() == 238.0) + + // Test bitwise XOR operator + let xorOp = try image1 ^ image2 + #expect(try xorOp.avg() == 102.0) + + // Test with constants - bitwise ops with constants not supported + // let andConstOp = try image1 & 204.0 + // #expect(try andConstOp.avg() == 136.0) + } + + @Test + func testShiftOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 4.0) + + // Test left shift + let leftShift = try image << 2 + #expect(try leftShift.avg() == 16.0) + + // Test right shift + let rightShift = try image >> 1 + #expect(try rightShift.avg() == 2.0) + } + + // MARK: - Statistical Operations + + @Test + func testStatisticalOperations() throws { + // Create an image with varying values + let image = try VIPSImage.gaussnoise(width: 100, height: 100, sigma: 10.0, mean: 100.0) + + // Test avg + let avg = try image.avg() + #expect(abs(avg - 100.0) < 5.0) // Allow some variance + + // Test deviate (standard deviation) + let deviate = try image.deviate() + #expect(abs(deviate - 10.0) < 2.0) // Allow some variance + + // Test min + let minVal = try image.min() + #expect(minVal < 100.0) + + // Test max + let maxVal = try image.max() + #expect(maxVal > 100.0) + + // Test sum - sum is a static method that sums multiple images + // For summing pixels in an image, we need to use a different approach + // let sum = try image.sum() + // #expect(abs(sum - 1000000.0) < 50000.0) + + // Test stats (returns array: min, max, sum, sum_sq, mean, deviation) + let stats = try image.stats() + #expect(stats.bands == 1) + } + + @Test + func testHistogramOperations() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + .cast(format: .uchar) // Cast to uchar for proper histogram generation + + // Test histogram generation - for uchar images, histogram should be 256 wide + let hist = try image.histFind() + #expect(hist.width == 256) + #expect(hist.height == 1) + + // Test histogram operations + let cumulative = try hist.histCum() + #expect(cumulative.width > 0) + + let normalized = try hist.histNorm() + #expect(normalized.width > 0) + } + + // MARK: - Miscellaneous Arithmetic + + @Test + func testLinearOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 50.0) + + // Test linear transformation: output = a * input + b + let transformed = try image.linear(2.0, 10.0) + #expect(try transformed.avg() == 110.0) // 2 * 50 + 10 + + // Test linear with arrays for multi-band + let rgbImage = try image.bandjoin([image, image]) + let rgbTransformed = try rgbImage.linear([1.0, 2.0, 3.0], [10.0, 20.0, 30.0]) + #expect(rgbTransformed.bands == 3) + } + + @Test + func testInvertOperations() throws { + // Test standard invert - for uchar images, invert does 255-x + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 200.0) + .cast(format: .uchar) // Cast to uchar for proper inversion behavior + + let inverted = try image.invert() + #expect(try inverted.avg() == 55.0) // 255 - 200 + } + + @Test + func testAbsoluteValue() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, -50.0) + + let absImage = try image.abs() + #expect(try absImage.avg() == 50.0) + #expect(try absImage.min() >= 0) + } + + @Test + func testSignOperation() throws { + let positive = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 50.0) + let negative = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, -50.0) + let zero = try VIPSImage.black(width: 10, height: 10) + + #expect(try positive.sign().avg() == 1.0) + #expect(try negative.sign().avg() == -1.0) + #expect(try zero.sign().avg() == 0.0) + } + + @Test + func testRoundingOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 10.7) + + // Test different rounding modes + let rounded = try image.round(.rint) + #expect(try rounded.avg() == 11.0) + + let floored = try image.round(.floor) + #expect(try floored.avg() == 10.0) + + let ceiled = try image.round(.ceil) + #expect(try ceiled.avg() == 11.0) + } + + @Test + func testRelationalOperations() throws { + let image1 = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 50.0) + let image2 = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 100.0) + + // Test relational operations + let equal = try image1.relational(image1, relational: .equal) + #expect(try equal.avg() == 255.0) // All pixels equal + + let notEqual = try image1.relational(image2, relational: .noteq) + #expect(try notEqual.avg() == 255.0) // All pixels not equal + + let less = try image1.relational(image2, relational: .less) + #expect(try less.avg() == 255.0) // All pixels less + + let lessEq = try image1.relational(image2, relational: .lesseq) + #expect(try lessEq.avg() == 255.0) // All pixels less or equal + + let more = try image2.relational(image1, relational: .more) + #expect(try more.avg() == 255.0) // All pixels more + + let moreEq = try image2.relational(image1, relational: .moreeq) + #expect(try moreEq.avg() == 255.0) // All pixels more or equal + } + + @Test + func testRelationalConstOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 128.0) + + // Test relational operations with constants + let equal = try image.relationalConst(relational: .equal, c: [128.0]) + #expect(try equal.avg() == 255.0) + + let less = try image.relationalConst(relational: .less, c: [200.0]) + #expect(try less.avg() == 255.0) + + let more = try image.relationalConst(relational: .more, c: [100.0]) + #expect(try more.avg() == 255.0) + } + } +} diff --git a/Tests/VIPSTests/ConversionGeneratedTests.swift b/Tests/VIPSTests/ConversionGeneratedTests.swift deleted file mode 100644 index 00e5742..0000000 --- a/Tests/VIPSTests/ConversionGeneratedTests.swift +++ /dev/null @@ -1,463 +0,0 @@ -@testable import VIPS -import Cvips -import Testing -import Foundation - -@Suite(.vips) -struct ConversionGeneratedTests { - - // MARK: - Type Casting Operations - - @Test - func testCastOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 128.5) - - // Test casting to different formats - let ucharImage = try image.cast(.uchar) - #expect(try ucharImage.avg() == 128.0) // 128.5 truncated to 128 - - let floatImage = try image.cast(.float) - #expect(abs(try floatImage.avg() - 128.5) < 0.01) - - let intImage = try image.cast(.int) - #expect(try intImage.avg() == 128.0) - } - - // MARK: - Geometric Transformations - - @Test - func testFlipOperations() throws { - // Create a gradient image for testing - let image = try VIPSImage.xyz(width: 10, height: 10) - .extractBand(0) // Get x coordinate - - // Test horizontal flip - let hFlipped = try image.flip(direction: .horizontal) - #expect(hFlipped.width == image.width) - #expect(hFlipped.height == image.height) - - // Test vertical flip - let vFlipped = try image.flip(direction: .vertical) - #expect(vFlipped.width == image.width) - #expect(vFlipped.height == image.height) - } - - @Test - func testRotateOperations() throws { - let image = try VIPSImage.black(width: 10, height: 20) - .linear(1.0, 100.0) - - // Test 90 degree rotation - let rot90 = try image.rot(angle: .d90) - #expect(rot90.width == 20) - #expect(rot90.height == 10) - - // Test 180 degree rotation - let rot180 = try image.rot(angle: .d180) - #expect(rot180.width == 10) - #expect(rot180.height == 20) - - // Test 270 degree rotation - let rot270 = try image.rot(angle: .d270) - #expect(rot270.width == 20) - #expect(rot270.height == 10) - } - - @Test - func testRot45Operations() throws { - // rot45 requires images to be odd and square - let image = try VIPSImage.black(width: 11, height: 11) - .linear(1.0, 100.0) - - // Test 45 degree rotations - let rot45 = try image.rot45(angle: .d45) - #expect(rot45.width > 0) - #expect(rot45.height > 0) - } - - @Test - func testAutorotOperation() throws { - let image = try VIPSImage.black(width: 10, height: 20) - - // Test autorot (should be no-op without EXIF data) - let autorotated = try image.autorot() - #expect(autorotated.width == image.width) - #expect(autorotated.height == image.height) - } - - // MARK: - Resizing Operations - - @Test - func testShrinkOperations() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Test shrink by factor of 2 - let shrunk = try image.shrink(hshrink: 2.0, vshrink: 2.0) - #expect(shrunk.width == 50) - #expect(shrunk.height == 50) - #expect(abs(try shrunk.avg() - 128.0) < 1.0) - - // Test horizontal shrink only - let shrunkH = try image.shrinkh(hshrink: 2) - #expect(shrunkH.width == 50) - #expect(shrunkH.height == 100) - - // Test vertical shrink only - let shrunkV = try image.shrinkv(vshrink: 2) - #expect(shrunkV.width == 100) - #expect(shrunkV.height == 50) - } - - @Test - func testReduceOperations() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Test reduce by factor of 2.5 - let reduced = try image.reduce(hshrink: 2.5, vshrink: 2.5) - #expect(reduced.width == 40) - #expect(reduced.height == 40) - - // Test horizontal reduce only - let reducedH = try image.reduceh(hshrink: 2.5) - #expect(reducedH.width == 40) - #expect(reducedH.height == 100) - - // Test vertical reduce only - let reducedV = try image.reducev(vshrink: 2.5) - #expect(reducedV.width == 100) - #expect(reducedV.height == 40) - } - - @Test - func testZoomOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 128.0) - - // Test zoom by factor of 2 - let zoomed = try image.zoom(xfac: 2, yfac: 2) - #expect(zoomed.width == 20) - #expect(zoomed.height == 20) - #expect(abs(try zoomed.avg() - 128.0) < 1.0) - } - - // MARK: - Band Operations - - @Test - func testBandjoinOperations() throws { - let band1 = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 100.0) - let band2 = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 150.0) - let band3 = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 200.0) - - // Test joining bands - let joined = try band1.bandjoin([band2, band3]) - #expect(joined.bands == 3) - #expect(joined.width == 10) - #expect(joined.height == 10) - } - - @Test - func testBandjoinConstOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 100.0) - - // Test joining constant bands - let joined = try image.bandjoinConst(c: [150.0, 200.0]) - #expect(joined.bands == 3) - } - - @Test - func testBandmeanOperations() throws { - let rgb = try VIPSImage.black(width: 10, height: 10, bands: 3) - .linear([1.0, 1.0, 1.0], [100.0, 150.0, 200.0]) - - // Test band mean - let mean = try rgb.bandmean() - #expect(mean.bands == 1) - #expect(abs(try mean.avg() - 150.0) < 1.0) - } - - @Test - func testBandboolOperations() throws { - let rgb = try VIPSImage.black(width: 10, height: 10, bands: 3) - .linear([1.0, 1.0, 1.0], [170.0, 204.0, 136.0]) - - // Test band boolean operations - let andResult = try rgb.bandbool(.and) - #expect(andResult.bands == 1) - - let orResult = try rgb.bandbool(.or) - #expect(orResult.bands == 1) - - let eorResult = try rgb.bandbool(.eor) - #expect(eorResult.bands == 1) - } - - @Test - func testBandfoldUnfoldOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10, bands: 4) - .linear([1.0, 1.0, 1.0, 1.0], [100.0, 150.0, 200.0, 250.0]) - - // Test band fold - factor 2 means width/2, bands*2 - let folded = try image.bandfold(factor: 2) - #expect(folded.bands == 8) // 4 * 2 - #expect(folded.width == 5) // 10 / 2 - #expect(folded.height == 10) - - // Test band unfold - let unfolded = try folded.bandunfold(factor: 2) - #expect(unfolded.bands == 4) - #expect(unfolded.width == 10) - #expect(unfolded.height == 10) - } - - @Test - func testExtractBandOperations() throws { - let rgb = try VIPSImage.black(width: 10, height: 10, bands: 3) - .linear([1.0, 1.0, 1.0], [100.0, 150.0, 200.0]) - - // Test extracting single band - let band0 = try rgb.extractBand(0) - #expect(band0.bands == 1) - #expect(abs(try band0.avg() - 100.0) < 1.0) - - // Test extracting multiple bands - let bands12 = try rgb.extractBand(1, n: 2) - #expect(bands12.bands == 2) - } - - #if SHIM_VIPS_VERSION_8_16 - // MARK: - Alpha Channel Operations - @Test - func testAlphaOperations() throws { - let rgb = try VIPSImage.black(width: 10, height: 10, bands: 3) - .linear([1.0, 1.0, 1.0], [100.0, 150.0, 200.0]) - - // Test add alpha - let withAlpha = try rgb.addalpha() - #expect(withAlpha.bands == 4) - - // Test flatten (requires alpha channel) - let flattened = try withAlpha.flatten() - #expect(flattened.bands == 3) - - // Test premultiply - let premultiplied = try withAlpha.premultiply() - #expect(premultiplied.bands == 4) - - // Test unpremultiply - let unpremultiplied = try premultiplied.unpremultiply() - #expect(unpremultiplied.bands == 4) - } - #endif - - // MARK: - Area Operations - - @Test - func testExtractAreaOperations() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Test extracting area - let extracted = try image.extractArea(left: 10, top: 10, width: 50, height: 50) - #expect(extracted.width == 50) - #expect(extracted.height == 50) - #expect(abs(try extracted.avg() - 128.0) < 1.0) - } - - @Test - func testCropOperations() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Test crop - let cropped = try image.crop(left: 10, top: 10, width: 50, height: 50) - #expect(cropped.width == 50) - #expect(cropped.height == 50) - } - - @Test - func testSmartcropOperations() throws { - let image = try VIPSImage.gaussnoise(width: 100, height: 100, sigma: 30.0, mean: 128.0) - - // Test smartcrop - let cropped = try image.smartcrop(width: 50, height: 50) - #expect(cropped.width == 50) - #expect(cropped.height == 50) - } - - @Test - func testEmbedOperations() throws { - let image = try VIPSImage.black(width: 50, height: 50) - .linear(1.0, 255.0) - - // Test embed - let embedded = try image.embed(x: 25, y: 25, width: 100, height: 100) - #expect(embedded.width == 100) - #expect(embedded.height == 100) - - // Test gravity - let gravityEmbedded = try image.gravity(direction: .centre, width: 100, height: 100) - #expect(gravityEmbedded.width == 100) - #expect(gravityEmbedded.height == 100) - } - - // MARK: - Image Joining Operations - - @Test - func testJoinOperations() throws { - let image1 = try VIPSImage.black(width: 50, height: 50) - .linear(1.0, 100.0) - let image2 = try VIPSImage.black(width: 50, height: 50) - .linear(1.0, 200.0) - - // Test horizontal join - let hJoined = try image1.join(in2: image2, direction: .horizontal) - #expect(hJoined.width == 100) - #expect(hJoined.height == 50) - - // Test vertical join - let vJoined = try image1.join(in2: image2, direction: .vertical) - #expect(vJoined.width == 50) - #expect(vJoined.height == 100) - } - - @Test - func testInsertOperations() throws { - let background = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 50.0) - let insert = try VIPSImage.black(width: 20, height: 20) - .linear(1.0, 200.0) - - // Test insert - let result = try background.insert(sub: insert, x: 40, y: 40) - #expect(result.width == 100) - #expect(result.height == 100) - } - - @Test - func testArrayjoinOperations() throws { - let images = try (0..<4).map { i in - try VIPSImage.black(width: 50, height: 50) - .linear(1.0, Double(i * 50)) - } - - // Test array join - let joined = try VIPSImage.arrayjoin(images, across: 2) - #expect(joined.width == 100) - #expect(joined.height == 100) - } - - // MARK: - Caching Operations - - @Test - func testCacheOperations() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Test cache - skipped as cache() method doesn't exist - - // Test tilecache - let tilecached = try image.tilecache() - #expect(tilecached.width == image.width) - #expect(tilecached.height == image.height) - - // Test linecache - let linecached = try image.linecache() - #expect(linecached.width == image.width) - #expect(linecached.height == image.height) - - // Test sequential - let sequential = try image.sequential() - #expect(sequential.width == image.width) - #expect(sequential.height == image.height) - } - - // MARK: - Copy Operations - - @Test - func testCopyOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10, bands: 3) - .linear([1.0, 1.0, 1.0], [100.0, 150.0, 200.0]) - - // Test copy - let copied = try image.copy() - #expect(copied.width == image.width) - #expect(copied.height == image.height) - #expect(copied.bands == image.bands) - } - - // MARK: - Other Transformations - - @Test - func testWrapOperations() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Test wrap - let wrapped = try image.wrap() - #expect(wrapped.width == image.width) - #expect(wrapped.height == image.height) - } - - @Test - func testSubsampleOperations() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Test subsample - let subsampled = try image.subsample(xfac: 2, yfac: 2) - #expect(subsampled.width == 50) - #expect(subsampled.height == 50) - } - - @Test - func testMsbOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 65535.0) - .cast(.ushort) - - // Test msb (most significant byte) - let msb = try image.msb() - #expect(msb.width == image.width) - #expect(msb.height == image.height) - } - - @Test - func testByteswapOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 256.0) - .cast(.ushort) - - // Test byteswap - let swapped = try image.byteswap() - #expect(swapped.width == image.width) - #expect(swapped.height == image.height) - } - - @Test - func testFalsecolourOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0, 128.0) - - // Test falsecolour - let falsecoloured = try image.falsecolour() - #expect(falsecoloured.bands == 3) - } - - @Test - func testGammaOperations() throws { - let image = try VIPSImage.black(width: 10, height: 10) - .linear(1.0 / 255.0, 0.0) // Normalize to 0-1 range - - // Test gamma correction - let gammaCorrected = try image.gamma() - #expect(gammaCorrected.width == image.width) - #expect(gammaCorrected.height == image.height) - } -} \ No newline at end of file diff --git a/Tests/VIPSTests/ConversionTests.swift b/Tests/VIPSTests/ConversionTests.swift new file mode 100644 index 0000000..0083fc8 --- /dev/null +++ b/Tests/VIPSTests/ConversionTests.swift @@ -0,0 +1,467 @@ +import Cvips +import Foundation +import Testing + +@testable import VIPS + +extension VIPSTests { + @Suite(.vips, .serialized) + struct ConversionTests { + + // MARK: - Type Casting Operations + + @Test + func testCastOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 128.5) + + // Test casting to different formats + let ucharImage = try image.cast(.uchar) + #expect(try ucharImage.avg() == 128.0) // 128.5 truncated to 128 + + let floatImage = try image.cast(.float) + #expect(abs(try floatImage.avg() - 128.5) < 0.01) + + let intImage = try image.cast(.int) + #expect(try intImage.avg() == 128.0) + } + + // MARK: - Geometric Transformations + + @Test + func testFlipOperations() throws { + // Create a gradient image for testing + let image = try VIPSImage.xyz(width: 10, height: 10) + .extractBand(0) // Get x coordinate + + // Test horizontal flip + let hFlipped = try image.flip(direction: .horizontal) + #expect(hFlipped.width == image.width) + #expect(hFlipped.height == image.height) + + // Test vertical flip + let vFlipped = try image.flip(direction: .vertical) + #expect(vFlipped.width == image.width) + #expect(vFlipped.height == image.height) + } + + @Test + func testRotateOperations() throws { + let image = try VIPSImage.black(width: 10, height: 20) + .linear(1.0, 100.0) + + // Test 90 degree rotation + let rot90 = try image.rot(angle: .d90) + #expect(rot90.width == 20) + #expect(rot90.height == 10) + + // Test 180 degree rotation + let rot180 = try image.rot(angle: .d180) + #expect(rot180.width == 10) + #expect(rot180.height == 20) + + // Test 270 degree rotation + let rot270 = try image.rot(angle: .d270) + #expect(rot270.width == 20) + #expect(rot270.height == 10) + } + + @Test + func testRot45Operations() throws { + // rot45 requires images to be odd and square + let image = try VIPSImage.black(width: 11, height: 11) + .linear(1.0, 100.0) + + // Test 45 degree rotations + let rot45 = try image.rot45(angle: .d45) + #expect(rot45.width > 0) + #expect(rot45.height > 0) + } + + @Test + func testAutorotOperation() throws { + let image = try VIPSImage.black(width: 10, height: 20) + + // Test autorot (should be no-op without EXIF data) + let autorotated = try image.autorot() + #expect(autorotated.width == image.width) + #expect(autorotated.height == image.height) + } + + // MARK: - Resizing Operations + + @Test + func testShrinkOperations() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Test shrink by factor of 2 + let shrunk = try image.shrink(hshrink: 2.0, vshrink: 2.0) + #expect(shrunk.width == 50) + #expect(shrunk.height == 50) + #expect(abs(try shrunk.avg() - 128.0) < 1.0) + + // Test horizontal shrink only + let shrunkH = try image.shrinkh(hshrink: 2) + #expect(shrunkH.width == 50) + #expect(shrunkH.height == 100) + + // Test vertical shrink only + let shrunkV = try image.shrinkv(vshrink: 2) + #expect(shrunkV.width == 100) + #expect(shrunkV.height == 50) + } + + @Test + func testReduceOperations() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Test reduce by factor of 2.5 + let reduced = try image.reduce(hshrink: 2.5, vshrink: 2.5) + #expect(reduced.width == 40) + #expect(reduced.height == 40) + + // Test horizontal reduce only + let reducedH = try image.reduceh(hshrink: 2.5) + #expect(reducedH.width == 40) + #expect(reducedH.height == 100) + + // Test vertical reduce only + let reducedV = try image.reducev(vshrink: 2.5) + #expect(reducedV.width == 100) + #expect(reducedV.height == 40) + } + + @Test + func testZoomOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 128.0) + + // Test zoom by factor of 2 + let zoomed = try image.zoom(xfac: 2, yfac: 2) + #expect(zoomed.width == 20) + #expect(zoomed.height == 20) + #expect(abs(try zoomed.avg() - 128.0) < 1.0) + } + + // MARK: - Band Operations + + @Test + func testBandjoinOperations() throws { + let band1 = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 100.0) + let band2 = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 150.0) + let band3 = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 200.0) + + // Test joining bands + let joined = try band1.bandjoin([band2, band3]) + #expect(joined.bands == 3) + #expect(joined.width == 10) + #expect(joined.height == 10) + } + + @Test + func testBandjoinConstOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 100.0) + + // Test joining constant bands + let joined = try image.bandjoinConst(c: [150.0, 200.0]) + #expect(joined.bands == 3) + } + + @Test + func testBandmeanOperations() throws { + let rgb = try VIPSImage.black(width: 10, height: 10, bands: 3) + .linear([1.0, 1.0, 1.0], [100.0, 150.0, 200.0]) + + // Test band mean + let mean = try rgb.bandmean() + #expect(mean.bands == 1) + #expect(abs(try mean.avg() - 150.0) < 1.0) + } + + @Test + func testBandboolOperations() throws { + let rgb = try VIPSImage.black(width: 10, height: 10, bands: 3) + .linear([1.0, 1.0, 1.0], [170.0, 204.0, 136.0]) + + // Test band boolean operations + let andResult = try rgb.bandbool(.and) + #expect(andResult.bands == 1) + + let orResult = try rgb.bandbool(.or) + #expect(orResult.bands == 1) + + let eorResult = try rgb.bandbool(.eor) + #expect(eorResult.bands == 1) + } + + @Test + func testBandfoldUnfoldOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10, bands: 4) + .linear([1.0, 1.0, 1.0, 1.0], [100.0, 150.0, 200.0, 250.0]) + + // Test band fold - factor 2 means width/2, bands*2 + let folded = try image.bandfold(factor: 2) + #expect(folded.bands == 8) // 4 * 2 + #expect(folded.width == 5) // 10 / 2 + #expect(folded.height == 10) + + // Test band unfold + let unfolded = try folded.bandunfold(factor: 2) + #expect(unfolded.bands == 4) + #expect(unfolded.width == 10) + #expect(unfolded.height == 10) + } + + @Test + func testExtractBandOperations() throws { + let rgb = try VIPSImage.black(width: 10, height: 10, bands: 3) + .linear([1.0, 1.0, 1.0], [100.0, 150.0, 200.0]) + + // Test extracting single band + let band0 = try rgb.extractBand(0) + #expect(band0.bands == 1) + #expect(abs(try band0.avg() - 100.0) < 1.0) + + // Test extracting multiple bands + let bands12 = try rgb.extractBand(1, n: 2) + #expect(bands12.bands == 2) + } + + #if SHIM_VIPS_VERSION_8_16 + // MARK: - Alpha Channel Operations + @Test + func testAlphaOperations() throws { + let rgb = try VIPSImage.black(width: 10, height: 10, bands: 3) + .linear([1.0, 1.0, 1.0], [100.0, 150.0, 200.0]) + + // Test add alpha + let withAlpha = try rgb.addalpha() + #expect(withAlpha.bands == 4) + + // Test flatten (requires alpha channel) + let flattened = try withAlpha.flatten() + #expect(flattened.bands == 3) + + // Test premultiply + let premultiplied = try withAlpha.premultiply() + #expect(premultiplied.bands == 4) + + // Test unpremultiply + let unpremultiplied = try premultiplied.unpremultiply() + #expect(unpremultiplied.bands == 4) + } + #endif + + // MARK: - Area Operations + + @Test + func testExtractAreaOperations() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Test extracting area + let extracted = try image.extractArea(left: 10, top: 10, width: 50, height: 50) + #expect(extracted.width == 50) + #expect(extracted.height == 50) + #expect(abs(try extracted.avg() - 128.0) < 1.0) + } + + @Test + func testCropOperations() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Test crop + let cropped = try image.crop(left: 10, top: 10, width: 50, height: 50) + #expect(cropped.width == 50) + #expect(cropped.height == 50) + } + + @Test + func testSmartcropOperations() throws { + let image = try VIPSImage.gaussnoise(width: 100, height: 100, sigma: 30.0, mean: 128.0) + + // Test smartcrop + let cropped = try image.smartcrop(width: 50, height: 50) + #expect(cropped.width == 50) + #expect(cropped.height == 50) + } + + @Test + func testEmbedOperations() throws { + let image = try VIPSImage.black(width: 50, height: 50) + .linear(1.0, 255.0) + + // Test embed + let embedded = try image.embed(x: 25, y: 25, width: 100, height: 100) + #expect(embedded.width == 100) + #expect(embedded.height == 100) + + // Test gravity + let gravityEmbedded = try image.gravity(direction: .centre, width: 100, height: 100) + #expect(gravityEmbedded.width == 100) + #expect(gravityEmbedded.height == 100) + } + + // MARK: - Image Joining Operations + + @Test + func testJoinOperations() throws { + let image1 = try VIPSImage.black(width: 50, height: 50) + .linear(1.0, 100.0) + let image2 = try VIPSImage.black(width: 50, height: 50) + .linear(1.0, 200.0) + + // Test horizontal join + let hJoined = try image1.join(in2: image2, direction: .horizontal) + #expect(hJoined.width == 100) + #expect(hJoined.height == 50) + + // Test vertical join + let vJoined = try image1.join(in2: image2, direction: .vertical) + #expect(vJoined.width == 50) + #expect(vJoined.height == 100) + } + + @Test + func testInsertOperations() throws { + let background = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 50.0) + let insert = try VIPSImage.black(width: 20, height: 20) + .linear(1.0, 200.0) + + // Test insert + let result = try background.insert(sub: insert, x: 40, y: 40) + #expect(result.width == 100) + #expect(result.height == 100) + } + + @Test + func testArrayjoinOperations() throws { + let images = try (0..<4) + .map { i in + try VIPSImage.black(width: 50, height: 50) + .linear(1.0, Double(i * 50)) + } + + // Test array join + let joined = try VIPSImage.arrayjoin(images, across: 2) + #expect(joined.width == 100) + #expect(joined.height == 100) + } + + // MARK: - Caching Operations + + @Test + func testCacheOperations() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Test cache - skipped as cache() method doesn't exist + + // Test tilecache + let tilecached = try image.tilecache() + #expect(tilecached.width == image.width) + #expect(tilecached.height == image.height) + + // Test linecache + let linecached = try image.linecache() + #expect(linecached.width == image.width) + #expect(linecached.height == image.height) + + // Test sequential + let sequential = try image.sequential() + #expect(sequential.width == image.width) + #expect(sequential.height == image.height) + } + + // MARK: - Copy Operations + + @Test + func testCopyOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10, bands: 3) + .linear([1.0, 1.0, 1.0], [100.0, 150.0, 200.0]) + + // Test copy + let copied = try image.copy() + #expect(copied.width == image.width) + #expect(copied.height == image.height) + #expect(copied.bands == image.bands) + } + + // MARK: - Other Transformations + + @Test + func testWrapOperations() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Test wrap + let wrapped = try image.wrap() + #expect(wrapped.width == image.width) + #expect(wrapped.height == image.height) + } + + @Test + func testSubsampleOperations() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Test subsample + let subsampled = try image.subsample(xfac: 2, yfac: 2) + #expect(subsampled.width == 50) + #expect(subsampled.height == 50) + } + + @Test + func testMsbOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 65535.0) + .cast(.ushort) + + // Test msb (most significant byte) + let msb = try image.msb() + #expect(msb.width == image.width) + #expect(msb.height == image.height) + } + + @Test + func testByteswapOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 256.0) + .cast(.ushort) + + // Test byteswap + let swapped = try image.byteswap() + #expect(swapped.width == image.width) + #expect(swapped.height == image.height) + } + + @Test + func testFalsecolourOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0, 128.0) + + // Test falsecolour + let falsecoloured = try image.falsecolour() + #expect(falsecoloured.bands == 3) + } + + @Test + func testGammaOperations() throws { + let image = try VIPSImage.black(width: 10, height: 10) + .linear(1.0 / 255.0, 0.0) // Normalize to 0-1 range + + // Test gamma correction + let gammaCorrected = try image.gamma() + #expect(gammaCorrected.width == image.width) + #expect(gammaCorrected.height == image.height) + } + } +} diff --git a/Tests/VIPSTests/ConvolutionGeneratedTests.swift b/Tests/VIPSTests/ConvolutionGeneratedTests.swift deleted file mode 100644 index ac50283..0000000 --- a/Tests/VIPSTests/ConvolutionGeneratedTests.swift +++ /dev/null @@ -1,589 +0,0 @@ -@testable import VIPS -import Cvips -import Testing - -@Suite(.vips) -struct ConvolutionGeneratedTests { - - // MARK: - Basic Convolution Operations - - @Test - func testConvOperations() throws { - // Create a simple test image - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Create a simple blur kernel (3x3 box filter) - // Start with a black image and add a constant value to create uniform kernel - let kernel = try VIPSImage.black(width: 3, height: 3) - .linear(1.0, 1.0) // This creates a 3x3 kernel filled with 1.0 - - // Test basic convolution - let convolved = try image.conv(mask: kernel) - #expect(convolved.width == image.width) - #expect(convolved.height == image.height) - #expect(abs(try convolved.avg() - 1152.0) < 10.0) // 128 * 9 = 1152 - - // Test with precision parameter - let convolvedInt = try image.conv(mask: kernel, precision: .integer) - #expect(convolvedInt.width == image.width) - - let convolvedFloat = try image.conv(mask: kernel, precision: .float) - #expect(convolvedFloat.width == image.width) - - let convolvedApprox = try image.conv(mask: kernel, precision: .approximate) - #expect(convolvedApprox.width == image.width) - } - - @Test - func testConvaOperations() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Create integer kernel - let kernel = try VIPSImage.black(width: 3, height: 3) - .linear(0.0, 1.0) - .cast(.int) - - // Test approximate integer convolution - let convolved = try image.conva(mask: kernel) - #expect(convolved.width == image.width) - #expect(convolved.height == image.height) - - // Test with layers parameter - let convolvedLayers = try image.conva(mask: kernel, layers: 5) - #expect(convolvedLayers.width == image.width) - - // Test with cluster parameter - let convolvedCluster = try image.conva(mask: kernel, cluster: 2) - #expect(convolvedCluster.width == image.width) - } - - @Test - func testConvasepOperations() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Create a separable kernel (should be a 1D kernel) - let kernel = try VIPSImage.black(width: 1, height: 5) - .linear(0.0, 1.0) - .cast(.int) - - // Test approximate separable convolution - let convolved = try image.convasep(mask: kernel) - #expect(convolved.width == image.width) - #expect(convolved.height == image.height) - - // Test with layers - let convolvedLayers = try image.convasep(mask: kernel, layers: 3) - #expect(convolvedLayers.width == image.width) - } - - @Test - func testConvfOperations() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Create float kernel - let kernel = try VIPSImage.black(width: 3, height: 3) - .linear(0.0, 1.0 / 9.0) - .cast(.float) - - // Test float convolution - let convolved = try image.convf(mask: kernel) - #expect(convolved.width == image.width) - #expect(convolved.height == image.height) - #expect(abs(try convolved.avg() - 128.0) < 1.0) - } - - @Test - func testConviOperations() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Create integer kernel - let kernel = try VIPSImage.black(width: 3, height: 3) - .linear(0.0, 1.0) - .cast(.int) - - // Test integer convolution - let convolved = try image.convi(mask: kernel) - #expect(convolved.width == image.width) - #expect(convolved.height == image.height) - } - - @Test - func testConvsepOperations() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Create a separable kernel - let kernel = try VIPSImage.black(width: 1, height: 5) - .linear(0.0, 0.2) - - // Test separable convolution - let convolved = try image.convsep(mask: kernel) - #expect(convolved.width == image.width) - #expect(convolved.height == image.height) - - // Test with different precision - let convolvedInt = try image.convsep(mask: kernel, precision: .integer) - #expect(convolvedInt.width == image.width) - - let convolvedFloat = try image.convsep(mask: kernel, precision: .float) - #expect(convolvedFloat.width == image.width) - } - - // MARK: - Blur Operations - - @Test - func testGaussblurOperations() throws { - let image = try VIPSImage.gaussnoise(width: 100, height: 100, sigma: 30.0, mean: 128.0) - - // Test basic gaussian blur - let blurred = try image.gaussblur(sigma: 2.0) - #expect(blurred.width == image.width) - #expect(blurred.height == image.height) - - // The blurred image should have less variance than the original - let originalDev = try image.deviate() - let blurredDev = try blurred.deviate() - #expect(blurredDev < originalDev) - - // Test with minimum amplitude - let blurredMinAmpl = try image.gaussblur(sigma: 2.0, minAmpl: 0.1) - #expect(blurredMinAmpl.width == image.width) - - // Test with different precision - let blurredInt = try image.gaussblur(sigma: 2.0, precision: .integer) - #expect(blurredInt.width == image.width) - - let blurredFloat = try image.gaussblur(sigma: 2.0, precision: .float) - #expect(blurredFloat.width == image.width) - } - - // MARK: - Sharpening Operations - - @Test - func testSharpenOperations() throws { - // Create a slightly blurred image to sharpen - // Ensure it has a proper color interpretation for sharpen to work - let original = try VIPSImage.gaussnoise(width: 100, height: 100, sigma: 10.0, mean: 128.0) - .colourspace(space: .srgb) - let blurred = try original.gaussblur(sigma: 1.0) - - // Test basic sharpen - let sharpened = try blurred.sharpen() - #expect(sharpened.width == blurred.width) - #expect(sharpened.height == blurred.height) - - // Test with custom parameters - let sharpenedCustom = try blurred.sharpen( - sigma: 1.5, - x1: 2.0, - y2: 10.0, - y3: 20.0, - m1: 0.0, - m2: 3.0 - ) - #expect(sharpenedCustom.width == blurred.width) - #expect(sharpenedCustom.height == blurred.height) - } - - // MARK: - Edge Detection Operations - - @Test - func testSobelOperations() throws { - // Create an image with edges (a white square on black background) - let background = try VIPSImage.black(width: 100, height: 100) - let square = try VIPSImage.black(width: 50, height: 50) - .linear(1.0, 255.0) - let image = try background.insert(sub: square, x: 25, y: 25) - - // Test Sobel edge detection - let edges = try image.sobel() - #expect(edges.width == image.width) - #expect(edges.height == image.height) - - // Edge image should have non-zero values where edges are - #expect(try edges.max() > 0) - } - - @Test - func testCannyOperations() throws { - // Create an image with edges - let background = try VIPSImage.black(width: 100, height: 100) - let square = try VIPSImage.black(width: 50, height: 50) - .linear(1.0, 255.0) - let image = try background.insert(sub: square, x: 25, y: 25) - - // Test Canny edge detection - let edges = try image.canny() - #expect(edges.width == image.width) - #expect(edges.height == image.height) - - // Test with custom sigma - let edgesCustom = try image.canny(sigma: 2.0) - #expect(edgesCustom.width == image.width) - - // Test with precision - let edgesInt = try image.canny(precision: .integer) - #expect(edgesInt.width == image.width) - - let edgesFloat = try image.canny(precision: .float) - #expect(edgesFloat.width == image.width) - } - - // MARK: - Using Generated Kernels - - @Test - func testConvolutionWithGaussianKernel() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Create Gaussian kernel - let gaussKernel = try VIPSImage.gaussmat(sigma: 2.0, minAmpl: 0.2) - - // Convolve with Gaussian kernel - let convolved = try image.conv(mask: gaussKernel) - #expect(convolved.width == image.width) - #expect(convolved.height == image.height) - #expect(abs(try convolved.avg() - 128.0) < 1.0) - } - - @Test - func testConvolutionWithLaplacianKernel() throws { - let image = try VIPSImage.gaussnoise(width: 100, height: 100, sigma: 20.0, mean: 128.0) - - // Create Laplacian of Gaussian kernel - let logKernel = try VIPSImage.logmat(sigma: 1.5, minAmpl: 0.1) - - // Convolve with LoG kernel - let convolved = try image.conv(mask: logKernel) - #expect(convolved.width == image.width) - #expect(convolved.height == image.height) - } - - // MARK: - Edge Cases - - @Test - func testConvolutionWithSmallImages() throws { - // Test with very small image - let smallImage = try VIPSImage.black(width: 5, height: 5) - .linear(1.0, 100.0) - - let kernel = try VIPSImage.black(width: 3, height: 3) - .linear(0.0, 1.0 / 9.0) - - let convolved = try smallImage.conv(mask: kernel) - #expect(convolved.width == smallImage.width) - #expect(convolved.height == smallImage.height) - } - - @Test - func testConvolutionWithLargeKernels() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Create a large kernel - let largeKernel = try VIPSImage.black(width: 15, height: 15) - .linear(0.0, 1.0 / 225.0) - - let convolved = try image.conv(mask: largeKernel) - #expect(convolved.width == image.width) - #expect(convolved.height == image.height) - } - - @Test - func testConvolutionWithAsymmetricKernels() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - // Create asymmetric kernels - let horizontalKernel = try VIPSImage.black(width: 5, height: 1) - .linear(0.0, 0.2) - - let verticalKernel = try VIPSImage.black(width: 1, height: 5) - .linear(0.0, 0.2) - - let hConvolved = try image.conv(mask: horizontalKernel) - #expect(hConvolved.width == image.width) - - let vConvolved = try image.conv(mask: verticalKernel) - #expect(vConvolved.width == image.width) - } - - // MARK: - Enhanced Convolution Tests - - @Test - func testCustomKernelTypes() throws { - let testImage = try makeTestImage(width: 50, height: 50, value: 100.0) - - // Test identity kernel (should preserve image) - let identity = try VIPSImage.black(width: 3, height: 3) - let _ = try identity.getpoint(x: 1, y: 1) - let identityKernel = try identity.linear(0.0, 0.0).insert(sub: VIPSImage.black(width: 1, height: 1).linear(0.0, 1.0), x: 1, y: 1) - - let identityConv = try testImage.conv(mask: identityKernel) - assertAlmostEqual(try identityConv.avg(), 100.0, threshold: 1.0) - - // Test edge detection kernels - // Sobel X kernel: [-1, 0, 1; -2, 0, 2; -1, 0, 1] - let sobelX = try VIPSImage.black(width: 3, height: 3) - var sobelXData = try sobelX.linear(0.0, 0.0) // Start with zeros - sobelXData = try sobelXData.insert(sub: VIPSImage.black(width: 1, height: 1).linear(0.0, -1.0), x: 0, y: 0) - sobelXData = try sobelXData.insert(sub: VIPSImage.black(width: 1, height: 1).linear(0.0, 1.0), x: 2, y: 0) - sobelXData = try sobelXData.insert(sub: VIPSImage.black(width: 1, height: 1).linear(0.0, -2.0), x: 0, y: 1) - sobelXData = try sobelXData.insert(sub: VIPSImage.black(width: 1, height: 1).linear(0.0, 2.0), x: 2, y: 1) - sobelXData = try sobelXData.insert(sub: VIPSImage.black(width: 1, height: 1).linear(0.0, -1.0), x: 0, y: 2) - sobelXData = try sobelXData.insert(sub: VIPSImage.black(width: 1, height: 1).linear(0.0, 1.0), x: 2, y: 2) - - // Create gradient test image - let gradient = try makeGradientImage(width: 50, height: 50) - let edgeResult = try gradient.conv(mask: sobelXData) - - #expect(edgeResult.width == 50) - #expect(edgeResult.height == 50) - #expect(try edgeResult.max() > 0) // Should detect edges in gradient - } - - @Test - func testSeparableConvolutionVsRegular() throws { - let testImage = try makeTestImage(width: 80, height: 80, value: 128.0) - - // Create a separable Gaussian-like kernel - let sigma = 1.5 - let size = 7 - let center = size / 2 - - // Create 1D Gaussian kernel - let kernel1D = try VIPSImage.black(width: 1, height: size) - var gaussianKernel = try kernel1D.linear(0.0, 0.0) - - var sum = 0.0 - for i in 0.. dev2) - #expect(dev2 > dev3) - #expect(dev3 < originalDeviation) - - // All blurred images should preserve the mean approximately - assertAlmostEqual(try blur1.avg(), 128.0, threshold: 5.0) - assertAlmostEqual(try blur2.avg(), 128.0, threshold: 5.0) - assertAlmostEqual(try blur3.avg(), 128.0, threshold: 5.0) - } - - @Test(.disabled()) - func testConvolutionBoundaryHandling() throws { - // Create image with distinct regions to test boundary handling - let testImage = try makeRegionTestImage(regionSize: 25) - - // Use a simple 3x3 averaging kernel - let avgKernel = try VIPSImage.black(width: 3, height: 3).linear(0.0, 1.0/9.0) - - let convolved = try testImage.conv(mask: avgKernel) - - #expect(convolved.width == testImage.width) - #expect(convolved.height == testImage.height) - - // Center of uniform regions should be unchanged - let centerValue = try convolved.getpoint(x: 25, y: 25)[0] - assertAlmostEqual(centerValue, 10.0, threshold: 0.1) // First region value - - // Boundary regions should show blending - let boundaryValue = try convolved.getpoint(x: 25, y: 24)[0] // Just across boundary - #expect(boundaryValue != 10.0) // Should be different due to mixing - #expect(boundaryValue > 10.0 && boundaryValue < 20.0) // Should be between region values - } - - - @Test - func testEdgeDetectionCharacteristics() throws { - // Create test image with known edges - let background = try VIPSImage.black(width: 100, height: 100) - let square = try VIPSImage.black(width: 40, height: 40).linear(0.0, 255.0) - let testImage = try background.insert(sub: square, x: 30, y: 30) - - // Test Sobel edge detection - let sobelResult = try testImage.sobel() - - // Edge detection should produce higher values at edges - let centerValue = try sobelResult.getpoint(x: 50, y: 50)[0] // Inside square - let edgeValue = try sobelResult.getpoint(x: 30, y: 50)[0] // At left edge - - #expect(edgeValue > centerValue) - #expect(edgeValue > 0) - - // Test Canny edge detection with different parameters - let canny1 = try testImage.canny(sigma: 1.0) - let canny2 = try testImage.canny(sigma: 2.0) - - #expect(canny1.width == testImage.width) - #expect(canny2.width == testImage.width) - - // Different sigma should produce different results - let diff = try (canny1 - canny2).abs().max() - #expect(diff > 0) // Should be different - } - - @Test - func testSharpenEffectiveness() throws { - // Create slightly blurred test image - let original = try makeRegionTestImage(regionSize: 25).colourspace(space: .srgb) - let blurred = try original.gaussblur(sigma: 2.0) - - // Apply sharpening - let sharpened = try blurred.sharpen() - let sharpenedStrong = try blurred.sharpen(sigma: 1.0, x1: 2.0, y2: 10.0, y3: 20.0) - - #expect(sharpened.width == blurred.width) - #expect(sharpenedStrong.width == blurred.width) - - // Sharpened image should have higher variance than blurred - let blurredDev = try blurred.deviate() - let sharpenedDev = try sharpened.deviate() - let sharpenedStrongDev = try sharpenedStrong.deviate() - - #expect(sharpenedDev >= blurredDev) - #expect(sharpenedStrongDev >= sharpenedDev) // Stronger sharpening should increase variance more - } - - @Test - func testMultiBandConvolution() throws { - // Test convolution with multi-band images - let r = try makeTestImage(width: 50, height: 50, value: 100.0) - let g = try makeTestImage(width: 50, height: 50, value: 150.0) - let b = try makeTestImage(width: 50, height: 50, value: 200.0) - let rgb = try r.bandjoin([g, b]) - - // Apply blur kernel - let blurKernel = try VIPSImage.black(width: 5, height: 5).linear(0.0, 1.0/25.0) - let blurredRGB = try rgb.conv(mask: blurKernel) - - #expect(blurredRGB.width == rgb.width) - #expect(blurredRGB.height == rgb.height) - #expect(blurredRGB.bands == 3) - - // Each band should preserve its average approximately - let rBlurred = try blurredRGB.extractBand(0) - let gBlurred = try blurredRGB.extractBand(1) - let bBlurred = try blurredRGB.extractBand(2) - - assertAlmostEqual(try rBlurred.avg(), 100.0, threshold: 2.0) - assertAlmostEqual(try gBlurred.avg(), 150.0, threshold: 2.0) - assertAlmostEqual(try bBlurred.avg(), 200.0, threshold: 2.0) - } - - @Test - func testKernelNormalization() throws { - let testImage = try makeTestImage(width: 40, height: 40, value: 50.0) - - // Test normalized vs unnormalized kernels - let unnormalizedKernel = try VIPSImage.black(width: 3, height: 3).linear(0.0, 1.0) // Sum = 9 - let normalizedKernel = try unnormalizedKernel.linear(1.0/9.0, 0.0) // Sum = 1 - - let unnormalizedResult = try testImage.conv(mask: unnormalizedKernel) - let normalizedResult = try testImage.conv(mask: normalizedKernel) - - // Unnormalized should amplify the signal by the kernel sum - assertAlmostEqual(try unnormalizedResult.avg(), 50.0 * 9.0, threshold: 5.0) - assertAlmostEqual(try normalizedResult.avg(), 50.0, threshold: 1.0) - } - - @Test - func testConvolutionWithDifferentFormats() throws { - let baseImage = try makeTestImage(width: 30, height: 30, value: 100.0) - let kernel = try VIPSImage.black(width: 3, height: 3).linear(0.0, 1.0/9.0).cast(.float) - - for format in nonComplexFormats { - let typedImage = try baseImage.cast(format) - let convolved = try typedImage.conv(mask: kernel) - - #expect(convolved.width == typedImage.width) - #expect(convolved.height == typedImage.height) - - // Should preserve value approximately - let formatName = String(describing: format) - let expectedMax = maxValue[formatName] ?? 255.0 - let scaledValue = min(100.0, expectedMax) - let tolerance = max(2.0, expectedMax * 0.02) - - assertAlmostEqual(try convolved.avg(), scaledValue, threshold: tolerance) - } - } - - @Test - func testLargeKernelPerformance() throws { - let testImage = try makeTestImage(width: 100, height: 100, value: 128.0) - - // Test with increasingly large kernels - let sizes = [5, 9, 15, 21] - - for size in sizes { - let kernel = try VIPSImage.black(width: size, height: size) - .linear(0.0, 1.0 / Double(size * size)) - - let result = try testImage.conv(mask: kernel) - - #expect(result.width == testImage.width) - #expect(result.height == testImage.height) - assertAlmostEqual(try result.avg(), 128.0, threshold: 2.0) - } - } - - @Test - func testConvolutionMemoryConsistency() throws { - // Test that repeated convolutions produce consistent results - let testImage = try makeTestImage(width: 50, height: 50, value: 64.0) - let kernel = try VIPSImage.black(width: 3, height: 3).linear(0.0, 1.0/9.0) - - let result1 = try testImage.conv(mask: kernel) - let result2 = try testImage.conv(mask: kernel) - let result3 = try testImage.conv(mask: kernel) - - // All results should be identical - try assertImagesEqual(result1, result2, maxDiff: 0.001) - try assertImagesEqual(result2, result3, maxDiff: 0.001) - - assertAlmostEqual(try result1.avg(), try result2.avg(), threshold: 0.001) - assertAlmostEqual(try result2.avg(), try result3.avg(), threshold: 0.001) - } -} \ No newline at end of file diff --git a/Tests/VIPSTests/ConvolutionTests.swift b/Tests/VIPSTests/ConvolutionTests.swift new file mode 100644 index 0000000..6589d2c --- /dev/null +++ b/Tests/VIPSTests/ConvolutionTests.swift @@ -0,0 +1,627 @@ +import Cvips +import Testing + +@testable import VIPS + +extension VIPSTests { + @Suite(.vips) + struct ConvolutionGeneratedTests { + + // MARK: - Basic Convolution Operations + + @Test + func testConvOperations() throws { + // Create a simple test image + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Create a simple blur kernel (3x3 box filter) + // Start with a black image and add a constant value to create uniform kernel + let kernel = try VIPSImage.black(width: 3, height: 3) + .linear(1.0, 1.0) // This creates a 3x3 kernel filled with 1.0 + + // Test basic convolution + let convolved = try image.conv(mask: kernel) + #expect(convolved.width == image.width) + #expect(convolved.height == image.height) + #expect(abs(try convolved.avg() - 1152.0) < 10.0) // 128 * 9 = 1152 + + // Test with precision parameter + let convolvedInt = try image.conv(mask: kernel, precision: .integer) + #expect(convolvedInt.width == image.width) + + let convolvedFloat = try image.conv(mask: kernel, precision: .float) + #expect(convolvedFloat.width == image.width) + + let convolvedApprox = try image.conv(mask: kernel, precision: .approximate) + #expect(convolvedApprox.width == image.width) + } + + @Test + func testConvaOperations() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Create integer kernel + let kernel = try VIPSImage.black(width: 3, height: 3) + .linear(0.0, 1.0) + .cast(.int) + + // Test approximate integer convolution + let convolved = try image.conva(mask: kernel) + #expect(convolved.width == image.width) + #expect(convolved.height == image.height) + + // Test with layers parameter + let convolvedLayers = try image.conva(mask: kernel, layers: 5) + #expect(convolvedLayers.width == image.width) + + // Test with cluster parameter + let convolvedCluster = try image.conva(mask: kernel, cluster: 2) + #expect(convolvedCluster.width == image.width) + } + + @Test + func testConvasepOperations() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Create a separable kernel (should be a 1D kernel) + let kernel = try VIPSImage.black(width: 1, height: 5) + .linear(0.0, 1.0) + .cast(.int) + + // Test approximate separable convolution + let convolved = try image.convasep(mask: kernel) + #expect(convolved.width == image.width) + #expect(convolved.height == image.height) + + // Test with layers + let convolvedLayers = try image.convasep(mask: kernel, layers: 3) + #expect(convolvedLayers.width == image.width) + } + + @Test + func testConvfOperations() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Create float kernel + let kernel = try VIPSImage.black(width: 3, height: 3) + .linear(0.0, 1.0 / 9.0) + .cast(.float) + + // Test float convolution + let convolved = try image.convf(mask: kernel) + #expect(convolved.width == image.width) + #expect(convolved.height == image.height) + #expect(abs(try convolved.avg() - 128.0) < 1.0) + } + + @Test + func testConviOperations() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Create integer kernel + let kernel = try VIPSImage.black(width: 3, height: 3) + .linear(0.0, 1.0) + .cast(.int) + + // Test integer convolution + let convolved = try image.convi(mask: kernel) + #expect(convolved.width == image.width) + #expect(convolved.height == image.height) + } + + @Test + func testConvsepOperations() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Create a separable kernel + let kernel = try VIPSImage.black(width: 1, height: 5) + .linear(0.0, 0.2) + + // Test separable convolution + let convolved = try image.convsep(mask: kernel) + #expect(convolved.width == image.width) + #expect(convolved.height == image.height) + + // Test with different precision + let convolvedInt = try image.convsep(mask: kernel, precision: .integer) + #expect(convolvedInt.width == image.width) + + let convolvedFloat = try image.convsep(mask: kernel, precision: .float) + #expect(convolvedFloat.width == image.width) + } + + // MARK: - Blur Operations + + @Test + func testGaussblurOperations() throws { + let image = try VIPSImage.gaussnoise(width: 100, height: 100, sigma: 30.0, mean: 128.0) + + // Test basic gaussian blur + let blurred = try image.gaussblur(sigma: 2.0) + #expect(blurred.width == image.width) + #expect(blurred.height == image.height) + + // The blurred image should have less variance than the original + let originalDev = try image.deviate() + let blurredDev = try blurred.deviate() + #expect(blurredDev < originalDev) + + // Test with minimum amplitude + let blurredMinAmpl = try image.gaussblur(sigma: 2.0, minAmpl: 0.1) + #expect(blurredMinAmpl.width == image.width) + + // Test with different precision + let blurredInt = try image.gaussblur(sigma: 2.0, precision: .integer) + #expect(blurredInt.width == image.width) + + let blurredFloat = try image.gaussblur(sigma: 2.0, precision: .float) + #expect(blurredFloat.width == image.width) + } + + // MARK: - Sharpening Operations + + @Test + func testSharpenOperations() throws { + // Create a slightly blurred image to sharpen + // Ensure it has a proper color interpretation for sharpen to work + let original = + try VIPSImage.gaussnoise(width: 100, height: 100, sigma: 10.0, mean: 128.0) + .colourspace(space: .srgb) + let blurred = try original.gaussblur(sigma: 1.0) + + // Test basic sharpen + let sharpened = try blurred.sharpen() + #expect(sharpened.width == blurred.width) + #expect(sharpened.height == blurred.height) + + // Test with custom parameters + let sharpenedCustom = try blurred.sharpen( + sigma: 1.5, + x1: 2.0, + y2: 10.0, + y3: 20.0, + m1: 0.0, + m2: 3.0 + ) + #expect(sharpenedCustom.width == blurred.width) + #expect(sharpenedCustom.height == blurred.height) + } + + // MARK: - Edge Detection Operations + + @Test + func testSobelOperations() throws { + // Create an image with edges (a white square on black background) + let background = try VIPSImage.black(width: 100, height: 100) + let square = try VIPSImage.black(width: 50, height: 50) + .linear(1.0, 255.0) + let image = try background.insert(sub: square, x: 25, y: 25) + + // Test Sobel edge detection + let edges = try image.sobel() + #expect(edges.width == image.width) + #expect(edges.height == image.height) + + // Edge image should have non-zero values where edges are + #expect(try edges.max() > 0) + } + + @Test + func testCannyOperations() throws { + // Create an image with edges + let background = try VIPSImage.black(width: 100, height: 100) + let square = try VIPSImage.black(width: 50, height: 50) + .linear(1.0, 255.0) + let image = try background.insert(sub: square, x: 25, y: 25) + + // Test Canny edge detection + let edges = try image.canny() + #expect(edges.width == image.width) + #expect(edges.height == image.height) + + // Test with custom sigma + let edgesCustom = try image.canny(sigma: 2.0) + #expect(edgesCustom.width == image.width) + + // Test with precision + let edgesInt = try image.canny(precision: .integer) + #expect(edgesInt.width == image.width) + + let edgesFloat = try image.canny(precision: .float) + #expect(edgesFloat.width == image.width) + } + + // MARK: - Using Generated Kernels + + @Test + func testConvolutionWithGaussianKernel() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Create Gaussian kernel + let gaussKernel = try VIPSImage.gaussmat(sigma: 2.0, minAmpl: 0.2) + + // Convolve with Gaussian kernel + let convolved = try image.conv(mask: gaussKernel) + #expect(convolved.width == image.width) + #expect(convolved.height == image.height) + #expect(abs(try convolved.avg() - 128.0) < 1.0) + } + + @Test + func testConvolutionWithLaplacianKernel() throws { + let image = try VIPSImage.gaussnoise(width: 100, height: 100, sigma: 20.0, mean: 128.0) + + // Create Laplacian of Gaussian kernel + let logKernel = try VIPSImage.logmat(sigma: 1.5, minAmpl: 0.1) + + // Convolve with LoG kernel + let convolved = try image.conv(mask: logKernel) + #expect(convolved.width == image.width) + #expect(convolved.height == image.height) + } + + // MARK: - Edge Cases + + @Test + func testConvolutionWithSmallImages() throws { + // Test with very small image + let smallImage = try VIPSImage.black(width: 5, height: 5) + .linear(1.0, 100.0) + + let kernel = try VIPSImage.black(width: 3, height: 3) + .linear(0.0, 1.0 / 9.0) + + let convolved = try smallImage.conv(mask: kernel) + #expect(convolved.width == smallImage.width) + #expect(convolved.height == smallImage.height) + } + + @Test + func testConvolutionWithLargeKernels() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Create a large kernel + let largeKernel = try VIPSImage.black(width: 15, height: 15) + .linear(0.0, 1.0 / 225.0) + + let convolved = try image.conv(mask: largeKernel) + #expect(convolved.width == image.width) + #expect(convolved.height == image.height) + } + + @Test + func testConvolutionWithAsymmetricKernels() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + // Create asymmetric kernels + let horizontalKernel = try VIPSImage.black(width: 5, height: 1) + .linear(0.0, 0.2) + + let verticalKernel = try VIPSImage.black(width: 1, height: 5) + .linear(0.0, 0.2) + + let hConvolved = try image.conv(mask: horizontalKernel) + #expect(hConvolved.width == image.width) + + let vConvolved = try image.conv(mask: verticalKernel) + #expect(vConvolved.width == image.width) + } + + // MARK: - Enhanced Convolution Tests + + @Test + func testCustomKernelTypes() throws { + let testImage = try makeTestImage(width: 50, height: 50, value: 100.0) + + // Test identity kernel (should preserve image) + let identity = try VIPSImage.black(width: 3, height: 3) + let _ = try identity.getpoint(x: 1, y: 1) + let identityKernel = try identity.linear(0.0, 0.0) + .insert(sub: VIPSImage.black(width: 1, height: 1).linear(0.0, 1.0), x: 1, y: 1) + + let identityConv = try testImage.conv(mask: identityKernel) + assertAlmostEqual(try identityConv.avg(), 100.0, threshold: 1.0) + + // Test edge detection kernels + // Sobel X kernel: [-1, 0, 1; -2, 0, 2; -1, 0, 1] + let sobelX = try VIPSImage.black(width: 3, height: 3) + var sobelXData = try sobelX.linear(0.0, 0.0) // Start with zeros + sobelXData = try sobelXData.insert( + sub: VIPSImage.black(width: 1, height: 1).linear(0.0, -1.0), + x: 0, + y: 0 + ) + sobelXData = try sobelXData.insert( + sub: VIPSImage.black(width: 1, height: 1).linear(0.0, 1.0), + x: 2, + y: 0 + ) + sobelXData = try sobelXData.insert( + sub: VIPSImage.black(width: 1, height: 1).linear(0.0, -2.0), + x: 0, + y: 1 + ) + sobelXData = try sobelXData.insert( + sub: VIPSImage.black(width: 1, height: 1).linear(0.0, 2.0), + x: 2, + y: 1 + ) + sobelXData = try sobelXData.insert( + sub: VIPSImage.black(width: 1, height: 1).linear(0.0, -1.0), + x: 0, + y: 2 + ) + sobelXData = try sobelXData.insert( + sub: VIPSImage.black(width: 1, height: 1).linear(0.0, 1.0), + x: 2, + y: 2 + ) + + // Create gradient test image + let gradient = try makeGradientImage(width: 50, height: 50) + let edgeResult = try gradient.conv(mask: sobelXData) + + #expect(edgeResult.width == 50) + #expect(edgeResult.height == 50) + #expect(try edgeResult.max() > 0) // Should detect edges in gradient + } + + @Test + func testSeparableConvolutionVsRegular() throws { + let testImage = try makeTestImage(width: 80, height: 80, value: 128.0) + + // Create a separable Gaussian-like kernel + let sigma = 1.5 + let size = 7 + let center = size / 2 + + // Create 1D Gaussian kernel + let kernel1D = try VIPSImage.black(width: 1, height: size) + var gaussianKernel = try kernel1D.linear(0.0, 0.0) + + var sum = 0.0 + for i in 0.. dev2) + #expect(dev2 > dev3) + #expect(dev3 < originalDeviation) + + // All blurred images should preserve the mean approximately + assertAlmostEqual(try blur1.avg(), 128.0, threshold: 5.0) + assertAlmostEqual(try blur2.avg(), 128.0, threshold: 5.0) + assertAlmostEqual(try blur3.avg(), 128.0, threshold: 5.0) + } + + @Test(.disabled()) + func testConvolutionBoundaryHandling() throws { + // Create image with distinct regions to test boundary handling + let testImage = try makeRegionTestImage(regionSize: 25) + + // Use a simple 3x3 averaging kernel + let avgKernel = try VIPSImage.black(width: 3, height: 3).linear(0.0, 1.0 / 9.0) + + let convolved = try testImage.conv(mask: avgKernel) + + #expect(convolved.width == testImage.width) + #expect(convolved.height == testImage.height) + + // Center of uniform regions should be unchanged + let centerValue = try convolved.getpoint(x: 25, y: 25)[0] + assertAlmostEqual(centerValue, 10.0, threshold: 0.1) // First region value + + // Boundary regions should show blending + let boundaryValue = try convolved.getpoint(x: 25, y: 24)[0] // Just across boundary + #expect(boundaryValue != 10.0) // Should be different due to mixing + #expect(boundaryValue > 10.0 && boundaryValue < 20.0) // Should be between region values + } + + @Test + func testEdgeDetectionCharacteristics() throws { + // Create test image with known edges + let background = try VIPSImage.black(width: 100, height: 100) + let square = try VIPSImage.black(width: 40, height: 40).linear(0.0, 255.0) + let testImage = try background.insert(sub: square, x: 30, y: 30) + + // Test Sobel edge detection + let sobelResult = try testImage.sobel() + + // Edge detection should produce higher values at edges + let centerValue = try sobelResult.getpoint(x: 50, y: 50)[0] // Inside square + let edgeValue = try sobelResult.getpoint(x: 30, y: 50)[0] // At left edge + + #expect(edgeValue > centerValue) + #expect(edgeValue > 0) + + // Test Canny edge detection with different parameters + let canny1 = try testImage.canny(sigma: 1.0) + let canny2 = try testImage.canny(sigma: 2.0) + + #expect(canny1.width == testImage.width) + #expect(canny2.width == testImage.width) + + // Different sigma should produce different results + let diff = try (canny1 - canny2).abs().max() + #expect(diff > 0) // Should be different + } + + @Test + func testSharpenEffectiveness() throws { + // Create slightly blurred test image + let original = try makeRegionTestImage(regionSize: 25).colourspace(space: .srgb) + let blurred = try original.gaussblur(sigma: 2.0) + + // Apply sharpening + let sharpened = try blurred.sharpen() + let sharpenedStrong = try blurred.sharpen(sigma: 1.0, x1: 2.0, y2: 10.0, y3: 20.0) + + #expect(sharpened.width == blurred.width) + #expect(sharpenedStrong.width == blurred.width) + + // Sharpened image should have higher variance than blurred + let blurredDev = try blurred.deviate() + let sharpenedDev = try sharpened.deviate() + let sharpenedStrongDev = try sharpenedStrong.deviate() + + #expect(sharpenedDev >= blurredDev) + #expect(sharpenedStrongDev >= sharpenedDev) // Stronger sharpening should increase variance more + } + + @Test + func testMultiBandConvolution() throws { + // Test convolution with multi-band images + let r = try makeTestImage(width: 50, height: 50, value: 100.0) + let g = try makeTestImage(width: 50, height: 50, value: 150.0) + let b = try makeTestImage(width: 50, height: 50, value: 200.0) + let rgb = try r.bandjoin([g, b]) + + // Apply blur kernel + let blurKernel = try VIPSImage.black(width: 5, height: 5).linear(0.0, 1.0 / 25.0) + let blurredRGB = try rgb.conv(mask: blurKernel) + + #expect(blurredRGB.width == rgb.width) + #expect(blurredRGB.height == rgb.height) + #expect(blurredRGB.bands == 3) + + // Each band should preserve its average approximately + let rBlurred = try blurredRGB.extractBand(0) + let gBlurred = try blurredRGB.extractBand(1) + let bBlurred = try blurredRGB.extractBand(2) + + assertAlmostEqual(try rBlurred.avg(), 100.0, threshold: 2.0) + assertAlmostEqual(try gBlurred.avg(), 150.0, threshold: 2.0) + assertAlmostEqual(try bBlurred.avg(), 200.0, threshold: 2.0) + } + + @Test + func testKernelNormalization() throws { + let testImage = try makeTestImage(width: 40, height: 40, value: 50.0) + + // Test normalized vs unnormalized kernels + let unnormalizedKernel = try VIPSImage.black(width: 3, height: 3).linear(0.0, 1.0) // Sum = 9 + let normalizedKernel = try unnormalizedKernel.linear(1.0 / 9.0, 0.0) // Sum = 1 + + let unnormalizedResult = try testImage.conv(mask: unnormalizedKernel) + let normalizedResult = try testImage.conv(mask: normalizedKernel) + + // Unnormalized should amplify the signal by the kernel sum + assertAlmostEqual(try unnormalizedResult.avg(), 50.0 * 9.0, threshold: 5.0) + assertAlmostEqual(try normalizedResult.avg(), 50.0, threshold: 1.0) + } + + @Test + func testConvolutionWithDifferentFormats() throws { + let baseImage = try makeTestImage(width: 30, height: 30, value: 100.0) + let kernel = try VIPSImage.black(width: 3, height: 3).linear(0.0, 1.0 / 9.0) + .cast(.float) + + for format in nonComplexFormats { + let typedImage = try baseImage.cast(format) + let convolved = try typedImage.conv(mask: kernel) + + #expect(convolved.width == typedImage.width) + #expect(convolved.height == typedImage.height) + + // Should preserve value approximately + let formatName = String(describing: format) + let expectedMax = maxValue[formatName] ?? 255.0 + let scaledValue = min(100.0, expectedMax) + let tolerance = max(2.0, expectedMax * 0.02) + + assertAlmostEqual(try convolved.avg(), scaledValue, threshold: tolerance) + } + } + + @Test + func testLargeKernelPerformance() throws { + let testImage = try makeTestImage(width: 100, height: 100, value: 128.0) + + // Test with increasingly large kernels + let sizes = [5, 9, 15, 21] + + for size in sizes { + let kernel = try VIPSImage.black(width: size, height: size) + .linear(0.0, 1.0 / Double(size * size)) + + let result = try testImage.conv(mask: kernel) + + #expect(result.width == testImage.width) + #expect(result.height == testImage.height) + assertAlmostEqual(try result.avg(), 128.0, threshold: 2.0) + } + } + + @Test + func testConvolutionMemoryConsistency() throws { + // Test that repeated convolutions produce consistent results + let testImage = try makeTestImage(width: 50, height: 50, value: 64.0) + let kernel = try VIPSImage.black(width: 3, height: 3).linear(0.0, 1.0 / 9.0) + + let result1 = try testImage.conv(mask: kernel) + let result2 = try testImage.conv(mask: kernel) + let result3 = try testImage.conv(mask: kernel) + + // All results should be identical + try assertImagesEqual(result1, result2, maxDiff: 0.001) + try assertImagesEqual(result2, result3, maxDiff: 0.001) + + assertAlmostEqual(try result1.avg(), try result2.avg(), threshold: 0.001) + assertAlmostEqual(try result2.avg(), try result3.avg(), threshold: 0.001) + } + } +} diff --git a/Tests/VIPSTests/CoreTests.swift b/Tests/VIPSTests/CoreTests.swift new file mode 100644 index 0000000..bd169e8 --- /dev/null +++ b/Tests/VIPSTests/CoreTests.swift @@ -0,0 +1,60 @@ +import Cvips +import CvipsShim +import Foundation +import Testing + +@testable import VIPS + +extension VIPSTests { + @Suite(.vips, .serialized) + struct CoreTests { + + var testPath: String { + testUrl.path + } + + var testUrl: URL { + Bundle.module.resourceURL! + .appendingPathComponent("data") + .appendingPathComponent("bay.jpg") + } + + var mythicalGiantPath: String { + Bundle.module.resourceURL! + .appendingPathComponent("data") + .appendingPathComponent("mythical_giant.jpg") + .path + } + + @Test() + func testProgressReporting() async throws { + do { + let image = try VIPSImage(fromFilePath: mythicalGiantPath) + + var preEval: VIPSProgress? + var postEval: VIPSProgress? + var progressRecieved : [VIPSProgress] = [] + + + image.setProgressReportingEnabled(true) + + image.onPreeval { _, progress in preEval = progress } + image.onPosteval { _, progress in postEval = progress } + + image.onEval { [weak image] imageRef, progress in + guard let image else { return } + progressRecieved.append(progress) + #expect(image.image == imageRef.image, "Image references match") + } + + _ = try image.writeToBuffer(suffix: ".jpg") + + let pre = try #require(preEval) + #expect(pre.percent == 0, "Pre-eval progress is 0%") + #expect(progressRecieved.count > 0, "Got progress") + let post = try #require(postEval) + #expect(post.percent == 100, "Post-eval progress is 100%") + } + } + } +} diff --git a/Tests/VIPSTests/CreateGeneratedTests.swift b/Tests/VIPSTests/CreateTests.swift similarity index 99% rename from Tests/VIPSTests/CreateGeneratedTests.swift rename to Tests/VIPSTests/CreateTests.swift index 980f2b4..ca99c89 100644 --- a/Tests/VIPSTests/CreateGeneratedTests.swift +++ b/Tests/VIPSTests/CreateTests.swift @@ -3,8 +3,9 @@ import Cvips import Testing import Foundation -@Suite(.vips) -struct CreateGeneratedTests { +extension VIPSTests { +@Suite(.vips, .serialized) +struct CreateTests { // MARK: - Basic Image Creation @@ -429,4 +430,4 @@ struct CreateGeneratedTests { let deviate = try perlin.deviate() #expect(deviate > 0.0) } -} \ No newline at end of file +}} \ No newline at end of file diff --git a/Tests/VIPSTests/ForeignTests.swift b/Tests/VIPSTests/ForeignTests.swift index 753adb5..e81c13f 100644 --- a/Tests/VIPSTests/ForeignTests.swift +++ b/Tests/VIPSTests/ForeignTests.swift @@ -1,562 +1,564 @@ -@testable import VIPS import Cvips -import Testing import Foundation +import Testing +@testable import VIPS -@Suite(.vips) -struct ForeignTests { - - // MARK: - JPEG Format Tests - - @Test - func testJpegBasicIO() throws { - let original = try VIPSImage(fromFilePath: TestImages.colour.path) - - // Test JPEG save and load - let jpegData = try original.jpegsave() - let loaded = try VIPSImage.jpegload(buffer: jpegData) - - #expect(loaded.width == original.width) - #expect(loaded.height == original.height) - #expect(loaded.bands == original.bands) - - // JPEG is lossy, so we allow for some difference - let diff = try (original - loaded).abs().avg() - #expect(diff < 20.0) // Should be reasonably close - } - - @Test - func testJpegQualityLevels() throws { - let testImage = try makeTestImage(width: 100, height: 100, value: 128.0) - .colourspace(space: .srgb) - .cast(.uchar) - - // Test different quality levels - let qualities = [10, 50, 85, 95] - var previousSize = Int.max - - for quality in qualities { - let jpegData = try testImage.jpegsave(quality:quality) +extension VIPSTests { + @Suite(.vips, .serialized) + struct ForeignTests { + + // MARK: - JPEG Format Tests + + @Test + func testJpegBasicIO() throws { + let original = try VIPSImage(fromFilePath: TestImages.colour.path) + + // Test JPEG save and load + let jpegData = try original.jpegsave() let loaded = try VIPSImage.jpegload(buffer: jpegData) - - #expect(loaded.width == testImage.width) - #expect(loaded.height == testImage.height) - - // Higher quality should generally result in larger file sizes - let currentSize = jpegData.count - if quality > 10 { // Skip first comparison - // Allow some variation in file sizes - #expect(currentSize >= Int(Double(previousSize) * 0.7)) + + #expect(loaded.width == original.width) + #expect(loaded.height == original.height) + #expect(loaded.bands == original.bands) + + // JPEG is lossy, so we allow for some difference + let diff = try (original - loaded).abs().avg() + #expect(diff < 20.0) // Should be reasonably close + } + + @Test + func testJpegQualityLevels() throws { + let testImage = try makeTestImage(width: 100, height: 100, value: 128.0) + .colourspace(space: .srgb) + .cast(.uchar) + + // Test different quality levels + let qualities = [10, 50, 85, 95] + var previousSize = Int.max + + for quality in qualities { + let jpegData = try testImage.jpegsave(quality: quality) + let loaded = try VIPSImage.jpegload(buffer: jpegData) + + #expect(loaded.width == testImage.width) + #expect(loaded.height == testImage.height) + + // Higher quality should generally result in larger file sizes + let currentSize = jpegData.count + if quality > 10 { // Skip first comparison + // Allow some variation in file sizes + #expect(currentSize >= Int(Double(previousSize) * 0.7)) + } + previousSize = currentSize } - previousSize = currentSize } - } - - @Test - func testJpegProgressive() throws { - let testImage = try makeTestImage(width: 200, height: 200, value: 100.0) - .colourspace(space: .srgb) - .cast(.uchar) - - // Test progressive vs baseline JPEG - let baselineData = try testImage.jpegsave(interlace: false) - let progressiveData = try testImage.jpegsave(interlace: true) - - let baselineLoaded = try VIPSImage.jpegload(buffer: baselineData) - let progressiveLoaded = try VIPSImage.jpegload(buffer: progressiveData) - - #expect(baselineLoaded.width == testImage.width) - #expect(progressiveLoaded.width == testImage.width) - - // Both should decode to similar images - let diff = try (baselineLoaded - progressiveLoaded).abs().avg() - #expect(diff < 2.0) - } - - @Test - func testJpegOptimisation() throws { - let testImage = try makeRegionTestImage(regionSize: 50) - .colourspace(space: .srgb) - .cast(.uchar) - - // Test with and without optimization - let normalData: VIPSBlob = try testImage.jpegsave(optimizeCoding: false) - let optimizedData: VIPSBlob = try testImage.jpegsave(optimizeCoding: true) - - let normalLoaded = try VIPSImage.jpegload(buffer: normalData) - let optimizedLoaded = try VIPSImage.jpegload(buffer: optimizedData) - - #expect(normalLoaded.width == testImage.width) - #expect(optimizedLoaded.width == testImage.width) - - // Optimized should typically be smaller or same size - #expect(optimizedData.count <= normalData.count + 100) // Allow small margin - - // Should decode to very similar images - let diff = try (normalLoaded - optimizedLoaded).abs().avg() - #expect(diff < 1.0) - } - - // MARK: - PNG Format Tests - - @Test - func testPngBasicIO() throws { - let original = try VIPSImage(fromFilePath: TestImages.png.path) - - // Test PNG save and load - let pngData = try original.pngsave() - let loaded = try VIPSImage.pngload(buffer: pngData) - - #expect(loaded.width == original.width) - #expect(loaded.height == original.height) - #expect(loaded.bands == original.bands) - - // PNG is lossless - try assertImagesEqual(original, loaded, maxDiff: 0.0) - } - - @Test - func testPngCompressionLevels() throws { - let testImage = try makeTestImage(width: 100, height: 100, value: 128.0) - .cast(.uchar) - - // Test different compression levels - let compressions = [0, 3, 6, 9] - - for compression in compressions { - let pngData = try testImage.pngsave(compression: compression) + + @Test + func testJpegProgressive() throws { + let testImage = try makeTestImage(width: 200, height: 200, value: 100.0) + .colourspace(space: .srgb) + .cast(.uchar) + + // Test progressive vs baseline JPEG + let baselineData = try testImage.jpegsave(interlace: false) + let progressiveData = try testImage.jpegsave(interlace: true) + + let baselineLoaded = try VIPSImage.jpegload(buffer: baselineData) + let progressiveLoaded = try VIPSImage.jpegload(buffer: progressiveData) + + #expect(baselineLoaded.width == testImage.width) + #expect(progressiveLoaded.width == testImage.width) + + // Both should decode to similar images + let diff = try (baselineLoaded - progressiveLoaded).abs().avg() + #expect(diff < 2.0) + } + + @Test + func testJpegOptimisation() throws { + let testImage = try makeRegionTestImage(regionSize: 50) + .colourspace(space: .srgb) + .cast(.uchar) + + // Test with and without optimization + let normalData: VIPSBlob = try testImage.jpegsave(optimizeCoding: false) + let optimizedData: VIPSBlob = try testImage.jpegsave(optimizeCoding: true) + + let normalLoaded = try VIPSImage.jpegload(buffer: normalData) + let optimizedLoaded = try VIPSImage.jpegload(buffer: optimizedData) + + #expect(normalLoaded.width == testImage.width) + #expect(optimizedLoaded.width == testImage.width) + + // Optimized should typically be smaller or same size + #expect(optimizedData.count <= normalData.count + 100) // Allow small margin + + // Should decode to very similar images + let diff = try (normalLoaded - optimizedLoaded).abs().avg() + #expect(diff < 1.0) + } + + // MARK: - PNG Format Tests + + @Test + func testPngBasicIO() throws { + let original = try VIPSImage(fromFilePath: TestImages.png.path) + + // Test PNG save and load + let pngData = try original.pngsave() let loaded = try VIPSImage.pngload(buffer: pngData) - - #expect(loaded.width == testImage.width) - #expect(loaded.height == testImage.height) - - // PNG is lossless regardless of compression - try assertImagesEqual(testImage, loaded, maxDiff: 0.0) - - // All compression levels should produce valid images - #expect(pngData.count > 0) + + #expect(loaded.width == original.width) + #expect(loaded.height == original.height) + #expect(loaded.bands == original.bands) + + // PNG is lossless + try assertImagesEqual(original, loaded, maxDiff: 0.0) } - } - - @Test - func testPngInterlacing() throws { - let testImage = try makeRegionTestImage(regionSize: 50).cast(.uchar) - - // Test interlaced vs non-interlaced PNG - let normalData = try testImage.pngsave(interlace: false) - let interlacedData = try testImage.pngsave(interlace: true) - - let normalLoaded = try VIPSImage.pngload(buffer: normalData) - let interlacedLoaded = try VIPSImage.pngload(buffer: interlacedData) - - #expect(normalLoaded.width == testImage.width) - #expect(interlacedLoaded.width == testImage.width) - - // Both should decode to identical images (PNG is lossless) - try assertImagesEqual(normalLoaded, interlacedLoaded, maxDiff: 0.0) - try assertImagesEqual(testImage, normalLoaded, maxDiff: 0.0) - } - - @Test - func testPngWithTransparency() throws { - // Create RGBA test image - let r = try makeTestImage(width: 50, height: 50, value: 255.0) - let g = try makeTestImage(width: 50, height: 50, value: 128.0) - let b = try makeTestImage(width: 50, height: 50, value: 64.0) - let a = try makeTestImage(width: 50, height: 50, value: 200.0) - let rgba = try r.bandjoin([g, b, a]).cast(.uchar) - - let pngData = try rgba.pngsave() - let loaded = try VIPSImage.pngload(buffer: pngData) - - #expect(loaded.width == rgba.width) - #expect(loaded.height == rgba.height) - #expect(loaded.bands == 4) // Should preserve alpha channel - - try assertImagesEqual(rgba, loaded, maxDiff: 0.0) - } - - // MARK: - WebP Format Tests - - @Test - func testWebpBasicIO() throws { - let original = try VIPSImage(fromFilePath: TestImages.webp.path) - - // Test WebP save and load - let webpData = try original.webpsave() - let loaded = try VIPSImage.webpload(buffer: webpData) - - #expect(loaded.width == original.width) - #expect(loaded.height == original.height) - #expect(loaded.bands == original.bands) - } - - @Test - func testWebpLossyVsLossless() throws { - let testImage = try VIPSImage(fromFilePath: TestImages.colour.path) - - // Test lossy WebP - let lossyData = try testImage.webpsave(quality:80, lossless: false) - let lossyLoaded = try VIPSImage.webpload(buffer: lossyData) - - // Test lossless WebP - let losslessData = try testImage.webpsave(lossless: true) - let losslessLoaded = try VIPSImage.webpload(buffer: losslessData) - - #expect(lossyLoaded.width == testImage.width) - #expect(losslessLoaded.width == testImage.width) - - // Lossless should be identical - try assertImagesEqual(testImage, losslessLoaded, maxDiff: 0.0) - - // Lossy should be close but not identical - let lossyDiff = try (testImage - lossyLoaded).abs().avg() - #expect(lossyDiff > 0.0) // Should have some difference - #expect(lossyDiff < 10.0) // But not too much - } - - @Test - func testWebpQualitySettings() throws { - let testImage = try makeRegionTestImage(regionSize: 50) - .colourspace(space: .srgb) - .cast(.uchar) - - let qualities = [20, 50, 80, 95] - var previousSize = 0 - - for quality in qualities { - let webpData = try testImage.webpsave(quality:quality, lossless: false) + + @Test + func testPngCompressionLevels() throws { + let testImage = try makeTestImage(width: 100, height: 100, value: 128.0) + .cast(.uchar) + + // Test different compression levels + let compressions = [0, 3, 6, 9] + + for compression in compressions { + let pngData = try testImage.pngsave(compression: compression) + let loaded = try VIPSImage.pngload(buffer: pngData) + + #expect(loaded.width == testImage.width) + #expect(loaded.height == testImage.height) + + // PNG is lossless regardless of compression + try assertImagesEqual(testImage, loaded, maxDiff: 0.0) + + // All compression levels should produce valid images + #expect(pngData.count > 0) + } + } + + @Test + func testPngInterlacing() throws { + let testImage = try makeRegionTestImage(regionSize: 50).cast(.uchar) + + // Test interlaced vs non-interlaced PNG + let normalData = try testImage.pngsave(interlace: false) + let interlacedData = try testImage.pngsave(interlace: true) + + let normalLoaded = try VIPSImage.pngload(buffer: normalData) + let interlacedLoaded = try VIPSImage.pngload(buffer: interlacedData) + + #expect(normalLoaded.width == testImage.width) + #expect(interlacedLoaded.width == testImage.width) + + // Both should decode to identical images (PNG is lossless) + try assertImagesEqual(normalLoaded, interlacedLoaded, maxDiff: 0.0) + try assertImagesEqual(testImage, normalLoaded, maxDiff: 0.0) + } + + @Test + func testPngWithTransparency() throws { + // Create RGBA test image + let r = try makeTestImage(width: 50, height: 50, value: 255.0) + let g = try makeTestImage(width: 50, height: 50, value: 128.0) + let b = try makeTestImage(width: 50, height: 50, value: 64.0) + let a = try makeTestImage(width: 50, height: 50, value: 200.0) + let rgba = try r.bandjoin([g, b, a]).cast(.uchar) + + let pngData = try rgba.pngsave() + let loaded = try VIPSImage.pngload(buffer: pngData) + + #expect(loaded.width == rgba.width) + #expect(loaded.height == rgba.height) + #expect(loaded.bands == 4) // Should preserve alpha channel + + try assertImagesEqual(rgba, loaded, maxDiff: 0.0) + } + + // MARK: - WebP Format Tests + + @Test + func testWebpBasicIO() throws { + let original = try VIPSImage(fromFilePath: TestImages.webp.path) + + // Test WebP save and load + let webpData = try original.webpsave() let loaded = try VIPSImage.webpload(buffer: webpData) - - #expect(loaded.width == testImage.width) - #expect(loaded.height == testImage.height) - - let currentSize = webpData.count - if quality > 20 { - // Higher quality should generally result in larger files - #expect(currentSize >= previousSize || abs(currentSize - previousSize) < 1000) + + #expect(loaded.width == original.width) + #expect(loaded.height == original.height) + #expect(loaded.bands == original.bands) + } + + @Test + func testWebpLossyVsLossless() throws { + let testImage = try VIPSImage(fromFilePath: TestImages.colour.path) + + // Test lossy WebP + let lossyData = try testImage.webpsave(quality: 80, lossless: false) + let lossyLoaded = try VIPSImage.webpload(buffer: lossyData) + + // Test lossless WebP + let losslessData = try testImage.webpsave(lossless: true) + let losslessLoaded = try VIPSImage.webpload(buffer: losslessData) + + #expect(lossyLoaded.width == testImage.width) + #expect(losslessLoaded.width == testImage.width) + + // Lossless should be identical + try assertImagesEqual(testImage, losslessLoaded, maxDiff: 0.0) + + // Lossy should be close but not identical + let lossyDiff = try (testImage - lossyLoaded).abs().avg() + #expect(lossyDiff > 0.0) // Should have some difference + #expect(lossyDiff < 10.0) // But not too much + } + + @Test + func testWebpQualitySettings() throws { + let testImage = try makeRegionTestImage(regionSize: 50) + .colourspace(space: .srgb) + .cast(.uchar) + + let qualities = [20, 50, 80, 95] + var previousSize = 0 + + for quality in qualities { + let webpData = try testImage.webpsave(quality: quality, lossless: false) + let loaded = try VIPSImage.webpload(buffer: webpData) + + #expect(loaded.width == testImage.width) + #expect(loaded.height == testImage.height) + + let currentSize = webpData.count + if quality > 20 { + // Higher quality should generally result in larger files + #expect(currentSize >= previousSize || abs(currentSize - previousSize) < 1000) + } + previousSize = currentSize } - previousSize = currentSize } - } - - // MARK: - TIFF Format Tests - - @Test - func testTiffBasicIO() throws { - let original = try VIPSImage(fromFilePath: TestImages.tiff.path) - - // Test TIFF save and load - let tiffData = try original.tiffsave() - let loaded = try VIPSImage.tiffload(buffer: tiffData) - - #expect(loaded.width == original.width) - #expect(loaded.height == original.height) - #expect(loaded.bands == original.bands) - - // TIFF should be lossless by default - let diff = try (original - loaded).abs().avg() - #expect(diff < 1.0) // Should be very close or identical - } - - @Test - func testTiffCompressionTypes() throws { - let testImage = try makeTestImage(width: 100, height: 100, value: 128.0).cast(.uchar) - - let compressions: [VipsForeignTiffCompression] = [.none, .lzw, .deflate] - - for compression in compressions { - let tiffData = try testImage.tiffsave(compression: compression) + + // MARK: - TIFF Format Tests + + @Test + func testTiffBasicIO() throws { + let original = try VIPSImage(fromFilePath: TestImages.tiff.path) + + // Test TIFF save and load + let tiffData = try original.tiffsave() let loaded = try VIPSImage.tiffload(buffer: tiffData) - - #expect(loaded.width == testImage.width) - #expect(loaded.height == testImage.height) - - // All compression types should be lossless - try assertImagesEqual(testImage, loaded, maxDiff: 0.0) + + #expect(loaded.width == original.width) + #expect(loaded.height == original.height) + #expect(loaded.bands == original.bands) + + // TIFF should be lossless by default + let diff = try (original - loaded).abs().avg() + #expect(diff < 1.0) // Should be very close or identical } - } - - @Test - func testTiffMultipage() throws { - // Test with multipage TIFF - let multipage = try VIPSImage(fromFilePath: TestImages.multiPage.path) - - // Load all pages - let allPages = try VIPSImage.tiffload(filename: TestImages.multiPage.path, n: -1) - - #expect(allPages.width == multipage.width) - #expect(allPages.height > multipage.height) // Should be taller due to multiple pages - - // Test saving multipage - let page1 = try makeTestImage(width: 50, height: 50, value: 100.0).cast(.uchar) - let page2 = try makeTestImage(width: 50, height: 50, value: 200.0).cast(.uchar) - let combined = try page1.join(in2: page2, direction: .vertical) - - let multipageData = try combined.tiffsave() - let loadedMultipage = try VIPSImage.tiffload(buffer: multipageData) - - #expect(loadedMultipage.width == combined.width) - #expect(loadedMultipage.height == combined.height) - } - - // MARK: - GIF Format Tests - - @Test - func testGifBasicIO() throws { - let original = try VIPSImage(fromFilePath: TestImages.gif.path) - - // GIF loading should work - #expect(original.width > 0) - #expect(original.height > 0) - #expect(original.bands >= 1) - - // Note: GIF saving might not be available in all VIPS builds - // So we test loading only for basic functionality - } - - // MARK: - Format Detection and Conversion Tests - - @Test - func testFormatDetection() throws { - // Test that VIPS can detect formats from buffers - let jpegData = try Data(contentsOf: TestImages.colour) - let pngData = try Data(contentsOf: TestImages.png) - let webpData = try Data(contentsOf: TestImages.webp) - - // These should load without explicit format specification - let jpegImage = try VIPSImage(data: jpegData) - let pngImage = try VIPSImage(data: pngData) - let webpImage = try VIPSImage(data: webpData) - - #expect(jpegImage.width > 0) - #expect(pngImage.width > 0) - #expect(webpImage.width > 0) - } - - @Test - func testFormatConversion() throws { - let original = try VIPSImage(fromFilePath: TestImages.colour.path) - - // Convert between formats - let jpegData = try original.jpegsave(quality:90) - let pngData = try original.pngsave() - let webpData = try original.webpsave(quality:90, lossless: false) - - let fromJpeg = try VIPSImage.jpegload(buffer: jpegData) - let fromPng = try VIPSImage.pngload(buffer: pngData) - let fromWebp = try VIPSImage.webpload(buffer: webpData) - - #expect(fromJpeg.width == original.width) - #expect(fromPng.width == original.width) - #expect(fromWebp.width == original.width) - - // PNG should be closest to original (lossless) - let pngDiff = try (original - fromPng).abs().avg() - let jpegDiff = try (original - fromJpeg).abs().avg() - - #expect(pngDiff <= jpegDiff + 1.0) // PNG should be better or similar - } - - // MARK: - Buffer vs File Operations - - @Test - func testBufferVsFileConsistency() throws { - let testImage = try makeTestImage(width: 50, height: 50, value: 150.0) - .colourspace(space: .srgb) - .cast(.uchar) - - // Create temporary file path - let tempDir = FileManager.default.temporaryDirectory - let tempFile = tempDir.appendingPathComponent("test_\(UUID().uuidString).jpg") - - defer { - try? FileManager.default.removeItem(at: tempFile) + + @Test + func testTiffCompressionTypes() throws { + let testImage = try makeTestImage(width: 100, height: 100, value: 128.0).cast(.uchar) + + let compressions: [VipsForeignTiffCompression] = [.none, .lzw, .deflate] + + for compression in compressions { + let tiffData = try testImage.tiffsave(compression: compression) + let loaded = try VIPSImage.tiffload(buffer: tiffData) + + #expect(loaded.width == testImage.width) + #expect(loaded.height == testImage.height) + + // All compression types should be lossless + try assertImagesEqual(testImage, loaded, maxDiff: 0.0) + } } - - // Save to buffer and file - let bufferData : VIPSBlob = try testImage.jpegsave(quality:85) - try testImage.jpegsave(filename: tempFile.path, quality:85) - - // Load from both - let fromBuffer = try VIPSImage.jpegload(buffer: bufferData) - let fromFile = try VIPSImage.jpegload(filename: tempFile.path) - - // Should be identical - try assertImagesEqual(fromBuffer, fromFile, maxDiff: 0.0) - - // File size should match buffer size (approximately) - let fileData = try Data(contentsOf: tempFile) - let sizeDiff = abs(fileData.count - bufferData.count) - #expect(sizeDiff <= 10) // Should be very close - } - - // MARK: - Error Handling Tests - - @Test - func testInvalidFormatHandling() throws { - // Test with invalid data - let invalidData = VIPSBlob(Data(repeating: 0xFF, count: 100)) - - #expect(throws: Error.self) { - _ = try VIPSImage.jpegload(buffer: invalidData) + + @Test + func testTiffMultipage() throws { + // Test with multipage TIFF + let multipage = try VIPSImage(fromFilePath: TestImages.multiPage.path) + + // Load all pages + let allPages = try VIPSImage.tiffload(filename: TestImages.multiPage.path, n: -1) + + #expect(allPages.width == multipage.width) + #expect(allPages.height > multipage.height) // Should be taller due to multiple pages + + // Test saving multipage + let page1 = try makeTestImage(width: 50, height: 50, value: 100.0).cast(.uchar) + let page2 = try makeTestImage(width: 50, height: 50, value: 200.0).cast(.uchar) + let combined = try page1.join(in2: page2, direction: .vertical) + + let multipageData = try combined.tiffsave() + let loadedMultipage = try VIPSImage.tiffload(buffer: multipageData) + + #expect(loadedMultipage.width == combined.width) + #expect(loadedMultipage.height == combined.height) } - - #expect(throws: Error.self) { - _ = try VIPSImage.pngload(buffer: invalidData) + + // MARK: - GIF Format Tests + + @Test + func testGifBasicIO() throws { + let original = try VIPSImage(fromFilePath: TestImages.gif.path) + + // GIF loading should work + #expect(original.width > 0) + #expect(original.height > 0) + #expect(original.bands >= 1) + + // Note: GIF saving might not be available in all VIPS builds + // So we test loading only for basic functionality } - } - - // MARK: - Performance and Memory Tests - - @Test - func testLargeImageHandling() throws { - // Create a moderately large test image - let largeImage = try VIPSImage(fromFilePath: TestImages.mythicalGiant.path) - - // Test JPEG compression of large image - let jpegData = try largeImage.jpegsave(quality:80) - let loaded = try VIPSImage.jpegload(buffer: jpegData) - - #expect(loaded.width == largeImage.width) - #expect(loaded.height == largeImage.height) - - // Compression ratio should be reasonable - let originalBytes = largeImage.width * largeImage.height * largeImage.bands - let compressedBytes = jpegData.count - let compressionRatio = Double(originalBytes) / Double(compressedBytes) - - #expect(compressionRatio > 5.0) // Should achieve reasonable compression - #expect(compressionRatio < 100.0) // But not impossibly high - } - - @Test - func testMemoryUsageWithMultipleFormats() throws { - let testImage = try makeRegionTestImage(regionSize: 100).cast(.uchar) - - // Convert to multiple formats - let formats: [(String, () throws -> VIPSBlob)] = [ - ("JPEG", { try testImage.colourspace(space: .srgb).jpegsave(quality:85) }), - ("PNG", { try testImage.pngsave() }), - ("WebP", { try testImage.colourspace(space: .srgb).webpsave(quality:85) }), - ("TIFF", { try testImage.tiffsave() }) - ] - - for (formatName, saveFunc) in formats { - let data = try saveFunc() - #expect(data.count > 0, "Empty data for format \(formatName)") - - // Load back and verify - let loaded = try VIPSImage(data: data) - #expect(loaded.width == testImage.width, "Width mismatch for \(formatName)") - #expect(loaded.height == testImage.height, "Height mismatch for \(formatName)") + + // MARK: - Format Detection and Conversion Tests + + @Test + func testFormatDetection() throws { + // Test that VIPS can detect formats from buffers + let jpegData = try Data(contentsOf: TestImages.colour) + let pngData = try Data(contentsOf: TestImages.png) + let webpData = try Data(contentsOf: TestImages.webp) + + // These should load without explicit format specification + let jpegImage = try VIPSImage(data: jpegData) + let pngImage = try VIPSImage(data: pngData) + let webpImage = try VIPSImage(data: webpData) + + #expect(jpegImage.width > 0) + #expect(pngImage.width > 0) + #expect(webpImage.width > 0) } - } - - // MARK: - Round-trip Testing - - @Test - func testLosslessRoundTrip() throws { - let originalImage = try makeRegionTestImage(regionSize: 50).cast(.uchar) - - // Test PNG round-trip (should be perfect) - let pngData = try originalImage.pngsave() - let pngLoaded = try VIPSImage.pngload(buffer: pngData) - try assertImagesEqual(originalImage, pngLoaded, maxDiff: 0.0) - - // Test TIFF round-trip (should be perfect) - let tiffData = try originalImage.tiffsave() - let tiffLoaded = try VIPSImage.tiffload(buffer: tiffData) - try assertImagesEqual(originalImage, tiffLoaded, maxDiff: 0.0) - - // Test WebP lossless round-trip (should be perfect) - let webpData = try originalImage.colourspace(space: .srgb).webpsave(lossless: true) - let webpLoaded = try VIPSImage.webpload(buffer: webpData) - try assertImagesEqual(originalImage.colourspace(space: .srgb), webpLoaded, maxDiff: 0.0) - } - - @Test - func testLossyRoundTripQuality() throws { - let originalImage = try makeRegionTestImage(regionSize: 50) - .colourspace(space: .srgb) - .cast(.uchar) - - let qualities = [50, 80, 95] - var previousError = Double.infinity - - for quality in qualities { - // JPEG round-trip - let jpegData = try originalImage.jpegsave(quality:quality) - let jpegLoaded = try VIPSImage.jpegload(buffer: jpegData) - let jpegError = try (originalImage - jpegLoaded).abs().avg() - - // WebP round-trip - let webpData = try originalImage.webpsave(quality:quality, lossless: false) + + @Test + func testFormatConversion() throws { + let original = try VIPSImage(fromFilePath: TestImages.colour.path) + + // Convert between formats + let jpegData = try original.jpegsave(quality: 90) + let pngData = try original.pngsave() + let webpData = try original.webpsave(quality: 90, lossless: false) + + let fromJpeg = try VIPSImage.jpegload(buffer: jpegData) + let fromPng = try VIPSImage.pngload(buffer: pngData) + let fromWebp = try VIPSImage.webpload(buffer: webpData) + + #expect(fromJpeg.width == original.width) + #expect(fromPng.width == original.width) + #expect(fromWebp.width == original.width) + + // PNG should be closest to original (lossless) + let pngDiff = try (original - fromPng).abs().avg() + let jpegDiff = try (original - fromJpeg).abs().avg() + + #expect(pngDiff <= jpegDiff + 1.0) // PNG should be better or similar + } + + // MARK: - Buffer vs File Operations + + @Test + func testBufferVsFileConsistency() throws { + let testImage = try makeTestImage(width: 50, height: 50, value: 150.0) + .colourspace(space: .srgb) + .cast(.uchar) + + // Create temporary file path + let tempDir = FileManager.default.temporaryDirectory + let tempFile = tempDir.appendingPathComponent("test_\(UUID().uuidString).jpg") + + defer { + try? FileManager.default.removeItem(at: tempFile) + } + + // Save to buffer and file + let bufferData: VIPSBlob = try testImage.jpegsave(quality: 85) + try testImage.jpegsave(filename: tempFile.path, quality: 85) + + // Load from both + let fromBuffer = try VIPSImage.jpegload(buffer: bufferData) + let fromFile = try VIPSImage.jpegload(filename: tempFile.path) + + // Should be identical + try assertImagesEqual(fromBuffer, fromFile, maxDiff: 0.0) + + // File size should match buffer size (approximately) + let fileData = try Data(contentsOf: tempFile) + let sizeDiff = abs(fileData.count - bufferData.count) + #expect(sizeDiff <= 10) // Should be very close + } + + // MARK: - Error Handling Tests + + @Test + func testInvalidFormatHandling() throws { + // Test with invalid data + let invalidData = VIPSBlob(Data(repeating: 0xFF, count: 100)) + + #expect(throws: Error.self) { + _ = try VIPSImage.jpegload(buffer: invalidData) + } + + #expect(throws: Error.self) { + _ = try VIPSImage.pngload(buffer: invalidData) + } + } + + // MARK: - Performance and Memory Tests + + @Test + func testLargeImageHandling() throws { + // Create a moderately large test image + let largeImage = try VIPSImage(fromFilePath: TestImages.mythicalGiant.path) + + // Test JPEG compression of large image + let jpegData = try largeImage.jpegsave(quality: 80) + let loaded = try VIPSImage.jpegload(buffer: jpegData) + + #expect(loaded.width == largeImage.width) + #expect(loaded.height == largeImage.height) + + // Compression ratio should be reasonable + let originalBytes = largeImage.width * largeImage.height * largeImage.bands + let compressedBytes = jpegData.count + let compressionRatio = Double(originalBytes) / Double(compressedBytes) + + #expect(compressionRatio > 5.0) // Should achieve reasonable compression + #expect(compressionRatio < 100.0) // But not impossibly high + } + + @Test + func testMemoryUsageWithMultipleFormats() throws { + let testImage = try makeRegionTestImage(regionSize: 100).cast(.uchar) + + // Convert to multiple formats + let formats: [(String, () throws -> VIPSBlob)] = [ + ("JPEG", { try testImage.colourspace(space: .srgb).jpegsave(quality: 85) }), + ("PNG", { try testImage.pngsave() }), + ("WebP", { try testImage.colourspace(space: .srgb).webpsave(quality: 85) }), + ("TIFF", { try testImage.tiffsave() }), + ] + + for (formatName, saveFunc) in formats { + let data = try saveFunc() + #expect(data.count > 0, "Empty data for format \(formatName)") + + // Load back and verify + let loaded = try VIPSImage(data: data) + #expect(loaded.width == testImage.width, "Width mismatch for \(formatName)") + #expect(loaded.height == testImage.height, "Height mismatch for \(formatName)") + } + } + + // MARK: - Round-trip Testing + + @Test + func testLosslessRoundTrip() throws { + let originalImage = try makeRegionTestImage(regionSize: 50).cast(.uchar) + + // Test PNG round-trip (should be perfect) + let pngData = try originalImage.pngsave() + let pngLoaded = try VIPSImage.pngload(buffer: pngData) + try assertImagesEqual(originalImage, pngLoaded, maxDiff: 0.0) + + // Test TIFF round-trip (should be perfect) + let tiffData = try originalImage.tiffsave() + let tiffLoaded = try VIPSImage.tiffload(buffer: tiffData) + try assertImagesEqual(originalImage, tiffLoaded, maxDiff: 0.0) + + // Test WebP lossless round-trip (should be perfect) + let webpData = try originalImage.colourspace(space: .srgb).webpsave(lossless: true) let webpLoaded = try VIPSImage.webpload(buffer: webpData) - let webpError = try (originalImage - webpLoaded).abs().avg() - - // Higher quality should result in lower error - if quality > 50 { - #expect(jpegError <= previousError + 1.0) // Allow some tolerance + try assertImagesEqual(originalImage.colourspace(space: .srgb), webpLoaded, maxDiff: 0.0) + } + + @Test + func testLossyRoundTripQuality() throws { + let originalImage = try makeRegionTestImage(regionSize: 50) + .colourspace(space: .srgb) + .cast(.uchar) + + let qualities = [50, 80, 95] + var previousError = Double.infinity + + for quality in qualities { + // JPEG round-trip + let jpegData = try originalImage.jpegsave(quality: quality) + let jpegLoaded = try VIPSImage.jpegload(buffer: jpegData) + let jpegError = try (originalImage - jpegLoaded).abs().avg() + + // WebP round-trip + let webpData = try originalImage.webpsave(quality: quality, lossless: false) + let webpLoaded = try VIPSImage.webpload(buffer: webpData) + let webpError = try (originalImage - webpLoaded).abs().avg() + + // Higher quality should result in lower error + if quality > 50 { + #expect(jpegError <= previousError + 1.0) // Allow some tolerance + } + + // Both formats should preserve dimensions + #expect(jpegLoaded.width == originalImage.width) + #expect(webpLoaded.width == originalImage.width) + + previousError = min(jpegError, webpError) } - - // Both formats should preserve dimensions - #expect(jpegLoaded.width == originalImage.width) - #expect(webpLoaded.width == originalImage.width) - - previousError = min(jpegError, webpError) } - } - - // MARK: - Format-Specific Options Testing - - @Test - func testAdvancedJpegOptions() throws { - let testImage = try makeRegionTestImage(regionSize: 100) - .colourspace(space: .srgb) - .cast(.uchar) - - // Test various JPEG-specific options - let options: [(String, () throws -> VIPSBlob)] = [ - ("baseline", { try testImage.jpegsave(quality:80, interlace: false) }), - ("progressive", { try testImage.jpegsave(quality:80, interlace: true) }), - ("optimized", { try testImage.jpegsave(quality:80, optimizeCoding: true) }), - ("high_quality", { try testImage.jpegsave(quality:95) }), - ("low_quality", { try testImage.jpegsave(quality:30) }) - ] - - for (optionName, saveFunc) in options { - let data = try saveFunc() - let loaded = try VIPSImage.jpegload(buffer: data) - - #expect(loaded.width == testImage.width, "Width mismatch for \(optionName)") - #expect(loaded.height == testImage.height, "Height mismatch for \(optionName)") - #expect(loaded.bands == testImage.bands, "Bands mismatch for \(optionName)") + + // MARK: - Format-Specific Options Testing + + @Test + func testAdvancedJpegOptions() throws { + let testImage = try makeRegionTestImage(regionSize: 100) + .colourspace(space: .srgb) + .cast(.uchar) + + // Test various JPEG-specific options + let options: [(String, () throws -> VIPSBlob)] = [ + ("baseline", { try testImage.jpegsave(quality: 80, interlace: false) }), + ("progressive", { try testImage.jpegsave(quality: 80, interlace: true) }), + ("optimized", { try testImage.jpegsave(quality: 80, optimizeCoding: true) }), + ("high_quality", { try testImage.jpegsave(quality: 95) }), + ("low_quality", { try testImage.jpegsave(quality: 30) }), + ] + + for (optionName, saveFunc) in options { + let data = try saveFunc() + let loaded = try VIPSImage.jpegload(buffer: data) + + #expect(loaded.width == testImage.width, "Width mismatch for \(optionName)") + #expect(loaded.height == testImage.height, "Height mismatch for \(optionName)") + #expect(loaded.bands == testImage.bands, "Bands mismatch for \(optionName)") + } } - } - - @Test - func testAdvancedPngOptions() throws { - let testImage = try makeTestImage(width: 100, height: 100, value: 128.0).cast(.uchar) - - // Test PNG with different options - let options: [(String, () throws -> VIPSBlob)] = [ - ("no_compression", { try testImage.pngsave(compression: 0) }), - ("max_compression", { try testImage.pngsave(compression: 9) }), - ("interlaced", { try testImage.pngsave(interlace: true) }), - ("non_interlaced", { try testImage.pngsave(interlace: false) }) - ] - - for (optionName, saveFunc) in options { - let data = try saveFunc() - let loaded = try VIPSImage.pngload(buffer: data) - - #expect(loaded.width == testImage.width, "Width mismatch for \(optionName)") - #expect(loaded.height == testImage.height, "Height mismatch for \(optionName)") - - // PNG is lossless - should be identical - try assertImagesEqual(testImage, loaded, maxDiff: 0.0) + + @Test + func testAdvancedPngOptions() throws { + let testImage = try makeTestImage(width: 100, height: 100, value: 128.0).cast(.uchar) + + // Test PNG with different options + let options: [(String, () throws -> VIPSBlob)] = [ + ("no_compression", { try testImage.pngsave(compression: 0) }), + ("max_compression", { try testImage.pngsave(compression: 9) }), + ("interlaced", { try testImage.pngsave(interlace: true) }), + ("non_interlaced", { try testImage.pngsave(interlace: false) }), + ] + + for (optionName, saveFunc) in options { + let data = try saveFunc() + let loaded = try VIPSImage.pngload(buffer: data) + + #expect(loaded.width == testImage.width, "Width mismatch for \(optionName)") + #expect(loaded.height == testImage.height, "Height mismatch for \(optionName)") + + // PNG is lossless - should be identical + try assertImagesEqual(testImage, loaded, maxDiff: 0.0) + } } } -} \ No newline at end of file +} diff --git a/Tests/VIPSTests/HistogramTests.swift b/Tests/VIPSTests/HistogramTests.swift index 1ca18eb..cc58c80 100644 --- a/Tests/VIPSTests/HistogramTests.swift +++ b/Tests/VIPSTests/HistogramTests.swift @@ -1,339 +1,347 @@ -@testable import VIPS import Cvips -import Testing import Foundation +import Testing + +@testable import VIPS // Helper function to calculate the sum of all values in a histogram private func histogramSum(_ histogram: VIPSImage) throws -> Double { return try VIPSImage.sum([histogram]).avg() * Double(histogram.width * histogram.height) } -@Suite(.vips) -struct HistogramTests { - - // MARK: - Basic Histogram Operations - - @Test - func testHistFind() throws { - // Create a simple test image with known pixel values - let testImage = try makeTestImage(width: 10, height: 10, value: 128.0).cast(.uchar) - - // Find histogram - let histogram = try testImage.histFind() - - // Histogram should be 1D image with 256 bins for uchar - #expect(histogram.width == 256) - #expect(histogram.height == 1) - #expect(histogram.bands == 1) - - // All pixels have value 128, so bin 128 should have count 100 - let bin128 = try histogram.getpoint(x: 128, y: 0)[0] - assertAlmostEqual(bin128, 100.0, threshold: 0.1) - - // Other bins should be empty or nearly empty - let bin0 = try histogram.getpoint(x: 0, y: 0)[0] - let bin255 = try histogram.getpoint(x: 255, y: 0)[0] - assertAlmostEqual(bin0, 0.0, threshold: 0.1) - assertAlmostEqual(bin255, 0.0, threshold: 0.1) - } +extension VIPSTests { + @Suite(.vips, .serialized) + struct HistogramTests { - - // MARK: - Histogram Equalization - - @Test - func testHistEqual() throws { - // Create image with limited dynamic range - let lowContrast = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 100.0) // Values around 100 - .add(try VIPSImage.gaussnoise(width: 100, height: 100, sigma: 20.0)) - .cast(.uchar) - - // Apply histogram equalization - let equalized = try lowContrast.histEqual() - - #expect(equalized.width == lowContrast.width) - #expect(equalized.height == lowContrast.height) - #expect(equalized.bands == lowContrast.bands) - - // Equalized image should use more of the dynamic range - let originalMax = try lowContrast.max() - let originalMin = try lowContrast.min() - let originalRange = originalMax - originalMin - let equalizedMax = try equalized.max() - let equalizedMin = try equalized.min() - let equalizedRange = equalizedMax - equalizedMin - - #expect(equalizedRange >= originalRange) // Should not decrease range - - // Standard deviation should typically increase (better contrast) - let originalDev = try lowContrast.deviate() - let equalizedDev = try equalized.deviate() - #expect(equalizedDev >= originalDev * 0.8) // Allow some tolerance - } - - - - @Test - func testHistEqualBands() throws { - // Test equalizing bands independently - let testImage = try makeRegionTestImage(regionSize: 25).cast(.uchar) - let rgb = try testImage.bandjoin([testImage.linear(0.5), testImage.linear(1.5)]) - - // Note: hist_equal_bands not directly available, use per-band equalization - let band0 = try rgb.extractBand(0).histEqual() - let band1 = try rgb.extractBand(1).histEqual() - let band2 = try rgb.extractBand(2).histEqual() - let equalized = try band0.bandjoin([band1, band2]) - - #expect(equalized.width == rgb.width) - #expect(equalized.height == rgb.height) - #expect(equalized.bands == 3) - - // Each band should be equalized independently - for bandIndex in 0..<3 { - let originalBand = try rgb.extractBand(bandIndex) - let equalizedBand = try equalized.extractBand(bandIndex) - - let originalMax = try originalBand.max() - let originalMin = try originalBand.min() + // MARK: - Basic Histogram Operations + + @Test + func testHistFind() throws { + // Create a simple test image with known pixel values + let testImage = try makeTestImage(width: 10, height: 10, value: 128.0).cast(.uchar) + + // Find histogram + let histogram = try testImage.histFind() + + // Histogram should be 1D image with 256 bins for uchar + #expect(histogram.width == 256) + #expect(histogram.height == 1) + #expect(histogram.bands == 1) + + // All pixels have value 128, so bin 128 should have count 100 + let bin128 = try histogram.getpoint(x: 128, y: 0)[0] + assertAlmostEqual(bin128, 100.0, threshold: 0.1) + + // Other bins should be empty or nearly empty + let bin0 = try histogram.getpoint(x: 0, y: 0)[0] + let bin255 = try histogram.getpoint(x: 255, y: 0)[0] + assertAlmostEqual(bin0, 0.0, threshold: 0.1) + assertAlmostEqual(bin255, 0.0, threshold: 0.1) + } + + // MARK: - Histogram Equalization + + @Test + func testHistEqual() throws { + // Create image with limited dynamic range + let lowContrast = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 100.0) // Values around 100 + .add(try VIPSImage.gaussnoise(width: 100, height: 100, sigma: 20.0)) + .cast(.uchar) + + // Apply histogram equalization + let equalized = try lowContrast.histEqual() + + #expect(equalized.width == lowContrast.width) + #expect(equalized.height == lowContrast.height) + #expect(equalized.bands == lowContrast.bands) + + // Equalized image should use more of the dynamic range + let originalMax = try lowContrast.max() + let originalMin = try lowContrast.min() let originalRange = originalMax - originalMin - let equalizedMax = try equalizedBand.max() - let equalizedMin = try equalizedBand.min() + let equalizedMax = try equalized.max() + let equalizedMin = try equalized.min() let equalizedRange = equalizedMax - equalizedMin - - #expect(equalizedRange >= originalRange * 0.9) // Should maintain or improve range - } - } - - - - // MARK: - Histogram Analysis and Properties - - @Test - func testHistogramProperties() throws { - // Create test image with known distribution - let values = [50.0, 100.0, 150.0, 200.0] - let pixelsPerValue = 25 - - // Create image with 4 distinct regions - var combinedImage = try makeTestImage(width: pixelsPerValue, height: pixelsPerValue, value: values[0]).cast(.uchar) - - for i in 1..= originalRange) // Should not decrease range + + // Standard deviation should typically increase (better contrast) + let originalDev = try lowContrast.deviate() + let equalizedDev = try equalized.deviate() + #expect(equalizedDev >= originalDev * 0.8) // Allow some tolerance } - - let histogram = try combinedImage.histFind() - - // Check that each expected value has the right count - for value in values { - let binCount = try histogram.getpoint(x: Int(value), y: 0)[0] - assertAlmostEqual(binCount, Double(pixelsPerValue * pixelsPerValue), threshold: 1.0) + + @Test + func testHistEqualBands() throws { + // Test equalizing bands independently + let testImage = try makeRegionTestImage(regionSize: 25).cast(.uchar) + let rgb = try testImage.bandjoin([testImage.linear(0.5), testImage.linear(1.5)]) + + // Note: hist_equal_bands not directly available, use per-band equalization + let band0 = try rgb.extractBand(0).histEqual() + let band1 = try rgb.extractBand(1).histEqual() + let band2 = try rgb.extractBand(2).histEqual() + let equalized = try band0.bandjoin([band1, band2]) + + #expect(equalized.width == rgb.width) + #expect(equalized.height == rgb.height) + #expect(equalized.bands == 3) + + // Each band should be equalized independently + for bandIndex in 0..<3 { + let originalBand = try rgb.extractBand(bandIndex) + let equalizedBand = try equalized.extractBand(bandIndex) + + let originalMax = try originalBand.max() + let originalMin = try originalBand.min() + let originalRange = originalMax - originalMin + let equalizedMax = try equalizedBand.max() + let equalizedMin = try equalizedBand.min() + let equalizedRange = equalizedMax - equalizedMin + + #expect(equalizedRange >= originalRange * 0.9) // Should maintain or improve range + } } - - // Check that intermediate values are empty - let bin75 = try histogram.getpoint(x: 75, y: 0)[0] // Between 50 and 100 - let bin125 = try histogram.getpoint(x: 125, y: 0)[0] // Between 100 and 150 - assertAlmostEqual(bin75, 0.0, threshold: 0.1) - assertAlmostEqual(bin125, 0.0, threshold: 0.1) - - // Total should match total pixels - let totalPixels = try histogramSum(histogram) - let expectedTotal = Double(values.count * pixelsPerValue * pixelsPerValue) - assertAlmostEqual(totalPixels, expectedTotal, threshold: 1.0) - } - - @Test - func testHistogramStatistics() throws { - // Create gradient image for testing - let gradient = try VIPSImage.xyz(width: 256, height: 1) - .extractBand(0) // X coordinate gives 0-255 gradient + + // MARK: - Histogram Analysis and Properties + + @Test + func testHistogramProperties() throws { + // Create test image with known distribution + let values = [50.0, 100.0, 150.0, 200.0] + let pixelsPerValue = 25 + + // Create image with 4 distinct regions + var combinedImage = try makeTestImage( + width: pixelsPerValue, + height: pixelsPerValue, + value: values[0] + ) .cast(.uchar) - - let histogram = try gradient.histFind() - - // For a perfect gradient, each bin should have exactly 1 pixel - for i in 0..<256 { - let binValue = try histogram.getpoint(x: i, y: 0)[0] - assertAlmostEqual(binValue, 1.0, threshold: 0.1) + + for i in 1.. 0) - #expect(histogram.height > 0) - - // All histograms should have the same total (number of pixels) + + @Test + func testHistogramStatistics() throws { + // Create gradient image for testing + let gradient = try VIPSImage.xyz(width: 256, height: 1) + .extractBand(0) // X coordinate gives 0-255 gradient + .cast(.uchar) + + let histogram = try gradient.histFind() + + // For a perfect gradient, each bin should have exactly 1 pixel + for i in 0..<256 { + let binValue = try histogram.getpoint(x: i, y: 0)[0] + assertAlmostEqual(binValue, 1.0, threshold: 0.1) + } + + // Total should be 256 let total = try histogramSum(histogram) - assertAlmostEqual(total, 2500.0, threshold: 1.0) // 50*50 pixels + assertAlmostEqual(total, 256.0, threshold: 0.1) + + // Test histogram cumsum + let cumsum = try histogram.histCum() + + #expect(cumsum.width == 256) + #expect(cumsum.height == 1) + + // Cumulative sum should increase monotonically + let firstBin = try cumsum.getpoint(x: 0, y: 0)[0] + let lastBin = try cumsum.getpoint(x: 255, y: 0)[0] + + assertAlmostEqual(firstBin, 1.0, threshold: 0.1) + assertAlmostEqual(lastBin, 256.0, threshold: 0.1) } - } - - - @Test - func testHistogramWithNoise() throws { - // Test histogram with noisy image - let noisyImage = try VIPSImage.gaussnoise(width: 100, height: 100, sigma: 50.0, mean: 128.0) - .cast(.uchar) - - let histogram = try noisyImage.histFind() - - #expect(histogram.width == 256) - #expect(histogram.height == 1) - - // Total should match number of pixels - let total = try histogramSum(histogram) - assertAlmostEqual(total, 10000.0, threshold: 1.0) // 100*100 pixels - - // For gaussian noise, the peak should be around the mean (128) - let peakBin = try histogram.getpoint(x: 128, y: 0)[0] - let edgeBin = try histogram.getpoint(x: 10, y: 0)[0] - - #expect(peakBin > edgeBin) // Should have more pixels near the mean - - // Most bins should have some pixels (due to noise) - var nonEmptyBins = 0 - for i in 0..<256 { - let binValue = try histogram.getpoint(x: i, y: 0)[0] - if binValue > 0.5 { - nonEmptyBins += 1 + + @Test + func testHistogramCumulative() throws { + // Create simple two-value image + let half1 = try makeTestImage(width: 50, height: 50, value: 100.0).cast(.uchar) + let half2 = try makeTestImage(width: 50, height: 50, value: 200.0).cast(.uchar) + let combined = try half1.join(in2: half2, direction: .horizontal) + + let histogram = try combined.histFind() + let cumulative = try histogram.histCum() + + #expect(cumulative.width == 256) + #expect(cumulative.height == 1) + + // Check cumulative values + let cumBefore100 = try cumulative.getpoint(x: 99, y: 0)[0] + let cumAt100 = try cumulative.getpoint(x: 100, y: 0)[0] + let cumAt200 = try cumulative.getpoint(x: 200, y: 0)[0] + let cumAfter200 = try cumulative.getpoint(x: 255, y: 0)[0] + + assertAlmostEqual(cumBefore100, 0.0, threshold: 0.1) // No pixels before 100 + assertAlmostEqual(cumAt100, 2500.0, threshold: 1.0) // Half the pixels (50*50) + assertAlmostEqual(cumAt200, 5000.0, threshold: 1.0) // All pixels (100*50) + assertAlmostEqual(cumAfter200, 5000.0, threshold: 1.0) // Still all pixels + } + + // MARK: - Performance and Edge Cases + + @Test + func testHistogramWithDifferentFormats() throws { + let baseImage = try makeTestImage(width: 50, height: 50, value: 100.0) + + // Test histogram computation with different input formats + let formats: [VipsBandFormat] = [.uchar, .ushort, .float] + + for format in formats { + let typedImage = try baseImage.cast(format) + let histogram = try typedImage.histFind() + + #expect(histogram.width > 0) + #expect(histogram.height > 0) + + // All histograms should have the same total (number of pixels) + let total = try histogramSum(histogram) + assertAlmostEqual(total, 2500.0, threshold: 1.0) // 50*50 pixels } } - - #expect(nonEmptyBins > 50) // Noise should spread across many bins - } - - // MARK: - Integration with Other Operations - - @Test - func testHistogramAfterOperations() throws { - let originalImage = try makeTestImage(width: 50, height: 50, value: 100.0).cast(.uchar) - - // Apply various operations and check histograms - let brightened = try originalImage.linear(1.0, 50.0).cast(.uchar) // Add 50 - let doubled = try originalImage.linear(2.0, 0.0).cast(.uchar) // Multiply by 2 - - let originalHist = try originalImage.histFind() - let brightenedHist = try brightened.histFind() - let doubledHist = try doubled.histFind() - - // Original should peak at 100 - let originalPeak = try originalHist.getpoint(x: 100, y: 0)[0] - assertAlmostEqual(originalPeak, 2500.0, threshold: 1.0) - - // Brightened should peak at 150 (100 + 50) - let brightenedPeak = try brightenedHist.getpoint(x: 150, y: 0)[0] - assertAlmostEqual(brightenedPeak, 2500.0, threshold: 1.0) - - // Doubled should peak at 200 (100 * 2) - let doubledPeak = try doubledHist.getpoint(x: 200, y: 0)[0] - assertAlmostEqual(doubledPeak, 2500.0, threshold: 1.0) - - // All should have same total - assertAlmostEqual(try histogramSum(originalHist), 2500.0, threshold: 1.0) - assertAlmostEqual(try histogramSum(brightenedHist), 2500.0, threshold: 1.0) - assertAlmostEqual(try histogramSum(doubledHist), 2500.0, threshold: 1.0) - } - - @Test - func testHistogramMemoryConsistency() throws { - // Test that histogram operations produce consistent results - let testImage = try makeRegionTestImage(regionSize: 50).cast(.uchar) - - // Compute histograms multiple times - let hist1 = try testImage.histFind() - let hist2 = try testImage.histFind() - let hist3 = try testImage.histFind() - - // All should be identical - try assertImagesEqual(hist1, hist2, maxDiff: 0.0) - try assertImagesEqual(hist2, hist3, maxDiff: 0.0) - - // Sums should be identical - let sum1 = try histogramSum(hist1) - let sum2 = try histogramSum(hist2) - let sum3 = try histogramSum(hist3) - - assertAlmostEqual(sum1, sum2, threshold: 0.001) - assertAlmostEqual(sum2, sum3, threshold: 0.001) - } - - @Test - func testHistogramLargeImages() throws { - // Test histogram computation on larger images - let largeImage = try makeTestImage(width: 500, height: 400, value: 128.0).cast(.uchar) - - let histogram = try largeImage.histFind() - - #expect(histogram.width == 256) - #expect(histogram.height == 1) - - // Should have all pixels in one bin - let peakBin = try histogram.getpoint(x: 128, y: 0)[0] - let totalPixels = 500 * 400 - assertAlmostEqual(peakBin, Double(totalPixels), threshold: 1.0) - - // Total should match - let total = try histogramSum(histogram) - assertAlmostEqual(total, Double(totalPixels), threshold: 1.0) - - // Test equalization on large image - let equalized = try largeImage.histEqual() - #expect(equalized.width == largeImage.width) - #expect(equalized.height == largeImage.height) + + @Test + func testHistogramWithNoise() throws { + // Test histogram with noisy image + let noisyImage = + try VIPSImage.gaussnoise(width: 100, height: 100, sigma: 50.0, mean: 128.0) + .cast(.uchar) + + let histogram = try noisyImage.histFind() + + #expect(histogram.width == 256) + #expect(histogram.height == 1) + + // Total should match number of pixels + let total = try histogramSum(histogram) + assertAlmostEqual(total, 10000.0, threshold: 1.0) // 100*100 pixels + + // For gaussian noise, the peak should be around the mean (128) + let peakBin = try histogram.getpoint(x: 128, y: 0)[0] + let edgeBin = try histogram.getpoint(x: 10, y: 0)[0] + + #expect(peakBin > edgeBin) // Should have more pixels near the mean + + // Most bins should have some pixels (due to noise) + var nonEmptyBins = 0 + for i in 0..<256 { + let binValue = try histogram.getpoint(x: i, y: 0)[0] + if binValue > 0.5 { + nonEmptyBins += 1 + } + } + + #expect(nonEmptyBins > 50) // Noise should spread across many bins + } + + // MARK: - Integration with Other Operations + + @Test + func testHistogramAfterOperations() throws { + let originalImage = try makeTestImage(width: 50, height: 50, value: 100.0).cast(.uchar) + + // Apply various operations and check histograms + let brightened = try originalImage.linear(1.0, 50.0).cast(.uchar) // Add 50 + let doubled = try originalImage.linear(2.0, 0.0).cast(.uchar) // Multiply by 2 + + let originalHist = try originalImage.histFind() + let brightenedHist = try brightened.histFind() + let doubledHist = try doubled.histFind() + + // Original should peak at 100 + let originalPeak = try originalHist.getpoint(x: 100, y: 0)[0] + assertAlmostEqual(originalPeak, 2500.0, threshold: 1.0) + + // Brightened should peak at 150 (100 + 50) + let brightenedPeak = try brightenedHist.getpoint(x: 150, y: 0)[0] + assertAlmostEqual(brightenedPeak, 2500.0, threshold: 1.0) + + // Doubled should peak at 200 (100 * 2) + let doubledPeak = try doubledHist.getpoint(x: 200, y: 0)[0] + assertAlmostEqual(doubledPeak, 2500.0, threshold: 1.0) + + // All should have same total + assertAlmostEqual(try histogramSum(originalHist), 2500.0, threshold: 1.0) + assertAlmostEqual(try histogramSum(brightenedHist), 2500.0, threshold: 1.0) + assertAlmostEqual(try histogramSum(doubledHist), 2500.0, threshold: 1.0) + } + + @Test + func testHistogramMemoryConsistency() throws { + // Test that histogram operations produce consistent results + let testImage = try makeRegionTestImage(regionSize: 50).cast(.uchar) + + // Compute histograms multiple times + let hist1 = try testImage.histFind() + let hist2 = try testImage.histFind() + let hist3 = try testImage.histFind() + + // All should be identical + try assertImagesEqual(hist1, hist2, maxDiff: 0.0) + try assertImagesEqual(hist2, hist3, maxDiff: 0.0) + + // Sums should be identical + let sum1 = try histogramSum(hist1) + let sum2 = try histogramSum(hist2) + let sum3 = try histogramSum(hist3) + + assertAlmostEqual(sum1, sum2, threshold: 0.001) + assertAlmostEqual(sum2, sum3, threshold: 0.001) + } + + @Test + func testHistogramLargeImages() throws { + // Test histogram computation on larger images + let largeImage = try makeTestImage(width: 500, height: 400, value: 128.0).cast(.uchar) + + let histogram = try largeImage.histFind() + + #expect(histogram.width == 256) + #expect(histogram.height == 1) + + // Should have all pixels in one bin + let peakBin = try histogram.getpoint(x: 128, y: 0)[0] + let totalPixels = 500 * 400 + assertAlmostEqual(peakBin, Double(totalPixels), threshold: 1.0) + + // Total should match + let total = try histogramSum(histogram) + assertAlmostEqual(total, Double(totalPixels), threshold: 1.0) + + // Test equalization on large image + let equalized = try largeImage.histEqual() + #expect(equalized.width == largeImage.width) + #expect(equalized.height == largeImage.height) + } } -} \ No newline at end of file +} diff --git a/Tests/VIPSTests/ResampleTests.swift b/Tests/VIPSTests/ResampleTests.swift index afbc3a3..1e7f135 100644 --- a/Tests/VIPSTests/ResampleTests.swift +++ b/Tests/VIPSTests/ResampleTests.swift @@ -1,429 +1,437 @@ -@testable import VIPS import Cvips -import Testing import Foundation +import Testing -@Suite(.vips) -struct ResampleTests { - - // MARK: - Basic Resize Operations - - @Test - func testResizeBasic() throws { - let original = try VIPSImage(fromFilePath: TestImages.colour.path) - let originalWidth = original.width - let originalHeight = original.height - - // Test halving size - let resized = try original.resize(scale: 0.5) - - #expect(resized.width == originalWidth / 2) - #expect(resized.height == originalHeight / 2) - #expect(resized.bands == original.bands) - } - - @Test - func testResizeInterpolation() throws { - for format in nonComplexFormats { - let testImage = try makeTestImage(width: 100, height: 100, value: 128.0).cast(format) - - // Test different interpolation methods - let bilinear = try testImage.resize(scale: 2.0, kernel: .linear) - let bicubic = try testImage.resize(scale: 2.0, kernel: .cubic) - let lanczos = try testImage.resize(scale: 2.0, kernel: .lanczos3) - - #expect(bilinear.width == 200) - #expect(bicubic.width == 200) - #expect(lanczos.width == 200) - - // All should maintain the same number of bands - #expect(bilinear.bands == testImage.bands) - #expect(bicubic.bands == testImage.bands) - #expect(lanczos.bands == testImage.bands) +@testable import VIPS + +extension VIPSTests { + @Suite(.vips, .serialized) + struct ResampleTests { + + // MARK: - Basic Resize Operations + + @Test + func testResizeBasic() throws { + let original = try VIPSImage(fromFilePath: TestImages.colour.path) + let originalWidth = original.width + let originalHeight = original.height + + // Test halving size + let resized = try original.resize(scale: 0.5) + + #expect(resized.width == originalWidth / 2) + #expect(resized.height == originalHeight / 2) + #expect(resized.bands == original.bands) } - } - - @Test - func testResizeGap() throws { - let testImage = try makeTestImage(width: 10, height: 10, value: 100.0) - - // Test with gap parameter - this controls how much of the input is used - let resized = try testImage.resize(scale: 2.0, gap: 2.0) - - #expect(resized.width == 20) - #expect(resized.height == 20) - } - - @Test - func testShrinkCeiling() throws { - let testImage = try makeTestImage(width: 101, height: 101, value: 75.0) - - // Test ceiling behavior with odd dimensions - let shrunk = try testImage.shrink(hshrink: 2, vshrink: 2, ceil: true) - - // With ceil=true, should round up: 101/2 = 50.5 -> 51 - #expect(shrunk.width == 51) - #expect(shrunk.height == 51) - } - - // MARK: - Reduce Operations - - @Test - func testReduceBasic() throws { - let original = try VIPSImage(fromFilePath: TestImages.colour.path) - - // Test basic reduction - let reduced = try original.reduce(hshrink: 2.0, vshrink: 2.0) - - #expect(reduced.width <= original.width / 2 + 1) - #expect(reduced.height <= original.height / 2 + 1) - #expect(reduced.bands == original.bands) - } - - @Test - func testReduceKernel() throws { - let testImage = try makeTestImage(width: 100, height: 100, value: 64.0) - - // Test different reduction kernels - let linear = try testImage.reduce(hshrink: 2.0, vshrink: 2.0, kernel: .linear) - let cubic = try testImage.reduce(hshrink: 2.0, vshrink: 2.0, kernel: .cubic) - let lanczos = try testImage.reduce(hshrink: 2.0, vshrink: 2.0, kernel: .lanczos3) - - #expect(linear.width == 50) - #expect(cubic.width == 50) - #expect(lanczos.width == 50) - - // Values should be approximately preserved - assertAlmostEqual(try linear.avg(), 64.0, threshold: 5.0) - assertAlmostEqual(try cubic.avg(), 64.0, threshold: 5.0) - assertAlmostEqual(try lanczos.avg(), 64.0, threshold: 5.0) - } - - @Test - func testReduceGap() throws { - let testImage = try makeTestImage(width: 200, height: 200, value: 128.0) - - // Test gap parameter affects quality vs speed tradeoff - let normalGap = try testImage.reduce(hshrink: 4.0, vshrink: 4.0, gap: 2.0) - let largeGap = try testImage.reduce(hshrink: 4.0, vshrink: 4.0, gap: 4.0) - - #expect(normalGap.width == 50) - #expect(largeGap.width == 50) - - // Both should preserve roughly the same average - assertAlmostEqual(try normalGap.avg(), 128.0, threshold: 10.0) - assertAlmostEqual(try largeGap.avg(), 128.0, threshold: 10.0) - } - - // MARK: - Thumbnail Operations - - @Test - func testThumbnailImage() throws { - let original = try VIPSImage(fromFilePath: TestImages.colour.path) - - // Create thumbnail maintaining aspect ratio - let thumb = try original.thumbnailImage(width: 100) - - #expect(thumb.width <= 100) - #expect(thumb.height <= 100) - #expect(thumb.bands == original.bands) - - // Should maintain aspect ratio - let aspectRatio = Double(original.width) / Double(original.height) - let thumbRatio = Double(thumb.width) / Double(thumb.height) - assertAlmostEqual(aspectRatio, thumbRatio, threshold: 0.1) - } - - @Test - func testThumbnailImageHeight() throws { - let original = try VIPSImage(fromFilePath: TestImages.colour.path) - - // Create thumbnail with specific height - let thumb = try original.thumbnailImage(width: 200, height: 100) - - #expect(thumb.width <= 200) - #expect(thumb.height <= 100) - } - - @Test - func testThumbnailImageSize() throws { - let original = try VIPSImage(fromFilePath: TestImages.colour.path) - - // Test size parameter - VIPS_SIZE_BOTH means fit within box - let thumb = try original.thumbnailImage(width: 80, height: 80, size: .both) - - #expect(thumb.width <= 80) - #expect(thumb.height <= 80) - - // At least one dimension should be close to the target - let maxDimension = max(thumb.width, thumb.height) - #expect(maxDimension >= 70) // Should be close to 80 - } - - @Test - func testThumbnailImageNoShrinkOnLoad() throws { - let original = try VIPSImage(fromFilePath: TestImages.colour.path) - - // Test with different parameters - let thumb1 = try original.thumbnailImage(width: 100) - let thumb2 = try original.thumbnailImage(width: 100, linear: true) - - // Both should have similar dimensions - #expect(thumb1.width <= 100) - #expect(thumb2.width <= 100) - #expect(abs(thumb1.width - thumb2.width) <= 10) - } - - @Test - func testThumbnailBuffer() throws { - // Load image to buffer first - let data = try Data(contentsOf: TestImages.colour) - - let thumb = try VIPSImage.thumbnail(buffer: .init(data), width: 100) - - #expect(thumb.width <= 100) - #expect(thumb.height <= 100) - #expect(thumb.bands >= 1) - } - - @Test - func testThumbnailBufferWithOptions() throws { - let data = try Data(contentsOf: TestImages.colour) - - let thumb = try VIPSImage.thumbnail(buffer: .init(data), width: 150, height: 100, - crop: .centre, intent: .relative) - - #expect(thumb.width <= 150) - #expect(thumb.height <= 100) - } - - // MARK: - Scale and Aspect Ratio Tests - - @Test - func testAspectRatioPreservation() throws { - let original = try makeTestImage(width: 200, height: 100, value: 50.0) - let aspectRatio = Double(original.width) / Double(original.height) // 2:1 - - // Test uniform scaling preserves aspect ratio - let scaled = try original.resize(scale: 0.5) - let scaledRatio = Double(scaled.width) / Double(scaled.height) - - assertAlmostEqual(aspectRatio, scaledRatio, threshold: 0.01) - #expect(scaled.width == 100) - #expect(scaled.height == 50) - } - - @Test - func testNonUniformScaling() throws { - let original = try makeTestImage(width: 100, height: 100, value: 75.0) - - // Test non-uniform scaling changes aspect ratio - let stretched = try original.resize(scale: 2.0, vscale: 0.5) - - #expect(stretched.width == 200) - #expect(stretched.height == 50) - - let newRatio = Double(stretched.width) / Double(stretched.height) // 4:1 - assertAlmostEqual(newRatio, 4.0, threshold: 0.01) - } - - @Test - func testUpscalingVsDownscaling() throws { - let original = try makeTestImage(width: 50, height: 50, value: 100.0) - - // Test upscaling (>1.0) - let upscaled = try original.resize(scale: 3.0) - #expect(upscaled.width == 150) - #expect(upscaled.height == 150) - assertAlmostEqual(try upscaled.avg(), 100.0, threshold: 5.0) - - // Test downscaling (<1.0) - let downscaled = try original.resize(scale: 0.2) - #expect(downscaled.width == 10) - #expect(downscaled.height == 10) - assertAlmostEqual(try downscaled.avg(), 100.0, threshold: 5.0) - } - - // MARK: - Edge Cases and Error Conditions - - @Test - func testExtremeScaling() throws { - let testImage = try makeTestImage(width: 100, height: 100, value: 50.0) - - // Test very small scaling - let tiny = try testImage.resize(scale: 0.01) - #expect(tiny.width >= 1) - #expect(tiny.height >= 1) - - // Test large scaling - let large = try testImage.resize(scale: 10.0) - #expect(large.width == 1000) - #expect(large.height == 1000) - } - - @Test - func testNegativeValues() throws { - let testImage = try makeTestImage(width: 10, height: 10, value: 25.0) - - - // Negative scaling should throw error - #expect(throws: Error.self) { - _ = try testImage.resize(scale: -1.0) + + @Test + func testResizeInterpolation() throws { + for format in nonComplexFormats { + let testImage = try makeTestImage(width: 100, height: 100, value: 128.0) + .cast(format) + + // Test different interpolation methods + let bilinear = try testImage.resize(scale: 2.0, kernel: .linear) + let bicubic = try testImage.resize(scale: 2.0, kernel: .cubic) + let lanczos = try testImage.resize(scale: 2.0, kernel: .lanczos3) + + #expect(bilinear.width == 200) + #expect(bicubic.width == 200) + #expect(lanczos.width == 200) + + // All should maintain the same number of bands + #expect(bilinear.bands == testImage.bands) + #expect(bicubic.bands == testImage.bands) + #expect(lanczos.bands == testImage.bands) + } } - } - - @Test - func testSmallImageResize() throws { - // Test with very small images - let tiny = try makeTestImage(width: 2, height: 2, value: 200.0) - - let enlarged = try tiny.resize(scale: 5.0) - #expect(enlarged.width == 10) - #expect(enlarged.height == 10) - assertAlmostEqual(try enlarged.avg(), 200.0, threshold: 10.0) - - let shrunk = try tiny.resize(scale: 0.5) - #expect(shrunk.width == 1) - #expect(shrunk.height == 1) - } - - // MARK: - Performance and Quality Tests - - @Test - func testKernelPerformanceCharacteristics() throws { - let testImage = try makeTestImage(width: 200, height: 200, value: 128.0) - - // Different kernels should produce different results but similar averages - let nearest = try testImage.resize(scale: 0.5, kernel: .nearest) - let linear = try testImage.resize(scale: 0.5, kernel: .linear) - let cubic = try testImage.resize(scale: 0.5, kernel: .cubic) - let lanczos = try testImage.resize(scale: 0.5, kernel: .lanczos3) - - // All should be same size - #expect(nearest.width == 100) - #expect(linear.width == 100) - #expect(cubic.width == 100) - #expect(lanczos.width == 100) - - // All should preserve average reasonably well - assertAlmostEqual(try nearest.avg(), 128.0, threshold: 10.0) - assertAlmostEqual(try linear.avg(), 128.0, threshold: 5.0) - assertAlmostEqual(try cubic.avg(), 128.0, threshold: 5.0) - assertAlmostEqual(try lanczos.avg(), 128.0, threshold: 5.0) - - // Nearest neighbor should be exactly preserved for uniform images - assertAlmostEqual(try nearest.avg(), 128.0, threshold: 0.1) - } - - @Test - func testResamplingPreservesDataRange() throws { - for format in intFormats { - // Test that resampling doesn't introduce values outside original range - let testImage = try makeRegionTestImage(regionSize: 25).cast(format) - let originalMin = try testImage.min() - let originalMax = try testImage.max() - - let resampled = try testImage.resize(scale: 2.0, kernel: .linear) - let resampledMin = try resampled.min() - let resampledMax = try resampled.max() - - // Resampled values should stay within reasonable bounds - #expect(resampledMin >= originalMin - 1.0) - #expect(resampledMax <= originalMax + 1.0) + + @Test + func testResizeGap() throws { + let testImage = try makeTestImage(width: 10, height: 10, value: 100.0) + + // Test with gap parameter - this controls how much of the input is used + let resized = try testImage.resize(scale: 2.0, gap: 2.0) + + #expect(resized.width == 20) + #expect(resized.height == 20) } - } - - // MARK: - Real Image Testing - - @Test - func testRealImageThumbnails() throws { - // Test with various real image formats - let testFiles = [ - TestImages.colour, - TestImages.png, - TestImages.webp - ] - - for testFile in testFiles { - let original = try VIPSImage(fromFilePath: testFile.path) - - // Create thumbnail + + @Test + func testShrinkCeiling() throws { + let testImage = try makeTestImage(width: 101, height: 101, value: 75.0) + + // Test ceiling behavior with odd dimensions + let shrunk = try testImage.shrink(hshrink: 2, vshrink: 2, ceil: true) + + // With ceil=true, should round up: 101/2 = 50.5 -> 51 + #expect(shrunk.width == 51) + #expect(shrunk.height == 51) + } + + // MARK: - Reduce Operations + + @Test + func testReduceBasic() throws { + let original = try VIPSImage(fromFilePath: TestImages.colour.path) + + // Test basic reduction + let reduced = try original.reduce(hshrink: 2.0, vshrink: 2.0) + + #expect(reduced.width <= original.width / 2 + 1) + #expect(reduced.height <= original.height / 2 + 1) + #expect(reduced.bands == original.bands) + } + + @Test + func testReduceKernel() throws { + let testImage = try makeTestImage(width: 100, height: 100, value: 64.0) + + // Test different reduction kernels + let linear = try testImage.reduce(hshrink: 2.0, vshrink: 2.0, kernel: .linear) + let cubic = try testImage.reduce(hshrink: 2.0, vshrink: 2.0, kernel: .cubic) + let lanczos = try testImage.reduce(hshrink: 2.0, vshrink: 2.0, kernel: .lanczos3) + + #expect(linear.width == 50) + #expect(cubic.width == 50) + #expect(lanczos.width == 50) + + // Values should be approximately preserved + assertAlmostEqual(try linear.avg(), 64.0, threshold: 5.0) + assertAlmostEqual(try cubic.avg(), 64.0, threshold: 5.0) + assertAlmostEqual(try lanczos.avg(), 64.0, threshold: 5.0) + } + + @Test + func testReduceGap() throws { + let testImage = try makeTestImage(width: 200, height: 200, value: 128.0) + + // Test gap parameter affects quality vs speed tradeoff + let normalGap = try testImage.reduce(hshrink: 4.0, vshrink: 4.0, gap: 2.0) + let largeGap = try testImage.reduce(hshrink: 4.0, vshrink: 4.0, gap: 4.0) + + #expect(normalGap.width == 50) + #expect(largeGap.width == 50) + + // Both should preserve roughly the same average + assertAlmostEqual(try normalGap.avg(), 128.0, threshold: 10.0) + assertAlmostEqual(try largeGap.avg(), 128.0, threshold: 10.0) + } + + // MARK: - Thumbnail Operations + + @Test + func testThumbnailImage() throws { + let original = try VIPSImage(fromFilePath: TestImages.colour.path) + + // Create thumbnail maintaining aspect ratio let thumb = try original.thumbnailImage(width: 100) - + #expect(thumb.width <= 100) #expect(thumb.height <= 100) #expect(thumb.bands == original.bands) - - // Should have reasonable aspect ratio - if original.width > 0 && original.height > 0 { - let originalRatio = Double(original.width) / Double(original.height) - let thumbRatio = Double(thumb.width) / Double(thumb.height) - assertAlmostEqual(originalRatio, thumbRatio, threshold: 0.2) + + // Should maintain aspect ratio + let aspectRatio = Double(original.width) / Double(original.height) + let thumbRatio = Double(thumb.width) / Double(thumb.height) + assertAlmostEqual(aspectRatio, thumbRatio, threshold: 0.1) + } + + @Test + func testThumbnailImageHeight() throws { + let original = try VIPSImage(fromFilePath: TestImages.colour.path) + + // Create thumbnail with specific height + let thumb = try original.thumbnailImage(width: 200, height: 100) + + #expect(thumb.width <= 200) + #expect(thumb.height <= 100) + } + + @Test + func testThumbnailImageSize() throws { + let original = try VIPSImage(fromFilePath: TestImages.colour.path) + + // Test size parameter - VIPS_SIZE_BOTH means fit within box + let thumb = try original.thumbnailImage(width: 80, height: 80, size: .both) + + #expect(thumb.width <= 80) + #expect(thumb.height <= 80) + + // At least one dimension should be close to the target + let maxDimension = max(thumb.width, thumb.height) + #expect(maxDimension >= 70) // Should be close to 80 + } + + @Test + func testThumbnailImageNoShrinkOnLoad() throws { + let original = try VIPSImage(fromFilePath: TestImages.colour.path) + + // Test with different parameters + let thumb1 = try original.thumbnailImage(width: 100) + let thumb2 = try original.thumbnailImage(width: 100, linear: true) + + // Both should have similar dimensions + #expect(thumb1.width <= 100) + #expect(thumb2.width <= 100) + #expect(abs(thumb1.width - thumb2.width) <= 10) + } + + @Test + func testThumbnailBuffer() throws { + // Load image to buffer first + let data = try Data(contentsOf: TestImages.colour) + + let thumb = try VIPSImage.thumbnail(buffer: .init(data), width: 100) + + #expect(thumb.width <= 100) + #expect(thumb.height <= 100) + #expect(thumb.bands >= 1) + } + + @Test + func testThumbnailBufferWithOptions() throws { + let data = try Data(contentsOf: TestImages.colour) + + let thumb = try VIPSImage.thumbnail( + buffer: .init(data), + width: 150, + height: 100, + crop: .centre, + intent: .relative + ) + + #expect(thumb.width <= 150) + #expect(thumb.height <= 100) + } + + // MARK: - Scale and Aspect Ratio Tests + + @Test + func testAspectRatioPreservation() throws { + let original = try makeTestImage(width: 200, height: 100, value: 50.0) + let aspectRatio = Double(original.width) / Double(original.height) // 2:1 + + // Test uniform scaling preserves aspect ratio + let scaled = try original.resize(scale: 0.5) + let scaledRatio = Double(scaled.width) / Double(scaled.height) + + assertAlmostEqual(aspectRatio, scaledRatio, threshold: 0.01) + #expect(scaled.width == 100) + #expect(scaled.height == 50) + } + + @Test + func testNonUniformScaling() throws { + let original = try makeTestImage(width: 100, height: 100, value: 75.0) + + // Test non-uniform scaling changes aspect ratio + let stretched = try original.resize(scale: 2.0, vscale: 0.5) + + #expect(stretched.width == 200) + #expect(stretched.height == 50) + + let newRatio = Double(stretched.width) / Double(stretched.height) // 4:1 + assertAlmostEqual(newRatio, 4.0, threshold: 0.01) + } + + @Test + func testUpscalingVsDownscaling() throws { + let original = try makeTestImage(width: 50, height: 50, value: 100.0) + + // Test upscaling (>1.0) + let upscaled = try original.resize(scale: 3.0) + #expect(upscaled.width == 150) + #expect(upscaled.height == 150) + assertAlmostEqual(try upscaled.avg(), 100.0, threshold: 5.0) + + // Test downscaling (<1.0) + let downscaled = try original.resize(scale: 0.2) + #expect(downscaled.width == 10) + #expect(downscaled.height == 10) + assertAlmostEqual(try downscaled.avg(), 100.0, threshold: 5.0) + } + + // MARK: - Edge Cases and Error Conditions + + @Test + func testExtremeScaling() throws { + let testImage = try makeTestImage(width: 100, height: 100, value: 50.0) + + // Test very small scaling + let tiny = try testImage.resize(scale: 0.01) + #expect(tiny.width >= 1) + #expect(tiny.height >= 1) + + // Test large scaling + let large = try testImage.resize(scale: 10.0) + #expect(large.width == 1000) + #expect(large.height == 1000) + } + + @Test + func testNegativeValues() throws { + let testImage = try makeTestImage(width: 10, height: 10, value: 25.0) + + // Negative scaling should throw error + #expect(throws: Error.self) { + _ = try testImage.resize(scale: -1.0) } } - } - - @Test - func testMultiBandResampling() throws { - // Test resampling preserves all bands correctly - let r = try makeTestImage(width: 50, height: 50, value: 100.0) - let g = try makeTestImage(width: 50, height: 50, value: 150.0) - let b = try makeTestImage(width: 50, height: 50, value: 200.0) - let rgb = try r.bandjoin([g, b]) - - let resized = try rgb.resize(scale: 2.0) - - #expect(resized.width == 100) - #expect(resized.height == 100) - #expect(resized.bands == 3) - - // Check each band separately - let rBand = try resized.extractBand(0) - let gBand = try resized.extractBand(1) - let bBand = try resized.extractBand(2) - - assertAlmostEqual(try rBand.avg(), 100.0, threshold: 5.0) - assertAlmostEqual(try gBand.avg(), 150.0, threshold: 5.0) - assertAlmostEqual(try bBand.avg(), 200.0, threshold: 5.0) - } - - // MARK: - Integration with Format Variations - - @Test - func testResampleAcrossFormats() throws { - let baseImage = try makeTestImage(width: 40, height: 40, value: 127.0) - - for format in nonComplexFormats { - let typedImage = try baseImage.cast(format) - let resized = try typedImage.resize(scale: 1.5) - - #expect(resized.width == 60) - #expect(resized.height == 60) - #expect(resized.bands == 1) - - // Should preserve format - #expect(resized.format == format) - - // Should preserve value reasonably well - let formatName = String(describing: format) - let expectedMax = maxValue[formatName] ?? 255.0 - let tolerance = min(5.0, expectedMax * 0.05) - assertAlmostEqual(try resized.avg(), 127.0, threshold: tolerance) + + @Test + func testSmallImageResize() throws { + // Test with very small images + let tiny = try makeTestImage(width: 2, height: 2, value: 200.0) + + let enlarged = try tiny.resize(scale: 5.0) + #expect(enlarged.width == 10) + #expect(enlarged.height == 10) + assertAlmostEqual(try enlarged.avg(), 200.0, threshold: 10.0) + + let shrunk = try tiny.resize(scale: 0.5) + #expect(shrunk.width == 1) + #expect(shrunk.height == 1) + } + + // MARK: - Performance and Quality Tests + + @Test + func testKernelPerformanceCharacteristics() throws { + let testImage = try makeTestImage(width: 200, height: 200, value: 128.0) + + // Different kernels should produce different results but similar averages + let nearest = try testImage.resize(scale: 0.5, kernel: .nearest) + let linear = try testImage.resize(scale: 0.5, kernel: .linear) + let cubic = try testImage.resize(scale: 0.5, kernel: .cubic) + let lanczos = try testImage.resize(scale: 0.5, kernel: .lanczos3) + + // All should be same size + #expect(nearest.width == 100) + #expect(linear.width == 100) + #expect(cubic.width == 100) + #expect(lanczos.width == 100) + + // All should preserve average reasonably well + assertAlmostEqual(try nearest.avg(), 128.0, threshold: 10.0) + assertAlmostEqual(try linear.avg(), 128.0, threshold: 5.0) + assertAlmostEqual(try cubic.avg(), 128.0, threshold: 5.0) + assertAlmostEqual(try lanczos.avg(), 128.0, threshold: 5.0) + + // Nearest neighbor should be exactly preserved for uniform images + assertAlmostEqual(try nearest.avg(), 128.0, threshold: 0.1) + } + + @Test + func testResamplingPreservesDataRange() throws { + for format in intFormats { + // Test that resampling doesn't introduce values outside original range + let testImage = try makeRegionTestImage(regionSize: 25).cast(format) + let originalMin = try testImage.min() + let originalMax = try testImage.max() + + let resampled = try testImage.resize(scale: 2.0, kernel: .linear) + let resampledMin = try resampled.min() + let resampledMax = try resampled.max() + + // Resampled values should stay within reasonable bounds + #expect(resampledMin >= originalMin - 1.0) + #expect(resampledMax <= originalMax + 1.0) + } + } + + // MARK: - Real Image Testing + + @Test + func testRealImageThumbnails() throws { + // Test with various real image formats + let testFiles = [ + TestImages.colour, + TestImages.png, + TestImages.webp, + ] + + for testFile in testFiles { + let original = try VIPSImage(fromFilePath: testFile.path) + + // Create thumbnail + let thumb = try original.thumbnailImage(width: 100) + + #expect(thumb.width <= 100) + #expect(thumb.height <= 100) + #expect(thumb.bands == original.bands) + + // Should have reasonable aspect ratio + if original.width > 0 && original.height > 0 { + let originalRatio = Double(original.width) / Double(original.height) + let thumbRatio = Double(thumb.width) / Double(thumb.height) + assertAlmostEqual(originalRatio, thumbRatio, threshold: 0.2) + } + } + } + + @Test + func testMultiBandResampling() throws { + // Test resampling preserves all bands correctly + let r = try makeTestImage(width: 50, height: 50, value: 100.0) + let g = try makeTestImage(width: 50, height: 50, value: 150.0) + let b = try makeTestImage(width: 50, height: 50, value: 200.0) + let rgb = try r.bandjoin([g, b]) + + let resized = try rgb.resize(scale: 2.0) + + #expect(resized.width == 100) + #expect(resized.height == 100) + #expect(resized.bands == 3) + + // Check each band separately + let rBand = try resized.extractBand(0) + let gBand = try resized.extractBand(1) + let bBand = try resized.extractBand(2) + + assertAlmostEqual(try rBand.avg(), 100.0, threshold: 5.0) + assertAlmostEqual(try gBand.avg(), 150.0, threshold: 5.0) + assertAlmostEqual(try bBand.avg(), 200.0, threshold: 5.0) + } + + // MARK: - Integration with Format Variations + + @Test + func testResampleAcrossFormats() throws { + let baseImage = try makeTestImage(width: 40, height: 40, value: 127.0) + + for format in nonComplexFormats { + let typedImage = try baseImage.cast(format) + let resized = try typedImage.resize(scale: 1.5) + + #expect(resized.width == 60) + #expect(resized.height == 60) + #expect(resized.bands == 1) + + // Should preserve format + #expect(resized.format == format) + + // Should preserve value reasonably well + let formatName = String(describing: format) + let expectedMax = maxValue[formatName] ?? 255.0 + let tolerance = min(5.0, expectedMax * 0.05) + assertAlmostEqual(try resized.avg(), 127.0, threshold: tolerance) + } + } + + @Test + func testShrinkVsResizeEquivalence() throws { + let testImage = try makeTestImage(width: 100, height: 100, value: 64.0) + + // shrink by 2 should be similar to resize by 0.5 + let shrunk = try testImage.shrink(hshrink: 2, vshrink: 2) + let resized = try testImage.resize(scale: 0.5) + + #expect(shrunk.width == resized.width) + #expect(shrunk.height == resized.height) + + // Values should be very similar for uniform images + assertAlmostEqual(try shrunk.avg(), try resized.avg(), threshold: 2.0) } } - - @Test - func testShrinkVsResizeEquivalence() throws { - let testImage = try makeTestImage(width: 100, height: 100, value: 64.0) - - // shrink by 2 should be similar to resize by 0.5 - let shrunk = try testImage.shrink(hshrink: 2, vshrink: 2) - let resized = try testImage.resize(scale: 0.5) - - #expect(shrunk.width == resized.width) - #expect(shrunk.height == resized.height) - - // Values should be very similar for uniform images - assertAlmostEqual(try shrunk.avg(), try resized.avg(), threshold: 2.0) - } -} \ No newline at end of file +} diff --git a/Tests/VIPSTests/TestSetup.swift b/Tests/VIPSTests/TestSetup.swift index 9c2b1f8..1891b6d 100644 --- a/Tests/VIPSTests/TestSetup.swift +++ b/Tests/VIPSTests/TestSetup.swift @@ -22,12 +22,13 @@ struct VIPSTestScopeProvider: TestScoping { func provideScope(for test: Test, testCase: Test.Case?, performing function: @Sendable () async throws -> Void) async throws { // Ensure VIPS is started before running any test TestSetup.ensureSetup() + //try VIPS.start() // Run the test try await function() - // Note: We don't stop VIPS here because it's shared across all tests - // and stopping it would break subsequent tests + // stop vips + //VIPS.shutdown() } } diff --git a/Tests/VIPSTests/VIPSBlobTests.swift b/Tests/VIPSTests/VIPSBlobTests.swift index cf6291e..6464382 100644 --- a/Tests/VIPSTests/VIPSBlobTests.swift +++ b/Tests/VIPSTests/VIPSBlobTests.swift @@ -2,62 +2,64 @@ import Foundation import Testing import VIPS -@Suite(.vips) -struct VIPSBlobTests { - @Test - func testCreateFromArray() { - let array: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - let blob = VIPSBlob(array) - #expect(blob.count == array.count) - #expect(Array(blob) == array) - } +extension VIPSTests { + @Suite(.vips, .serialized) + struct VIPSBlobTests { + @Test + func testCreateFromArray() { + let array: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + let blob = VIPSBlob(array) + #expect(blob.count == array.count) + #expect(Array(blob) == array) + } - @Test - func testLifeTime() { - var data: SafeData! = SafeData([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - let blob = VIPSBlob( - noCopy: .init(data.raw), - onDealloc: { [data] in - data!.free() - } - ) - data = nil - #expect(blob.count == 10) - #expect(blob == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - } + @Test + func testLifeTime() { + var data: SafeData! = SafeData([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + let blob = VIPSBlob( + noCopy: .init(data.raw), + onDealloc: { [data] in + data!.free() + } + ) + data = nil + #expect(blob.count == 10) + #expect(blob == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + } - @Test func testZeroCopyToData() async throws { - var blob: VIPSBlob? = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - let data = blob! - .withUnsafeBytesAndStorageManagement { buffer, storageManagement in - _ = storageManagement.retain() - return Data( - bytesNoCopy: .init(mutating: buffer.baseAddress!), - count: buffer.count, - deallocator: .custom { ptr, _ in - storageManagement.release() - } - ) - } + @Test func testZeroCopyToData() async throws { + var blob: VIPSBlob? = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + let data = blob! + .withUnsafeBytesAndStorageManagement { buffer, storageManagement in + _ = storageManagement.retain() + return Data( + bytesNoCopy: .init(mutating: buffer.baseAddress!), + count: buffer.count, + deallocator: .custom { ptr, _ in + storageManagement.release() + } + ) + } - blob = nil - #expect(data == Data([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) + blob = nil + #expect(data == Data([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) + } } -} -struct SafeData { - let raw: UnsafeMutableRawBufferPointer + struct SafeData { + let raw: UnsafeMutableRawBufferPointer - init(_ data: some Collection) { - let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: data.count, alignment: 1) - data.withContiguousStorageIfAvailable { p in - buffer.copyMemory(from: .init(p)) + init(_ data: some Collection) { + let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: data.count, alignment: 1) + data.withContiguousStorageIfAvailable { p in + buffer.copyMemory(from: .init(p)) + } + self.raw = buffer } - self.raw = buffer - } - func free() { - print("free called") - raw.deallocate() + func free() { + print("free called") + raw.deallocate() + } } } diff --git a/Tests/VIPSTests/VIPSSourceCustomTests.swift b/Tests/VIPSTests/VIPSSourceCustomTests.swift new file mode 100644 index 0000000..e01b921 --- /dev/null +++ b/Tests/VIPSTests/VIPSSourceCustomTests.swift @@ -0,0 +1,38 @@ +import VIPS +import Testing + + +extension VIPSTests { + @Suite(.vips, .serialized) + struct VIPSSourceCustomTests { + + @Test + func testCustomSignals() throws { + var buffer : [UInt8] = Array("Hello, VIPS!".utf8) + + let source = VIPSSourceCustom() + source.onUnsafeRead { destBuf in + let toRead = min(destBuf.count, buffer.count) + let slice = buffer[..= 0) - } - - @Test - func signOperation() throws { - let image = try VIPSImage(fromFilePath: testPath) - .linear(1.0, -128.0) - - let signImage = try image.sign() - - let maxValue = try signImage.max() - let minValue = try signImage.min() - #expect(maxValue <= 1.0) - #expect(minValue >= -1.0) - } - - @Test - func roundOperations() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 10.7) - - let rounded = try image.round(.rint) - let floored = try image.floor() - let ceiled = try image.ceil() - - let roundAvg = try rounded.avg() - let floorAvg = try floored.avg() - let ceilAvg = try ceiled.avg() - - #expect(floorAvg < ceilAvg) - #expect(floorAvg < roundAvg) - #expect(roundAvg <= ceilAvg) - } - - @Test - func relationalOperations() throws { - let image1 = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 50.0) - let image2 = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 100.0) - - let equal = try image1.equal(image1) - let notEqual = try image1.notequal(image2) - let _ = try image1.less(image2) - let _ = try image1.lesseq(image2) - let _ = try image2.more(image1) - let _ = try image2.moreeq(image1) - - let equalAvg = try equal.avg() - #expect(equalAvg == 255.0) - - let notEqualAvg = try notEqual.avg() - #expect(notEqualAvg == 255.0) - } - - @Test - func relationalConstOperations() throws { - let image = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 128.0) - - let equalConst = try image.equal(128.0) - let lessConst = try image.less(200.0) - let moreConst = try image.more(100.0) - - - let equalAvg = try equalConst.avg() - let lessAvg = try lessConst.avg() - let moreAvg = try moreConst.avg() - - #expect(equalAvg == 255.0) - #expect(lessAvg == 255.0) - #expect(moreAvg == 255.0) - } - - @Test - func comparisonOperators() throws { - let image1 = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 50.0) - let image2 = try VIPSImage.black(width: 100, height: 100) - .linear(1.0, 100.0) - - // Test image-to-image comparison operators - let equal = try image1 == image1 - let notEqual = try image1 != image2 - let less = try image1 < image2 - let lessEq = try image1 <= image2 - let more = try image2 > image1 - let moreEq = try image2 >= image1 - - #expect(try equal.avg() == 255.0) - #expect(try notEqual.avg() == 255.0) - #expect(try less.avg() == 255.0) - #expect(try lessEq.avg() == 255.0) - #expect(try more.avg() == 255.0) - #expect(try moreEq.avg() == 255.0) - - // Test image-to-constant comparison operators - let equalConst = try image1 == 50.0 - let lessConst = try image1 < 100.0 - let moreConst = try image1 > 0.0 - - #expect(try equalConst.avg() == 255.0) - #expect(try lessConst.avg() == 255.0) - #expect(try moreConst.avg() == 255.0) - - // Test constant-to-image comparison operators - let constLess = try 0.0 < image1 - let constMore = try 100.0 > image1 - - #expect(try constLess.avg() == 255.0) - #expect(try constMore.avg() == 255.0) - } - @Test() - func webp() throws { - let image = try VIPSImage(fromFilePath: mythicalGiantPath) - .thumbnailImage(width: 512) - let full = try image - .webpsave() - - let stripped = try image - .webpsave(keep: VipsForeignKeep.none) - - #expect(full.count > stripped.count) - - let jpeg = try image.jpegsave(keep: VipsForeignKeep.none) - - #expect(jpeg.count > stripped.count) - } + var mythicalGiantPath: String { + Bundle.module.resourceURL! + .appendingPathComponent("data") + .appendingPathComponent("mythical_giant.jpg") + .path + } - @Test - func avif() throws { - let image = try VIPSImage(fromFilePath: mythicalGiantPath) - .thumbnailImage(width: 512) - let avif = try image.heifsave(compression: .av1) - #expect(avif.count > 0) - - let imported = try VIPSImage(data: avif) - #expect(imported.size.width == image.size.width) - #expect(imported.size.height == image.size.height) - - } - - @Test - func loadImageFromFile() throws { - let image = try VIPSImage(fromFilePath: testPath) - try image.writeToFile("/tmp/swift-vips/test_out.jpg") - } - - @Test - func loadImageFromSource() throws { - - let data = try Data(contentsOf: testUrl) - - var slice = data[...] - let source = VIPSSourceCustom() - source.onRead { bytesToRead, buffer in - let bytes = slice.prefix(bytesToRead) - buffer = Array(bytes) - slice = slice[(slice.startIndex + bytes.count)...] + @Test + func loadImageFromMemory() throws { + + let data = try Data(contentsOf: testUrl) + + let jpeg = try VIPSImage(data: Array(data)) + .thumbnailImage(width: 100) + .jpegsave(quality: 80) + try Data(jpeg).write(to: URL(fileURLWithPath: "/tmp/swift-vips/test_out.jpg")) + #expect(FileManager.default.fileExists(atPath: "/tmp/swift-vips/test_out.jpg")) } - - let image = try VIPSImage(fromSource: source) - let exported = Data(try image.resize(scale: 0.5).jpegsave()) - try exported.write(to: URL(fileURLWithPath: "/tmp/swift-vips/example-source_0.5.jpg")) - - } + @Test + func resize() throws { + let image = try VIPSImage(fromFilePath: testPath) + + let resized = try image.resize(scale: 0.5) + try resized.writeToFile("/tmp/swift-vips/test_out_0.5.jpg") + #expect(FileManager.default.fileExists(atPath: "/tmp/swift-vips/test_out_0.5.jpg")) + } + + @Test + func thumbnail() throws { + let image = try VIPSImage(fromFilePath: testPath) + + let resized = try image.thumbnailImage(width: 100, height: 100, crop: .attention) + try resized.writeToFile("/tmp/swift-vips/out_w200.jpg") + #expect(FileManager.default.fileExists(atPath: "/tmp/swift-vips/out_w200.jpg")) + } + + @Test + func average() throws { + let image = try VIPSImage(fromFilePath: testPath) + _ = try image.avg() + } + + @Test + func size() throws { + let image = try VIPSImage(fromFilePath: testPath) + let size = image.size + #expect(size.width == 1500) + #expect(size.height == 625) + } + + @Test + func exportJpeg() throws { + let image = try VIPSImage(fromFilePath: testPath) + let jpeg = try image.jpegsave(quality: 80) + try Data(jpeg).write(to: URL(fileURLWithPath: "/tmp/swift-vips/out_exported.jpg")) + } + + @Test + func text() throws { + let text = try (try VIPSImage.text("hello world") * 0.3) + .cast(VIPS_FORMAT_UCHAR) + let jpeg = try text.pngsave() + try Data(jpeg).write(to: URL(fileURLWithPath: "/tmp/swift-vips/out_text_exported.jpg")) + } + + @Test + func deletionOfFile() throws { + let tmpFile = "/tmp/\(UUID().uuidString).jpg" + try FileManager.default.copyItem(atPath: testPath, toPath: tmpFile) + defer { + try? FileManager.default.removeItem(atPath: tmpFile) + } + + let image = try VIPSImage(fromFilePath: tmpFile) + + try FileManager.default.removeItem(atPath: tmpFile) + + #expect(throws: VIPSError.self) { + try image.avg() + } + } + + @Test + func divideOperation() throws { + let image = try VIPSImage(fromFilePath: testPath) + let image2 = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 10.0) + + let divided = try image.divide(image2) + #expect(divided.size.width == image.size.width) + + // Test division operator + let dividedWithOperator = try image / image2 + #expect(dividedWithOperator.size.width == image.size.width) + } + + @Test + func absOperation() throws { + let image = try VIPSImage(fromFilePath: testPath) + .linear(-1.0, 0.0) + + let absImage = try image.abs() + + let minValue = try absImage.min() + #expect(minValue >= 0) + } + + @Test + func signOperation() throws { + let image = try VIPSImage(fromFilePath: testPath) + .linear(1.0, -128.0) + + let signImage = try image.sign() + + let maxValue = try signImage.max() + let minValue = try signImage.min() + #expect(maxValue <= 1.0) + #expect(minValue >= -1.0) + } + + @Test + func roundOperations() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 10.7) + + let rounded = try image.round(.rint) + let floored = try image.floor() + let ceiled = try image.ceil() + + let roundAvg = try rounded.avg() + let floorAvg = try floored.avg() + let ceilAvg = try ceiled.avg() + + #expect(floorAvg < ceilAvg) + #expect(floorAvg < roundAvg) + #expect(roundAvg <= ceilAvg) + } + + @Test + func relationalOperations() throws { + let image1 = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 50.0) + let image2 = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 100.0) + + let equal = try image1.equal(image1) + let notEqual = try image1.notequal(image2) + let _ = try image1.less(image2) + let _ = try image1.lesseq(image2) + let _ = try image2.more(image1) + let _ = try image2.moreeq(image1) + + let equalAvg = try equal.avg() + #expect(equalAvg == 255.0) + + let notEqualAvg = try notEqual.avg() + #expect(notEqualAvg == 255.0) + } + + @Test + func relationalConstOperations() throws { + let image = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 128.0) + + let equalConst = try image.equal(128.0) + let lessConst = try image.less(200.0) + let moreConst = try image.more(100.0) + + let equalAvg = try equalConst.avg() + let lessAvg = try lessConst.avg() + let moreAvg = try moreConst.avg() + + #expect(equalAvg == 255.0) + #expect(lessAvg == 255.0) + #expect(moreAvg == 255.0) + } + + @Test + func comparisonOperators() throws { + let image1 = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 50.0) + let image2 = try VIPSImage.black(width: 100, height: 100) + .linear(1.0, 100.0) + + // Test image-to-image comparison operators + let equal = try image1 == image1 + let notEqual = try image1 != image2 + let less = try image1 < image2 + let lessEq = try image1 <= image2 + let more = try image2 > image1 + let moreEq = try image2 >= image1 + + #expect(try equal.avg() == 255.0) + #expect(try notEqual.avg() == 255.0) + #expect(try less.avg() == 255.0) + #expect(try lessEq.avg() == 255.0) + #expect(try more.avg() == 255.0) + #expect(try moreEq.avg() == 255.0) + + // Test image-to-constant comparison operators + let equalConst = try image1 == 50.0 + let lessConst = try image1 < 100.0 + let moreConst = try image1 > 0.0 + + #expect(try equalConst.avg() == 255.0) + #expect(try lessConst.avg() == 255.0) + #expect(try moreConst.avg() == 255.0) + + // Test constant-to-image comparison operators + let constLess = try 0.0 < image1 + let constMore = try 100.0 > image1 + + #expect(try constLess.avg() == 255.0) + #expect(try constMore.avg() == 255.0) + } + + @Test() + func webp() throws { + let image = try VIPSImage(fromFilePath: mythicalGiantPath) + .thumbnailImage(width: 512) + let full = + try image + .webpsave() + + let stripped = + try image + .webpsave(keep: VipsForeignKeep.none) + + #expect(full.count > stripped.count) + + let jpeg = try image.jpegsave(keep: VipsForeignKeep.none) + + #expect(jpeg.count > stripped.count) + } + + @Test + func avif() throws { + let image = try VIPSImage(fromFilePath: mythicalGiantPath) + .thumbnailImage(width: 512) + let avif = try image.heifsave(compression: .av1) + #expect(avif.count > 0) + + let imported = try VIPSImage(data: avif) + #expect(imported.size.width == image.size.width) + #expect(imported.size.height == image.size.height) + + } + + @Test + func loadImageFromFile() throws { + let image = try VIPSImage(fromFilePath: testPath) + try image.writeToFile("/tmp/swift-vips/test_out.jpg") + } + + @Test + func loadImageFromSource() throws { + + let data = try Data(contentsOf: testUrl) + + var slice = data[...] + let source = VIPSSourceCustom() + source.onUnsafeRead { destBuf in + let bytes = slice.prefix(destBuf.count) + destBuf.copyBytes(from: bytes) + slice = slice[(slice.startIndex + bytes.count)...] + return bytes.count + } + + let image = try VIPSImage(fromSource: source) + let exported = Data(try image.resize(scale: 0.5).jpegsave()) + try exported.write(to: URL(fileURLWithPath: "/tmp/swift-vips/example-source_0.5.jpg")) + + } + } } diff --git a/docs/VIPS.md b/docs/VIPS.md new file mode 100644 index 0000000..bcb90c8 --- /dev/null +++ b/docs/VIPS.md @@ -0,0 +1,1650 @@ +## Module `VIPS` + +### Table of Contents + +| Type | Name | +| --- | --- | +| class | `VIPSObject` | +| class | `VIPSBlob` | +| class | `VIPSImage` | +| struct | `VIPSImage.Size` | +| class | `VIPSInterpolate` | +| class | `VIPSTarget` | +| class | `VIPSTargetCustom` | +| struct | `UnownedVIPSObject` | +| struct | `VIPSError` | +| struct | `VIPSOption` | +| struct | `Whence` | +| enum | `VIPS` | +| protocol | `VIPSLoggingDelegate` | +| func | `vipsFindLoader(path:)` | +| typealias | `VIPSProgress` | +| typealias | `VipsAccess` | +| typealias | `VipsBandFormat` | +| typealias | `VipsIntent` | +| typealias | `VipsInteresting` | +| typealias | `VipsInterpretation` | +| typealias | `VipsKernel` | +| typealias | `VipsOperationBoolean` | +| typealias | `VipsOperationComplex` | +| typealias | `VipsOperationComplex2` | +| typealias | `VipsOperationComplexget` | +| typealias | `VipsOperationMath` | +| typealias | `VipsOperationMath2` | +| typealias | `VipsOperationRelational` | +| typealias | `VipsOperationRound` | +| typealias | `VipsPCS` | +| typealias | `VipsPrecision` | +| typealias | `VipsSize` | +| Array extension | `Array` | +| Collection extension | `Collection` | + +### Public interface + +#### class VIPSObject + +```swift +public class VIPSObject { + public var type: GType { get } + + public @discardableResult func connectPreClose(_ handler: @escaping (UnownedVIPSObject) -> Void) -> Int +} +``` + +#### class VIPSBlob + +```swift +public final class VIPSBlob: Collection, ExpressibleByArrayLiteral, Sendable, SendableMetatype, Sequence { + /// The number of elements in the collection. + /// + /// To check whether a collection is empty, use its `isEmpty` property + /// instead of comparing `count` to zero. Unless the collection guarantees + /// random-access performance, calculating `count` can be an O(*n*) + /// operation. + /// + /// - Complexity: O(1) if the collection conforms to + /// `RandomAccessCollection`; otherwise, O(*n*), where *n* is the length + /// of the collection. + public var count: Int { get } + + /// A textual representation of this instance, suitable for debugging. + /// + /// Calling this property directly is discouraged. Instead, convert an + /// instance of any type to a string by using the `String(reflecting:)` + /// initializer. This initializer works with any type, and uses the custom + /// `debugDescription` property for types that conform to + /// `CustomDebugStringConvertible`: + /// + /// struct Point: CustomDebugStringConvertible { + /// let x: Int, y: Int + /// + /// var debugDescription: String { + /// return "(\(x), \(y))" + /// } + /// } + /// + /// let p = Point(x: 21, y: 30) + /// let s = String(reflecting: p) + /// print(s) + /// // Prints "(21, 30)" + /// + /// The conversion of `p` to a string in the assignment to `s` uses the + /// `Point` type's `debugDescription` property. + public var debugDescription: String { get } + + /// The collection's "past the end" position---that is, the position one + /// greater than the last valid subscript argument. + /// + /// When you need a range that includes the last element of a collection, use + /// the half-open range operator (`..<`) with `endIndex`. The `..<` operator + /// creates a range that doesn't include the upper bound, so it's always + /// safe to use with `endIndex`. For example: + /// + /// let numbers = [10, 20, 30, 40, 50] + /// if let index = numbers.firstIndex(of: 30) { + /// print(numbers[index ..< numbers.endIndex]) + /// } + /// // Prints "[30, 40, 50]" + /// + /// If the collection is empty, `endIndex` is equal to `startIndex`. + public var endIndex: Int { get } + + /// The position of the first element in a nonempty collection. + /// + /// If the collection is empty, `startIndex` is equal to `endIndex`. + public var startIndex: Int { get } + + public func copy() -> VIPSBlob + + public func findLoader() -> String? + + /// Returns the position immediately after the given index. + /// + /// The successor of an index must be well defined. For an index `i` into a + /// collection `c`, calling `c.index(after: i)` returns the same index every + /// time. + /// + /// - Parameter i: A valid index of the collection. `i` must be less than + /// `endIndex`. + /// - Returns: The index value immediately after `i`. + public func index(after i: Int) -> Int + + /// Creates a new VIPSBlob by copying the bytes from the given collection. + public init(_ buffer: some Collection) + + /// Creates an instance initialized with the given elements. + public convenience init(arrayLiteral elements: UInt8...) + + /// Create a VIPSBlob from a buffer without copying the data. + /// IMPORTANT: It is the responsibility of the caller to ensure that the buffer remains valid for the lifetime + /// of the VIPSBlob or any derived images. + public init(noCopy buffer: UnsafeRawBufferPointer) + + /// Create a VIPSBlob from a buffer without copying the data. + /// Will call `onDealloc` when the lifetime of the blob ends. + /// IMPORTANT: It is the responsibility of the caller to ensure that the buffer remains valid for the lifetime + /// of the VIPSBlob or any derived images. + public init(noCopy buffer: UnsafeRawBufferPointer, onDealloc: @escaping () -> Void) + + /// Yield the contiguous buffer of this blob. + /// + /// Never returns nil, unless `body` returns nil. + public func withContiguousStorageIfAvailable(_ body: (UnsafeBufferPointer) throws -> R) rethrows -> R? + + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R + + /// Access the raw bytes of the blob. + /// + /// If you escape the pointer from the closure, you _must_ call `storageManagement.retain()` to get ownership to + /// the bytes and you also must call `storageManagement.release()` if you no longer require those bytes. Calls to + /// `retain` and `release` must be balanced. + public func withUnsafeBytesAndStorageManagement(_ body: (UnsafeRawBufferPointer, Unmanaged) throws -> R) rethrows -> R +} +``` + +#### class VIPSImage + +```swift +public class VIPSImage: VIPSObject { + /// A structure representing the dimensions of an image. + public struct Size { + /// The height of the image in pixels. + public var height: Int + + /// The width of the image in pixels. + public var width: Int + + /// Creates a new Size with the specified width and height. + public init(width: Int, height: Int) + } + + /// The number of bands (channels) in the image. + /// + /// For example, RGB images have 3 bands, RGBA images have 4 bands, + /// and grayscale images have 1 band. + /// - Returns: The number of bands in the image + public var bands: Int { get } + + /// The name of the file the image was loaded from. + /// + /// Returns the filename that was used to load this image, or `nil` + /// if the image was created programmatically or from memory. + /// - Returns: The filename, or `nil` if no filename is available + public var filename: String? { get } + + /// Whether the image has an alpha channel. + /// + /// This checks if the image has transparency information. For example, + /// RGBA images return true, while RGB images return false. + /// - Returns: `true` if the image has an alpha channel, `false` otherwise + public var hasAlpha: Bool { get } + + /// Whether the image has an embedded ICC color profile. + /// + /// ICC profiles contain information about the color characteristics + /// of the image and are used for accurate color reproduction. + /// - Returns: `true` if the image has an ICC profile, `false` otherwise + public var hasProfile: Bool { get } + + /// The number of pixels down the image. + /// - Returns: The image height in pixels + public var height: Int { get } + + /// The processing history of the image. + /// + /// VIPS maintains a log of operations that have been performed on an image. + /// This can be useful for debugging or understanding how an image was created. + /// - Returns: The history string, or `nil` if no history is available + public var history: String? { get } + + /// The image mode as a string. + /// + /// This is an optional string field that can be used to store additional + /// information about how the image should be interpreted or processed. + /// - Returns: The mode string, or `nil` if no mode is set + public var mode: String? { get } + + /// The number of pages in the image file. + /// + /// This is the number of pages in the original image file, not necessarily + /// the number of pages that have been loaded into this image object. + /// For single-page images, this returns 1. + /// - Returns: The number of pages in the image file + public var nPages: Int { get } + + /// The number of sub-image file directories. + /// + /// Some image formats (particularly TIFF) can contain multiple sub-images + /// or sub-directories. This returns the count of such structures. + /// Returns 0 if not present or not applicable to the format. + /// - Returns: The number of sub-IFDs in the image file + public var nSubifds: Int { get } + + /// The offset value for matrix images. + /// + /// Matrix images can have an optional offset field for use by integer + /// convolution operations. The offset is added after convolution + /// and scaling. + /// - Returns: The offset value, typically 0.0 for non-matrix images + public var offset: Double { get } + + /// The EXIF orientation value for the image. + /// + /// Returns the orientation value from EXIF metadata, if present. + /// Values range from 1-8 according to the EXIF specification. + /// - Returns: The EXIF orientation value, or 1 if no orientation is set + public var orientation: Int { get } + + /// Whether applying the orientation would swap width and height. + /// + /// Some EXIF orientations require rotating the image by 90 or 270 degrees, + /// which would swap the width and height dimensions. This property + /// indicates if such a swap would occur. + /// - Returns: `true` if width and height would swap when applying orientation + public var orientationSwap: Bool { get } + + /// The height of each page in multi-page images. + /// + /// Multi-page images (such as animated GIFs or multi-page TIFFs) can have + /// a page height different from the total image height. If page-height is + /// not set, it defaults to the image height. + /// - Returns: The height of each page in pixels + public var pageHeight: Int { get } + + /// The scale factor for matrix images. + /// + /// Matrix images can have an optional scale field for use by integer + /// convolution operations. The scale is applied after convolution + /// to normalize the result. + /// - Returns: The scale factor, typically 1.0 for non-matrix images + public var scale: Double { get } + + /// The size of the image as a Size struct containing width and height. + /// - Returns: A Size struct with the image dimensions in pixels + public var size: Size { get } + + /// The interpretation of the image as a human-readable string. + /// + /// This returns the string representation of the image's interpretation, + /// such as "srgb", "rgb", "cmyk", "lab", etc. + /// - Returns: A string describing the image interpretation + public var space: String { get } + + /// The number of pixels across the image. + /// - Returns: The image width in pixels + public var width: Int { get } + + /// The horizontal position of the image origin, in pixels. + /// + /// This is a hint about where this image should be positioned relative + /// to some larger canvas. It's often used in image tiling operations. + /// - Returns: The horizontal offset in pixels + public var xoffset: Int { get } + + /// The horizontal image resolution in pixels per millimeter. + /// + /// This represents the physical resolution of the image when printed + /// or displayed. A value of 1.0 means 1 pixel per millimeter. + /// - Returns: The horizontal resolution in pixels per millimeter + public var xres: Double { get } + + /// The vertical position of the image origin, in pixels. + /// + /// This is a hint about where this image should be positioned relative + /// to some larger canvas. It's often used in image tiling operations. + /// - Returns: The vertical offset in pixels + public var yoffset: Int { get } + + /// The vertical image resolution in pixels per millimeter. + /// + /// This represents the physical resolution of the image when printed + /// or displayed. A value of 1.0 means 1 pixel per millimeter. + /// - Returns: The vertical resolution in pixels per millimeter + public var yres: Double { get } + + /// Bandwise join a set of images + /// + public static func bandjoin(_ in: [VIPSImage]) throws -> VIPSImage + + /// Band-wise rank of a set of images + /// + public static func bandrank(_ in: [VIPSImage], index: Int? = nil) throws -> VIPSImage + + /// Make a black image + /// + public static func black(width: Int, height: Int, bands: Int? = nil) throws -> VIPSImage + + /// Make an image showing the eye's spatial response + /// + public static func eye(width: Int, height: Int, uchar: Bool? = nil, factor: Double? = nil) throws -> VIPSImage + + /// Make a fractal surface + /// + public static func fractsurf(width: Int, height: Int, fractalDimension: Double) throws -> VIPSImage + + /// Make a gaussnoise image + /// + public static func gaussnoise(width: Int, height: Int, sigma: Double? = nil, mean: Double? = nil, seed: Int? = nil) throws -> VIPSImage + + /// Make a grey ramp image + /// + public static func grey(width: Int, height: Int, uchar: Bool? = nil) throws -> VIPSImage + + /// Make a 1d image where pixel values are indexes + /// + public static func identity(bands: Int? = nil, ushort: Bool? = nil, size: Int? = nil) throws -> VIPSImage + + /// Make a butterworth filter + /// + public static func maskButterworth(width: Int, height: Int, order: Double, frequencyCutoff: Double, amplitudeCutoff: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage + + /// Make a butterworth_band filter + /// + public static func maskButterworthBand(width: Int, height: Int, order: Double, frequencyCutoffX: Double, frequencyCutoffY: Double, radius: Double, amplitudeCutoff: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage + + /// Make a butterworth ring filter + /// + public static func maskButterworthRing(width: Int, height: Int, order: Double, frequencyCutoff: Double, amplitudeCutoff: Double, ringwidth: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage + + /// Make fractal filter + /// + public static func maskFractal(width: Int, height: Int, fractalDimension: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage + + /// Make a gaussian filter + /// + public static func maskGaussian(width: Int, height: Int, frequencyCutoff: Double, amplitudeCutoff: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage + + /// Make a gaussian filter + /// + public static func maskGaussianBand(width: Int, height: Int, frequencyCutoffX: Double, frequencyCutoffY: Double, radius: Double, amplitudeCutoff: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage + + /// Make a gaussian ring filter + /// + public static func maskGaussianRing(width: Int, height: Int, frequencyCutoff: Double, amplitudeCutoff: Double, ringwidth: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage + + /// Make an ideal filter + /// + public static func maskIdeal(width: Int, height: Int, frequencyCutoff: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage + + /// Make an ideal band filter + /// + public static func maskIdealBand(width: Int, height: Int, frequencyCutoffX: Double, frequencyCutoffY: Double, radius: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage + + /// Make an ideal ring filter + /// + public static func maskIdealRing(width: Int, height: Int, frequencyCutoff: Double, ringwidth: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage + + /// Create an empty matrix image + public static func matrix(width: Int, height: Int) throws -> VIPSImage + + /// Create a matrix image from a double array + public static func matrix(width: Int, height: Int, data: [Double]) throws -> VIPSImage + + /// Make a perlin noise image + /// + public static func perlin(width: Int, height: Int, cellSize: Int? = nil, uchar: Bool? = nil, seed: Int? = nil) throws -> VIPSImage + + /// Load named icc profile + /// + public static func profileLoad(name: String) throws -> VIPSBlob + + /// Make a 2d sine wave + /// + public static func sines(width: Int, height: Int, uchar: Bool? = nil, hfreq: Double? = nil, vfreq: Double? = nil) throws -> VIPSImage + + /// Sum an array of images + /// + public static func sum(_ in: [VIPSImage]) throws -> VIPSImage + + /// Find the index of the first non-zero pixel in tests + /// + public static func `switch`(tests: [VIPSImage]) throws -> VIPSImage + + /// Build a look-up table + /// + public static func tonelut(inMax: Int? = nil, outMax: Int? = nil, Lb: Double? = nil, Lw: Double? = nil, Ps: Double? = nil, Pm: Double? = nil, Ph: Double? = nil, S: Double? = nil, M: Double? = nil, H: Double? = nil) throws -> VIPSImage + + /// Make a worley noise image + /// + public static func worley(width: Int, height: Int, cellSize: Int? = nil, seed: Int? = nil) throws -> VIPSImage + + /// Make an image where pixel values are coordinates + /// + public static func xyz(width: Int, height: Int, csize: Int? = nil, dsize: Int? = nil, esize: Int? = nil) throws -> VIPSImage + + /// Make a zone plate + /// + public static func zone(width: Int, height: Int, uchar: Bool? = nil) throws -> VIPSImage + + /// Transform lch to cmc + public func CMC2LCh() throws -> VIPSImage + + /// Transform cmyk to xyz + public func CMYK2XYZ() throws -> VIPSImage + + /// Transform hsv to srgb + public func HSV2sRGB() throws -> VIPSImage + + /// Transform lch to cmc + public func LCh2CMC() throws -> VIPSImage + + /// Transform lch to lab + public func LCh2Lab() throws -> VIPSImage + + /// Transform lab to lch + public func Lab2LCh() throws -> VIPSImage + + /// Transform float lab to labq coding + public func Lab2LabQ() throws -> VIPSImage + + /// Transform float lab to signed short + public func Lab2LabS() throws -> VIPSImage + + /// Transform cielab to xyz + /// + public func Lab2XYZ(temp: [Double]? = nil) throws -> VIPSImage + + /// Unpack a labq image to float lab + public func LabQ2Lab() throws -> VIPSImage + + /// Unpack a labq image to short lab + public func LabQ2LabS() throws -> VIPSImage + + /// Convert a labq image to srgb + public func LabQ2sRGB() throws -> VIPSImage + + /// Transform signed short lab to float + public func LabS2Lab() throws -> VIPSImage + + /// Transform short lab to labq coding + public func LabS2LabQ() throws -> VIPSImage + + /// Transform xyz to cmyk + public func XYZ2CMYK() throws -> VIPSImage + + /// Transform xyz to lab + /// + public func XYZ2Lab(temp: [Double]? = nil) throws -> VIPSImage + + /// Transform xyz to yxy + public func XYZ2Yxy() throws -> VIPSImage + + /// Transform xyz to scrgb + public func XYZ2scRGB() throws -> VIPSImage + + /// Transform yxy to xyz + public func Yxy2XYZ() throws -> VIPSImage + + /// Absolute value of an image + public func abs() throws -> VIPSImage + + /// Calculate arccosine of image values (result in degrees) + public func acos() throws -> VIPSImage + + /// Calculate inverse hyperbolic cosine of image values + public func acosh() throws -> VIPSImage + + /// Add two images + /// + public func add(_ rhs: VIPSImage) throws -> VIPSImage + + /// Bitwise AND of two images + /// + public func andimage(_ rhs: VIPSImage) throws -> VIPSImage + + /// Calculate arcsine of image values (result in degrees) + public func asin() throws -> VIPSImage + + /// Calculate inverse hyperbolic sine of image values + public func asinh() throws -> VIPSImage + + /// Calculate arctangent of image values (result in degrees) + public func atan() throws -> VIPSImage + + /// Calculate two-argument arctangent of y/x in degrees + public func atan2(_ x: VIPSImage) throws -> VIPSImage + + /// Calculate inverse hyperbolic tangent of image values + public func atanh() throws -> VIPSImage + + /// Autorotate image by exif tag + public func autorot() throws -> VIPSImage + + /// Same as `autorot()` + /// + /// See: `VIPSImage.autorot()` + public func autorotate() throws -> VIPSImage + + /// Find image average + public func avg() throws -> Double + + /// Perform bitwise AND operation across bands. + /// + /// Reduces multiple bands to a single band by performing bitwise AND + /// on corresponding pixels across all bands. + /// + /// - Returns: A new single-band image + /// - Throws: `VIPSError` if the operation fails + public func bandand() throws -> VIPSImage + + /// Perform bitwise XOR (exclusive OR) operation across bands. + /// + /// Reduces multiple bands to a single band by performing bitwise XOR + /// on corresponding pixels across all bands. + /// + /// - Returns: A new single-band image + /// - Throws: `VIPSError` if the operation fails + public func bandeor() throws -> VIPSImage + + /// Fold up x axis into bands + /// + public func bandfold(factor: Int? = nil) throws -> VIPSImage + + /// Append a constant band to an image + /// + public func bandjoinConst(c: [Double]) throws -> VIPSImage + + /// Band-wise average + public func bandmean() throws -> VIPSImage + + /// Perform bitwise OR operation across bands. + /// + /// Reduces multiple bands to a single band by performing bitwise OR + /// on corresponding pixels across all bands. + /// + /// - Returns: A new single-band image + /// - Throws: `VIPSError` if the operation fails + public func bandor() throws -> VIPSImage + + /// Unfold image bands into x axis + /// + public func bandunfold(factor: Int? = nil) throws -> VIPSImage + + /// Build a look-up table + public func buildlut() throws -> VIPSImage + + /// Byteswap an image + public func byteswap() throws -> VIPSImage + + /// Use pixel values to pick cases from an array of images + /// + public func `case`(cases: [VIPSImage]) throws -> VIPSImage + + public func ceil() throws -> VIPSImage + + /// Clamp values of an image + /// + public func clamp(min: Double? = nil, max: Double? = nil) throws -> VIPSImage + + /// Create a complex image from real and imaginary parts + public func complex(_ imaginary: VIPSImage) throws -> VIPSImage + + /// Form a complex image from two real images + /// + public func complexform(_ rhs: VIPSImage) throws -> VIPSImage + + /// Get the concurrency hint for this image. + /// + /// This returns the suggested level of parallelism for operations on this + /// image. It can be used to optimize performance by limiting the number + /// of threads used for processing. + /// + /// - Parameter defaultConcurrency: The default value to return if no hint is set + /// - Returns: The suggested concurrency level + public func concurrency(default defaultConcurrency: Int = 1) -> Int + + /// Calculate complex conjugate + public func conj() throws -> VIPSImage + + /// Approximate integer convolution + /// + public func conva(mask: VIPSImage, layers: Int? = nil, cluster: Int? = nil) throws -> VIPSImage + + /// Approximate separable integer convolution + /// + public func convasep(mask: VIPSImage, layers: Int? = nil) throws -> VIPSImage + + /// Float convolution operation + /// + public func convf(mask: VIPSImage) throws -> VIPSImage + + /// Int convolution operation + /// + public func convi(mask: VIPSImage) throws -> VIPSImage + + /// This function allocates memory, renders image into it, builds a new image + /// around the memory area, and returns that. + /// + /// If the image is already a simple area of memory, it just refs image and + /// returns it. + public func copyMemory() throws -> VIPSImage + + /// Calculate cosine of image values (in degrees) + public func cos() throws -> VIPSImage + + /// Calculate hyperbolic cosine of image values + public func cosh() throws -> VIPSImage + + /// Extract an area from an image + /// + public func crop(left: Int, top: Int, width: Int, height: Int) throws -> VIPSImage + + /// Calculate de00 + /// + public func dE00(_ rhs: VIPSImage) throws -> VIPSImage + + /// Calculate de76 + /// + public func dE76(_ rhs: VIPSImage) throws -> VIPSImage + + /// Calculate decmc + /// + public func dECMC(_ rhs: VIPSImage) throws -> VIPSImage + + /// Find image standard deviation + public func deviate() throws -> Double + + /// Divide two images + /// + public func divide(_ rhs: VIPSImage) throws -> VIPSImage + + /// Bitwise XOR of two images + /// + public func eorimage(_ rhs: VIPSImage) throws -> VIPSImage + + /// Test for equality + /// + public func equal(_ value: Double) throws -> VIPSImage + + /// Calculate e^x for each pixel + public func exp() throws -> VIPSImage + + /// Calculate 10^x for each pixel + public func exp10() throws -> VIPSImage + + /// Extract an area from an image + /// + public func extractArea(left: Int, top: Int, width: Int, height: Int) throws -> VIPSImage + + /// Extract band from an image + /// + public func extractBand(_ band: Int, n: Int? = nil) throws -> VIPSImage + + /// False-color an image + public func falsecolour() throws -> VIPSImage + + /// Fast correlation + /// + public func fastcor(ref: VIPSImage) throws -> VIPSImage + + /// Fill image zeros with nearest non-zero pixel + public func fillNearest() throws -> VIPSImage + + /// Search an image for non-edge areas + /// + public func findTrim(threshold: Double? = nil, background: [Double]? = nil, lineArt: Bool? = nil) throws -> Int + + /// Flatten alpha out of an image + /// + public func flatten(background: [Double]? = nil, maxAlpha: Double? = nil) throws -> VIPSImage + + /// Transform float rgb to radiance coding + public func float2rad() throws -> VIPSImage + + public func floor() throws -> VIPSImage + + /// Frequency-domain filtering + /// + public func freqmult(mask: VIPSImage) throws -> VIPSImage + + /// Forward fft + public func fwfft() throws -> VIPSImage + + /// Gamma an image + /// + public func gamma(exponent: Double? = nil) throws -> VIPSImage + + /// Get the value of the pixel at the specified coordinates. + /// + /// This is a convenience method that calls the main `getpoint` implementation. + /// The pixel value is returned as an array of doubles, with one element + /// per band in the image. + /// + public func getpoint(_ x: Int, _ y: Int) throws -> [Double] + + /// Read a point from an image + /// + public func getpoint(x: Int, y: Int, unpackComplex: Bool? = nil) throws -> [Double] + + /// Global balance an image mosaic + /// + public func globalbalance(gamma: Double? = nil, intOutput: Bool? = nil) throws -> VIPSImage + + /// Grid an image + /// + public func grid(tileHeight: Int, across: Int, down: Int) throws -> VIPSImage + + /// Form cumulative histogram + public func histCum() throws -> VIPSImage + + /// Estimate image entropy + public func histEntropy() throws -> Double + + /// Histogram equalisation + /// + public func histEqual(band: Int? = nil) throws -> VIPSImage + + /// Find image histogram + /// + public func histFind(band: Int? = nil) throws -> VIPSImage + + /// Find n-dimensional image histogram + /// + public func histFindNdim(bins: Int? = nil) throws -> VIPSImage + + /// Test for monotonicity + public func histIsmonotonic() throws -> Bool + + /// Local histogram equalisation + /// + public func histLocal(width: Int, height: Int, maxSlope: Int? = nil) throws -> VIPSImage + + /// Match two histograms + /// + public func histMatch(ref: VIPSImage) throws -> VIPSImage + + /// Normalise histogram + public func histNorm() throws -> VIPSImage + + /// Plot histogram + public func histPlot() throws -> VIPSImage + + /// Find hough circle transform + /// + public func houghCircle(scale: Int? = nil, minRadius: Int? = nil, maxRadius: Int? = nil) throws -> VIPSImage + + /// Find hough line transform + /// + public func houghLine(width: Int? = nil, height: Int? = nil) throws -> VIPSImage + + /// Ifthenelse an image + /// + public func ifthenelse(in1: VIPSImage, in2: VIPSImage, blend: Bool? = nil) throws -> VIPSImage + + /// Extract imaginary part of complex image + public func imag() throws -> VIPSImage + + /// Creates a new image by loading the given data + /// + /// The image will reference the data from the blob. + /// + public convenience init(blob: VIPSBlob, loader: String? = nil, options: String? = nil) throws + + /// Creates a new image by loading the given data. + /// + /// The image will NOT copy the data into its own memory. + /// You need to ensure that the data remains valid for the lifetime of the image and all its descendants. + /// + public convenience init(bufferNoCopy data: UnsafeRawBufferPointer, loader: String? = nil, options: String? = nil) throws + + /// Creates a new image by loading the given data + /// + /// The image will copy the data into its own memory. + /// + public convenience init(data: some Collection, loader: String? = nil, options: String? = nil) throws + + /// Creates a VIPSImage from 64-bit floating point data with memory copy. + /// + /// This function creates a VipsImage from a memory area. The memory area must be a simple array, + /// for example RGBRGBRGB, left-to-right, top-to-bottom. The memory will be copied into the image. + /// + public init(data: some Collection, width: Int, height: Int, bands: Int) throws + + /// Creates a new image by loading the given data + /// + /// The image will NOT copy the data into its own memory. You must + /// ensure that the data remain valid for the lifetime of the image + /// and all its descendants. + /// + public convenience init(unsafeData: UnsafeRawBufferPointer, loader: String? = nil, options: String? = nil) throws + + /// Creates a VIPSImage from a memory area containing 64-bit floating point data. + /// + /// This function wraps a VipsImage around a memory area. The memory area must be a simple array, + /// for example RGBRGBRGB, left-to-right, top-to-bottom. + /// + /// **DANGER**: VIPS does not take responsibility for the memory area. The memory will NOT be copied + /// into the image. You must ensure the memory remains valid for the lifetime of the image and all + /// its descendants. Use the copy variant if you are unsure about memory management. + /// + public init(unsafeData buffer: UnsafeBufferPointer, width: Int, height: Int, bands: Int) throws + + /// Insert image @sub into @main at @x, @y + /// + public func insert(sub: VIPSImage, x: Int, y: Int, expand: Bool? = nil, background: [Double]? = nil) throws -> VIPSImage + + /// Invert an image + public func invert() throws -> VIPSImage + + /// Build an inverted look-up table + /// + public func invertlut(size: Int? = nil) throws -> VIPSImage + + /// Inverse fft + /// + public func invfft(real: Bool? = nil) throws -> VIPSImage + + /// Label regions in an image + public func labelregions() throws -> VIPSImage + + /// Test for less than + /// + public func less(_ value: Double) throws -> VIPSImage + + /// Test for less than or equal + /// + public func lesseq(_ value: Double) throws -> VIPSImage + + public func linear(_ a: [Double], _ b: [Double], uchar: Bool? = nil) throws -> VIPSImage + + /// Calculate natural logarithm (ln) of each pixel + public func log() throws -> VIPSImage + + /// Calculate base-10 logarithm of each pixel + public func log10() throws -> VIPSImage + + /// Left shift + /// + public func lshift(_ amount: Int) throws -> VIPSImage + + /// Map an image though a lut + /// + public func maplut(lut: VIPSImage, band: Int? = nil) throws -> VIPSImage + + /// First-order match of two images + /// + public func match(sec: VIPSImage, xr1: Int, yr1: Int, xs1: Int, ys1: Int, xr2: Int, yr2: Int, xs2: Int, ys2: Int, hwindow: Int? = nil, harea: Int? = nil, search: Bool? = nil, interpolate: VIPSInterpolate? = nil) throws -> VIPSImage + + /// Invert an matrix + public func matrixinvert() throws -> VIPSImage + + /// Find image maximum + /// + public func max(size: Int? = nil) throws -> Double + + /// Maximum of a pair of images + /// + public func maxpair(_ rhs: VIPSImage) throws -> VIPSImage + + /// Measure a set of patches on a color chart + /// + public func measure(h: Int, v: Int, left: Int? = nil, top: Int? = nil, width: Int? = nil, height: Int? = nil) throws -> VIPSImage + + /// Find image minimum + /// + public func min(size: Int? = nil) throws -> Double + + /// Minimum of a pair of images + /// + public func minpair(_ rhs: VIPSImage) throws -> VIPSImage + + /// Test for greater than + /// + public func more(_ value: Double) throws -> VIPSImage + + /// Test for greater than or equal + /// + public func moreeq(_ value: Double) throws -> VIPSImage + + /// Pick most-significant byte from an image + /// + public func msb(band: Int? = nil) throws -> VIPSImage + + /// Multiply two images + /// + public func multiply(_ rhs: VIPSImage) throws -> VIPSImage + + public func new(_ colors: [Double]) throws -> VIPSImage + + /// Test for inequality + /// + public func notequal(_ value: Double) throws -> VIPSImage + + /// Bitwise OR of two images + /// + public func orimage(_ rhs: VIPSImage) throws -> VIPSImage + + /// Find threshold for percent of pixels + /// + public func percent(_ percent: Double) throws -> Int + + /// Calculate phase correlation + /// + public func phasecor(in2: VIPSImage) throws -> VIPSImage + + /// Convert complex image to polar form + public func polar() throws -> VIPSImage + + /// Raise image values to a constant power (integer overload) + public func pow(_ exponent: Int) throws -> VIPSImage + + /// Prewitt edge detector + public func prewitt() throws -> VIPSImage + + /// Extract profiles from an image. + /// + /// Creates 1D profiles by averaging across rows and columns. + /// Returns two images: column profile (vertical average) and row profile (horizontal average). + /// + /// - Returns: A tuple containing (columns profile, rows profile) + /// - Throws: `VIPSError` if the operation fails + public func profile() throws -> (columns: VIPSImage, rows: VIPSImage) + + /// Project rows and columns to get sums. + /// + /// Returns two 1D images containing the sum of each row and column. + /// Useful for creating projections and histograms. + /// + /// - Returns: A tuple containing (row sums, column sums) + /// - Throws: `VIPSError` if the operation fails + public func project() throws -> (rows: VIPSImage, columns: VIPSImage) + + /// Resample an image with a quadratic transform + /// + public func quadratic(coeff: VIPSImage, interpolate: VIPSInterpolate? = nil) throws -> VIPSImage + + /// Unpack radiance coding to float rgb + public func rad2float() throws -> VIPSImage + + /// Rank filter + /// + public func rank(width: Int, height: Int, index: Int) throws -> VIPSImage + + /// Extract real part of complex image + public func real() throws -> VIPSImage + + /// Linear recombination with matrix + /// + public func recomb(m: VIPSImage) throws -> VIPSImage + + /// Convert polar image to rectangular form + public func rect() throws -> VIPSImage + + /// Remainder after integer division of an image and a constant + /// + public func remainder(_ value: Int) throws -> VIPSImage + + /// Remainder after integer division of an image and a constant + /// + public func remainderConst(c: [Double]) throws -> VIPSImage + + /// Replicate an image + /// + public func replicate(across: Int, down: Int) throws -> VIPSImage + + /// Rotate an image by a number of degrees + /// + public func rotate(angle: Double, interpolate: VIPSInterpolate? = nil, background: [Double]? = nil, odx: Double? = nil, ody: Double? = nil, idx: Double? = nil, idy: Double? = nil) throws -> VIPSImage + + public func round() throws -> VIPSImage + + /// Right shift + /// + public func rshift(_ amount: Int) throws -> VIPSImage + + /// Transform srgb to hsv + public func sRGB2HSV() throws -> VIPSImage + + /// Convert an srgb image to scrgb + public func sRGB2scRGB() throws -> VIPSImage + + /// Convert scrgb to bw + /// + public func scRGB2BW(depth: Int? = nil) throws -> VIPSImage + + /// Transform scrgb to xyz + public func scRGB2XYZ() throws -> VIPSImage + + /// Convert an scrgb image to srgb + /// + public func scRGB2sRGB(depth: Int? = nil) throws -> VIPSImage + + /// Scale an image to uchar + /// + public func scale(exp: Double? = nil, log: Bool? = nil) throws -> VIPSImage + + /// Scharr edge detector + public func scharr() throws -> VIPSImage + + /// Check sequential access + /// + public func sequential(tileHeight: Int? = nil) throws -> VIPSImage + + public func setProgressReportingEnabled(_ enabled: Bool) + + /// Unsharp masking for print + /// + public func sharpen(sigma: Double? = nil, x1: Double? = nil, y2: Double? = nil, y3: Double? = nil, m1: Double? = nil, m2: Double? = nil) throws -> VIPSImage + + /// Shrink an image + /// + public func shrink(hshrink: Double, vshrink: Double, ceil: Bool? = nil) throws -> VIPSImage + + /// Shrink an image horizontally + /// + public func shrinkh(hshrink: Int, ceil: Bool? = nil) throws -> VIPSImage + + /// Shrink an image vertically + /// + public func shrinkv(vshrink: Int, ceil: Bool? = nil) throws -> VIPSImage + + /// Unit vector of pixel + public func sign() throws -> VIPSImage + + /// Similarity transform of an image + /// + public func similarity(scale: Double? = nil, angle: Double? = nil, interpolate: VIPSInterpolate? = nil, background: [Double]? = nil, odx: Double? = nil, ody: Double? = nil, idx: Double? = nil, idy: Double? = nil) throws -> VIPSImage + + /// Calculate sine of image values (in degrees) + public func sin() throws -> VIPSImage + + /// Calculate hyperbolic sine of image values + public func sinh() throws -> VIPSImage + + /// Sobel edge detector + public func sobel() throws -> VIPSImage + + /// Spatial correlation + /// + public func spcor(ref: VIPSImage) throws -> VIPSImage + + /// Make displayable power spectrum + public func spectrum() throws -> VIPSImage + + /// Find many image stats + public func stats() throws -> VIPSImage + + /// Statistical difference + /// + public func stdif(width: Int, height: Int, s0: Double? = nil, b: Double? = nil, m0: Double? = nil, a: Double? = nil) throws -> VIPSImage + + /// Subsample an image + /// + public func subsample(xfac: Int, yfac: Int, point: Bool? = nil) throws -> VIPSImage + + /// Subtract two images + /// + public func subtract(_ rhs: VIPSImage) throws -> VIPSImage + + /// Calculate tangent of image values (in degrees) + public func tan() throws -> VIPSImage + + /// Calculate hyperbolic tangent of image values + public func tanh() throws -> VIPSImage + + /// Transpose3d an image + /// + public func transpose3d(pageHeight: Int? = nil) throws -> VIPSImage + + /// Raise a constant to the power of this image (integer overload) + public func wop(_ base: Int) throws -> VIPSImage + + /// Wrap image origin + /// + public func wrap(x: Int? = nil, y: Int? = nil) throws -> VIPSImage + + /// Writes the image to a memory buffer in the specified format. + /// + /// This method writes the image to a memory buffer using a format determined by the suffix. + /// Save options may be appended to the suffix as `[name=value,...]` or given in the params + /// dictionary. Options given in the function call override options given in the filename. + /// + /// Currently TIFF, JPEG, PNG and other formats are supported depending on your libvips build. + /// You can call the various save operations directly if you wish, see `jpegsave(buffer:)` for example. + /// + public func writeToBuffer(suffix: String, quality: Int? = nil, options params: [String : Any] = [:], additionalOptions: String? = nil) throws -> VIPSBlob + + /// Writes the image to a file. + /// + /// This method writes the image to a file using the saver recommended by the filename extension. + /// Save options may be appended to the filename as `[name=value,...]` or given in the options + /// dictionary. Options given in the function call override options given in the filename. + /// + public func writeToFile(_ path: String, quality: Int? = nil, options: [String : Any] = [:], additionalOptions: String? = nil) throws + + /// Writes the image to a target in the specified format. + /// + /// This method writes the image to a target using a format determined by the suffix. + /// Save options may be appended to the suffix as `[name=value,...]` or given in the params + /// dictionary. Options given in the function call override options given in the filename. + /// + /// You can call the various save operations directly if you wish, see `jpegsave(target:)` for example. + /// + public func writeToTarget(suffix: String, target: VIPSTarget, quality: Int? = nil, options params: [String : Any] = [:], additionalOptions: String? = nil) throws + + /// Zoom an image + /// + public func zoom(xfac: Int, yfac: Int) throws -> VIPSImage +} +``` + +#### class VIPSInterpolate + +```swift +public class VIPSInterpolate: VIPSObject { + public var bilinear: VIPSInterpolate { get } + + public var nearest: VIPSInterpolate { get } + + public init(_ name: String) throws +} +``` + +#### class VIPSTarget + +```swift +/// A VIPSTarget represents a data destination for saving images in libvips. +/// +/// Targets can write to various destinations including files, memory buffers, +/// file descriptors, and custom writers. Targets support both seekable +/// operations (for files and memory) and streaming operations (for pipes and +/// network connections). +/// +/// # Target Types +/// +/// Different target types have different capabilities: +/// - **File targets**: Support seeking and random access for formats that require it (like TIFF) +/// - **Memory targets**: Store output in memory, accessible via steal() methods +/// - **Pipe targets**: Support only sequential writing, suitable for streaming +/// - **Custom targets**: Behavior depends on the custom implementation +/// +/// # Usage Patterns +/// +/// ```swift +/// // Save to file +/// let target = try VIPSTarget(toFile: "/path/to/output.jpg") +/// try image.write(to: target) +/// +/// // Save to memory +/// let target = try VIPSTarget(toMemory: ()) +/// try image.write(to: target) +/// let data = try target.steal() +/// +/// // Custom target with callback +/// let customTarget = VIPSTargetCustom() +/// customTarget.onWrite { bytes in +/// // Process the written bytes +/// return bytes.count // Return bytes processed +/// } +/// ``` +/// +/// # Thread Safety +/// +/// VIPSTarget instances are not thread-safe. Each target should be used +/// from a single thread, or access should be synchronized externally. +public class VIPSTarget: VIPSObject { + /// Returns true if the target has been ended. + /// + /// Once a target is ended, no further write operations should be performed. + /// This is set to true after calling end(). + public var isEnded: Bool { get } + + /// Returns true if this is a memory target. + /// + /// Memory targets accumulate written data in an internal buffer + /// that can be retrieved with steal() methods. + public var isMemory: Bool { get } + + /// Creates a target that writes to memory. + /// + /// Data written to this target is accumulated in an internal buffer. + /// Use steal() or stealText() to retrieve the accumulated data. + /// + /// - Throws: VIPSError if the target cannot be created + public static func toMemory() throws -> VIPSTarget + + /// Ends the target and flushes all remaining data. + /// + /// This finalizes the target, ensuring all buffered data is written + /// and any cleanup is performed. After calling this method, no further + /// write operations should be performed on this target. + /// + /// Note: This replaces the deprecated finish() method. + /// + /// - Throws: VIPSError if the end operation fails + public func end() throws + + /// Flushes any buffered data to the underlying destination. + /// + /// This forces any data that has been written to the target but not yet + /// sent to the underlying destination (file, descriptor, etc.) to be written. + /// + /// - Throws: VIPSError if the flush operation fails + public func flush() throws + + /// Creates a temporary target based on another target. + /// + /// This creates a temporary target that can be used for intermediate + /// operations before writing to the final target. + /// + /// - Parameter basedOn: The target to base the temporary target on + /// - Throws: VIPSError if the target cannot be created + public init(temp basedOn: VIPSTarget) throws + + /// Creates a target from a file descriptor. + /// + /// The file descriptor is not closed when the target is destroyed. + /// The caller is responsible for managing the file descriptor's lifecycle. + /// + /// - Parameter descriptor: An open file descriptor to write to + /// - Throws: VIPSError if the target cannot be created + public init(toDescriptor descriptor: Int32) throws + + /// Creates a target that will write to the named file. + /// + /// The file is created immediately. If the file already exists, it will be + /// truncated. The directory containing the file must exist. + /// + /// - Parameter path: Path to the file to write to + /// - Throws: VIPSError if the target cannot be created + public init(toFile path: String) throws + + /// Writes a single character to the target. + /// + /// - Parameter character: The character to write (as an ASCII value) + /// - Throws: VIPSError if the write operation fails + public func putc(_ character: Int) throws + + /// Reads data from the target into the provided buffer. + /// + /// This operation is only supported on seekable targets (like files). + /// Some formats (like TIFF) require the ability to read back data + /// that has been written. + /// + /// - Parameter buffer: Buffer to read data into + /// - Returns: Number of bytes actually read + /// - Throws: VIPSError if the read operation fails or is not supported + public @discardableResult func read(into buffer: UnsafeMutableRawBufferPointer) throws -> Int + + /// Changes the current position within the target. + /// + /// This operation is only supported on seekable targets (like files). + /// Some formats (like TIFF) require the ability to seek within the target. + /// + /// The whence parameter determines how the offset is interpreted: + /// - SEEK_SET (0): Absolute position from start + /// - SEEK_CUR (1): Relative to current position + /// - SEEK_END (2): Relative to end of target + /// + public @discardableResult func seek(offset: Int64, whence: Whence) throws -> Int64 + + /// Sets the current position to an absolute position from the start. + /// + /// This is a convenience wrapper for seek(offset:whence:) with SEEK_SET. + /// + /// - Parameter position: The absolute position from the start + /// - Returns: The new position (should equal the input position) + /// - Throws: VIPSError if seeking fails or is not supported + public @discardableResult func setPosition(_ position: Int64) throws -> Int64 + + /// Steals the accumulated data from a memory target. + /// + /// This extracts all data that has been written to the target. + /// This can only be used on memory targets created with init(toMemory:). + /// The target should be ended before calling this method. + /// + /// - Returns: Array containing all the written bytes + /// - Throws: VIPSError if the target is not a memory target or steal fails + public func steal() throws -> [UInt8] + + /// Steals the accumulated data from a memory target. + /// + /// This extracts all data that has been written to the target. + /// This can only be used on memory targets created with init(toMemory:). + /// The target should be ended before calling this method. + /// + /// - Parameter work: Closure that receives the stolen data buffer. The buffer + /// may not escape the closure. + /// - Returns: The result of the work closure + /// - Throws: VIPSError if the target is not a memory target or steal fails + public func steal(_ work: (UnsafeRawBufferPointer) throws -> Result) throws -> Result + + /// Steals the accumulated data as a UTF-8 string from a memory target. + /// + /// This extracts all data that has been written to the target and + /// interprets it as a UTF-8 string. This can only be used on memory + /// targets created with init(toMemory:). The target should be ended + /// before calling this method. + /// + /// - Returns: String containing all the written data + /// - Throws: VIPSError if the target is not a memory target or steal fails + public func stealText() throws -> String + + /// Writes raw data from a pointer to the target. + /// + /// This allows writing data from unsafe pointers without copying. + /// The memory must remain valid for the duration of the call. + /// + public @discardableResult func write(_ data: UnsafeRawBufferPointer) throws -> Int + + /// Writes a string with XML-style entity encoding. + /// + /// This writes a string while encoding XML entities like &, <, >, etc. + /// This is useful when writing XML or HTML content. + /// + /// - Parameter string: The string to write with entity encoding + /// - Throws: VIPSError if the write operation fails + public func writeAmp(_ string: String) throws + + /// Writes a string to the target. + /// + /// The string is written as UTF-8 encoded bytes without a null terminator. + /// + /// - Parameter string: The string to write + /// - Throws: VIPSError if the write operation fails + public func writes(_ string: String) throws +} +``` + +#### class VIPSTargetCustom + +```swift +/// A custom VIPSTarget that allows you to implement your own write behavior. +/// +/// This class provides a way to create targets with custom write, seek, and +/// read implementations. You can use this to write to custom destinations +/// like network streams, compressed files, or any other custom storage. +/// +/// # Usage Example +/// +/// ```swift +/// let customTarget = VIPSTargetCustom() +/// +/// // Set up write handler +/// customTarget.onWrite { bytes in +/// // Write bytes to your custom destination +/// // Return the number of bytes actually written +/// return writeToCustomDestination(bytes) +/// } +/// +/// // Set up end handler (replaces deprecated finish) +/// customTarget.onEnd { +/// // Finalize your custom destination +/// finalizeCustomDestination() +/// return 0 // 0 for success +/// } +/// +/// // For seekable custom targets, also implement: +/// customTarget.onSeek { offset, whence in +/// return seekInCustomDestination(offset, whence) +/// } +/// +/// customTarget.onRead { length in +/// return readFromCustomDestination(length) +/// } +/// ``` +/// +/// # Callback Requirements +/// +/// - **Write callback**: Must consume as many bytes as possible and return the actual count +/// - **End callback**: Should finalize the destination and release resources +/// - **Seek callback**: Should change position and return new absolute position (-1 for error) +/// - **Read callback**: Should read up to the requested bytes and return actual data read +public final class VIPSTargetCustom: VIPSTarget { + /// Creates a new custom target with default (no-op) implementations. + /// + /// After creation, set up the appropriate callback handlers using + /// onWrite(), onEnd(), onSeek(), and onRead() methods. + public init() + + /// Sets the end handler for this custom target. + /// + /// The end handler will be called when the target is being ended. + /// Use this for cleanup operations like closing files or network connections. + /// This replaces the deprecated finish handler. + /// + /// - Parameter handler: A closure called when the target is ended + /// - Returns: 0 for success, non-zero for error + public func onEnd(_ handler: @escaping () -> Int) + + /// Sets the read handler for this custom target. + /// + /// The read handler enables reading back data that was previously written, + /// which is required by some image formats (like TIFF). If your custom + /// target doesn't support reading, don't set this handler. + /// + /// - Parameter handler: A closure that handles read operations + /// - Parameter length: Maximum number of bytes to read + /// - Returns: Array of bytes actually read (can be shorter than requested) + public func onRead(_ handler: @escaping (Int) -> [UInt8]) + + /// Sets the seek handler for this custom target. + /// + /// The seek handler enables random access within the target, which is + /// required by some image formats (like TIFF). If your custom target + /// doesn't support seeking, don't set this handler. + /// + /// - Parameter handler: A closure that handles seek operations + /// - Parameter offset: Byte offset for the seek operation + /// - Parameter whence: How to interpret offset (SEEK_SET=0, SEEK_CUR=1, SEEK_END=2) + /// - Returns: New absolute position, or -1 for error + public func onSeek(_ handler: @escaping (Int64, Int32) -> Int64) + + /// Sets the write handler for this custom target. + /// + /// The write handler will be called whenever data needs to be written + /// to the target. It should consume as many bytes as possible and + /// return the actual number of bytes written. + /// + /// - Parameter handler: A closure that receives data and returns bytes written + /// - Parameter data: Buffer to write + /// - Returns: Number of bytes actually written (should be <= data.count) + public func onWrite(_ handler: @escaping (UnsafeRawBufferPointer) -> Int) +} +``` + +#### struct UnownedVIPSObject + +```swift +public struct UnownedVIPSObject { +} +``` + +#### struct VIPSError + +```swift +public struct VIPSError: Error, Sendable, SendableMetatype { + /// A textual representation of this instance. + /// + /// Calling this property directly is discouraged. Instead, convert an + /// instance of any type to a string by using the `String(describing:)` + /// initializer. This initializer works with any type, and uses the custom + /// `description` property for types that conform to + /// `CustomStringConvertible`: + /// + /// struct Point: CustomStringConvertible { + /// let x: Int, y: Int + /// + /// var description: String { + /// return "(\(x), \(y))" + /// } + /// } + /// + /// let p = Point(x: 21, y: 30) + /// let s = String(describing: p) + /// print(s) + /// // Prints "(21, 30)" + /// + /// The conversion of `p` to a string in the assignment to `s` uses the + /// `Point` type's `description` property. + public var description: String { get } + + public let message: String +} +``` + +#### struct VIPSOption + +```swift +public struct VIPSOption { + public init() + + public mutating func set(_ name: String, value: V) where V : RawRepresentable, V.RawValue : BinaryInteger + + public mutating func setAny(_ name: String, value: Any) throws +} +``` + +#### struct Whence + +```swift +public struct Whence: RawRepresentable, Sendable, SendableMetatype { + public static var current: Whence { get } + + public static var end: Whence { get } + + public static var set: Whence { get } + + /// The corresponding value of the raw type. + /// + /// A new instance initialized with `rawValue` will be equivalent to this + /// instance. For example: + /// + /// enum PaperSize: String { + /// case A4, A5, Letter, Legal + /// } + /// + /// let selectedSize = PaperSize.Letter + /// print(selectedSize.rawValue) + /// // Prints "Letter" + /// + /// print(selectedSize == PaperSize(rawValue: selectedSize.rawValue)!) + /// // Prints "true" + public var rawValue: Int32 + + /// Creates a new instance with the specified raw value. + /// + /// If there is no value of the type that corresponds with the specified raw + /// value, this initializer returns `nil`. For example: + /// + /// enum PaperSize: String { + /// case A4, A5, Letter, Legal + /// } + /// + /// print(PaperSize(rawValue: "Legal")) + /// // Prints "Optional(PaperSize.Legal)" + /// + /// print(PaperSize(rawValue: "Tabloid")) + /// // Prints "nil" + /// + /// - Parameter rawValue: The raw value to use for the new instance. + public init?(rawValue: Int32) +} +``` + +#### enum VIPS + +```swift +public enum VIPS { + public static func findLoader(buffer: Buffer) throws -> String where Buffer : Sequence, Buffer.Element == UInt8 + + public static func findLoader(filename: String) -> String? + + public static func shutdown() + + public static func start(concurrency: Int = 0, logger: Logger = Logger(label: "VIPS"), loggingDelegate: VIPSLoggingDelegate? = nil) throws +} +``` + +#### protocol VIPSLoggingDelegate + +```swift +public protocol VIPSLoggingDelegate : AnyObject { + public func debug(_ message: String) + + public func error(_ message: String) + + public func info(_ message: String) + + public func warning(_ message: String) +} +``` + +#### Array extension + +```swift +extension Array { +} +``` + +#### Collection extension + +```swift +extension Collection { + public func vips_findLoader() throws -> String +} +``` + +#### Globals + +```swift +public func vipsFindLoader(path: String) -> String? + +public typealias VIPSProgress = Cvips.VipsProgress + +public typealias VipsAccess = Cvips.VipsAccess + +public typealias VipsBandFormat = Cvips.VipsBandFormat + +public typealias VipsIntent = Cvips.VipsIntent + +public typealias VipsInteresting = Cvips.VipsInteresting + +public typealias VipsInterpretation = Cvips.VipsInterpretation + +public typealias VipsKernel = Cvips.VipsKernel + +public typealias VipsOperationBoolean = Cvips.VipsOperationBoolean + +public typealias VipsOperationComplex = Cvips.VipsOperationComplex + +public typealias VipsOperationComplex2 = Cvips.VipsOperationComplex2 + +public typealias VipsOperationComplexget = Cvips.VipsOperationComplexget + +public typealias VipsOperationMath = Cvips.VipsOperationMath + +public typealias VipsOperationMath2 = Cvips.VipsOperationMath2 + +public typealias VipsOperationRelational = Cvips.VipsOperationRelational + +public typealias VipsOperationRound = Cvips.VipsOperationRound + +public typealias VipsPCS = Cvips.VipsPCS + +public typealias VipsPrecision = Cvips.VipsPrecision + +public typealias VipsSize = Cvips.VipsSize +``` + + From 02baddea8a136d1dbc1b7b0d6bbc9ac0f038c813 Mon Sep 17 00:00:00 2001 From: Tobias Haeberle Date: Sat, 15 Nov 2025 11:35:40 +0100 Subject: [PATCH 02/14] before struct --- Sources/VIPS/Core/VIPSImage.swift | 177 ++++++++++++++------------ Sources/VIPS/Core/VIPSObject.swift | 41 +----- Sources/VIPS/Core/VIPSOperation.swift | 13 +- Tests/VIPSTests/CoreTests.swift | 29 +++++ docs/VIPS.md | 158 +++++++++++++++++------ 5 files changed, 266 insertions(+), 152 deletions(-) diff --git a/Sources/VIPS/Core/VIPSImage.swift b/Sources/VIPS/Core/VIPSImage.swift index 191c059..d6dd546 100644 --- a/Sources/VIPS/Core/VIPSImage.swift +++ b/Sources/VIPS/Core/VIPSImage.swift @@ -3,20 +3,18 @@ import CvipsShim public typealias VIPSProgress = Cvips.VipsProgress -open class VIPSImage: VIPSObject, VIPSImageProtocol { - - internal var other: Any? = nil - - public var image: UnsafeMutablePointer! +public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { + public let ptr: UnsafeMutableRawPointer! + public var image: UnsafeMutablePointer! { + return self.ptr.assumingMemoryBound(to: VipsImage.self) + } public required init(_ ptr: UnsafeMutableRawPointer) { - self.image = ptr.assumingMemoryBound(to: VipsImage.self) - super.init(ptr) + self.ptr = ptr } - public init(_ image: UnsafeMutablePointer!) { - self.image = image - super.init(shim_vips_object(image)) + public required convenience init(_ image: UnsafeMutablePointer) { + self.init(UnsafeMutableRawPointer(image)) } func withVipsImage(_ body: (UnsafeMutablePointer) -> R) -> R { @@ -58,15 +56,13 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { } if let maybe = maybe { - self.image = maybe - super.init(shim_vips_object(maybe)) + super.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } } @@ -111,15 +107,13 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { } if let maybe = maybe { - self.image = maybe - super.init(shim_vips_object(maybe)) + super.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } } @@ -164,15 +158,13 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { } if let maybe = maybe { - self.image = maybe - super.init(shim_vips_object(maybe)) + super.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } } @@ -217,15 +209,13 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { } if let maybe = maybe { - self.image = maybe - super.init(shim_vips_object(maybe)) + super.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } } @@ -270,15 +260,13 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { } if let maybe = maybe { - self.image = maybe - super.init(shim_vips_object(maybe)) + super.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } } @@ -323,15 +311,13 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { } if let maybe = maybe { - self.image = maybe - super.init(shim_vips_object(maybe)) + super.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } } @@ -376,15 +362,13 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { } if let maybe = maybe { - self.image = maybe - super.init(shim_vips_object(maybe)) + super.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } } @@ -429,15 +413,13 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { } if let maybe = maybe { - self.image = maybe - super.init(shim_vips_object(maybe)) + super.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } } @@ -475,8 +457,7 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { throw VIPSError() } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } /// Creates a VIPSImage from a memory area containing signed 8-bit integer data. @@ -513,8 +494,7 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { throw VIPSError() } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } /// Creates a VIPSImage from a memory area containing unsigned 16-bit integer data. @@ -551,8 +531,7 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { throw VIPSError() } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } /// Creates a VIPSImage from a memory area containing signed 16-bit integer data. @@ -589,8 +568,7 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { throw VIPSError() } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } /// Creates a VIPSImage from a memory area containing unsigned 32-bit integer data. @@ -627,8 +605,7 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { throw VIPSError() } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } /// Creates a VIPSImage from a memory area containing signed 32-bit integer data. @@ -665,8 +642,7 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { throw VIPSError() } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } /// Creates a VIPSImage from a memory area containing 32-bit floating point data. @@ -703,8 +679,7 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { throw VIPSError() } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } /// Creates a VIPSImage from a memory area containing 64-bit floating point data. @@ -741,8 +716,7 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { throw VIPSError() } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } /// Creates a new image by loading the given data. @@ -946,8 +920,7 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { throw VIPSError(vips_error_buffer()) } - self.image = image - super.init(shim_vips_object(image)) + super.init(image) } public convenience init( @@ -975,16 +948,16 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { } try block(&image.pointee) precondition(image.pointee != nil, "Image pointer cannot be nil after init.") - self.image = image.pointee! + super.init(image.pointee!) self.other = other - super.init(shim_vips_object(self.image)) } - func withUnsafeMutablePointer(_ block: (inout UnsafeMutablePointer) throws -> (T)) + /*func withUnsafeMutablePointer(_ block: (inout UnsafeMutablePointer) throws -> (T)) rethrows -> T { - return try block(&self.image) - } + var ptr = self.image! + return try block(&ptr) + }*/ @usableFromInline static func call( @@ -998,7 +971,7 @@ open class VIPSImage: VIPSObject, VIPSImageProtocol { @usableFromInline static func call(_ name: String, optionsString: String? = nil, options: inout VIPSOption) throws { - let op = try VIPSOperation(name: name) + var op = try VIPSOperation(name: name) if let options = optionsString { try op.setFromString(options: options) @@ -1082,11 +1055,12 @@ extension VIPSImage { } -public protocol VIPSImageProtocol: VIPSObjectProtocol { +public protocol VIPSImageProtocol: VIPSObjectProtocol, ~Escapable, ~Copyable { + init(_ image: UnsafeMutablePointer) var image: UnsafeMutablePointer! { get } } -extension VIPSImageProtocol { +extension VIPSImageProtocol where Self: ~Copyable, Self: ~Escapable { @inlinable public var width: Int { return Int(vips_image_get_width(self.image)) @@ -1106,9 +1080,15 @@ extension VIPSImageProtocol { get { return vips_image_iskilled(self.image) != 0 } + /* Compiler bug: set { - vips_image_set_kill(self.image, newValue ? .true : .false) + vips_image_set_kill(self.image, newValue ? 1 : 0) } + */ + } + + func setKill(_ kill: Bool) { + vips_image_set_kill(self.image, kill ? 1 : 0) } func setProgressReportingEnabled(_ enabled: Bool) { @@ -1116,21 +1096,21 @@ extension VIPSImageProtocol { } @discardableResult - public func onPreeval(_ handler: @escaping (VIPSImageRef, VIPSProgress) -> Void) -> Int { + public func onPreeval(_ handler: @escaping (UnownedVIPSImageRef, VIPSProgress) -> Void) -> Int { self.onProgress(signal: "preeval", handler: handler) } @discardableResult - public func onEval(_ handler: @escaping (VIPSImageRef, VIPSProgress) -> Void) -> Int { + public func onEval(_ handler: @escaping (UnownedVIPSImageRef, VIPSProgress) -> Void) -> Int { self.onProgress(signal: "eval", handler: handler) } @discardableResult - public func onPosteval(_ handler: @escaping (VIPSImageRef, VIPSProgress) -> Void) -> Int { + public func onPosteval(_ handler: @escaping (UnownedVIPSImageRef, VIPSProgress) -> Void) -> Int { self.onProgress(signal: "posteval", handler: handler) } - private func onProgress(signal: String, handler: @escaping (VIPSImageRef, VIPSProgress) -> Void) + private func onProgress(signal: String, handler: @escaping (UnownedVIPSImageRef, VIPSProgress) -> Void) -> Int { let cHandler: @@ -1147,12 +1127,12 @@ extension VIPSImageProtocol { } let holder = Unmanaged< - ClosureHolder<(VIPSImageRef, VIPSProgress), Void> + ClosureHolder<(UnownedVIPSImageRef, VIPSProgress), Void> > .fromOpaque(userData).takeUnretainedValue() - holder.closure((VIPSImageRef(imagePtr), progressPtr.pointee)) + holder.closure((UnownedVIPSImageRef(imagePtr), progressPtr.pointee)) } - let closureHolder = ClosureHolder<(VIPSImageRef, VIPSProgress), Void>(handler) + let closureHolder = ClosureHolder<(UnownedVIPSImageRef, VIPSProgress), Void>(handler) let userData = Unmanaged.passRetained(closureHolder).toOpaque() return self.connect( @@ -1162,7 +1142,7 @@ extension VIPSImageProtocol { destroyData: { userData, _ in if let userData { Unmanaged< - ClosureHolder<(VIPSImageRef, VIPSProgress), Void> + ClosureHolder<(UnownedVIPSImageRef, VIPSProgress), Void> > .fromOpaque(userData) .release() @@ -1172,9 +1152,13 @@ extension VIPSImageProtocol { } } -public struct VIPSImageRef: VIPSImageProtocol { +public struct UnownedVIPSImageRef: VIPSImageProtocol, ~Escapable { public var ptr: UnsafeMutableRawPointer! + public init(_ image: UnsafeMutablePointer) { + self.ptr = UnsafeMutableRawPointer(image) + } + public init(_ ptr: UnsafeMutableRawPointer) { self.ptr = ptr } @@ -1183,3 +1167,40 @@ public struct VIPSImageRef: VIPSImageProtocol { return ptr.assumingMemoryBound(to: VipsImage.self) } } + + +public struct VIPSImageRef: ~Copyable { + public init(_ image: UnsafeMutablePointer) { + self.ptr = UnsafeMutableRawPointer(image) + } + + public var ptr: UnsafeMutableRawPointer! + + public init(_ ptr: UnsafeMutableRawPointer) { + self.ptr = ptr + } + + public var image: UnsafeMutablePointer! { + return ptr.assumingMemoryBound(to: VipsImage.self) + } + + deinit { + g_object_unref(self.ptr) + } + + func thumbnail() throws -> VIPSImageRef { + let out: UnsafeMutablePointer?> = .allocate(capacity: 1) + out.initialize(to: nil) + defer { + out.deallocate() + } + + var options = VIPSOption() + options.set("out", value: out) + options.set("in", value: self.image) + + try VIPSImage.call("thumbnail", options: &options) + + return VIPSImageRef(out.pointee!) + } +} diff --git a/Sources/VIPS/Core/VIPSObject.swift b/Sources/VIPS/Core/VIPSObject.swift index a891ddd..59b9e13 100644 --- a/Sources/VIPS/Core/VIPSObject.swift +++ b/Sources/VIPS/Core/VIPSObject.swift @@ -10,10 +10,6 @@ final class ClosureHolder where Input: ~Copyable, Input: ~Escapab } open class VIPSObject: VIPSObjectProtocol { - public func unref() { - g_object_unref(self.ptr) - } - public required init(_ ptr: UnsafeMutableRawPointer) { self.ptr = ptr } @@ -35,8 +31,6 @@ open class VIPSObject: VIPSObjectProtocol { return try body(self.object) } - - deinit { guard let ptr = self.ptr else { return } g_object_unref(ptr) @@ -44,19 +38,16 @@ open class VIPSObject: VIPSObjectProtocol { } } -public protocol PointerWrapper: ~Copyable { +public protocol PointerWrapper: ~Copyable, ~Escapable { init(_ ptr: UnsafeMutableRawPointer) var ptr: UnsafeMutableRawPointer! { get } } -public protocol VIPSObjectProtocol: PointerWrapper, ~Copyable { +public protocol VIPSObjectProtocol: PointerWrapper, ~Copyable, ~Escapable { var object: UnsafeMutablePointer! { get } - - func ref() -> Self - consuming func unref() } -extension VIPSObjectProtocol where Self : ~Copyable { +extension VIPSObjectProtocol where Self : ~Copyable, Self: ~Escapable { public var object: UnsafeMutablePointer! { return self.ptr.assumingMemoryBound(to: VipsObject.self) } @@ -136,15 +127,6 @@ extension VIPSObjectProtocol where Self : ~Copyable { public func disconnect(signalHandler: Int) { g_signal_handler_disconnect(self.ptr, gulong(signalHandler)) } - - public func ref() -> Self { - g_object_ref(self.ptr) - return Self(self.ptr) - } - - public func unref() { - g_object_unref(self.ptr) - } } public struct VIPSObjectRef: VIPSObjectProtocol, ~Copyable { @@ -154,9 +136,9 @@ public struct VIPSObjectRef: VIPSObjectProtocol, ~Copyable { self.ptr = ptr } - public consuming func unref() { - g_object_unref(self.ptr) - discard self + public init(borrowing ref: UnownedVIPSObjectRef) { + self.ptr = ref.ptr + g_object_ref(ref.ptr) } deinit { @@ -164,19 +146,10 @@ public struct VIPSObjectRef: VIPSObjectProtocol, ~Copyable { } } -public struct UnownedVIPSObjectRef: VIPSObjectProtocol { +public struct UnownedVIPSObjectRef: VIPSObjectProtocol, ~Escapable { public let ptr: UnsafeMutableRawPointer! public init(_ ptr: UnsafeMutableRawPointer) { self.ptr = ptr } - - public func ref() -> UnownedVIPSObjectRef { - g_object_ref(self.ptr) - return self - } - - public func unref() { - g_object_unref(self.ptr) - } } \ No newline at end of file diff --git a/Sources/VIPS/Core/VIPSOperation.swift b/Sources/VIPS/Core/VIPSOperation.swift index c959f2c..edbe26a 100644 --- a/Sources/VIPS/Core/VIPSOperation.swift +++ b/Sources/VIPS/Core/VIPSOperation.swift @@ -1,19 +1,24 @@ import Cvips import CvipsShim -final class VIPSOperation { +struct VIPSOperation: ~Copyable { + init(_ op: UnsafeMutablePointer!) { + self.op = op + } + var op: UnsafeMutablePointer! - + + init(name: String) throws { let op = vips_operation_new(name) guard op != nil else { throw VIPSError() } - self.op = op + self.init(op) } init(name: UnsafePointer!) throws { let op = vips_operation_new(name) guard op != nil else { throw VIPSError() } - self.op = op + self.init(op) } func setFromString(options: String) throws { diff --git a/Tests/VIPSTests/CoreTests.swift b/Tests/VIPSTests/CoreTests.swift index bd169e8..7fc093a 100644 --- a/Tests/VIPSTests/CoreTests.swift +++ b/Tests/VIPSTests/CoreTests.swift @@ -56,5 +56,34 @@ extension VIPSTests { #expect(post.percent == 100, "Post-eval progress is 100%") } } + + @Test() + func cancellation() async throws { + do { + let image = try VIPSImage(fromFilePath: mythicalGiantPath) + + image.onEval { imageRef, progress in + if Task.isCancelled { + imageRef.setKill(true) + } + } + + image.setProgressReportingEnabled(true) + + let task = Task { + try? await Task.sleep(for: .milliseconds(50)) + return try image.writeToBuffer(suffix: ".jpg") + } + task.cancel() + do { + _ = try await task.value + Issue.record("Expected cancellation to throw") + } catch { + let message = "\(error)" + #expect(message.starts(with: "VipsImage: "), "Cancellation error from VIPS") + } + + } + } } } diff --git a/docs/VIPS.md b/docs/VIPS.md index bcb90c8..4364acf 100644 --- a/docs/VIPS.md +++ b/docs/VIPS.md @@ -11,12 +11,17 @@ | class | `VIPSInterpolate` | | class | `VIPSTarget` | | class | `VIPSTargetCustom` | -| struct | `UnownedVIPSObject` | +| struct | `UnownedVIPSObjectRef` | | struct | `VIPSError` | +| struct | `VIPSImageRef` | +| struct | `VIPSObjectRef` | | struct | `VIPSOption` | | struct | `Whence` | | enum | `VIPS` | +| protocol | `PointerWrapper` | +| protocol | `VIPSImageProtocol` | | protocol | `VIPSLoggingDelegate` | +| protocol | `VIPSObjectProtocol` | | func | `vipsFindLoader(path:)` | | typealias | `VIPSProgress` | | typealias | `VipsAccess` | @@ -44,10 +49,14 @@ #### class VIPSObject ```swift -public class VIPSObject { +public class VIPSObject: PointerWrapper, VIPSObjectProtocol { + public var ptr: UnsafeMutableRawPointer! + public var type: GType { get } - public @discardableResult func connectPreClose(_ handler: @escaping (UnownedVIPSObject) -> Void) -> Int + public required init(_ ptr: UnsafeMutableRawPointer) + + public func unref() } ``` @@ -165,7 +174,7 @@ public final class VIPSBlob: Collection, ExpressibleByArrayLiteral, Sendable, Se #### class VIPSImage ```swift -public class VIPSImage: VIPSObject { +public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjectProtocol { /// A structure representing the dimensions of an image. public struct Size { /// The height of the image in pixels. @@ -794,6 +803,8 @@ public class VIPSImage: VIPSObject { /// Extract imaginary part of complex image public func imag() throws -> VIPSImage + public required init(_ ptr: UnsafeMutableRawPointer) + /// Creates a new image by loading the given data /// /// The image will reference the data from the blob. @@ -1040,8 +1051,6 @@ public class VIPSImage: VIPSObject { /// public func sequential(tileHeight: Int? = nil) throws -> VIPSImage - public func setProgressReportingEnabled(_ enabled: Bool) - /// Unsharp masking for print /// public func sharpen(sigma: Double? = nil, x1: Double? = nil, y2: Double? = nil, y3: Double? = nil, m1: Double? = nil, m2: Double? = nil) throws -> VIPSImage @@ -1151,7 +1160,7 @@ public class VIPSImage: VIPSObject { #### class VIPSInterpolate ```swift -public class VIPSInterpolate: VIPSObject { +public class VIPSInterpolate: VIPSObject, PointerWrapper, VIPSObjectProtocol { public var bilinear: VIPSInterpolate { get } public var nearest: VIPSInterpolate { get } @@ -1202,7 +1211,7 @@ public class VIPSInterpolate: VIPSObject { /// /// VIPSTarget instances are not thread-safe. Each target should be used /// from a single thread, or access should be synchronized externally. -public class VIPSTarget: VIPSObject { +public class VIPSTarget: VIPSObject, PointerWrapper, VIPSObjectProtocol { /// Returns true if the target has been ended. /// /// Once a target is ended, no further write operations should be performed. @@ -1234,13 +1243,7 @@ public class VIPSTarget: VIPSObject { /// - Throws: VIPSError if the end operation fails public func end() throws - /// Flushes any buffered data to the underlying destination. - /// - /// This forces any data that has been written to the target but not yet - /// sent to the underlying destination (file, descriptor, etc.) to be written. - /// - /// - Throws: VIPSError if the flush operation fails - public func flush() throws + public required init(_ ptr: UnsafeMutableRawPointer) /// Creates a temporary target based on another target. /// @@ -1275,16 +1278,7 @@ public class VIPSTarget: VIPSObject { /// - Throws: VIPSError if the write operation fails public func putc(_ character: Int) throws - /// Reads data from the target into the provided buffer. - /// - /// This operation is only supported on seekable targets (like files). - /// Some formats (like TIFF) require the ability to read back data - /// that has been written. - /// - /// - Parameter buffer: Buffer to read data into - /// - Returns: Number of bytes actually read - /// - Throws: VIPSError if the read operation fails or is not supported - public @discardableResult func read(into buffer: UnsafeMutableRawBufferPointer) throws -> Int + public func read(into span: inout OutputRawSpan) throws /// Changes the current position within the target. /// @@ -1340,6 +1334,17 @@ public class VIPSTarget: VIPSObject { /// - Throws: VIPSError if the target is not a memory target or steal fails public func stealText() throws -> String + /// Reads data from the target into the provided buffer. + /// + /// This operation is only supported on seekable targets (like files). + /// Some formats (like TIFF) require the ability to read back data + /// that has been written. + /// + /// - Parameter buffer: Buffer to read data into + /// - Returns: Number of bytes actually read + /// - Throws: VIPSError if the read operation fails or is not supported + public @discardableResult func unsafeRead(into buffer: UnsafeMutableRawBufferPointer) throws -> Int + /// Writes raw data from a pointer to the target. /// /// This allows writing data from unsafe pointers without copying. @@ -1410,13 +1415,15 @@ public class VIPSTarget: VIPSObject { /// - **End callback**: Should finalize the destination and release resources /// - **Seek callback**: Should change position and return new absolute position (-1 for error) /// - **Read callback**: Should read up to the requested bytes and return actual data read -public final class VIPSTargetCustom: VIPSTarget { +public final class VIPSTargetCustom: VIPSTarget, PointerWrapper, VIPSObjectProtocol { /// Creates a new custom target with default (no-op) implementations. /// /// After creation, set up the appropriate callback handlers using /// onWrite(), onEnd(), onSeek(), and onRead() methods. public init() + public required init(_ ptr: UnsafeMutableRawPointer) + /// Sets the end handler for this custom target. /// /// The end handler will be called when the target is being ended. @@ -1425,18 +1432,17 @@ public final class VIPSTargetCustom: VIPSTarget { /// /// - Parameter handler: A closure called when the target is ended /// - Returns: 0 for success, non-zero for error - public func onEnd(_ handler: @escaping () -> Int) + public @discardableResult func onEnd(_ handler: @escaping () -> Int) -> Int - /// Sets the read handler for this custom target. + /// Adds a read handler to this custom target. /// /// The read handler enables reading back data that was previously written, /// which is required by some image formats (like TIFF). If your custom /// target doesn't support reading, don't set this handler. /// /// - Parameter handler: A closure that handles read operations - /// - Parameter length: Maximum number of bytes to read - /// - Returns: Array of bytes actually read (can be shorter than requested) - public func onRead(_ handler: @escaping (Int) -> [UInt8]) + /// - Parameter outputSpan: The destination span to read bytes into. + public @discardableResult func onRead(_ handler: @escaping (inout OutputRawSpan) -> Void) -> Int /// Sets the seek handler for this custom target. /// @@ -1448,7 +1454,18 @@ public final class VIPSTargetCustom: VIPSTarget { /// - Parameter offset: Byte offset for the seek operation /// - Parameter whence: How to interpret offset (SEEK_SET=0, SEEK_CUR=1, SEEK_END=2) /// - Returns: New absolute position, or -1 for error - public func onSeek(_ handler: @escaping (Int64, Int32) -> Int64) + public @discardableResult func onSeek(_ handler: @escaping (Int64, Whence) -> Int64) -> Int + + /// Adds a read handler to this custom target. + /// + /// The read handler enables reading back data that was previously written, + /// which is required by some image formats (like TIFF). If your custom + /// target doesn't support reading, don't set this handler. + /// + /// - Parameter handler: A closure that handles read operations + /// - Parameter buffer: The destination buffer to read bytes into. + /// - Returns: The actual number of bytes written to buffer. + public @discardableResult func onUnsafeRead(_ handler: @escaping (UnsafeMutableRawBufferPointer) -> (Int)) -> Int /// Sets the write handler for this custom target. /// @@ -1459,14 +1476,23 @@ public final class VIPSTargetCustom: VIPSTarget { /// - Parameter handler: A closure that receives data and returns bytes written /// - Parameter data: Buffer to write /// - Returns: Number of bytes actually written (should be <= data.count) - public func onWrite(_ handler: @escaping (UnsafeRawBufferPointer) -> Int) + public @discardableResult func onUnsafeWrite(_ handler: @escaping (UnsafeRawBufferPointer) -> Int) -> Int + + public @discardableResult func onWrite(_ handler: @escaping (RawSpan) -> Int) -> Int } ``` -#### struct UnownedVIPSObject +#### struct UnownedVIPSObjectRef ```swift -public struct UnownedVIPSObject { +public struct UnownedVIPSObjectRef: PointerWrapper, VIPSObjectProtocol { + public let ptr: UnsafeMutableRawPointer! + + public init(_ ptr: UnsafeMutableRawPointer) + + public func ref() -> UnownedVIPSObjectRef + + public func unref() } ``` @@ -1503,6 +1529,28 @@ public struct VIPSError: Error, Sendable, SendableMetatype { } ``` +#### struct VIPSImageRef + +```swift +public struct VIPSImageRef: PointerWrapper, VIPSImageProtocol, VIPSObjectProtocol { + public var ptr: UnsafeMutableRawPointer! + + public init(_ ptr: UnsafeMutableRawPointer) +} +``` + +#### struct VIPSObjectRef + +```swift +public struct VIPSObjectRef: PointerWrapper, VIPSObjectProtocol { + public let ptr: UnsafeMutableRawPointer! + + public init(_ ptr: UnsafeMutableRawPointer) + + public consuming func unref() +} +``` + #### struct VIPSOption ```swift @@ -1576,6 +1624,28 @@ public enum VIPS { } ``` +#### protocol PointerWrapper + +```swift +public protocol PointerWrapper : ~Copyable { + public var ptr: UnsafeMutableRawPointer! { get } + + public init(_ ptr: UnsafeMutableRawPointer) +} +``` + +#### protocol VIPSImageProtocol + +```swift +public protocol VIPSImageProtocol : VIPSObjectProtocol { + public var bands: Int { get } + + public var height: Int { get } + + public var width: Int { get } +} +``` + #### protocol VIPSLoggingDelegate ```swift @@ -1590,6 +1660,22 @@ public protocol VIPSLoggingDelegate : AnyObject { } ``` +#### protocol VIPSObjectProtocol + +```swift +public protocol VIPSObjectProtocol : PointerWrapper, ~Copyable { + public var type: GType { get } + + public func disconnect(signalHandler: Int) + + public @discardableResult func onPreClose(_ handler: @escaping (UnownedVIPSObjectRef) -> Void) -> Int + + public func ref() -> Self + + public func unref() +} +``` + #### Array extension ```swift @@ -1647,4 +1733,4 @@ public typealias VipsPrecision = Cvips.VipsPrecision public typealias VipsSize = Cvips.VipsSize ``` - + From 429006c5d39d7b329271950476483643051a8d62 Mon Sep 17 00:00:00 2001 From: Tobias Haeberle Date: Sat, 15 Nov 2025 12:33:25 +0100 Subject: [PATCH 03/14] final class VIPSImage --- Sources/VIPS/Arithmetic/arithmetic.swift | 4 +- Sources/VIPS/Conversion/conversion.swift | 4 +- Sources/VIPS/Core/VIPSImage.swift | 187 ++++++++---------- Sources/VIPS/Core/VIPSOption.swift | 2 +- .../Foreign/foreign_gif.generated.swift | 21 +- .../Foreign/foreign_heif.generated.swift | 6 +- .../Foreign/foreign_jpeg.generated.swift | 6 +- .../Foreign/foreign_other.generated.swift | 142 ++++++++++--- .../Foreign/foreign_pdf.generated.swift | 6 +- .../Foreign/foreign_png.generated.swift | 6 +- .../Foreign/foreign_svg.generated.swift | 42 +++- .../Foreign/foreign_tiff.generated.swift | 60 ++++-- .../Foreign/foreign_webp.generated.swift | 6 +- .../VIPS/Generated/arithmetic.generated.swift | 86 ++++---- Sources/VIPS/Generated/colour.generated.swift | 56 +++--- .../VIPS/Generated/conversion.generated.swift | 72 +++---- .../Generated/convolution.generated.swift | 20 +- Sources/VIPS/Generated/create.generated.swift | 50 ++--- .../VIPS/Generated/freqfilt.generated.swift | 6 +- .../VIPS/Generated/histogram.generated.swift | 26 +-- Sources/VIPS/Generated/misc.generated.swift | 92 +++++---- .../VIPS/Generated/morphology.generated.swift | 4 +- .../VIPS/Generated/resample.generated.swift | 76 +++---- Sources/VIPS/Histogram/histogram.swift | 12 +- tools/generate-swift-wrappers.py | 50 +---- 25 files changed, 580 insertions(+), 462 deletions(-) diff --git a/Sources/VIPS/Arithmetic/arithmetic.swift b/Sources/VIPS/Arithmetic/arithmetic.swift index 7f01bf4..f39db14 100644 --- a/Sources/VIPS/Arithmetic/arithmetic.swift +++ b/Sources/VIPS/Arithmetic/arithmetic.swift @@ -222,7 +222,7 @@ extension VIPSImage { /// arrays have more than one element and the image only has a single band, the result is /// a many-band image where each band corresponds to one array element. public func linear(_ a: Double = 1.0, _ b: Double = 0, uchar: Bool? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self.image) @@ -242,7 +242,7 @@ extension VIPSImage { } public func linear(_ a: [Double], _ b: [Double], uchar: Bool? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self.image) diff --git a/Sources/VIPS/Conversion/conversion.swift b/Sources/VIPS/Conversion/conversion.swift index cf6f8cf..630fb8c 100644 --- a/Sources/VIPS/Conversion/conversion.swift +++ b/Sources/VIPS/Conversion/conversion.swift @@ -5,7 +5,7 @@ extension VIPSImage { /// See VIPSImage.bandjoin(`in`:) public func bandjoin(_ other: [VIPSImage]) throws -> VIPSImage { - return try VIPSImage([self] + other) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: [self] + other) @@ -27,7 +27,7 @@ extension VIPSImage { /// - Returns: A new single-band image /// - Throws: `VIPSError` if the operation fails public func bandbool(_ operation: VipsOperationBoolean) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self.image) diff --git a/Sources/VIPS/Core/VIPSImage.swift b/Sources/VIPS/Core/VIPSImage.swift index d6dd546..6683c05 100644 --- a/Sources/VIPS/Core/VIPSImage.swift +++ b/Sources/VIPS/Core/VIPSImage.swift @@ -3,29 +3,35 @@ import CvipsShim public typealias VIPSProgress = Cvips.VipsProgress -public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { - public let ptr: UnsafeMutableRawPointer! +public final class VIPSImage: VIPSImageProtocol { + public var ptr: UnsafeMutableRawPointer! + public var image: UnsafeMutablePointer! { return self.ptr.assumingMemoryBound(to: VipsImage.self) } - public required init(_ ptr: UnsafeMutableRawPointer) { + public init(_ ptr: UnsafeMutableRawPointer) { self.ptr = ptr } - public required convenience init(_ image: UnsafeMutablePointer) { - self.init(UnsafeMutableRawPointer(image)) + public init(_ image: UnsafeMutablePointer) { + self.ptr = UnsafeMutableRawPointer(image) } func withVipsImage(_ body: (UnsafeMutablePointer) -> R) -> R { return body(self.image) } + deinit { + guard let ptr else { return } + g_object_unref(ptr) + } + /// This function creates a VIPSImage from a memory area. /// The memory area must be a simple array, for example RGBRGBRGB, left-to-right, top-to-bottom. /// The memory will be copied into the image. @inlinable - public init( + public convenience init( data: some Collection, width: Int, height: Int, @@ -56,13 +62,13 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { } if let maybe = maybe { - super.init(maybe) + self.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - super.init(image) + self.init(image) } } @@ -78,7 +84,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - bands: Number of bands per pixel /// - Throws: VIPSError if image creation fails @inlinable - public init( + public convenience init( data: some Collection, width: Int, height: Int, @@ -107,13 +113,13 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { } if let maybe = maybe { - super.init(maybe) + self.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - super.init(image) + self.init(image) } } @@ -129,7 +135,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - bands: Number of bands per pixel /// - Throws: VIPSError if image creation fails @inlinable - public init( + public convenience init( data: some Collection, width: Int, height: Int, @@ -158,13 +164,13 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { } if let maybe = maybe { - super.init(maybe) + self.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - super.init(image) + self.init(image) } } @@ -180,7 +186,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - bands: Number of bands per pixel /// - Throws: VIPSError if image creation fails @inlinable - public init( + public convenience init( data: some Collection, width: Int, height: Int, @@ -209,13 +215,13 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { } if let maybe = maybe { - super.init(maybe) + self.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - super.init(image) + self.init(image) } } @@ -231,7 +237,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - bands: Number of bands per pixel /// - Throws: VIPSError if image creation fails @inlinable - public init( + public convenience init( data: some Collection, width: Int, height: Int, @@ -260,13 +266,13 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { } if let maybe = maybe { - super.init(maybe) + self.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - super.init(image) + self.init(image) } } @@ -282,7 +288,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - bands: Number of bands per pixel /// - Throws: VIPSError if image creation fails @inlinable - public init( + public convenience init( data: some Collection, width: Int, height: Int, @@ -311,13 +317,13 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { } if let maybe = maybe { - super.init(maybe) + self.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - super.init(image) + self.init(image) } } @@ -333,7 +339,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - bands: Number of bands per pixel /// - Throws: VIPSError if image creation fails @inlinable - public init( + public convenience init( data: some Collection, width: Int, height: Int, @@ -362,13 +368,13 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { } if let maybe = maybe { - super.init(maybe) + self.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - super.init(image) + self.init(image) } } @@ -384,7 +390,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - bands: Number of bands per pixel /// - Throws: VIPSError if image creation fails @inlinable - public init( + public convenience init( data: some Collection, width: Int, height: Int, @@ -413,13 +419,13 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { } if let maybe = maybe { - super.init(maybe) + self.init(maybe) } else { let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } - super.init(image) + self.init(image) } } @@ -438,7 +444,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - height: Image height in pixels /// - bands: Number of bands per pixel /// - Throws: VIPSError if image creation fails - public init( + public convenience init( unsafeData buffer: UnsafeBufferPointer, width: Int, height: Int, @@ -457,7 +463,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { throw VIPSError() } - super.init(image) + self.init(image) } /// Creates a VIPSImage from a memory area containing signed 8-bit integer data. @@ -475,7 +481,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - height: Image height in pixels /// - bands: Number of bands per pixel /// - Throws: VIPSError if image creation fails - public init( + public convenience init( unsafeData buffer: UnsafeBufferPointer, width: Int, height: Int, @@ -494,7 +500,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { throw VIPSError() } - super.init(image) + self.init(image) } /// Creates a VIPSImage from a memory area containing unsigned 16-bit integer data. @@ -512,7 +518,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - height: Image height in pixels /// - bands: Number of bands per pixel /// - Throws: VIPSError if image creation fails - public init( + public convenience init( unsafeData buffer: UnsafeBufferPointer, width: Int, height: Int, @@ -531,7 +537,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { throw VIPSError() } - super.init(image) + self.init(image) } /// Creates a VIPSImage from a memory area containing signed 16-bit integer data. @@ -549,7 +555,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - height: Image height in pixels /// - bands: Number of bands per pixel /// - Throws: VIPSError if image creation fails - public init( + public convenience init( unsafeData buffer: UnsafeBufferPointer, width: Int, height: Int, @@ -568,7 +574,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { throw VIPSError() } - super.init(image) + self.init(image) } /// Creates a VIPSImage from a memory area containing unsigned 32-bit integer data. @@ -586,7 +592,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - height: Image height in pixels /// - bands: Number of bands per pixel /// - Throws: VIPSError if image creation fails - public init( + public convenience init( unsafeData buffer: UnsafeBufferPointer, width: Int, height: Int, @@ -605,7 +611,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { throw VIPSError() } - super.init(image) + self.init(image) } /// Creates a VIPSImage from a memory area containing signed 32-bit integer data. @@ -623,7 +629,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - height: Image height in pixels /// - bands: Number of bands per pixel /// - Throws: VIPSError if image creation fails - public init( + public convenience init( unsafeData buffer: UnsafeBufferPointer, width: Int, height: Int, @@ -642,7 +648,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { throw VIPSError() } - super.init(image) + self.init(image) } /// Creates a VIPSImage from a memory area containing 32-bit floating point data. @@ -660,7 +666,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - height: Image height in pixels /// - bands: Number of bands per pixel /// - Throws: VIPSError if image creation fails - public init( + public convenience init( unsafeData buffer: UnsafeBufferPointer, width: Int, height: Int, @@ -679,7 +685,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { throw VIPSError() } - super.init(image) + self.init(image) } /// Creates a VIPSImage from a memory area containing 64-bit floating point data. @@ -697,7 +703,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - height: Image height in pixels /// - bands: Number of bands per pixel /// - Throws: VIPSError if image creation fails - public init( + public convenience init( unsafeData buffer: UnsafeBufferPointer, width: Int, height: Int, @@ -716,7 +722,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { throw VIPSError() } - super.init(image) + self.init(image) } /// Creates a new image by loading the given data. @@ -728,7 +734,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - bufferNoCopy: The image data to load /// - loader: The loader to use (optional) /// - options: The options to use (optional) - convenience public init( + public convenience init( bufferNoCopy data: UnsafeRawBufferPointer, loader: String? = nil, options: String? = nil @@ -748,7 +754,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { vips_area_unref(shim_vips_area(blob)) } - try self.init(nil) { out in + try self.init { out in var option = VIPSOption() option.set("buffer", value: blob) option.set("out", value: &out) @@ -765,7 +771,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - loader: The loader to use (optional) /// - options: The options to use (optional) @inlinable - convenience public init( + public convenience init( data: some Collection, loader: String? = nil, options: String? = nil @@ -792,7 +798,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { vips_area_unref(shim_vips_area(blob)) } - try self.init(nil) { out in + try self.init { out in var option = VIPSOption() option.set("buffer", value: blob) option.set("out", value: &out) @@ -811,7 +817,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - loader: The loader to use (optional) /// - options: The options to use (optional) @inlinable - convenience public init( + public convenience init( unsafeData: UnsafeRawBufferPointer, loader: String? = nil, options: String? = nil @@ -830,7 +836,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { vips_area_unref(shim_vips_area(blob)) } - try self.init(nil) { out in + try self.init { out in var option = VIPSOption() option.set("buffer", value: blob) option.set("out", value: &out) @@ -847,7 +853,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - loader: The loader to use (optional) /// - options: The options to use (optional) @inlinable - convenience public init( + public convenience init( blob: VIPSBlob, loader: String? = nil, options: String? = nil @@ -858,7 +864,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { throw VIPSError() } - try self.init(blob) { out in + try self.init { out in var option = VIPSOption() try blob.withVipsBlob { blob in option.set("buffer", value: blob) @@ -912,7 +918,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { /// - access: Access pattern hint (`.random` or `.sequential`) /// - inMemory: Force loading via memory instead of temporary files /// - Throws: VIPSError if the file cannot be opened or read - public init(fromFilePath path: String, access: VipsAccess = .random, inMemory: Bool = false) + public convenience init(fromFilePath path: String, access: VipsAccess = .random, inMemory: Bool = false) throws { guard let image = shim_vips_image_new_from_file(path, access, inMemory ? .true : .false) @@ -920,7 +926,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { throw VIPSError(vips_error_buffer()) } - super.init(image) + self.init(image) } public convenience init( @@ -931,7 +937,7 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { let loader = try loader ?? source.findLoader() - try self.init(source) { out in + try self.init { out in var option = VIPSOption() option.set("source", value: source.source) option.set("out", value: &out) @@ -939,19 +945,6 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { } } - @usableFromInline - init(_ other: Any?, _ block: (inout UnsafeMutablePointer?) throws -> Void) rethrows { - let image: UnsafeMutablePointer?> = .allocate(capacity: 1) - image.initialize(to: nil) - defer { - image.deallocate() - } - try block(&image.pointee) - precondition(image.pointee != nil, "Image pointer cannot be nil after init.") - super.init(image.pointee!) - self.other = other - } - /*func withUnsafeMutablePointer(_ block: (inout UnsafeMutablePointer) throws -> (T)) rethrows -> T { @@ -987,9 +980,23 @@ public struct VIPSImage: VIPSObject, VIPSImageProtocol, ~Copyable { } } -extension VIPSImage { +extension VIPSImageProtocol { + @usableFromInline + init(_ block: (inout UnsafeMutablePointer?) throws -> Void) rethrows { + let image: UnsafeMutablePointer?> = .allocate(capacity: 1) + image.initialize(to: nil) + defer { + image.deallocate() + } + try block(&image.pointee) + precondition(image.pointee != nil, "Image pointer cannot be nil after init.") + self.init(image.pointee!) + } +} + +extension VIPSImageProtocol { public func new(_ colors: [Double]) throws -> VIPSImage { - try VIPSImage(self) { out in + try VIPSImage { out in var c = colors out = vips_image_new_from_image(self.image, &c, Int32(c.count)) if out == nil { throw VIPSError() } @@ -1047,7 +1054,7 @@ extension VIPSImage { /// If the image is already a simple area of memory, it just refs image and /// returns it. public func copyMemory() throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in out = vips_image_copy_memory(self.image) if out == nil { throw VIPSError() } } @@ -1168,39 +1175,3 @@ public struct UnownedVIPSImageRef: VIPSImageProtocol, ~Escapable { } } - -public struct VIPSImageRef: ~Copyable { - public init(_ image: UnsafeMutablePointer) { - self.ptr = UnsafeMutableRawPointer(image) - } - - public var ptr: UnsafeMutableRawPointer! - - public init(_ ptr: UnsafeMutableRawPointer) { - self.ptr = ptr - } - - public var image: UnsafeMutablePointer! { - return ptr.assumingMemoryBound(to: VipsImage.self) - } - - deinit { - g_object_unref(self.ptr) - } - - func thumbnail() throws -> VIPSImageRef { - let out: UnsafeMutablePointer?> = .allocate(capacity: 1) - out.initialize(to: nil) - defer { - out.deallocate() - } - - var options = VIPSOption() - options.set("out", value: out) - options.set("in", value: self.image) - - try VIPSImage.call("thumbnail", options: &options) - - return VIPSImageRef(out.pointee!) - } -} diff --git a/Sources/VIPS/Core/VIPSOption.swift b/Sources/VIPS/Core/VIPSOption.swift index 9a08542..a8653e7 100644 --- a/Sources/VIPS/Core/VIPSOption.swift +++ b/Sources/VIPS/Core/VIPSOption.swift @@ -29,7 +29,7 @@ public struct VIPSOption { self.pairs.append(pair) } - public mutating func set(_ name: String, value: VIPSObject) { + public mutating func set(_ name: String, value: some VIPSObjectProtocol) { let pair = Pair(name: name, input: true) g_value_init(&pair.value, value.type) g_value_set_object(&pair.value, value.object) diff --git a/Sources/VIPS/Generated/Foreign/foreign_gif.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_gif.generated.swift index 85416f7..6157f96 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_gif.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_gif.generated.swift @@ -29,7 +29,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -79,7 +79,7 @@ extension VIPSImage { ) throws -> VIPSImage { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out in + try VIPSImage { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -160,7 +160,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) @@ -199,6 +199,7 @@ extension VIPSImage { /// - reuse: Reuse palette from input /// - interpaletteMaxerror: Maximum inter-palette error for palette reusage /// - interlace: Generate an interlaced (progressive) GIF + /// - keepDuplicateFrames: Keep duplicate frames in the output instead of combining them /// - keep: Which metadata to retain /// - background: Background value /// - pageHeight: Set page height for multipage save @@ -212,6 +213,7 @@ extension VIPSImage { reuse: Bool? = nil, interpaletteMaxerror: Double? = nil, interlace: Bool? = nil, + keepDuplicateFrames: Bool? = nil, keep: VipsForeignKeep? = nil, background: [Double]? = nil, pageHeight: Int? = nil, @@ -242,6 +244,9 @@ extension VIPSImage { if let interlace = interlace { opt.set("interlace", value: interlace) } + if let keepDuplicateFrames = keepDuplicateFrames { + opt.set("keep_duplicate_frames", value: keepDuplicateFrames) + } if let keep = keep { opt.set("keep", value: keep) } @@ -268,6 +273,7 @@ extension VIPSImage { /// - reuse: Reuse palette from input /// - interpaletteMaxerror: Maximum inter-palette error for palette reusage /// - interlace: Generate an interlaced (progressive) GIF + /// - keepDuplicateFrames: Keep duplicate frames in the output instead of combining them /// - keep: Which metadata to retain /// - background: Background value /// - pageHeight: Set page height for multipage save @@ -280,6 +286,7 @@ extension VIPSImage { reuse: Bool? = nil, interpaletteMaxerror: Double? = nil, interlace: Bool? = nil, + keepDuplicateFrames: Bool? = nil, keep: VipsForeignKeep? = nil, background: [Double]? = nil, pageHeight: Int? = nil, @@ -315,6 +322,9 @@ extension VIPSImage { if let interlace = interlace { opt.set("interlace", value: interlace) } + if let keepDuplicateFrames = keepDuplicateFrames { + opt.set("keep_duplicate_frames", value: keepDuplicateFrames) + } if let keep = keep { opt.set("keep", value: keep) } @@ -349,6 +359,7 @@ extension VIPSImage { /// - reuse: Reuse palette from input /// - interpaletteMaxerror: Maximum inter-palette error for palette reusage /// - interlace: Generate an interlaced (progressive) GIF + /// - keepDuplicateFrames: Keep duplicate frames in the output instead of combining them /// - keep: Which metadata to retain /// - background: Background value /// - pageHeight: Set page height for multipage save @@ -362,6 +373,7 @@ extension VIPSImage { reuse: Bool? = nil, interpaletteMaxerror: Double? = nil, interlace: Bool? = nil, + keepDuplicateFrames: Bool? = nil, keep: VipsForeignKeep? = nil, background: [Double]? = nil, pageHeight: Int? = nil, @@ -392,6 +404,9 @@ extension VIPSImage { if let interlace = interlace { opt.set("interlace", value: interlace) } + if let keepDuplicateFrames = keepDuplicateFrames { + opt.set("keep_duplicate_frames", value: keepDuplicateFrames) + } if let keep = keep { opt.set("keep", value: keep) } diff --git a/Sources/VIPS/Generated/Foreign/foreign_heif.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_heif.generated.swift index 7742b5a..5473f1f 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_heif.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_heif.generated.swift @@ -33,7 +33,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -93,7 +93,7 @@ extension VIPSImage { ) throws -> VIPSImage { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out in + try VIPSImage { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -190,7 +190,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) diff --git a/Sources/VIPS/Generated/Foreign/foreign_jpeg.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_jpeg.generated.swift index 3930ea4..4da322c 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_jpeg.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_jpeg.generated.swift @@ -31,7 +31,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -86,7 +86,7 @@ extension VIPSImage { ) throws -> VIPSImage { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out in + try VIPSImage { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -175,7 +175,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) diff --git a/Sources/VIPS/Generated/Foreign/foreign_other.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_other.generated.swift index dc59b1e..0ab7beb 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_other.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_other.generated.swift @@ -25,7 +25,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -70,7 +70,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -127,7 +127,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) @@ -581,7 +581,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -618,7 +618,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) @@ -680,6 +680,7 @@ extension VIPSImage { /// - Parameters: /// - filename: Filename to load from /// - page: Load this page from the image + /// - oneshot: Load images a frame at a time /// - memory: Force open via memory /// - access: Required access pattern for this file /// - failOn: Error level to fail on @@ -687,18 +688,22 @@ extension VIPSImage { public static func jp2kload( filename: String, page: Int? = nil, + oneshot: Bool? = nil, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) if let page = page { opt.set("page", value: page) } + if let oneshot = oneshot { + opt.set("oneshot", value: oneshot) + } if let memory = memory { opt.set("memory", value: memory) } @@ -722,6 +727,7 @@ extension VIPSImage { /// - Parameters: /// - buffer: Buffer to load from /// - page: Load this page from the image + /// - oneshot: Load images a frame at a time /// - memory: Force open via memory /// - access: Required access pattern for this file /// - failOn: Error level to fail on @@ -730,6 +736,7 @@ extension VIPSImage { public static func jp2kload( buffer: VIPSBlob, page: Int? = nil, + oneshot: Bool? = nil, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, @@ -737,13 +744,16 @@ extension VIPSImage { ) throws -> VIPSImage { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out in + try VIPSImage { out in var opt = VIPSOption() opt.set("buffer", value: blob) if let page = page { opt.set("page", value: page) } + if let oneshot = oneshot { + opt.set("oneshot", value: oneshot) + } if let memory = memory { opt.set("memory", value: memory) } @@ -769,6 +779,7 @@ extension VIPSImage { /// - Parameters: /// - buffer: Buffer to load from /// - page: Load this page from the image + /// - oneshot: Load images a frame at a time /// - memory: Force open via memory /// - access: Required access pattern for this file /// - failOn: Error level to fail on @@ -777,6 +788,7 @@ extension VIPSImage { public static func jp2kload( unsafeBuffer buffer: UnsafeRawBufferPointer, page: Int? = nil, + oneshot: Bool? = nil, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, @@ -786,6 +798,7 @@ extension VIPSImage { return try jp2kload( buffer: blob, page: page, + oneshot: oneshot, memory: memory, access: access, failOn: failOn, @@ -798,6 +811,7 @@ extension VIPSImage { /// - Parameters: /// - source: Source to load from /// - page: Load this page from the image + /// - oneshot: Load images a frame at a time /// - memory: Force open via memory /// - access: Required access pattern for this file /// - failOn: Error level to fail on @@ -805,18 +819,22 @@ extension VIPSImage { public static func jp2kload( source: VIPSSource, page: Int? = nil, + oneshot: Bool? = nil, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) if let page = page { opt.set("page", value: page) } + if let oneshot = oneshot { + opt.set("oneshot", value: oneshot) + } if let memory = memory { opt.set("memory", value: memory) } @@ -1044,7 +1062,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -1094,7 +1112,7 @@ extension VIPSImage { ) throws -> VIPSImage { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out in + try VIPSImage { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -1175,7 +1193,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) @@ -1414,7 +1432,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -1469,7 +1487,7 @@ extension VIPSImage { ) throws -> VIPSImage { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out in + try VIPSImage { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -1682,7 +1700,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -1719,7 +1737,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -1756,7 +1774,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) @@ -1863,7 +1881,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -1910,7 +1928,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -1972,7 +1990,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) @@ -2024,7 +2042,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -2046,7 +2064,75 @@ extension VIPSImage { } } - /// Load ppm base class + /// Load ppm from buffer + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func ppmload( + buffer: VIPSBlob, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> VIPSImage { + // the operation will retain the blob + try buffer.withVipsBlob { blob in + try VIPSImage { out in + var opt = VIPSOption() + + opt.set("buffer", value: blob) + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try VIPSImage.call("ppmload_buffer", options: &opt) + } + } + } + + /// Load ppm from buffer without copying the data. The caller must ensure the buffer remains valid for + /// the lifetime of the returned image and all its descendants. + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func ppmload( + unsafeBuffer buffer: UnsafeRawBufferPointer, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> VIPSImage { + let blob = VIPSBlob(noCopy: buffer) + return try ppmload( + buffer: blob, + memory: memory, + access: access, + failOn: failOn, + revalidate: revalidate + ) + } + + /// Load ppm from source /// /// - Parameters: /// - source: Source to load from @@ -2061,7 +2147,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) @@ -2223,7 +2309,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -2263,7 +2349,7 @@ extension VIPSImage { ) throws -> VIPSImage { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out in + try VIPSImage { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -2328,7 +2414,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) @@ -2492,7 +2578,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -2656,7 +2742,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -2693,7 +2779,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) diff --git a/Sources/VIPS/Generated/Foreign/foreign_pdf.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_pdf.generated.swift index cd1c7bb..66840a8 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_pdf.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_pdf.generated.swift @@ -37,7 +37,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -107,7 +107,7 @@ extension VIPSImage { ) throws -> VIPSImage { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out in + try VIPSImage { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -220,7 +220,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) diff --git a/Sources/VIPS/Generated/Foreign/foreign_png.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_png.generated.swift index 95665f8..d89c02f 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_png.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_png.generated.swift @@ -27,7 +27,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -72,7 +72,7 @@ extension VIPSImage { ) throws -> VIPSImage { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out in + try VIPSImage { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -145,7 +145,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) diff --git a/Sources/VIPS/Generated/Foreign/foreign_svg.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_svg.generated.swift index 3e40af5..f74839c 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_svg.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_svg.generated.swift @@ -17,6 +17,8 @@ extension VIPSImage { /// - dpi: Render at this DPI /// - scale: Scale output by this factor /// - unlimited: Allow SVG of any size + /// - stylesheet: Custom CSS + /// - highBitdepth: Enable scRGB 128-bit output (32-bit per channel) /// - memory: Force open via memory /// - access: Required access pattern for this file /// - failOn: Error level to fail on @@ -26,12 +28,14 @@ extension VIPSImage { dpi: Double? = nil, scale: Double? = nil, unlimited: Bool? = nil, + stylesheet: String? = nil, + highBitdepth: Bool? = nil, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -44,6 +48,12 @@ extension VIPSImage { if let unlimited = unlimited { opt.set("unlimited", value: unlimited) } + if let stylesheet = stylesheet { + opt.set("stylesheet", value: stylesheet) + } + if let highBitdepth = highBitdepth { + opt.set("high_bitdepth", value: highBitdepth) + } if let memory = memory { opt.set("memory", value: memory) } @@ -69,6 +79,8 @@ extension VIPSImage { /// - dpi: Render at this DPI /// - scale: Scale output by this factor /// - unlimited: Allow SVG of any size + /// - stylesheet: Custom CSS + /// - highBitdepth: Enable scRGB 128-bit output (32-bit per channel) /// - memory: Force open via memory /// - access: Required access pattern for this file /// - failOn: Error level to fail on @@ -79,6 +91,8 @@ extension VIPSImage { dpi: Double? = nil, scale: Double? = nil, unlimited: Bool? = nil, + stylesheet: String? = nil, + highBitdepth: Bool? = nil, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, @@ -86,7 +100,7 @@ extension VIPSImage { ) throws -> VIPSImage { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out in + try VIPSImage { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -99,6 +113,12 @@ extension VIPSImage { if let unlimited = unlimited { opt.set("unlimited", value: unlimited) } + if let stylesheet = stylesheet { + opt.set("stylesheet", value: stylesheet) + } + if let highBitdepth = highBitdepth { + opt.set("high_bitdepth", value: highBitdepth) + } if let memory = memory { opt.set("memory", value: memory) } @@ -126,6 +146,8 @@ extension VIPSImage { /// - dpi: Render at this DPI /// - scale: Scale output by this factor /// - unlimited: Allow SVG of any size + /// - stylesheet: Custom CSS + /// - highBitdepth: Enable scRGB 128-bit output (32-bit per channel) /// - memory: Force open via memory /// - access: Required access pattern for this file /// - failOn: Error level to fail on @@ -136,6 +158,8 @@ extension VIPSImage { dpi: Double? = nil, scale: Double? = nil, unlimited: Bool? = nil, + stylesheet: String? = nil, + highBitdepth: Bool? = nil, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, @@ -147,6 +171,8 @@ extension VIPSImage { dpi: dpi, scale: scale, unlimited: unlimited, + stylesheet: stylesheet, + highBitdepth: highBitdepth, memory: memory, access: access, failOn: failOn, @@ -161,6 +187,8 @@ extension VIPSImage { /// - dpi: Render at this DPI /// - scale: Scale output by this factor /// - unlimited: Allow SVG of any size + /// - stylesheet: Custom CSS + /// - highBitdepth: Enable scRGB 128-bit output (32-bit per channel) /// - memory: Force open via memory /// - access: Required access pattern for this file /// - failOn: Error level to fail on @@ -170,12 +198,14 @@ extension VIPSImage { dpi: Double? = nil, scale: Double? = nil, unlimited: Bool? = nil, + stylesheet: String? = nil, + highBitdepth: Bool? = nil, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) @@ -188,6 +218,12 @@ extension VIPSImage { if let unlimited = unlimited { opt.set("unlimited", value: unlimited) } + if let stylesheet = stylesheet { + opt.set("stylesheet", value: stylesheet) + } + if let highBitdepth = highBitdepth { + opt.set("high_bitdepth", value: highBitdepth) + } if let memory = memory { opt.set("memory", value: memory) } diff --git a/Sources/VIPS/Generated/Foreign/foreign_tiff.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_tiff.generated.swift index 5fc45cb..cee2a11 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_tiff.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_tiff.generated.swift @@ -15,9 +15,10 @@ extension VIPSImage { /// - Parameters: /// - filename: Filename to load from /// - page: First page to load - /// - subifd: Subifd index /// - n: Number of pages to load, -1 for all /// - autorotate: Rotate image using orientation tag + /// - subifd: Subifd index + /// - unlimited: Remove all denial of service limits /// - memory: Force open via memory /// - access: Required access pattern for this file /// - failOn: Error level to fail on @@ -25,30 +26,34 @@ extension VIPSImage { public static func tiffload( filename: String, page: Int? = nil, - subifd: Int? = nil, n: Int? = nil, autorotate: Bool? = nil, + subifd: Int? = nil, + unlimited: Bool? = nil, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) if let page = page { opt.set("page", value: page) } - if let subifd = subifd { - opt.set("subifd", value: subifd) - } if let n = n { opt.set("n", value: n) } if let autorotate = autorotate { opt.set("autorotate", value: autorotate) } + if let subifd = subifd { + opt.set("subifd", value: subifd) + } + if let unlimited = unlimited { + opt.set("unlimited", value: unlimited) + } if let memory = memory { opt.set("memory", value: memory) } @@ -72,9 +77,10 @@ extension VIPSImage { /// - Parameters: /// - buffer: Buffer to load from /// - page: First page to load - /// - subifd: Subifd index /// - n: Number of pages to load, -1 for all /// - autorotate: Rotate image using orientation tag + /// - subifd: Subifd index + /// - unlimited: Remove all denial of service limits /// - memory: Force open via memory /// - access: Required access pattern for this file /// - failOn: Error level to fail on @@ -83,9 +89,10 @@ extension VIPSImage { public static func tiffload( buffer: VIPSBlob, page: Int? = nil, - subifd: Int? = nil, n: Int? = nil, autorotate: Bool? = nil, + subifd: Int? = nil, + unlimited: Bool? = nil, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, @@ -93,22 +100,25 @@ extension VIPSImage { ) throws -> VIPSImage { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out in + try VIPSImage { out in var opt = VIPSOption() opt.set("buffer", value: blob) if let page = page { opt.set("page", value: page) } - if let subifd = subifd { - opt.set("subifd", value: subifd) - } if let n = n { opt.set("n", value: n) } if let autorotate = autorotate { opt.set("autorotate", value: autorotate) } + if let subifd = subifd { + opt.set("subifd", value: subifd) + } + if let unlimited = unlimited { + opt.set("unlimited", value: unlimited) + } if let memory = memory { opt.set("memory", value: memory) } @@ -134,9 +144,10 @@ extension VIPSImage { /// - Parameters: /// - buffer: Buffer to load from /// - page: First page to load - /// - subifd: Subifd index /// - n: Number of pages to load, -1 for all /// - autorotate: Rotate image using orientation tag + /// - subifd: Subifd index + /// - unlimited: Remove all denial of service limits /// - memory: Force open via memory /// - access: Required access pattern for this file /// - failOn: Error level to fail on @@ -145,9 +156,10 @@ extension VIPSImage { public static func tiffload( unsafeBuffer buffer: UnsafeRawBufferPointer, page: Int? = nil, - subifd: Int? = nil, n: Int? = nil, autorotate: Bool? = nil, + subifd: Int? = nil, + unlimited: Bool? = nil, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, @@ -157,9 +169,10 @@ extension VIPSImage { return try tiffload( buffer: blob, page: page, - subifd: subifd, n: n, autorotate: autorotate, + subifd: subifd, + unlimited: unlimited, memory: memory, access: access, failOn: failOn, @@ -172,9 +185,10 @@ extension VIPSImage { /// - Parameters: /// - source: Source to load from /// - page: First page to load - /// - subifd: Subifd index /// - n: Number of pages to load, -1 for all /// - autorotate: Rotate image using orientation tag + /// - subifd: Subifd index + /// - unlimited: Remove all denial of service limits /// - memory: Force open via memory /// - access: Required access pattern for this file /// - failOn: Error level to fail on @@ -182,30 +196,34 @@ extension VIPSImage { public static func tiffload( source: VIPSSource, page: Int? = nil, - subifd: Int? = nil, n: Int? = nil, autorotate: Bool? = nil, + subifd: Int? = nil, + unlimited: Bool? = nil, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) if let page = page { opt.set("page", value: page) } - if let subifd = subifd { - opt.set("subifd", value: subifd) - } if let n = n { opt.set("n", value: n) } if let autorotate = autorotate { opt.set("autorotate", value: autorotate) } + if let subifd = subifd { + opt.set("subifd", value: subifd) + } + if let unlimited = unlimited { + opt.set("unlimited", value: unlimited) + } if let memory = memory { opt.set("memory", value: memory) } diff --git a/Sources/VIPS/Generated/Foreign/foreign_webp.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_webp.generated.swift index 2f3288a..b17e462 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_webp.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_webp.generated.swift @@ -31,7 +31,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -86,7 +86,7 @@ extension VIPSImage { ) throws -> VIPSImage { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out in + try VIPSImage { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -175,7 +175,7 @@ extension VIPSImage { failOn: VipsFailOn? = nil, revalidate: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) diff --git a/Sources/VIPS/Generated/arithmetic.generated.swift b/Sources/VIPS/Generated/arithmetic.generated.swift index 7f76bd3..f9eaa0a 100644 --- a/Sources/VIPS/Generated/arithmetic.generated.swift +++ b/Sources/VIPS/Generated/arithmetic.generated.swift @@ -11,7 +11,7 @@ extension VIPSImage { /// Transform float lab to signed short public func Lab2LabS() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -23,7 +23,7 @@ extension VIPSImage { /// Unpack a labq image to short lab public func LabQ2LabS() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -35,7 +35,7 @@ extension VIPSImage { /// Transform signed short lab to float public func LabS2Lab() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -47,7 +47,7 @@ extension VIPSImage { /// Transform short lab to labq coding public func LabS2LabQ() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -59,7 +59,7 @@ extension VIPSImage { /// Absolute value of an image public func abs() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -74,7 +74,7 @@ extension VIPSImage { /// - Parameters: /// - `right`: Right-hand image argument public func add(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("left", value: self) @@ -88,7 +88,7 @@ extension VIPSImage { #if SHIM_VIPS_VERSION_8_16 /// Append an alpha channel public func addalpha() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -119,7 +119,7 @@ extension VIPSImage { /// - `right`: Right-hand image argument /// - boolean: Boolean to perform public func boolean(_ rhs: VIPSImage, boolean: VipsOperationBoolean) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("left", value: self) @@ -137,7 +137,7 @@ extension VIPSImage { /// - boolean: Boolean to perform /// - c: Array of constants public func booleanConst(boolean: VipsOperationBoolean, c: [Double]) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -154,7 +154,7 @@ extension VIPSImage { /// - Parameters: /// - cmplx: Complex to perform public func complex(cmplx: VipsOperationComplex) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -171,7 +171,7 @@ extension VIPSImage { /// - `right`: Right-hand image argument /// - cmplx: Binary complex operation to perform public func complex2(_ rhs: VIPSImage, cmplx: VipsOperationComplex2) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("left", value: self) @@ -188,7 +188,7 @@ extension VIPSImage { /// - Parameters: /// - `right`: Right-hand image argument public func complexform(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("left", value: self) @@ -204,7 +204,7 @@ extension VIPSImage { /// - Parameters: /// - `get`: Complex to perform public func complexget(`get`: VipsOperationComplexget) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -234,7 +234,7 @@ extension VIPSImage { /// - Parameters: /// - `right`: Right-hand image argument public func divide(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("left", value: self) @@ -247,7 +247,7 @@ extension VIPSImage { /// Invert an image public func invert() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -262,7 +262,7 @@ extension VIPSImage { /// - Parameters: /// - size: LUT size to generate public func invertlut(size: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -280,7 +280,7 @@ extension VIPSImage { /// - Parameters: /// - math: Math to perform public func math(_ math: VipsOperationMath) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -297,7 +297,7 @@ extension VIPSImage { /// - `right`: Right-hand image argument /// - math2: Math to perform public func math2(_ rhs: VIPSImage, math2: VipsOperationMath2) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("left", value: self) @@ -315,7 +315,7 @@ extension VIPSImage { /// - math2: Math to perform /// - c: Array of constants public func math2Const(math2: VipsOperationMath2, c: [Double]) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -327,9 +327,9 @@ extension VIPSImage { } } - /// Invert an matrix + /// Invert a matrix public func matrixinvert() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -339,6 +339,22 @@ extension VIPSImage { } } + /// Multiply two matrices + /// + /// - Parameters: + /// - `right`: Second matrix to multiply + public func matrixmultiply(_ rhs: VIPSImage) throws -> VIPSImage { + return try VIPSImage { out in + var opt = VIPSOption() + + opt.set("left", value: self) + opt.set("right", value: rhs) + opt.set("out", value: &out) + + try VIPSImage.call("matrixmultiply", options: &opt) + } + } + /// Find image maximum /// /// - Parameters: @@ -364,7 +380,7 @@ extension VIPSImage { /// - Parameters: /// - `right`: Right-hand image argument public func maxpair(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("left", value: self) @@ -400,7 +416,7 @@ extension VIPSImage { /// - Parameters: /// - `right`: Right-hand image argument public func minpair(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("left", value: self) @@ -416,7 +432,7 @@ extension VIPSImage { /// - Parameters: /// - `right`: Right-hand image argument public func multiply(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("left", value: self) @@ -433,7 +449,7 @@ extension VIPSImage { /// - Parameters: /// - maxAlpha: Maximum value of alpha channel public func premultiply(maxAlpha: Double? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -455,7 +471,7 @@ extension VIPSImage { public func relational(_ rhs: VIPSImage, relational: VipsOperationRelational) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("left", value: self) @@ -475,7 +491,7 @@ extension VIPSImage { public func relationalConst(relational: VipsOperationRelational, c: [Double]) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -492,7 +508,7 @@ extension VIPSImage { /// - Parameters: /// - `right`: Right-hand image argument public func remainder(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("left", value: self) @@ -524,7 +540,7 @@ extension VIPSImage { /// - Parameters: /// - c: Array of constants public func remainderConst(c: [Double]) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -540,7 +556,7 @@ extension VIPSImage { /// - Parameters: /// - round: Rounding operation to perform public func round(_ round: VipsOperationRound) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -553,7 +569,7 @@ extension VIPSImage { /// Unit vector of pixel public func sign() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -565,7 +581,7 @@ extension VIPSImage { /// Find many image stats public func stats() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -580,7 +596,7 @@ extension VIPSImage { /// - Parameters: /// - `right`: Right-hand image argument public func subtract(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("left", value: self) @@ -596,7 +612,7 @@ extension VIPSImage { /// - Parameters: /// - `in`: Array of input images public static func sum(_ `in`: [VIPSImage]) throws -> VIPSImage { - return try VIPSImage([`in`]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: `in`) @@ -615,7 +631,7 @@ extension VIPSImage { public func unpremultiply(maxAlpha: Double? = nil, alphaBand: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) diff --git a/Sources/VIPS/Generated/colour.generated.swift b/Sources/VIPS/Generated/colour.generated.swift index ab8c111..e6b0ec6 100644 --- a/Sources/VIPS/Generated/colour.generated.swift +++ b/Sources/VIPS/Generated/colour.generated.swift @@ -11,7 +11,7 @@ extension VIPSImage { /// Transform lch to cmc public func CMC2LCh() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -23,7 +23,7 @@ extension VIPSImage { /// Transform cmyk to xyz public func CMYK2XYZ() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -35,7 +35,7 @@ extension VIPSImage { /// Transform hsv to srgb public func HSV2sRGB() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -47,7 +47,7 @@ extension VIPSImage { /// Transform lch to cmc public func LCh2CMC() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -59,7 +59,7 @@ extension VIPSImage { /// Transform lch to lab public func LCh2Lab() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -71,7 +71,7 @@ extension VIPSImage { /// Transform lab to lch public func Lab2LCh() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -83,7 +83,7 @@ extension VIPSImage { /// Transform float lab to labq coding public func Lab2LabQ() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -98,7 +98,7 @@ extension VIPSImage { /// - Parameters: /// - temp: Color temperature public func Lab2XYZ(temp: [Double]? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -113,7 +113,7 @@ extension VIPSImage { /// Unpack a labq image to float lab public func LabQ2Lab() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -125,7 +125,7 @@ extension VIPSImage { /// Convert a labq image to srgb public func LabQ2sRGB() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -137,7 +137,7 @@ extension VIPSImage { /// Transform xyz to cmyk public func XYZ2CMYK() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -152,7 +152,7 @@ extension VIPSImage { /// - Parameters: /// - temp: Colour temperature public func XYZ2Lab(temp: [Double]? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -167,7 +167,7 @@ extension VIPSImage { /// Transform xyz to yxy public func XYZ2Yxy() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -179,7 +179,7 @@ extension VIPSImage { /// Transform xyz to scrgb public func XYZ2scRGB() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -191,7 +191,7 @@ extension VIPSImage { /// Transform yxy to xyz public func Yxy2XYZ() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -209,7 +209,7 @@ extension VIPSImage { public func colourspace(space: VipsInterpretation, sourceSpace: VipsInterpretation? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -225,7 +225,7 @@ extension VIPSImage { /// False-color an image public func falsecolour() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -250,7 +250,7 @@ extension VIPSImage { outputProfile: String? = nil, depth: Int? = nil ) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -290,7 +290,7 @@ extension VIPSImage { embedded: Bool? = nil, inputProfile: String? = nil ) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -334,7 +334,7 @@ extension VIPSImage { inputProfile: String? = nil, depth: Int? = nil ) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -365,7 +365,7 @@ extension VIPSImage { /// Label regions in an image public func labelregions() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -377,7 +377,7 @@ extension VIPSImage { /// Transform srgb to hsv public func sRGB2HSV() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -389,7 +389,7 @@ extension VIPSImage { /// Convert an srgb image to scrgb public func sRGB2scRGB() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -404,7 +404,7 @@ extension VIPSImage { /// - Parameters: /// - depth: Output device space depth in bits public func scRGB2BW(depth: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -419,7 +419,7 @@ extension VIPSImage { /// Transform scrgb to xyz public func scRGB2XYZ() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -429,12 +429,12 @@ extension VIPSImage { } } - /// Convert an scrgb image to srgb + /// Convert scrgb to srgb /// /// - Parameters: /// - depth: Output device space depth in bits public func scRGB2sRGB(depth: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -462,7 +462,7 @@ extension VIPSImage { dsize: Int? = nil, esize: Int? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) diff --git a/Sources/VIPS/Generated/conversion.generated.swift b/Sources/VIPS/Generated/conversion.generated.swift index c41f3cf..3fb456d 100644 --- a/Sources/VIPS/Generated/conversion.generated.swift +++ b/Sources/VIPS/Generated/conversion.generated.swift @@ -34,7 +34,7 @@ extension VIPSImage { premultiplied: Bool? = nil, extend: VipsExtend? = nil ) throws -> VIPSImage { - return try VIPSImage([self, interpolate as Any]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -93,7 +93,7 @@ extension VIPSImage { hspacing: Int? = nil, vspacing: Int? = nil ) throws -> VIPSImage { - return try VIPSImage([`in`]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: `in`) @@ -126,7 +126,7 @@ extension VIPSImage { /// Autorotate image by exif tag public func autorot() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -141,7 +141,7 @@ extension VIPSImage { /// - Parameters: /// - `in`: Array of input images public static func bandjoin(_ `in`: [VIPSImage]) throws -> VIPSImage { - return try VIPSImage([`in`]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: `in`) @@ -156,7 +156,7 @@ extension VIPSImage { /// - Parameters: /// - c: Array of constants to add public func bandjoinConst(c: [Double]) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -173,7 +173,7 @@ extension VIPSImage { /// - `in`: Array of input images /// - index: Select this band element from sorted list public static func bandrank(_ `in`: [VIPSImage], index: Int? = nil) throws -> VIPSImage { - return try VIPSImage([`in`]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: `in`) @@ -192,7 +192,7 @@ extension VIPSImage { /// - format: Format to cast to /// - shift: Shift integer values up and down public func cast(format: VipsBandFormat, shift: Bool? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -223,7 +223,7 @@ extension VIPSImage { compositingSpace: VipsInterpretation? = nil, premultiplied: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([`in`]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: `in`) @@ -263,7 +263,7 @@ extension VIPSImage { compositingSpace: VipsInterpretation? = nil, premultiplied: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage([self, overlay]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("base", value: self) @@ -312,7 +312,7 @@ extension VIPSImage { xoffset: Int? = nil, yoffset: Int? = nil ) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -360,7 +360,7 @@ extension VIPSImage { /// - width: Width of extract area /// - height: Height of extract area public func crop(`left`: Int, top: Int, width: Int, height: Int) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("input", value: self) @@ -391,7 +391,7 @@ extension VIPSImage { extend: VipsExtend? = nil, background: [Double]? = nil ) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -419,7 +419,7 @@ extension VIPSImage { /// - width: Width of extract area /// - height: Height of extract area public func extractArea(`left`: Int, top: Int, width: Int, height: Int) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("input", value: self) @@ -439,7 +439,7 @@ extension VIPSImage { /// - band: Band to extract /// - n: Number of bands to extract public func extractBand(_ band: Int, n: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -458,7 +458,7 @@ extension VIPSImage { /// - Parameters: /// - direction: Direction to flip image public func flip(direction: VipsDirection) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -476,7 +476,7 @@ extension VIPSImage { /// - across: Number of tiles across /// - down: Number of tiles down public func grid(tileHeight: Int, across: Int, down: Int) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -504,7 +504,7 @@ extension VIPSImage { expand: Bool? = nil, background: [Double]? = nil ) throws -> VIPSImage { - return try VIPSImage([self, sub]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("main", value: self) @@ -540,7 +540,7 @@ extension VIPSImage { background: [Double]? = nil, align: VipsAlign? = nil ) throws -> VIPSImage { - return try VIPSImage([self, in2]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in1", value: self) @@ -569,7 +569,7 @@ extension VIPSImage { /// - Parameters: /// - m: Matrix of coefficients public func recomb(m: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, m]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -593,7 +593,7 @@ extension VIPSImage { kernel: VipsKernel? = nil, gap: Double? = nil ) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -620,7 +620,7 @@ extension VIPSImage { public func reduceh(hshrink: Double, kernel: VipsKernel? = nil, gap: Double? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -646,7 +646,7 @@ extension VIPSImage { public func reducev(vshrink: Double, kernel: VipsKernel? = nil, gap: Double? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -676,7 +676,7 @@ extension VIPSImage { gap: Double? = nil, vscale: Double? = nil ) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -701,7 +701,7 @@ extension VIPSImage { /// - Parameters: /// - angle: Angle to rotate image public func rot(angle: VipsAngle) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -717,7 +717,7 @@ extension VIPSImage { /// - Parameters: /// - angle: Angle to rotate image public func rot45(angle: VipsAngle45? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -749,7 +749,7 @@ extension VIPSImage { idx: Double? = nil, idy: Double? = nil ) throws -> VIPSImage { - return try VIPSImage([self, interpolate as Any]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -784,7 +784,7 @@ extension VIPSImage { /// - exp: Exponent for log scale /// - log: Log scale public func scale(exp: Double? = nil, log: Bool? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -807,7 +807,7 @@ extension VIPSImage { /// - vshrink: Vertical shrink factor /// - ceil: Round-up output dimensions public func shrink(hshrink: Double, vshrink: Double, ceil: Bool? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -828,7 +828,7 @@ extension VIPSImage { /// - hshrink: Horizontal shrink factor /// - ceil: Round-up output dimensions public func shrinkh(hshrink: Int, ceil: Bool? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -848,7 +848,7 @@ extension VIPSImage { /// - vshrink: Vertical shrink factor /// - ceil: Round-up output dimensions public func shrinkv(vshrink: Int, ceil: Bool? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -883,7 +883,7 @@ extension VIPSImage { idx: Double? = nil, idy: Double? = nil ) throws -> VIPSImage { - return try VIPSImage([self, interpolate as Any]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -930,7 +930,7 @@ extension VIPSImage { interesting: VipsInteresting? = nil, premultiplied: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("input", value: self) @@ -965,7 +965,7 @@ extension VIPSImage { threaded: Bool? = nil, persistent: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -998,7 +998,7 @@ extension VIPSImage { /// - Parameters: /// - pageHeight: Height of each input page public func transpose3d(pageHeight: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -1017,7 +1017,7 @@ extension VIPSImage { /// - x: Left edge of input in output /// - y: Top edge of input in output public func wrap(x: Int? = nil, y: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -1039,7 +1039,7 @@ extension VIPSImage { /// - xfac: Horizontal zoom factor /// - yfac: Vertical zoom factor public func zoom(xfac: Int, yfac: Int) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("input", value: self) diff --git a/Sources/VIPS/Generated/convolution.generated.swift b/Sources/VIPS/Generated/convolution.generated.swift index 0017bd4..85681bf 100644 --- a/Sources/VIPS/Generated/convolution.generated.swift +++ b/Sources/VIPS/Generated/convolution.generated.swift @@ -15,7 +15,7 @@ extension VIPSImage { /// - sigma: Sigma of Gaussian /// - precision: Convolve with this precision public func canny(sigma: Double? = nil, precision: VipsPrecision? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -44,7 +44,7 @@ extension VIPSImage { layers: Int? = nil, cluster: Int? = nil ) throws -> VIPSImage { - return try VIPSImage([self, mask]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -72,7 +72,7 @@ extension VIPSImage { /// - cluster: Cluster lines closer than this in approximation public func conva(mask: VIPSImage, layers: Int? = nil, cluster: Int? = nil) throws -> VIPSImage { - return try VIPSImage([self, mask]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -95,7 +95,7 @@ extension VIPSImage { /// - mask: Input matrix image /// - layers: Use this many layers in approximation public func convasep(mask: VIPSImage, layers: Int? = nil) throws -> VIPSImage { - return try VIPSImage([self, mask]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -114,7 +114,7 @@ extension VIPSImage { /// - Parameters: /// - mask: Input matrix image public func convf(mask: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, mask]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -130,7 +130,7 @@ extension VIPSImage { /// - Parameters: /// - mask: Input matrix image public func convi(mask: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, mask]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -154,7 +154,7 @@ extension VIPSImage { layers: Int? = nil, cluster: Int? = nil ) throws -> VIPSImage { - return try VIPSImage([self, mask]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -183,7 +183,7 @@ extension VIPSImage { public func gaussblur(sigma: Double, minAmpl: Double? = nil, precision: VipsPrecision? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -217,7 +217,7 @@ extension VIPSImage { m1: Double? = nil, m2: Double? = nil ) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -247,7 +247,7 @@ extension VIPSImage { /// Sobel edge detector public func sobel() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) diff --git a/Sources/VIPS/Generated/create.generated.swift b/Sources/VIPS/Generated/create.generated.swift index bb064a4..8e428bb 100644 --- a/Sources/VIPS/Generated/create.generated.swift +++ b/Sources/VIPS/Generated/create.generated.swift @@ -16,7 +16,7 @@ extension VIPSImage { /// - height: Image height in pixels /// - bands: Number of bands in image public static func black(width: Int, height: Int, bands: Int? = nil) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -32,7 +32,7 @@ extension VIPSImage { /// Build a look-up table public func buildlut() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -52,7 +52,7 @@ extension VIPSImage { public static func eye(width: Int, height: Int, uchar: Bool? = nil, factor: Double? = nil) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -78,7 +78,7 @@ extension VIPSImage { public static func fractsurf(width: Int, height: Int, fractalDimension: Double) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -103,7 +103,7 @@ extension VIPSImage { separable: Bool? = nil, precision: VipsPrecision? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("sigma", value: sigma) @@ -135,7 +135,7 @@ extension VIPSImage { mean: Double? = nil, seed: Int? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -162,7 +162,7 @@ extension VIPSImage { /// - height: Image height in pixels /// - uchar: Output an unsigned char image public static func grey(width: Int, height: Int, uchar: Bool? = nil) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -185,7 +185,7 @@ extension VIPSImage { public static func identity(bands: Int? = nil, ushort: Bool? = nil, size: Int? = nil) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() if let bands = bands { @@ -216,7 +216,7 @@ extension VIPSImage { separable: Bool? = nil, precision: VipsPrecision? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("sigma", value: sigma) @@ -256,7 +256,7 @@ extension VIPSImage { reject: Bool? = nil, optical: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -309,7 +309,7 @@ extension VIPSImage { reject: Bool? = nil, optical: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -362,7 +362,7 @@ extension VIPSImage { reject: Bool? = nil, optical: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -408,7 +408,7 @@ extension VIPSImage { reject: Bool? = nil, optical: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -453,7 +453,7 @@ extension VIPSImage { reject: Bool? = nil, optical: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -503,7 +503,7 @@ extension VIPSImage { reject: Bool? = nil, optical: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -553,7 +553,7 @@ extension VIPSImage { reject: Bool? = nil, optical: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -598,7 +598,7 @@ extension VIPSImage { reject: Bool? = nil, optical: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -645,7 +645,7 @@ extension VIPSImage { reject: Bool? = nil, optical: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -692,7 +692,7 @@ extension VIPSImage { reject: Bool? = nil, optical: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -732,7 +732,7 @@ extension VIPSImage { uchar: Bool? = nil, seed: Int? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -767,7 +767,7 @@ extension VIPSImage { hfreq: Double? = nil, vfreq: Double? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -814,7 +814,7 @@ extension VIPSImage { rgba: Bool? = nil, wrap: VipsTextWrap? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("text", value: text) @@ -879,7 +879,7 @@ extension VIPSImage { M: Double? = nil, H: Double? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() if let inMax = inMax { @@ -928,7 +928,7 @@ extension VIPSImage { public static func worley(width: Int, height: Int, cellSize: Int? = nil, seed: Int? = nil) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -952,7 +952,7 @@ extension VIPSImage { /// - height: Image height in pixels /// - uchar: Output an unsigned char image public static func zone(width: Int, height: Int, uchar: Bool? = nil) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) diff --git a/Sources/VIPS/Generated/freqfilt.generated.swift b/Sources/VIPS/Generated/freqfilt.generated.swift index 0b34a04..f5dfb9d 100644 --- a/Sources/VIPS/Generated/freqfilt.generated.swift +++ b/Sources/VIPS/Generated/freqfilt.generated.swift @@ -14,7 +14,7 @@ extension VIPSImage { /// - Parameters: /// - mask: Input mask image public func freqmult(mask: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, mask]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -27,7 +27,7 @@ extension VIPSImage { /// Forward fft public func fwfft() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -42,7 +42,7 @@ extension VIPSImage { /// - Parameters: /// - real: Output only the real part of the transform public func invfft(real: Bool? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) diff --git a/Sources/VIPS/Generated/histogram.generated.swift b/Sources/VIPS/Generated/histogram.generated.swift index 7bea18e..7a08637 100644 --- a/Sources/VIPS/Generated/histogram.generated.swift +++ b/Sources/VIPS/Generated/histogram.generated.swift @@ -11,7 +11,7 @@ extension VIPSImage { /// Form cumulative histogram public func histCum() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -40,7 +40,7 @@ extension VIPSImage { /// - Parameters: /// - band: Equalise with this band public func histEqual(band: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -58,7 +58,7 @@ extension VIPSImage { /// - Parameters: /// - band: Find histogram of band public func histFind(band: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -77,7 +77,7 @@ extension VIPSImage { /// - index: Index image /// - combine: Combine bins like this public func histFindIndexed(index: VIPSImage, combine: VipsCombine? = nil) throws -> VIPSImage { - return try VIPSImage([self, index]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -96,7 +96,7 @@ extension VIPSImage { /// - Parameters: /// - bins: Number of bins in each dimension public func histFindNdim(bins: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -130,7 +130,7 @@ extension VIPSImage { /// - height: Window height in pixels /// - maxSlope: Maximum slope (CLAHE) public func histLocal(width: Int, height: Int, maxSlope: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -150,7 +150,7 @@ extension VIPSImage { /// - Parameters: /// - ref: Reference histogram public func histMatch(ref: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, ref]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -163,7 +163,7 @@ extension VIPSImage { /// Normalise histogram public func histNorm() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -175,7 +175,7 @@ extension VIPSImage { /// Plot histogram public func histPlot() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -194,7 +194,7 @@ extension VIPSImage { public func houghCircle(scale: Int? = nil, minRadius: Int? = nil, maxRadius: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -219,7 +219,7 @@ extension VIPSImage { /// - width: Horizontal size of parameter space /// - height: Vertical size of parameter space public func houghLine(width: Int? = nil, height: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -240,7 +240,7 @@ extension VIPSImage { /// - Parameters: /// - in2: Second input image public func phasecor(in2: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, in2]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -253,7 +253,7 @@ extension VIPSImage { /// Make displayable power spectrum public func spectrum() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) diff --git a/Sources/VIPS/Generated/misc.generated.swift b/Sources/VIPS/Generated/misc.generated.swift index d51c31b..848f825 100644 --- a/Sources/VIPS/Generated/misc.generated.swift +++ b/Sources/VIPS/Generated/misc.generated.swift @@ -15,7 +15,7 @@ extension VIPSImage { /// - Parameters: /// - boolean: Boolean to perform public func bandbool(boolean: VipsOperationBoolean) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -31,7 +31,7 @@ extension VIPSImage { /// - Parameters: /// - factor: Fold by this factor public func bandfold(factor: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -46,7 +46,7 @@ extension VIPSImage { /// Band-wise average public func bandmean() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -61,7 +61,7 @@ extension VIPSImage { /// - Parameters: /// - factor: Unfold by this factor public func bandunfold(factor: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -76,7 +76,7 @@ extension VIPSImage { /// Byteswap an image public func byteswap() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -91,7 +91,7 @@ extension VIPSImage { /// - Parameters: /// - cases: Array of case images public func `case`(cases: [VIPSImage]) throws -> VIPSImage { - return try VIPSImage([self, cases]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("index", value: self) @@ -108,7 +108,7 @@ extension VIPSImage { /// - min: Minimum value /// - max: Maximum value public func clamp(min: Double? = nil, max: Double? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -143,7 +143,7 @@ extension VIPSImage { layers: Int? = nil, cluster: Int? = nil ) throws -> VIPSImage { - return try VIPSImage([self, mask]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -177,7 +177,7 @@ extension VIPSImage { /// - Parameters: /// - `right`: Right-hand input image public func dE00(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("left", value: self) @@ -193,7 +193,7 @@ extension VIPSImage { /// - Parameters: /// - `right`: Right-hand input image public func dE76(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("left", value: self) @@ -209,7 +209,7 @@ extension VIPSImage { /// - Parameters: /// - `right`: Right-hand input image public func dECMC(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("left", value: self) @@ -225,7 +225,7 @@ extension VIPSImage { /// - Parameters: /// - ref: Input reference image public func fastcor(ref: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, ref]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -238,7 +238,7 @@ extension VIPSImage { /// Fill image zeros with nearest non-zero pixel public func fillNearest() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -286,7 +286,7 @@ extension VIPSImage { /// - background: Background value /// - maxAlpha: Maximum value of alpha channel public func flatten(background: [Double]? = nil, maxAlpha: Double? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -304,7 +304,7 @@ extension VIPSImage { /// Transform float rgb to radiance coding public func float2rad() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -319,7 +319,7 @@ extension VIPSImage { /// - Parameters: /// - exponent: Gamma factor public func gamma(exponent: Double? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -373,7 +373,7 @@ extension VIPSImage { /// - gamma: Image gamma /// - intOutput: Integer output public func globalbalance(gamma: Double? = nil, intOutput: Bool? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -404,7 +404,7 @@ extension VIPSImage { extend: VipsExtend? = nil, background: [Double]? = nil ) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -430,7 +430,7 @@ extension VIPSImage { /// - in2: Source for FALSE pixels /// - blend: Blend smoothly between then and else parts public func ifthenelse(in1: VIPSImage, in2: VIPSImage, blend: Bool? = nil) throws -> VIPSImage { - return try VIPSImage([self, in1, in2]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("cond", value: self) @@ -458,7 +458,7 @@ extension VIPSImage { threaded: Bool? = nil, persistent: Bool? = nil ) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -486,7 +486,7 @@ extension VIPSImage { /// - lut: Look-up table image /// - band: Apply one-band lut to this band of in public func maplut(lut: VIPSImage, band: Int? = nil) throws -> VIPSImage { - return try VIPSImage([self, lut]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -531,7 +531,7 @@ extension VIPSImage { search: Bool? = nil, interpolate: VIPSInterpolate? = nil ) throws -> VIPSImage { - return try VIPSImage([self, sec, interpolate as Any]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("ref", value: self) @@ -579,7 +579,7 @@ extension VIPSImage { width: Int? = nil, height: Int? = nil ) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -618,7 +618,7 @@ extension VIPSImage { dy: Int, mblend: Int? = nil ) throws -> VIPSImage { - return try VIPSImage([self, sec]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("ref", value: self) @@ -660,7 +660,7 @@ extension VIPSImage { mblend: Int? = nil, bandno: Int? = nil ) throws -> VIPSImage { - return try VIPSImage([self, sec]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("ref", value: self) @@ -723,7 +723,7 @@ extension VIPSImage { interpolate: VIPSInterpolate? = nil, mblend: Int? = nil ) throws -> VIPSImage { - return try VIPSImage([self, sec, interpolate as Any]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("ref", value: self) @@ -763,7 +763,7 @@ extension VIPSImage { /// - Parameters: /// - band: Band to msb public func msb(band: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -796,7 +796,7 @@ extension VIPSImage { /// Prewitt edge detector public func prewitt() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -808,7 +808,7 @@ extension VIPSImage { /// Unpack radiance coding to float rgb public func rad2float() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -818,13 +818,31 @@ extension VIPSImage { } } + /// Rebuild an mosaiced image + /// + /// - Parameters: + /// - oldStr: Search for this string + /// - newStr: And swap for this string + public func remosaic(oldStr: String, newStr: String) throws -> VIPSImage { + return try VIPSImage { out in + var opt = VIPSOption() + + opt.set("in", value: self) + opt.set("old_str", value: oldStr) + opt.set("new_str", value: newStr) + opt.set("out", value: &out) + + try VIPSImage.call("remosaic", options: &opt) + } + } + /// Replicate an image /// /// - Parameters: /// - across: Repeat this many times horizontally /// - down: Repeat this many times vertically public func replicate(across: Int, down: Int) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -838,7 +856,7 @@ extension VIPSImage { /// Scharr edge detector public func scharr() throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -868,7 +886,7 @@ extension VIPSImage { b: [Double]? = nil, corners: [Double]? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("width", value: width) @@ -898,7 +916,7 @@ extension VIPSImage { /// - Parameters: /// - tileHeight: Tile height in pixels public func sequential(tileHeight: Int? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -916,7 +934,7 @@ extension VIPSImage { /// - Parameters: /// - ref: Input reference image public func spcor(ref: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, ref]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -944,7 +962,7 @@ extension VIPSImage { m0: Double? = nil, a: Double? = nil ) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -975,7 +993,7 @@ extension VIPSImage { /// - yfac: Vertical subsample factor /// - point: Point sample public func subsample(xfac: Int, yfac: Int, point: Bool? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("input", value: self) @@ -995,7 +1013,7 @@ extension VIPSImage { /// - Parameters: /// - tests: Table of images to test public static func `switch`(tests: [VIPSImage]) throws -> VIPSImage { - return try VIPSImage([tests]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("tests", value: tests) diff --git a/Sources/VIPS/Generated/morphology.generated.swift b/Sources/VIPS/Generated/morphology.generated.swift index 63330cc..2c92c5d 100644 --- a/Sources/VIPS/Generated/morphology.generated.swift +++ b/Sources/VIPS/Generated/morphology.generated.swift @@ -33,7 +33,7 @@ extension VIPSImage { /// - mask: Input matrix image /// - morph: Morphological operation to perform public func morph(mask: VIPSImage, morph: VipsOperationMorphology) throws -> VIPSImage { - return try VIPSImage([self, mask]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -52,7 +52,7 @@ extension VIPSImage { /// - height: Window height in pixels /// - index: Select pixel at index public func rank(width: Int, height: Int, index: Int) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) diff --git a/Sources/VIPS/Generated/resample.generated.swift b/Sources/VIPS/Generated/resample.generated.swift index f5d6e34..36d3300 100644 --- a/Sources/VIPS/Generated/resample.generated.swift +++ b/Sources/VIPS/Generated/resample.generated.swift @@ -25,7 +25,7 @@ extension VIPSImage { premultiplied: Bool? = nil, extend: VipsExtend? = nil ) throws -> VIPSImage { - return try VIPSImage([self, index, interpolate as Any]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -55,7 +55,7 @@ extension VIPSImage { /// - interpolate: Interpolate values with this public func quadratic(coeff: VIPSImage, interpolate: VIPSInterpolate? = nil) throws -> VIPSImage { - return try VIPSImage([self, coeff, interpolate as Any]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -79,8 +79,8 @@ extension VIPSImage { /// - noRotate: Don't use orientation tags to rotate image upright /// - crop: Reduce to fill target rectangle, then crop /// - linear: Reduce in linear light - /// - importProfile: Fallback import profile - /// - exportProfile: Fallback export profile + /// - inputProfile: Fallback input profile + /// - outputProfile: Fallback output profile /// - intent: Rendering intent /// - failOn: Error level to fail on public static func thumbnail( @@ -91,12 +91,12 @@ extension VIPSImage { noRotate: Bool? = nil, crop: VipsInteresting? = nil, linear: Bool? = nil, - importProfile: String? = nil, - exportProfile: String? = nil, + inputProfile: String? = nil, + outputProfile: String? = nil, intent: VipsIntent? = nil, failOn: VipsFailOn? = nil ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -116,11 +116,11 @@ extension VIPSImage { if let linear = linear { opt.set("linear", value: linear) } - if let importProfile = importProfile { - opt.set("import_profile", value: importProfile) + if let inputProfile = inputProfile { + opt.set("input_profile", value: inputProfile) } - if let exportProfile = exportProfile { - opt.set("export_profile", value: exportProfile) + if let outputProfile = outputProfile { + opt.set("output_profile", value: outputProfile) } if let intent = intent { opt.set("intent", value: intent) @@ -145,8 +145,8 @@ extension VIPSImage { /// - noRotate: Don't use orientation tags to rotate image upright /// - crop: Reduce to fill target rectangle, then crop /// - linear: Reduce in linear light - /// - importProfile: Fallback import profile - /// - exportProfile: Fallback export profile + /// - inputProfile: Fallback input profile + /// - outputProfile: Fallback output profile /// - intent: Rendering intent /// - failOn: Error level to fail on @inlinable @@ -159,14 +159,14 @@ extension VIPSImage { noRotate: Bool? = nil, crop: VipsInteresting? = nil, linear: Bool? = nil, - importProfile: String? = nil, - exportProfile: String? = nil, + inputProfile: String? = nil, + outputProfile: String? = nil, intent: VipsIntent? = nil, failOn: VipsFailOn? = nil ) throws -> VIPSImage { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out in + try VIPSImage { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -189,11 +189,11 @@ extension VIPSImage { if let linear = linear { opt.set("linear", value: linear) } - if let importProfile = importProfile { - opt.set("import_profile", value: importProfile) + if let inputProfile = inputProfile { + opt.set("input_profile", value: inputProfile) } - if let exportProfile = exportProfile { - opt.set("export_profile", value: exportProfile) + if let outputProfile = outputProfile { + opt.set("output_profile", value: outputProfile) } if let intent = intent { opt.set("intent", value: intent) @@ -217,8 +217,8 @@ extension VIPSImage { /// - noRotate: Don't use orientation tags to rotate image upright /// - crop: Reduce to fill target rectangle, then crop /// - linear: Reduce in linear light - /// - importProfile: Fallback import profile - /// - exportProfile: Fallback export profile + /// - inputProfile: Fallback input profile + /// - outputProfile: Fallback output profile /// - intent: Rendering intent /// - failOn: Error level to fail on public func thumbnailImage( @@ -228,12 +228,12 @@ extension VIPSImage { noRotate: Bool? = nil, crop: VipsInteresting? = nil, linear: Bool? = nil, - importProfile: String? = nil, - exportProfile: String? = nil, + inputProfile: String? = nil, + outputProfile: String? = nil, intent: VipsIntent? = nil, failOn: VipsFailOn? = nil ) throws -> VIPSImage { - return try VIPSImage(self) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("in", value: self) @@ -253,11 +253,11 @@ extension VIPSImage { if let linear = linear { opt.set("linear", value: linear) } - if let importProfile = importProfile { - opt.set("import_profile", value: importProfile) + if let inputProfile = inputProfile { + opt.set("input_profile", value: inputProfile) } - if let exportProfile = exportProfile { - opt.set("export_profile", value: exportProfile) + if let outputProfile = outputProfile { + opt.set("output_profile", value: outputProfile) } if let intent = intent { opt.set("intent", value: intent) @@ -282,8 +282,8 @@ extension VIPSImage { /// - noRotate: Don't use orientation tags to rotate image upright /// - crop: Reduce to fill target rectangle, then crop /// - linear: Reduce in linear light - /// - importProfile: Fallback import profile - /// - exportProfile: Fallback export profile + /// - inputProfile: Fallback input profile + /// - outputProfile: Fallback output profile /// - intent: Rendering intent /// - failOn: Error level to fail on public static func thumbnail( @@ -295,12 +295,12 @@ extension VIPSImage { noRotate: Bool? = nil, crop: VipsInteresting? = nil, linear: Bool? = nil, - importProfile: String? = nil, - exportProfile: String? = nil, + inputProfile: String? = nil, + outputProfile: String? = nil, intent: VipsIntent? = nil, failOn: VipsFailOn? = nil ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + return try VIPSImage { out in var opt = VIPSOption() opt.set("source", value: source) @@ -323,11 +323,11 @@ extension VIPSImage { if let linear = linear { opt.set("linear", value: linear) } - if let importProfile = importProfile { - opt.set("import_profile", value: importProfile) + if let inputProfile = inputProfile { + opt.set("input_profile", value: inputProfile) } - if let exportProfile = exportProfile { - opt.set("export_profile", value: exportProfile) + if let outputProfile = outputProfile { + opt.set("output_profile", value: outputProfile) } if let intent = intent { opt.set("intent", value: intent) diff --git a/Sources/VIPS/Histogram/histogram.swift b/Sources/VIPS/Histogram/histogram.swift index 3ccfee8..3e13dfe 100644 --- a/Sources/VIPS/Histogram/histogram.swift +++ b/Sources/VIPS/Histogram/histogram.swift @@ -24,13 +24,11 @@ extension VIPSImage { throw VIPSError("Failed to get projection outputs") } - // Create output images with references to keep input alive - // Use the init with 'other' parameter to keep self alive - let rows = VIPSImage(self) { ptr in + let rows = VIPSImage { ptr in ptr = rowsPtr } - let columns = VIPSImage(self) { ptr in + let columns = VIPSImage { ptr in ptr = colsPtr } @@ -60,13 +58,11 @@ extension VIPSImage { throw VIPSError("Failed to get profile outputs") } - // Create output images with references to keep input alive - // Use the init with 'other' parameter to keep self alive - let columns = VIPSImage(self) { ptr in + let columns = VIPSImage { ptr in ptr = colsPtr } - let rows = VIPSImage(self) { ptr in + let rows = VIPSImage { ptr in ptr = rowsPtr } diff --git a/tools/generate-swift-wrappers.py b/tools/generate-swift-wrappers.py index 2b3f48a..a6e1306 100644 --- a/tools/generate-swift-wrappers.py +++ b/tools/generate-swift-wrappers.py @@ -374,9 +374,9 @@ def generate_const_overload(base_operation_name, const_operation_name): signature += ") throws -> VIPSImage {" result.append(signature) - + # Generate method body - call the const operation directly - result.append(" return try VIPSImage(self) { out in") + result.append(" return try VIPSImage { out in") result.append(" var opt = VIPSOption()") result.append("") result.append(f' opt.set("{const_intro.member_x}", value: self)') @@ -790,10 +790,10 @@ def generate_vipsblob_overload(operation_name): # Generate function body blob_param_swift = swiftize_param(blob_param_name) - + result.append(" // the operation will retain the blob") result.append(f" try {blob_param_swift}.withVipsBlob {{ blob in") - result.append(" try VIPSImage(nil) { out in") + result.append(" try VIPSImage { out in") result.append(" var opt = VIPSOption()") result.append("") result.append(f' opt.set("{blob_param_name}", value: blob)') @@ -1014,49 +1014,11 @@ def generate_swift_operation(operation_name): blob_param_swift = swiftize_param(blob_param_name) result.append(f" // the operation will retain the blob") result.append(f" try {blob_param_swift}.withVipsBlob {{ blob in") - result.append(" try VIPSImage(nil) { out in") + result.append(" try VIPSImage { out in") result.append(" var opt = VIPSOption()") result.append("") else: - # Build the array of Swift objects to keep alive - if is_instance_method: - if swift_object_params: - # We have additional Swift object parameters to keep alive - object_refs = [] - for name in swift_object_params: - if name == "right": - param_name = "rhs" - elif name == "in": - param_name = "`in`" - else: - param_name = swiftize_param(name) - # Cast optional parameters to Any to silence compiler warnings - if name in optional_input: - object_refs.append(f"{param_name} as Any") - else: - object_refs.append(param_name) - result.append(f" return try VIPSImage([self, {', '.join(object_refs)}]) {{ out in") - else: - result.append(" return try VIPSImage(self) { out in") - else: - if swift_object_params: - # Static method with Swift object parameters - object_refs = [] - for name in swift_object_params: - if name == "right": - param_name = "rhs" - elif name == "in": - param_name = "`in`" - else: - param_name = swiftize_param(name) - # Cast optional parameters to Any to silence compiler warnings - if name in optional_input: - object_refs.append(f"{param_name} as Any") - else: - object_refs.append(param_name) - result.append(f" return try VIPSImage([{', '.join(object_refs)}]) {{ out in") - else: - result.append(" return try VIPSImage(nil) { out in") + result.append(" return try VIPSImage { out in") result.append(" var opt = VIPSOption()") result.append("") elif has_output and not has_image_output: From a8c5ec47760653a2bbf9695b1e47a3a45f460e2f Mon Sep 17 00:00:00 2001 From: Tobias Haeberle Date: Sat, 15 Nov 2025 13:25:32 +0100 Subject: [PATCH 04/14] move methods on protocol --- Sources/VIPS/Core/VIPSBlob.swift | 2 +- Sources/VIPS/Core/VIPSImage.swift | 41 +- Sources/VIPS/Core/VIPSOption.swift | 6 +- .../Foreign/foreign_gif.generated.swift | 368 ++- .../Foreign/foreign_heif.generated.swift | 440 +-- .../Foreign/foreign_jpeg.generated.swift | 406 +-- .../Foreign/foreign_other.generated.swift | 2918 +++++++++-------- .../Foreign/foreign_pdf.generated.swift | 22 +- .../Foreign/foreign_png.generated.swift | 332 +- .../Foreign/foreign_svg.generated.swift | 22 +- .../Foreign/foreign_tiff.generated.swift | 476 +-- .../Foreign/foreign_webp.generated.swift | 406 +-- .../VIPS/Generated/arithmetic.generated.swift | 294 +- Sources/VIPS/Generated/colour.generated.swift | 168 +- .../VIPS/Generated/conversion.generated.swift | 456 +-- .../Generated/convolution.generated.swift | 67 +- Sources/VIPS/Generated/create.generated.swift | 176 +- .../VIPS/Generated/freqfilt.generated.swift | 20 +- .../VIPS/Generated/histogram.generated.swift | 86 +- Sources/VIPS/Generated/misc.generated.swift | 338 +- .../VIPS/Generated/morphology.generated.swift | 16 +- .../VIPS/Generated/resample.generated.swift | 165 +- Tests/VIPSTests/TestSetup.swift | 6 +- tools/generate-swift-wrappers.py | 88 +- 24 files changed, 3700 insertions(+), 3619 deletions(-) diff --git a/Sources/VIPS/Core/VIPSBlob.swift b/Sources/VIPS/Core/VIPSBlob.swift index 01a6b89..6367ef2 100644 --- a/Sources/VIPS/Core/VIPSBlob.swift +++ b/Sources/VIPS/Core/VIPSBlob.swift @@ -121,7 +121,7 @@ public final class VIPSBlob: @unchecked Sendable, Equatable, CustomDebugStringCo return try body(buffer, Unmanaged.passUnretained(self)) } - public func withVipsBlob(_ body: (UnsafeMutablePointer) throws -> R) rethrows -> R + public func withVipsBlob(_ body: (UnsafeMutablePointer) throws -> R) rethrows -> R where R: ~Copyable { return try body(self.blob) } diff --git a/Sources/VIPS/Core/VIPSImage.swift b/Sources/VIPS/Core/VIPSImage.swift index 6683c05..e2092a4 100644 --- a/Sources/VIPS/Core/VIPSImage.swift +++ b/Sources/VIPS/Core/VIPSImage.swift @@ -18,15 +18,15 @@ public final class VIPSImage: VIPSImageProtocol { self.ptr = UnsafeMutableRawPointer(image) } - func withVipsImage(_ body: (UnsafeMutablePointer) -> R) -> R { - return body(self.image) - } - deinit { guard let ptr else { return } g_object_unref(ptr) } + public func withVipsImage(_ body: (UnsafeMutablePointer) -> R) -> R { + return body(self.image) + } + /// This function creates a VIPSImage from a memory area. /// The memory area must be a simple array, for example RGBRGBRGB, left-to-right, top-to-bottom. /// The memory will be copied into the image. @@ -945,12 +945,9 @@ public final class VIPSImage: VIPSImageProtocol { } } - /*func withUnsafeMutablePointer(_ block: (inout UnsafeMutablePointer) throws -> (T)) - rethrows -> T - { - var ptr = self.image! - return try block(&ptr) - }*/ +} + +extension VIPSImageProtocol where Self: ~Copyable, Self: ~Escapable { @usableFromInline static func call( @@ -980,7 +977,7 @@ public final class VIPSImage: VIPSImageProtocol { } } -extension VIPSImageProtocol { +extension VIPSImageProtocol where Self: ~Copyable { @usableFromInline init(_ block: (inout UnsafeMutablePointer?) throws -> Void) rethrows { let image: UnsafeMutablePointer?> = .allocate(capacity: 1) @@ -994,9 +991,9 @@ extension VIPSImageProtocol { } } -extension VIPSImageProtocol { - public func new(_ colors: [Double]) throws -> VIPSImage { - try VIPSImage { out in +extension VIPSImageProtocol where Self: ~Copyable { + public func new(_ colors: [Double]) throws -> Self { + try Self { out in var c = colors out = vips_image_new_from_image(self.image, &c, Int32(c.count)) if out == nil { throw VIPSError() } @@ -1010,7 +1007,7 @@ extension VIPSImageProtocol { /// - data: Array of doubles, must have width * height elements /// - Returns: A new VIPSImage matrix /// - Note: The data is copied by libvips, so the input array does not need to remain alive - public static func matrix(width: Int, height: Int, data: [Double]) throws -> VIPSImage { + public static func matrix(width: Int, height: Int, data: [Double]) throws -> Self { guard data.count == width * height else { throw VIPSError( "Data array size (\(data.count)) must match width * height (\(width * height))" @@ -1032,7 +1029,7 @@ extension VIPSImageProtocol { throw VIPSError() } - return VIPSImage(image) + return Self(image) } /// Create an empty matrix image @@ -1040,12 +1037,12 @@ extension VIPSImageProtocol { /// - width: Width of the matrix /// - height: Height of the matrix /// - Returns: A new VIPSImage matrix filled with zeros - public static func matrix(width: Int, height: Int) throws -> VIPSImage { + public static func matrix(width: Int, height: Int) throws -> Self { guard let image = vips_image_new_matrix(Int32(width), Int32(height)) else { throw VIPSError() } - return VIPSImage(image) + return Self(image) } /// This function allocates memory, renders image into it, builds a new image @@ -1053,8 +1050,8 @@ extension VIPSImageProtocol { /// /// If the image is already a simple area of memory, it just refs image and /// returns it. - public func copyMemory() throws -> VIPSImage { - return try VIPSImage { out in + public func copyMemory() throws -> Self { + return try Self { out in out = vips_image_copy_memory(self.image) if out == nil { throw VIPSError() } } @@ -1068,6 +1065,10 @@ public protocol VIPSImageProtocol: VIPSObjectProtocol, ~Escapable, ~Copyable { } extension VIPSImageProtocol where Self: ~Copyable, Self: ~Escapable { + public func withVipsImage(_ body: (UnsafeMutablePointer) throws -> R) rethrows -> R { + try body(self.image) + } + @inlinable public var width: Int { return Int(vips_image_get_width(self.image)) diff --git a/Sources/VIPS/Core/VIPSOption.swift b/Sources/VIPS/Core/VIPSOption.swift index a8653e7..43746b2 100644 --- a/Sources/VIPS/Core/VIPSOption.swift +++ b/Sources/VIPS/Core/VIPSOption.swift @@ -29,7 +29,7 @@ public struct VIPSOption { self.pairs.append(pair) } - public mutating func set(_ name: String, value: some VIPSObjectProtocol) { + public mutating func set(_ name: String, value: borrowing Value) where Value: ~Copyable { let pair = Pair(name: name, input: true) g_value_init(&pair.value, value.type) g_value_set_object(&pair.value, value.object) @@ -95,7 +95,7 @@ public struct VIPSOption { self.pairs.append(pair) } - public mutating func set(_ name: String, value: VIPSImage) { + public mutating func set(_ name: String, value: some VIPSImageProtocol) { set(name, value: value.image) } @@ -110,7 +110,7 @@ public struct VIPSOption { set(name, value: value.target) } - public mutating func set(_ name: String, value: [VIPSImage]) { + public mutating func set(_ name: String, value: [some VIPSImage]) { let pair = Pair(name: name, input: true) g_value_init(&pair.value, vips_array_image_get_type()) vips_value_set_array_image(&pair.value, Int32(value.count)) diff --git a/Sources/VIPS/Generated/Foreign/foreign_gif.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_gif.generated.swift index 6157f96..5caa2ce 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_gif.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_gif.generated.swift @@ -8,185 +8,7 @@ import Cvips import CvipsShim -extension VIPSImage { - - /// Load gif with libnsgif - /// - /// - Parameters: - /// - filename: Filename to load from - /// - n: Number of pages to load, -1 for all - /// - page: First page to load - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func gifload( - filename: String, - n: Int? = nil, - page: Int? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("filename", value: filename) - if let n = n { - opt.set("n", value: n) - } - if let page = page { - opt.set("page", value: page) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("gifload", options: &opt) - } - } - - /// Load gif with libnsgif - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - n: Number of pages to load, -1 for all - /// - page: First page to load - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func gifload( - buffer: VIPSBlob, - n: Int? = nil, - page: Int? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - // the operation will retain the blob - try buffer.withVipsBlob { blob in - try VIPSImage { out in - var opt = VIPSOption() - - opt.set("buffer", value: blob) - if let n = n { - opt.set("n", value: n) - } - if let page = page { - opt.set("page", value: page) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("gifload_buffer", options: &opt) - } - } - } - - /// Load gif with libnsgif without copying the data. The caller must ensure the buffer remains valid for - /// the lifetime of the returned image and all its descendants. - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - n: Number of pages to load, -1 for all - /// - page: First page to load - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func gifload( - unsafeBuffer buffer: UnsafeRawBufferPointer, - n: Int? = nil, - page: Int? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - let blob = VIPSBlob(noCopy: buffer) - return try gifload( - buffer: blob, - n: n, - page: page, - memory: memory, - access: access, - failOn: failOn, - revalidate: revalidate - ) - } - - /// Load gif from source - /// - /// - Parameters: - /// - source: Source to load from - /// - n: Number of pages to load, -1 for all - /// - page: First page to load - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func gifload( - source: VIPSSource, - n: Int? = nil, - page: Int? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("source", value: source) - if let n = n { - opt.set("n", value: n) - } - if let page = page { - opt.set("page", value: page) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("gifload_source", options: &opt) - } - } +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Save as gif /// @@ -260,7 +82,7 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("gifsave", options: &opt) + try Self.call("gifsave", options: &opt) } /// Save as gif @@ -339,7 +161,7 @@ extension VIPSImage { } opt.set("buffer", value: out) - try VIPSImage.call("gifsave_buffer", options: &opt) + try Self.call("gifsave_buffer", options: &opt) guard let vipsBlob = out.pointee else { throw VIPSError("Failed to get buffer from gifsave_buffer") @@ -420,7 +242,189 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("gifsave_target", options: &opt) + try Self.call("gifsave_target", options: &opt) + } + +} + +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { + + /// Load gif with libnsgif + /// + /// - Parameters: + /// - filename: Filename to load from + /// - n: Number of pages to load, -1 for all + /// - page: First page to load + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func gifload( + filename: String, + n: Int? = nil, + page: Int? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("filename", value: filename) + if let n = n { + opt.set("n", value: n) + } + if let page = page { + opt.set("page", value: page) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("gifload", options: &opt) + } + } + + /// Load gif with libnsgif + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - n: Number of pages to load, -1 for all + /// - page: First page to load + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func gifload( + buffer: VIPSBlob, + n: Int? = nil, + page: Int? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + // the operation will retain the blob + try buffer.withVipsBlob { blob in + try Self { out in + var opt = VIPSOption() + + opt.set("buffer", value: blob) + if let n = n { + opt.set("n", value: n) + } + if let page = page { + opt.set("page", value: page) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("gifload_buffer", options: &opt) + } + } + } + + /// Load gif with libnsgif without copying the data. The caller must ensure the buffer remains valid for + /// the lifetime of the returned image and all its descendants. + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - n: Number of pages to load, -1 for all + /// - page: First page to load + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func gifload( + unsafeBuffer buffer: UnsafeRawBufferPointer, + n: Int? = nil, + page: Int? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + let blob = VIPSBlob(noCopy: buffer) + return try gifload( + buffer: blob, + n: n, + page: page, + memory: memory, + access: access, + failOn: failOn, + revalidate: revalidate + ) + } + + /// Load gif from source + /// + /// - Parameters: + /// - source: Source to load from + /// - n: Number of pages to load, -1 for all + /// - page: First page to load + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func gifload( + source: VIPSSource, + n: Int? = nil, + page: Int? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("source", value: source) + if let n = n { + opt.set("n", value: n) + } + if let page = page { + opt.set("page", value: page) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("gifload_source", options: &opt) + } } } diff --git a/Sources/VIPS/Generated/Foreign/foreign_heif.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_heif.generated.swift index 5473f1f..035fe8a 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_heif.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_heif.generated.swift @@ -8,221 +8,7 @@ import Cvips import CvipsShim -extension VIPSImage { - - /// Load a heif image - /// - /// - Parameters: - /// - filename: Filename to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - thumbnail: Fetch thumbnail image - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func heifload( - filename: String, - page: Int? = nil, - n: Int? = nil, - thumbnail: Bool? = nil, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("filename", value: filename) - if let page = page { - opt.set("page", value: page) - } - if let n = n { - opt.set("n", value: n) - } - if let thumbnail = thumbnail { - opt.set("thumbnail", value: thumbnail) - } - if let unlimited = unlimited { - opt.set("unlimited", value: unlimited) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("heifload", options: &opt) - } - } - - /// Load a heif image - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - thumbnail: Fetch thumbnail image - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func heifload( - buffer: VIPSBlob, - page: Int? = nil, - n: Int? = nil, - thumbnail: Bool? = nil, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - // the operation will retain the blob - try buffer.withVipsBlob { blob in - try VIPSImage { out in - var opt = VIPSOption() - - opt.set("buffer", value: blob) - if let page = page { - opt.set("page", value: page) - } - if let n = n { - opt.set("n", value: n) - } - if let thumbnail = thumbnail { - opt.set("thumbnail", value: thumbnail) - } - if let unlimited = unlimited { - opt.set("unlimited", value: unlimited) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("heifload_buffer", options: &opt) - } - } - } - - /// Load a heif image without copying the data. The caller must ensure the buffer remains valid for - /// the lifetime of the returned image and all its descendants. - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - thumbnail: Fetch thumbnail image - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func heifload( - unsafeBuffer buffer: UnsafeRawBufferPointer, - page: Int? = nil, - n: Int? = nil, - thumbnail: Bool? = nil, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - let blob = VIPSBlob(noCopy: buffer) - return try heifload( - buffer: blob, - page: page, - n: n, - thumbnail: thumbnail, - unlimited: unlimited, - memory: memory, - access: access, - failOn: failOn, - revalidate: revalidate - ) - } - - /// Load a heif image - /// - /// - Parameters: - /// - source: Source to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - thumbnail: Fetch thumbnail image - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func heifload( - source: VIPSSource, - page: Int? = nil, - n: Int? = nil, - thumbnail: Bool? = nil, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("source", value: source) - if let page = page { - opt.set("page", value: page) - } - if let n = n { - opt.set("n", value: n) - } - if let thumbnail = thumbnail { - opt.set("thumbnail", value: thumbnail) - } - if let unlimited = unlimited { - opt.set("unlimited", value: unlimited) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("heifload_source", options: &opt) - } - } +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Save image in heif format /// @@ -291,7 +77,7 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("heifsave", options: &opt) + try Self.call("heifsave", options: &opt) } /// Save image in heif format @@ -365,7 +151,7 @@ extension VIPSImage { } opt.set("buffer", value: out) - try VIPSImage.call("heifsave_buffer", options: &opt) + try Self.call("heifsave_buffer", options: &opt) guard let vipsBlob = out.pointee else { throw VIPSError("Failed to get buffer from heifsave_buffer") @@ -441,7 +227,225 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("heifsave_target", options: &opt) + try Self.call("heifsave_target", options: &opt) + } + +} + +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { + + /// Load a heif image + /// + /// - Parameters: + /// - filename: Filename to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - thumbnail: Fetch thumbnail image + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func heifload( + filename: String, + page: Int? = nil, + n: Int? = nil, + thumbnail: Bool? = nil, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("filename", value: filename) + if let page = page { + opt.set("page", value: page) + } + if let n = n { + opt.set("n", value: n) + } + if let thumbnail = thumbnail { + opt.set("thumbnail", value: thumbnail) + } + if let unlimited = unlimited { + opt.set("unlimited", value: unlimited) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("heifload", options: &opt) + } + } + + /// Load a heif image + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - thumbnail: Fetch thumbnail image + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func heifload( + buffer: VIPSBlob, + page: Int? = nil, + n: Int? = nil, + thumbnail: Bool? = nil, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + // the operation will retain the blob + try buffer.withVipsBlob { blob in + try Self { out in + var opt = VIPSOption() + + opt.set("buffer", value: blob) + if let page = page { + opt.set("page", value: page) + } + if let n = n { + opt.set("n", value: n) + } + if let thumbnail = thumbnail { + opt.set("thumbnail", value: thumbnail) + } + if let unlimited = unlimited { + opt.set("unlimited", value: unlimited) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("heifload_buffer", options: &opt) + } + } + } + + /// Load a heif image without copying the data. The caller must ensure the buffer remains valid for + /// the lifetime of the returned image and all its descendants. + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - thumbnail: Fetch thumbnail image + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func heifload( + unsafeBuffer buffer: UnsafeRawBufferPointer, + page: Int? = nil, + n: Int? = nil, + thumbnail: Bool? = nil, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + let blob = VIPSBlob(noCopy: buffer) + return try heifload( + buffer: blob, + page: page, + n: n, + thumbnail: thumbnail, + unlimited: unlimited, + memory: memory, + access: access, + failOn: failOn, + revalidate: revalidate + ) + } + + /// Load a heif image + /// + /// - Parameters: + /// - source: Source to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - thumbnail: Fetch thumbnail image + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func heifload( + source: VIPSSource, + page: Int? = nil, + n: Int? = nil, + thumbnail: Bool? = nil, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("source", value: source) + if let page = page { + opt.set("page", value: page) + } + if let n = n { + opt.set("n", value: n) + } + if let thumbnail = thumbnail { + opt.set("thumbnail", value: thumbnail) + } + if let unlimited = unlimited { + opt.set("unlimited", value: unlimited) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("heifload_source", options: &opt) + } } } diff --git a/Sources/VIPS/Generated/Foreign/foreign_jpeg.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_jpeg.generated.swift index 4da322c..1d911b0 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_jpeg.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_jpeg.generated.swift @@ -8,203 +8,7 @@ import Cvips import CvipsShim -extension VIPSImage { - - /// Load jpeg from file - /// - /// - Parameters: - /// - filename: Filename to load from - /// - shrink: Shrink factor on load - /// - autorotate: Rotate image using exif orientation - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func jpegload( - filename: String, - shrink: Int? = nil, - autorotate: Bool? = nil, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("filename", value: filename) - if let shrink = shrink { - opt.set("shrink", value: shrink) - } - if let autorotate = autorotate { - opt.set("autorotate", value: autorotate) - } - if let unlimited = unlimited { - opt.set("unlimited", value: unlimited) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("jpegload", options: &opt) - } - } - - /// Load jpeg from buffer - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - shrink: Shrink factor on load - /// - autorotate: Rotate image using exif orientation - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func jpegload( - buffer: VIPSBlob, - shrink: Int? = nil, - autorotate: Bool? = nil, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - // the operation will retain the blob - try buffer.withVipsBlob { blob in - try VIPSImage { out in - var opt = VIPSOption() - - opt.set("buffer", value: blob) - if let shrink = shrink { - opt.set("shrink", value: shrink) - } - if let autorotate = autorotate { - opt.set("autorotate", value: autorotate) - } - if let unlimited = unlimited { - opt.set("unlimited", value: unlimited) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("jpegload_buffer", options: &opt) - } - } - } - - /// Load jpeg from buffer without copying the data. The caller must ensure the buffer remains valid for - /// the lifetime of the returned image and all its descendants. - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - shrink: Shrink factor on load - /// - autorotate: Rotate image using exif orientation - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func jpegload( - unsafeBuffer buffer: UnsafeRawBufferPointer, - shrink: Int? = nil, - autorotate: Bool? = nil, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - let blob = VIPSBlob(noCopy: buffer) - return try jpegload( - buffer: blob, - shrink: shrink, - autorotate: autorotate, - unlimited: unlimited, - memory: memory, - access: access, - failOn: failOn, - revalidate: revalidate - ) - } - - /// Load image from jpeg source - /// - /// - Parameters: - /// - source: Source to load from - /// - shrink: Shrink factor on load - /// - autorotate: Rotate image using exif orientation - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func jpegload( - source: VIPSSource, - shrink: Int? = nil, - autorotate: Bool? = nil, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("source", value: source) - if let shrink = shrink { - opt.set("shrink", value: shrink) - } - if let autorotate = autorotate { - opt.set("autorotate", value: autorotate) - } - if let unlimited = unlimited { - opt.set("unlimited", value: unlimited) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("jpegload_source", options: &opt) - } - } +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Save image to jpeg file /// @@ -283,7 +87,7 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("jpegsave", options: &opt) + try Self.call("jpegsave", options: &opt) } /// Save image to jpeg buffer @@ -367,7 +171,7 @@ extension VIPSImage { } opt.set("buffer", value: out) - try VIPSImage.call("jpegsave_buffer", options: &opt) + try Self.call("jpegsave_buffer", options: &opt) guard let vipsBlob = out.pointee else { throw VIPSError("Failed to get buffer from jpegsave_buffer") @@ -450,7 +254,7 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("jpegsave_mime", options: &opt) + try Self.call("jpegsave_mime", options: &opt) } /// Save image to jpeg target @@ -530,7 +334,207 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("jpegsave_target", options: &opt) + try Self.call("jpegsave_target", options: &opt) + } + +} + +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { + + /// Load jpeg from file + /// + /// - Parameters: + /// - filename: Filename to load from + /// - shrink: Shrink factor on load + /// - autorotate: Rotate image using exif orientation + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func jpegload( + filename: String, + shrink: Int? = nil, + autorotate: Bool? = nil, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("filename", value: filename) + if let shrink = shrink { + opt.set("shrink", value: shrink) + } + if let autorotate = autorotate { + opt.set("autorotate", value: autorotate) + } + if let unlimited = unlimited { + opt.set("unlimited", value: unlimited) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("jpegload", options: &opt) + } + } + + /// Load jpeg from buffer + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - shrink: Shrink factor on load + /// - autorotate: Rotate image using exif orientation + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func jpegload( + buffer: VIPSBlob, + shrink: Int? = nil, + autorotate: Bool? = nil, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + // the operation will retain the blob + try buffer.withVipsBlob { blob in + try Self { out in + var opt = VIPSOption() + + opt.set("buffer", value: blob) + if let shrink = shrink { + opt.set("shrink", value: shrink) + } + if let autorotate = autorotate { + opt.set("autorotate", value: autorotate) + } + if let unlimited = unlimited { + opt.set("unlimited", value: unlimited) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("jpegload_buffer", options: &opt) + } + } + } + + /// Load jpeg from buffer without copying the data. The caller must ensure the buffer remains valid for + /// the lifetime of the returned image and all its descendants. + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - shrink: Shrink factor on load + /// - autorotate: Rotate image using exif orientation + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func jpegload( + unsafeBuffer buffer: UnsafeRawBufferPointer, + shrink: Int? = nil, + autorotate: Bool? = nil, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + let blob = VIPSBlob(noCopy: buffer) + return try jpegload( + buffer: blob, + shrink: shrink, + autorotate: autorotate, + unlimited: unlimited, + memory: memory, + access: access, + failOn: failOn, + revalidate: revalidate + ) + } + + /// Load image from jpeg source + /// + /// - Parameters: + /// - source: Source to load from + /// - shrink: Shrink factor on load + /// - autorotate: Rotate image using exif orientation + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func jpegload( + source: VIPSSource, + shrink: Int? = nil, + autorotate: Bool? = nil, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("source", value: source) + if let shrink = shrink { + opt.set("shrink", value: shrink) + } + if let autorotate = autorotate { + opt.set("autorotate", value: autorotate) + } + if let unlimited = unlimited { + opt.set("unlimited", value: unlimited) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("jpegload_source", options: &opt) + } } } diff --git a/Sources/VIPS/Generated/Foreign/foreign_other.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_other.generated.swift index 0ab7beb..4d9694d 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_other.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_other.generated.swift @@ -8,158 +8,7 @@ import Cvips import CvipsShim -extension VIPSImage { - - /// Load an analyze6 image - /// - /// - Parameters: - /// - filename: Filename to load from - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func analyzeload( - filename: String, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("filename", value: filename) - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("analyzeload", options: &opt) - } - } - - /// Load csv - /// - /// - Parameters: - /// - filename: Filename to load from - /// - skip: Skip this many lines at the start of the file - /// - lines: Read this many lines from the file - /// - whitespace: Set of whitespace characters - /// - separator: Set of separator characters - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func csvload( - filename: String, - skip: Int? = nil, - lines: Int? = nil, - whitespace: String? = nil, - separator: String? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("filename", value: filename) - if let skip = skip { - opt.set("skip", value: skip) - } - if let lines = lines { - opt.set("lines", value: lines) - } - if let whitespace = whitespace { - opt.set("whitespace", value: whitespace) - } - if let separator = separator { - opt.set("separator", value: separator) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("csvload", options: &opt) - } - } - - /// Load csv - /// - /// - Parameters: - /// - source: Source to load from - /// - skip: Skip this many lines at the start of the file - /// - lines: Read this many lines from the file - /// - whitespace: Set of whitespace characters - /// - separator: Set of separator characters - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func csvload( - source: VIPSSource, - skip: Int? = nil, - lines: Int? = nil, - whitespace: String? = nil, - separator: String? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("source", value: source) - if let skip = skip { - opt.set("skip", value: skip) - } - if let lines = lines { - opt.set("lines", value: lines) - } - if let whitespace = whitespace { - opt.set("whitespace", value: whitespace) - } - if let separator = separator { - opt.set("separator", value: separator) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("csvload_source", options: &opt) - } - } +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Save image to csv /// @@ -198,7 +47,7 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("csvsave", options: &opt) + try Self.call("csvsave", options: &opt) } /// Save image to csv @@ -238,7 +87,7 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("csvsave_target", options: &opt) + try Self.call("csvsave_target", options: &opt) } /// Save image to deepzoom file @@ -343,7 +192,7 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("dzsave", options: &opt) + try Self.call("dzsave", options: &opt) } /// Save image to dz buffer @@ -452,7 +301,7 @@ extension VIPSImage { } opt.set("buffer", value: out) - try VIPSImage.call("dzsave_buffer", options: &opt) + try Self.call("dzsave_buffer", options: &opt) guard let vipsBlob = out.pointee else { throw VIPSError("Failed to get buffer from dzsave_buffer") @@ -563,81 +412,7 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("dzsave_target", options: &opt) - } - - /// Load a fits image - /// - /// - Parameters: - /// - filename: Filename to load from - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func fitsload( - filename: String, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("filename", value: filename) - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("fitsload", options: &opt) - } - } - - /// Load fits from a source - /// - /// - Parameters: - /// - source: Source to load from - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func fitsload( - source: VIPSSource, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("source", value: source) - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("fitsload_source", options: &opt) - } + try Self.call("dzsave_target", options: &opt) } /// Save image to fits file @@ -672,213 +447,35 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("fitssave", options: &opt) + try Self.call("fitssave", options: &opt) } - /// Load jpeg2000 image + /// Save image in jpeg2000 format /// /// - Parameters: - /// - filename: Filename to load from - /// - page: Load this page from the image - /// - oneshot: Load images a frame at a time - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func jp2kload( + /// - filename: Filename to save to + /// - tileWidth: Tile width in pixels + /// - tileHeight: Tile height in pixels + /// - lossless: Enable lossless compression + /// - quality: Q factor + /// - subsampleMode: Select chroma subsample operation mode + /// - keep: Which metadata to retain + /// - background: Background value + /// - pageHeight: Set page height for multipage save + /// - profile: Filename of ICC profile to embed + public func jp2ksave( filename: String, - page: Int? = nil, - oneshot: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("filename", value: filename) - if let page = page { - opt.set("page", value: page) - } - if let oneshot = oneshot { - opt.set("oneshot", value: oneshot) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("jp2kload", options: &opt) - } - } - - /// Load jpeg2000 image - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - page: Load this page from the image - /// - oneshot: Load images a frame at a time - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func jp2kload( - buffer: VIPSBlob, - page: Int? = nil, - oneshot: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - // the operation will retain the blob - try buffer.withVipsBlob { blob in - try VIPSImage { out in - var opt = VIPSOption() - - opt.set("buffer", value: blob) - if let page = page { - opt.set("page", value: page) - } - if let oneshot = oneshot { - opt.set("oneshot", value: oneshot) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("jp2kload_buffer", options: &opt) - } - } - } - - /// Load jpeg2000 image without copying the data. The caller must ensure the buffer remains valid for - /// the lifetime of the returned image and all its descendants. - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - page: Load this page from the image - /// - oneshot: Load images a frame at a time - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func jp2kload( - unsafeBuffer buffer: UnsafeRawBufferPointer, - page: Int? = nil, - oneshot: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - let blob = VIPSBlob(noCopy: buffer) - return try jp2kload( - buffer: blob, - page: page, - oneshot: oneshot, - memory: memory, - access: access, - failOn: failOn, - revalidate: revalidate - ) - } - - /// Load jpeg2000 image - /// - /// - Parameters: - /// - source: Source to load from - /// - page: Load this page from the image - /// - oneshot: Load images a frame at a time - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func jp2kload( - source: VIPSSource, - page: Int? = nil, - oneshot: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("source", value: source) - if let page = page { - opt.set("page", value: page) - } - if let oneshot = oneshot { - opt.set("oneshot", value: oneshot) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("jp2kload_source", options: &opt) - } - } - - /// Save image in jpeg2000 format - /// - /// - Parameters: - /// - filename: Filename to save to - /// - tileWidth: Tile width in pixels - /// - tileHeight: Tile height in pixels - /// - lossless: Enable lossless compression - /// - quality: Q factor - /// - subsampleMode: Select chroma subsample operation mode - /// - keep: Which metadata to retain - /// - background: Background value - /// - pageHeight: Set page height for multipage save - /// - profile: Filename of ICC profile to embed - public func jp2ksave( - filename: String, - tileWidth: Int? = nil, - tileHeight: Int? = nil, - lossless: Bool? = nil, - quality: Int? = nil, - subsampleMode: VipsForeignSubsample? = nil, - keep: VipsForeignKeep? = nil, - background: [Double]? = nil, - pageHeight: Int? = nil, - profile: String? = nil - ) throws { - var opt = VIPSOption() + tileWidth: Int? = nil, + tileHeight: Int? = nil, + lossless: Bool? = nil, + quality: Int? = nil, + subsampleMode: VipsForeignSubsample? = nil, + keep: VipsForeignKeep? = nil, + background: [Double]? = nil, + pageHeight: Int? = nil, + profile: String? = nil + ) throws { + var opt = VIPSOption() opt.set("in", value: self) opt.set("filename", value: filename) @@ -910,7 +507,7 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("jp2ksave", options: &opt) + try Self.call("jp2ksave", options: &opt) } /// Save image in jpeg2000 format @@ -974,7 +571,7 @@ extension VIPSImage { } opt.set("buffer", value: out) - try VIPSImage.call("jp2ksave_buffer", options: &opt) + try Self.call("jp2ksave_buffer", options: &opt) guard let vipsBlob = out.pointee else { throw VIPSError("Failed to get buffer from jp2ksave_buffer") @@ -1040,218 +637,40 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("jp2ksave_target", options: &opt) + try Self.call("jp2ksave_target", options: &opt) } - /// Load jpeg-xl image + /// Save image in jpeg-xl format /// /// - Parameters: - /// - filename: Filename to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func jxlload( + /// - filename: Filename to save to + /// - tier: Decode speed tier + /// - distance: Target butteraugli distance + /// - effort: Encoding effort + /// - lossless: Enable lossless compression + /// - quality: Quality factor + /// - keep: Which metadata to retain + /// - background: Background value + /// - pageHeight: Set page height for multipage save + /// - profile: Filename of ICC profile to embed + public func jxlsave( filename: String, - page: Int? = nil, - n: Int? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("filename", value: filename) - if let page = page { - opt.set("page", value: page) - } - if let n = n { - opt.set("n", value: n) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) + tier: Int? = nil, + distance: Double? = nil, + effort: Int? = nil, + lossless: Bool? = nil, + quality: Int? = nil, + keep: VipsForeignKeep? = nil, + background: [Double]? = nil, + pageHeight: Int? = nil, + profile: String? = nil + ) throws { + var opt = VIPSOption() - try VIPSImage.call("jxlload", options: &opt) - } - } - - /// Load jpeg-xl image - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func jxlload( - buffer: VIPSBlob, - page: Int? = nil, - n: Int? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - // the operation will retain the blob - try buffer.withVipsBlob { blob in - try VIPSImage { out in - var opt = VIPSOption() - - opt.set("buffer", value: blob) - if let page = page { - opt.set("page", value: page) - } - if let n = n { - opt.set("n", value: n) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("jxlload_buffer", options: &opt) - } - } - } - - /// Load jpeg-xl image without copying the data. The caller must ensure the buffer remains valid for - /// the lifetime of the returned image and all its descendants. - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func jxlload( - unsafeBuffer buffer: UnsafeRawBufferPointer, - page: Int? = nil, - n: Int? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - let blob = VIPSBlob(noCopy: buffer) - return try jxlload( - buffer: blob, - page: page, - n: n, - memory: memory, - access: access, - failOn: failOn, - revalidate: revalidate - ) - } - - /// Load jpeg-xl image - /// - /// - Parameters: - /// - source: Source to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func jxlload( - source: VIPSSource, - page: Int? = nil, - n: Int? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("source", value: source) - if let page = page { - opt.set("page", value: page) - } - if let n = n { - opt.set("n", value: n) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("jxlload_source", options: &opt) - } - } - - /// Save image in jpeg-xl format - /// - /// - Parameters: - /// - filename: Filename to save to - /// - tier: Decode speed tier - /// - distance: Target butteraugli distance - /// - effort: Encoding effort - /// - lossless: Enable lossless compression - /// - quality: Quality factor - /// - keep: Which metadata to retain - /// - background: Background value - /// - pageHeight: Set page height for multipage save - /// - profile: Filename of ICC profile to embed - public func jxlsave( - filename: String, - tier: Int? = nil, - distance: Double? = nil, - effort: Int? = nil, - lossless: Bool? = nil, - quality: Int? = nil, - keep: VipsForeignKeep? = nil, - background: [Double]? = nil, - pageHeight: Int? = nil, - profile: String? = nil - ) throws { - var opt = VIPSOption() - - opt.set("in", value: self) - opt.set("filename", value: filename) - if let tier = tier { - opt.set("tier", value: tier) + opt.set("in", value: self) + opt.set("filename", value: filename) + if let tier = tier { + opt.set("tier", value: tier) } if let distance = distance { opt.set("distance", value: distance) @@ -1278,7 +697,7 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("jxlsave", options: &opt) + try Self.call("jxlsave", options: &opt) } /// Save image in jpeg-xl format @@ -1342,7 +761,7 @@ extension VIPSImage { } opt.set("buffer", value: out) - try VIPSImage.call("jxlsave_buffer", options: &opt) + try Self.call("jxlsave_buffer", options: &opt) guard let vipsBlob = out.pointee else { throw VIPSError("Failed to get buffer from jxlsave_buffer") @@ -1408,214 +827,70 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("jxlsave_target", options: &opt) + try Self.call("jxlsave_target", options: &opt) } - /// Load file with imagemagick7 + /// Save file with imagemagick /// /// - Parameters: - /// - filename: Filename to load from - /// - density: Canvas resolution for rendering vector formats like SVG - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func magickload( + /// - filename: Filename to save to + /// - format: Format to save in + /// - quality: Quality to use + /// - optimizeGifFrames: Apply GIF frames optimization + /// - optimizeGifTransparency: Apply GIF transparency optimization + /// - bitdepth: Number of bits per pixel + /// - keep: Which metadata to retain + /// - background: Background value + /// - pageHeight: Set page height for multipage save + /// - profile: Filename of ICC profile to embed + public func magicksave( filename: String, - density: String? = nil, - page: Int? = nil, - n: Int? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("filename", value: filename) - if let density = density { - opt.set("density", value: density) - } - if let page = page { - opt.set("page", value: page) - } - if let n = n { - opt.set("n", value: n) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) + format: String? = nil, + quality: Int? = nil, + optimizeGifFrames: Bool? = nil, + optimizeGifTransparency: Bool? = nil, + bitdepth: Int? = nil, + keep: VipsForeignKeep? = nil, + background: [Double]? = nil, + pageHeight: Int? = nil, + profile: String? = nil + ) throws { + var opt = VIPSOption() - try VIPSImage.call("magickload", options: &opt) + opt.set("in", value: self) + opt.set("filename", value: filename) + if let format = format { + opt.set("format", value: format) + } + if let quality = quality { + opt.set("quality", value: quality) + } + if let optimizeGifFrames = optimizeGifFrames { + opt.set("optimize_gif_frames", value: optimizeGifFrames) + } + if let optimizeGifTransparency = optimizeGifTransparency { + opt.set("optimize_gif_transparency", value: optimizeGifTransparency) + } + if let bitdepth = bitdepth { + opt.set("bitdepth", value: bitdepth) + } + if let keep = keep { + opt.set("keep", value: keep) + } + if let background = background { + opt.set("background", value: background) + } + if let pageHeight = pageHeight { + opt.set("page_height", value: pageHeight) } + if let profile = profile { + opt.set("profile", value: profile) + } + + try Self.call("magicksave", options: &opt) } - /// Load buffer with imagemagick7 - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - density: Canvas resolution for rendering vector formats like SVG - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func magickload( - buffer: VIPSBlob, - density: String? = nil, - page: Int? = nil, - n: Int? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - // the operation will retain the blob - try buffer.withVipsBlob { blob in - try VIPSImage { out in - var opt = VIPSOption() - - opt.set("buffer", value: blob) - if let density = density { - opt.set("density", value: density) - } - if let page = page { - opt.set("page", value: page) - } - if let n = n { - opt.set("n", value: n) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("magickload_buffer", options: &opt) - } - } - } - - /// Load buffer with imagemagick7 without copying the data. The caller must ensure the buffer remains valid for - /// the lifetime of the returned image and all its descendants. - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - density: Canvas resolution for rendering vector formats like SVG - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func magickload( - unsafeBuffer buffer: UnsafeRawBufferPointer, - density: String? = nil, - page: Int? = nil, - n: Int? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - let blob = VIPSBlob(noCopy: buffer) - return try magickload( - buffer: blob, - density: density, - page: page, - n: n, - memory: memory, - access: access, - failOn: failOn, - revalidate: revalidate - ) - } - - /// Save file with imagemagick - /// - /// - Parameters: - /// - filename: Filename to save to - /// - format: Format to save in - /// - quality: Quality to use - /// - optimizeGifFrames: Apply GIF frames optimization - /// - optimizeGifTransparency: Apply GIF transparency optimization - /// - bitdepth: Number of bits per pixel - /// - keep: Which metadata to retain - /// - background: Background value - /// - pageHeight: Set page height for multipage save - /// - profile: Filename of ICC profile to embed - public func magicksave( - filename: String, - format: String? = nil, - quality: Int? = nil, - optimizeGifFrames: Bool? = nil, - optimizeGifTransparency: Bool? = nil, - bitdepth: Int? = nil, - keep: VipsForeignKeep? = nil, - background: [Double]? = nil, - pageHeight: Int? = nil, - profile: String? = nil - ) throws { - var opt = VIPSOption() - - opt.set("in", value: self) - opt.set("filename", value: filename) - if let format = format { - opt.set("format", value: format) - } - if let quality = quality { - opt.set("quality", value: quality) - } - if let optimizeGifFrames = optimizeGifFrames { - opt.set("optimize_gif_frames", value: optimizeGifFrames) - } - if let optimizeGifTransparency = optimizeGifTransparency { - opt.set("optimize_gif_transparency", value: optimizeGifTransparency) - } - if let bitdepth = bitdepth { - opt.set("bitdepth", value: bitdepth) - } - if let keep = keep { - opt.set("keep", value: keep) - } - if let background = background { - opt.set("background", value: background) - } - if let pageHeight = pageHeight { - opt.set("page_height", value: pageHeight) - } - if let profile = profile { - opt.set("profile", value: profile) - } - - try VIPSImage.call("magicksave", options: &opt) - } - - /// Save image to magick buffer + /// Save image to magick buffer /// /// - Parameters: /// - format: Format to save in @@ -1676,7 +951,7 @@ extension VIPSImage { } opt.set("buffer", value: out) - try VIPSImage.call("magicksave_buffer", options: &opt) + try Self.call("magicksave_buffer", options: &opt) guard let vipsBlob = out.pointee else { throw VIPSError("Failed to get buffer from magicksave_buffer") @@ -1685,117 +960,6 @@ extension VIPSImage { return VIPSBlob(vipsBlob) } - /// Load mat from file - /// - /// - Parameters: - /// - filename: Filename to load from - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func matload( - filename: String, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("filename", value: filename) - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("matload", options: &opt) - } - } - - /// Load matrix - /// - /// - Parameters: - /// - filename: Filename to load from - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func matrixload( - filename: String, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("filename", value: filename) - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("matrixload", options: &opt) - } - } - - /// Load matrix - /// - /// - Parameters: - /// - source: Source to load from - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func matrixload( - source: VIPSSource, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("source", value: source) - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("matrixload_source", options: &opt) - } - } - /// Save image to matrix /// /// - Parameters: @@ -1828,7 +992,7 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("matrixsave", options: &opt) + try Self.call("matrixsave", options: &opt) } /// Save image to matrix @@ -1863,29 +1027,1269 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("matrixsave_target", options: &opt) + try Self.call("matrixsave_target", options: &opt) } - /// Load an openexr image + /// Save image to ppm file /// /// - Parameters: - /// - filename: Filename to load from - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func openexrload( + /// - filename: Filename to save to + /// - format: Format to save in + /// - ascii: Save as ascii + /// - bitdepth: Set to 1 to write as a 1 bit image + /// - keep: Which metadata to retain + /// - background: Background value + /// - pageHeight: Set page height for multipage save + /// - profile: Filename of ICC profile to embed + public func ppmsave( filename: String, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() + format: VipsForeignPpmFormat? = nil, + ascii: Bool? = nil, + bitdepth: Int? = nil, + keep: VipsForeignKeep? = nil, + background: [Double]? = nil, + pageHeight: Int? = nil, + profile: String? = nil + ) throws { + var opt = VIPSOption() - opt.set("filename", value: filename) - if let memory = memory { + opt.set("in", value: self) + opt.set("filename", value: filename) + if let format = format { + opt.set("format", value: format) + } + if let ascii = ascii { + opt.set("ascii", value: ascii) + } + if let bitdepth = bitdepth { + opt.set("bitdepth", value: bitdepth) + } + if let keep = keep { + opt.set("keep", value: keep) + } + if let background = background { + opt.set("background", value: background) + } + if let pageHeight = pageHeight { + opt.set("page_height", value: pageHeight) + } + if let profile = profile { + opt.set("profile", value: profile) + } + + try Self.call("ppmsave", options: &opt) + } + + /// Save to ppm + /// + /// - Parameters: + /// - target: Target to save to + /// - format: Format to save in + /// - ascii: Save as ascii + /// - bitdepth: Set to 1 to write as a 1 bit image + /// - keep: Which metadata to retain + /// - background: Background value + /// - pageHeight: Set page height for multipage save + /// - profile: Filename of ICC profile to embed + public func ppmsave( + target: VIPSTarget, + format: VipsForeignPpmFormat? = nil, + ascii: Bool? = nil, + bitdepth: Int? = nil, + keep: VipsForeignKeep? = nil, + background: [Double]? = nil, + pageHeight: Int? = nil, + profile: String? = nil + ) throws { + var opt = VIPSOption() + + opt.set("in", value: self) + opt.set("target", value: target) + if let format = format { + opt.set("format", value: format) + } + if let ascii = ascii { + opt.set("ascii", value: ascii) + } + if let bitdepth = bitdepth { + opt.set("bitdepth", value: bitdepth) + } + if let keep = keep { + opt.set("keep", value: keep) + } + if let background = background { + opt.set("background", value: background) + } + if let pageHeight = pageHeight { + opt.set("page_height", value: pageHeight) + } + if let profile = profile { + opt.set("profile", value: profile) + } + + try Self.call("ppmsave_target", options: &opt) + } + + /// Save image to radiance file + /// + /// - Parameters: + /// - filename: Filename to save to + /// - keep: Which metadata to retain + /// - background: Background value + /// - pageHeight: Set page height for multipage save + /// - profile: Filename of ICC profile to embed + public func radsave( + filename: String, + keep: VipsForeignKeep? = nil, + background: [Double]? = nil, + pageHeight: Int? = nil, + profile: String? = nil + ) throws { + var opt = VIPSOption() + + opt.set("in", value: self) + opt.set("filename", value: filename) + if let keep = keep { + opt.set("keep", value: keep) + } + if let background = background { + opt.set("background", value: background) + } + if let pageHeight = pageHeight { + opt.set("page_height", value: pageHeight) + } + if let profile = profile { + opt.set("profile", value: profile) + } + + try Self.call("radsave", options: &opt) + } + + /// Save image to radiance buffer + /// + /// - Parameters: + /// - keep: Which metadata to retain + /// - background: Background value + /// - pageHeight: Set page height for multipage save + /// - profile: Filename of ICC profile to embed + public func radsave( + keep: VipsForeignKeep? = nil, + background: [Double]? = nil, + pageHeight: Int? = nil, + profile: String? = nil + ) throws -> VIPSBlob { + var opt = VIPSOption() + + let out: UnsafeMutablePointer?> = .allocate(capacity: 1) + out.initialize(to: nil) + defer { + out.deallocate() + } + + opt.set("in", value: self.image) + if let keep = keep { + opt.set("keep", value: keep) + } + if let background = background { + opt.set("background", value: background) + } + if let pageHeight = pageHeight { + opt.set("page_height", value: pageHeight) + } + if let profile = profile { + opt.set("profile", value: profile) + } + opt.set("buffer", value: out) + + try Self.call("radsave_buffer", options: &opt) + + guard let vipsBlob = out.pointee else { + throw VIPSError("Failed to get buffer from radsave_buffer") + } + + return VIPSBlob(vipsBlob) + } + + /// Save image to radiance target + /// + /// - Parameters: + /// - target: Target to save to + /// - keep: Which metadata to retain + /// - background: Background value + /// - pageHeight: Set page height for multipage save + /// - profile: Filename of ICC profile to embed + public func radsave( + target: VIPSTarget, + keep: VipsForeignKeep? = nil, + background: [Double]? = nil, + pageHeight: Int? = nil, + profile: String? = nil + ) throws { + var opt = VIPSOption() + + opt.set("in", value: self) + opt.set("target", value: target) + if let keep = keep { + opt.set("keep", value: keep) + } + if let background = background { + opt.set("background", value: background) + } + if let pageHeight = pageHeight { + opt.set("page_height", value: pageHeight) + } + if let profile = profile { + opt.set("profile", value: profile) + } + + try Self.call("radsave_target", options: &opt) + } + + /// Save image to raw file + /// + /// - Parameters: + /// - filename: Filename to save to + /// - keep: Which metadata to retain + /// - background: Background value + /// - pageHeight: Set page height for multipage save + /// - profile: Filename of ICC profile to embed + public func rawsave( + filename: String, + keep: VipsForeignKeep? = nil, + background: [Double]? = nil, + pageHeight: Int? = nil, + profile: String? = nil + ) throws { + var opt = VIPSOption() + + opt.set("in", value: self) + opt.set("filename", value: filename) + if let keep = keep { + opt.set("keep", value: keep) + } + if let background = background { + opt.set("background", value: background) + } + if let pageHeight = pageHeight { + opt.set("page_height", value: pageHeight) + } + if let profile = profile { + opt.set("profile", value: profile) + } + + try Self.call("rawsave", options: &opt) + } + + /// Write raw image to buffer + /// + /// - Parameters: + /// - keep: Which metadata to retain + /// - background: Background value + /// - pageHeight: Set page height for multipage save + /// - profile: Filename of ICC profile to embed + public func rawsave( + keep: VipsForeignKeep? = nil, + background: [Double]? = nil, + pageHeight: Int? = nil, + profile: String? = nil + ) throws -> VIPSBlob { + var opt = VIPSOption() + + let out: UnsafeMutablePointer?> = .allocate(capacity: 1) + out.initialize(to: nil) + defer { + out.deallocate() + } + + opt.set("in", value: self.image) + if let keep = keep { + opt.set("keep", value: keep) + } + if let background = background { + opt.set("background", value: background) + } + if let pageHeight = pageHeight { + opt.set("page_height", value: pageHeight) + } + if let profile = profile { + opt.set("profile", value: profile) + } + opt.set("buffer", value: out) + + try Self.call("rawsave_buffer", options: &opt) + + guard let vipsBlob = out.pointee else { + throw VIPSError("Failed to get buffer from rawsave_buffer") + } + + return VIPSBlob(vipsBlob) + } + + /// Write raw image to target + /// + /// - Parameters: + /// - target: Target to save to + /// - keep: Which metadata to retain + /// - background: Background value + /// - pageHeight: Set page height for multipage save + /// - profile: Filename of ICC profile to embed + public func rawsave( + target: VIPSTarget, + keep: VipsForeignKeep? = nil, + background: [Double]? = nil, + pageHeight: Int? = nil, + profile: String? = nil + ) throws { + var opt = VIPSOption() + + opt.set("in", value: self) + opt.set("target", value: target) + if let keep = keep { + opt.set("keep", value: keep) + } + if let background = background { + opt.set("background", value: background) + } + if let pageHeight = pageHeight { + opt.set("page_height", value: pageHeight) + } + if let profile = profile { + opt.set("profile", value: profile) + } + + try Self.call("rawsave_target", options: &opt) + } + + /// Save image to file in vips format + /// + /// - Parameters: + /// - filename: Filename to save to + /// - keep: Which metadata to retain + /// - background: Background value + /// - pageHeight: Set page height for multipage save + /// - profile: Filename of ICC profile to embed + public func vipssave( + filename: String, + keep: VipsForeignKeep? = nil, + background: [Double]? = nil, + pageHeight: Int? = nil, + profile: String? = nil + ) throws { + var opt = VIPSOption() + + opt.set("in", value: self) + opt.set("filename", value: filename) + if let keep = keep { + opt.set("keep", value: keep) + } + if let background = background { + opt.set("background", value: background) + } + if let pageHeight = pageHeight { + opt.set("page_height", value: pageHeight) + } + if let profile = profile { + opt.set("profile", value: profile) + } + + try Self.call("vipssave", options: &opt) + } + + /// Save image to target in vips format + /// + /// - Parameters: + /// - target: Target to save to + /// - keep: Which metadata to retain + /// - background: Background value + /// - pageHeight: Set page height for multipage save + /// - profile: Filename of ICC profile to embed + public func vipssave( + target: VIPSTarget, + keep: VipsForeignKeep? = nil, + background: [Double]? = nil, + pageHeight: Int? = nil, + profile: String? = nil + ) throws { + var opt = VIPSOption() + + opt.set("in", value: self) + opt.set("target", value: target) + if let keep = keep { + opt.set("keep", value: keep) + } + if let background = background { + opt.set("background", value: background) + } + if let pageHeight = pageHeight { + opt.set("page_height", value: pageHeight) + } + if let profile = profile { + opt.set("profile", value: profile) + } + + try Self.call("vipssave_target", options: &opt) + } + +} + +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { + + /// Load an analyze6 image + /// + /// - Parameters: + /// - filename: Filename to load from + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func analyzeload( + filename: String, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("filename", value: filename) + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("analyzeload", options: &opt) + } + } + + /// Load csv + /// + /// - Parameters: + /// - filename: Filename to load from + /// - skip: Skip this many lines at the start of the file + /// - lines: Read this many lines from the file + /// - whitespace: Set of whitespace characters + /// - separator: Set of separator characters + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func csvload( + filename: String, + skip: Int? = nil, + lines: Int? = nil, + whitespace: String? = nil, + separator: String? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("filename", value: filename) + if let skip = skip { + opt.set("skip", value: skip) + } + if let lines = lines { + opt.set("lines", value: lines) + } + if let whitespace = whitespace { + opt.set("whitespace", value: whitespace) + } + if let separator = separator { + opt.set("separator", value: separator) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("csvload", options: &opt) + } + } + + /// Load csv + /// + /// - Parameters: + /// - source: Source to load from + /// - skip: Skip this many lines at the start of the file + /// - lines: Read this many lines from the file + /// - whitespace: Set of whitespace characters + /// - separator: Set of separator characters + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func csvload( + source: VIPSSource, + skip: Int? = nil, + lines: Int? = nil, + whitespace: String? = nil, + separator: String? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("source", value: source) + if let skip = skip { + opt.set("skip", value: skip) + } + if let lines = lines { + opt.set("lines", value: lines) + } + if let whitespace = whitespace { + opt.set("whitespace", value: whitespace) + } + if let separator = separator { + opt.set("separator", value: separator) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("csvload_source", options: &opt) + } + } + + /// Load a fits image + /// + /// - Parameters: + /// - filename: Filename to load from + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func fitsload( + filename: String, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("filename", value: filename) + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("fitsload", options: &opt) + } + } + + /// Load fits from a source + /// + /// - Parameters: + /// - source: Source to load from + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func fitsload( + source: VIPSSource, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("source", value: source) + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("fitsload_source", options: &opt) + } + } + + /// Load jpeg2000 image + /// + /// - Parameters: + /// - filename: Filename to load from + /// - page: Load this page from the image + /// - oneshot: Load images a frame at a time + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func jp2kload( + filename: String, + page: Int? = nil, + oneshot: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("filename", value: filename) + if let page = page { + opt.set("page", value: page) + } + if let oneshot = oneshot { + opt.set("oneshot", value: oneshot) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("jp2kload", options: &opt) + } + } + + /// Load jpeg2000 image + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - page: Load this page from the image + /// - oneshot: Load images a frame at a time + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func jp2kload( + buffer: VIPSBlob, + page: Int? = nil, + oneshot: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + // the operation will retain the blob + try buffer.withVipsBlob { blob in + try Self { out in + var opt = VIPSOption() + + opt.set("buffer", value: blob) + if let page = page { + opt.set("page", value: page) + } + if let oneshot = oneshot { + opt.set("oneshot", value: oneshot) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("jp2kload_buffer", options: &opt) + } + } + } + + /// Load jpeg2000 image without copying the data. The caller must ensure the buffer remains valid for + /// the lifetime of the returned image and all its descendants. + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - page: Load this page from the image + /// - oneshot: Load images a frame at a time + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func jp2kload( + unsafeBuffer buffer: UnsafeRawBufferPointer, + page: Int? = nil, + oneshot: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + let blob = VIPSBlob(noCopy: buffer) + return try jp2kload( + buffer: blob, + page: page, + oneshot: oneshot, + memory: memory, + access: access, + failOn: failOn, + revalidate: revalidate + ) + } + + /// Load jpeg2000 image + /// + /// - Parameters: + /// - source: Source to load from + /// - page: Load this page from the image + /// - oneshot: Load images a frame at a time + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func jp2kload( + source: VIPSSource, + page: Int? = nil, + oneshot: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("source", value: source) + if let page = page { + opt.set("page", value: page) + } + if let oneshot = oneshot { + opt.set("oneshot", value: oneshot) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("jp2kload_source", options: &opt) + } + } + + /// Load jpeg-xl image + /// + /// - Parameters: + /// - filename: Filename to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func jxlload( + filename: String, + page: Int? = nil, + n: Int? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("filename", value: filename) + if let page = page { + opt.set("page", value: page) + } + if let n = n { + opt.set("n", value: n) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("jxlload", options: &opt) + } + } + + /// Load jpeg-xl image + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func jxlload( + buffer: VIPSBlob, + page: Int? = nil, + n: Int? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + // the operation will retain the blob + try buffer.withVipsBlob { blob in + try Self { out in + var opt = VIPSOption() + + opt.set("buffer", value: blob) + if let page = page { + opt.set("page", value: page) + } + if let n = n { + opt.set("n", value: n) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("jxlload_buffer", options: &opt) + } + } + } + + /// Load jpeg-xl image without copying the data. The caller must ensure the buffer remains valid for + /// the lifetime of the returned image and all its descendants. + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func jxlload( + unsafeBuffer buffer: UnsafeRawBufferPointer, + page: Int? = nil, + n: Int? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + let blob = VIPSBlob(noCopy: buffer) + return try jxlload( + buffer: blob, + page: page, + n: n, + memory: memory, + access: access, + failOn: failOn, + revalidate: revalidate + ) + } + + /// Load jpeg-xl image + /// + /// - Parameters: + /// - source: Source to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func jxlload( + source: VIPSSource, + page: Int? = nil, + n: Int? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("source", value: source) + if let page = page { + opt.set("page", value: page) + } + if let n = n { + opt.set("n", value: n) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("jxlload_source", options: &opt) + } + } + + /// Load file with imagemagick7 + /// + /// - Parameters: + /// - filename: Filename to load from + /// - density: Canvas resolution for rendering vector formats like SVG + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func magickload( + filename: String, + density: String? = nil, + page: Int? = nil, + n: Int? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("filename", value: filename) + if let density = density { + opt.set("density", value: density) + } + if let page = page { + opt.set("page", value: page) + } + if let n = n { + opt.set("n", value: n) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("magickload", options: &opt) + } + } + + /// Load buffer with imagemagick7 + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - density: Canvas resolution for rendering vector formats like SVG + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func magickload( + buffer: VIPSBlob, + density: String? = nil, + page: Int? = nil, + n: Int? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + // the operation will retain the blob + try buffer.withVipsBlob { blob in + try Self { out in + var opt = VIPSOption() + + opt.set("buffer", value: blob) + if let density = density { + opt.set("density", value: density) + } + if let page = page { + opt.set("page", value: page) + } + if let n = n { + opt.set("n", value: n) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("magickload_buffer", options: &opt) + } + } + } + + /// Load buffer with imagemagick7 without copying the data. The caller must ensure the buffer remains valid for + /// the lifetime of the returned image and all its descendants. + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - density: Canvas resolution for rendering vector formats like SVG + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func magickload( + unsafeBuffer buffer: UnsafeRawBufferPointer, + density: String? = nil, + page: Int? = nil, + n: Int? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + let blob = VIPSBlob(noCopy: buffer) + return try magickload( + buffer: blob, + density: density, + page: page, + n: n, + memory: memory, + access: access, + failOn: failOn, + revalidate: revalidate + ) + } + + /// Load mat from file + /// + /// - Parameters: + /// - filename: Filename to load from + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func matload( + filename: String, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("filename", value: filename) + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("matload", options: &opt) + } + } + + /// Load matrix + /// + /// - Parameters: + /// - filename: Filename to load from + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func matrixload( + filename: String, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("filename", value: filename) + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("matrixload", options: &opt) + } + } + + /// Load matrix + /// + /// - Parameters: + /// - source: Source to load from + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func matrixload( + source: VIPSSource, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("source", value: source) + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("matrixload_source", options: &opt) + } + } + + /// Load an openexr image + /// + /// - Parameters: + /// - filename: Filename to load from + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func openexrload( + filename: String, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("filename", value: filename) + if let memory = memory { opt.set("memory", value: memory) } if let access = access { @@ -1899,7 +2303,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("openexrload", options: &opt) + try Self.call("openexrload", options: &opt) } } @@ -1927,8 +2331,8 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -1961,7 +2365,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("openslideload", options: &opt) + try Self.call("openslideload", options: &opt) } } @@ -1989,8 +2393,8 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("source", value: source) @@ -2023,7 +2427,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("openslideload_source", options: &opt) + try Self.call("openslideload_source", options: &opt) } } @@ -2041,8 +2445,8 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -2060,7 +2464,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("ppmload", options: &opt) + try Self.call("ppmload", options: &opt) } } @@ -2079,10 +2483,10 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { + ) throws -> Self { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage { out in + try Self { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -2100,7 +2504,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("ppmload_buffer", options: &opt) + try Self.call("ppmload_buffer", options: &opt) } } } @@ -2121,7 +2525,7 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { + ) throws -> Self { let blob = VIPSBlob(noCopy: buffer) return try ppmload( buffer: blob, @@ -2146,8 +2550,8 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("source", value: source) @@ -2165,108 +2569,8 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("ppmload_source", options: &opt) - } - } - - /// Save image to ppm file - /// - /// - Parameters: - /// - filename: Filename to save to - /// - format: Format to save in - /// - ascii: Save as ascii - /// - bitdepth: Set to 1 to write as a 1 bit image - /// - keep: Which metadata to retain - /// - background: Background value - /// - pageHeight: Set page height for multipage save - /// - profile: Filename of ICC profile to embed - public func ppmsave( - filename: String, - format: VipsForeignPpmFormat? = nil, - ascii: Bool? = nil, - bitdepth: Int? = nil, - keep: VipsForeignKeep? = nil, - background: [Double]? = nil, - pageHeight: Int? = nil, - profile: String? = nil - ) throws { - var opt = VIPSOption() - - opt.set("in", value: self) - opt.set("filename", value: filename) - if let format = format { - opt.set("format", value: format) - } - if let ascii = ascii { - opt.set("ascii", value: ascii) - } - if let bitdepth = bitdepth { - opt.set("bitdepth", value: bitdepth) - } - if let keep = keep { - opt.set("keep", value: keep) - } - if let background = background { - opt.set("background", value: background) - } - if let pageHeight = pageHeight { - opt.set("page_height", value: pageHeight) - } - if let profile = profile { - opt.set("profile", value: profile) - } - - try VIPSImage.call("ppmsave", options: &opt) - } - - /// Save to ppm - /// - /// - Parameters: - /// - target: Target to save to - /// - format: Format to save in - /// - ascii: Save as ascii - /// - bitdepth: Set to 1 to write as a 1 bit image - /// - keep: Which metadata to retain - /// - background: Background value - /// - pageHeight: Set page height for multipage save - /// - profile: Filename of ICC profile to embed - public func ppmsave( - target: VIPSTarget, - format: VipsForeignPpmFormat? = nil, - ascii: Bool? = nil, - bitdepth: Int? = nil, - keep: VipsForeignKeep? = nil, - background: [Double]? = nil, - pageHeight: Int? = nil, - profile: String? = nil - ) throws { - var opt = VIPSOption() - - opt.set("in", value: self) - opt.set("target", value: target) - if let format = format { - opt.set("format", value: format) - } - if let ascii = ascii { - opt.set("ascii", value: ascii) - } - if let bitdepth = bitdepth { - opt.set("bitdepth", value: bitdepth) - } - if let keep = keep { - opt.set("keep", value: keep) - } - if let background = background { - opt.set("background", value: background) - } - if let pageHeight = pageHeight { - opt.set("page_height", value: pageHeight) + try Self.call("ppmload_source", options: &opt) } - if let profile = profile { - opt.set("profile", value: profile) - } - - try VIPSImage.call("ppmsave_target", options: &opt) } /// Load named icc profile @@ -2285,7 +2589,7 @@ extension VIPSImage { opt.set("name", value: name) opt.set("profile", value: out) - try VIPSImage.call("profile_load", options: &opt) + try Self.call("profile_load", options: &opt) guard let vipsBlob = out.pointee else { throw VIPSError("Failed to get buffer from profile_load") @@ -2308,8 +2612,8 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -2327,7 +2631,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("radload", options: &opt) + try Self.call("radload", options: &opt) } } @@ -2346,10 +2650,10 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { + ) throws -> Self { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage { out in + try Self { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -2367,7 +2671,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("radload_buffer", options: &opt) + try Self.call("radload_buffer", options: &opt) } } } @@ -2376,179 +2680,64 @@ extension VIPSImage { /// the lifetime of the returned image and all its descendants. /// /// - Parameters: - /// - buffer: Buffer to load from - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func radload( - unsafeBuffer buffer: UnsafeRawBufferPointer, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - let blob = VIPSBlob(noCopy: buffer) - return try radload( - buffer: blob, - memory: memory, - access: access, - failOn: failOn, - revalidate: revalidate - ) - } - - /// Load rad from source - /// - /// - Parameters: - /// - source: Source to load from - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func radload( - source: VIPSSource, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("source", value: source) - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("radload_source", options: &opt) - } - } - - /// Save image to radiance file - /// - /// - Parameters: - /// - filename: Filename to save to - /// - keep: Which metadata to retain - /// - background: Background value - /// - pageHeight: Set page height for multipage save - /// - profile: Filename of ICC profile to embed - public func radsave( - filename: String, - keep: VipsForeignKeep? = nil, - background: [Double]? = nil, - pageHeight: Int? = nil, - profile: String? = nil - ) throws { - var opt = VIPSOption() - - opt.set("in", value: self) - opt.set("filename", value: filename) - if let keep = keep { - opt.set("keep", value: keep) - } - if let background = background { - opt.set("background", value: background) - } - if let pageHeight = pageHeight { - opt.set("page_height", value: pageHeight) - } - if let profile = profile { - opt.set("profile", value: profile) - } - - try VIPSImage.call("radsave", options: &opt) - } - - /// Save image to radiance buffer - /// - /// - Parameters: - /// - keep: Which metadata to retain - /// - background: Background value - /// - pageHeight: Set page height for multipage save - /// - profile: Filename of ICC profile to embed - public func radsave( - keep: VipsForeignKeep? = nil, - background: [Double]? = nil, - pageHeight: Int? = nil, - profile: String? = nil - ) throws -> VIPSBlob { - var opt = VIPSOption() - - let out: UnsafeMutablePointer?> = .allocate(capacity: 1) - out.initialize(to: nil) - defer { - out.deallocate() - } - - opt.set("in", value: self.image) - if let keep = keep { - opt.set("keep", value: keep) - } - if let background = background { - opt.set("background", value: background) - } - if let pageHeight = pageHeight { - opt.set("page_height", value: pageHeight) - } - if let profile = profile { - opt.set("profile", value: profile) - } - opt.set("buffer", value: out) - - try VIPSImage.call("radsave_buffer", options: &opt) - - guard let vipsBlob = out.pointee else { - throw VIPSError("Failed to get buffer from radsave_buffer") - } - - return VIPSBlob(vipsBlob) + /// - buffer: Buffer to load from + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func radload( + unsafeBuffer buffer: UnsafeRawBufferPointer, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + let blob = VIPSBlob(noCopy: buffer) + return try radload( + buffer: blob, + memory: memory, + access: access, + failOn: failOn, + revalidate: revalidate + ) } - /// Save image to radiance target + /// Load rad from source /// /// - Parameters: - /// - target: Target to save to - /// - keep: Which metadata to retain - /// - background: Background value - /// - pageHeight: Set page height for multipage save - /// - profile: Filename of ICC profile to embed - public func radsave( - target: VIPSTarget, - keep: VipsForeignKeep? = nil, - background: [Double]? = nil, - pageHeight: Int? = nil, - profile: String? = nil - ) throws { - var opt = VIPSOption() + /// - source: Source to load from + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func radload( + source: VIPSSource, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() - opt.set("in", value: self) - opt.set("target", value: target) - if let keep = keep { - opt.set("keep", value: keep) - } - if let background = background { - opt.set("background", value: background) - } - if let pageHeight = pageHeight { - opt.set("page_height", value: pageHeight) - } - if let profile = profile { - opt.set("profile", value: profile) - } + opt.set("source", value: source) + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) - try VIPSImage.call("radsave_target", options: &opt) + try Self.call("radload_source", options: &opt) + } } /// Load raw data from a file @@ -2577,8 +2766,8 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -2608,123 +2797,8 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("rawload", options: &opt) - } - } - - /// Save image to raw file - /// - /// - Parameters: - /// - filename: Filename to save to - /// - keep: Which metadata to retain - /// - background: Background value - /// - pageHeight: Set page height for multipage save - /// - profile: Filename of ICC profile to embed - public func rawsave( - filename: String, - keep: VipsForeignKeep? = nil, - background: [Double]? = nil, - pageHeight: Int? = nil, - profile: String? = nil - ) throws { - var opt = VIPSOption() - - opt.set("in", value: self) - opt.set("filename", value: filename) - if let keep = keep { - opt.set("keep", value: keep) - } - if let background = background { - opt.set("background", value: background) - } - if let pageHeight = pageHeight { - opt.set("page_height", value: pageHeight) - } - if let profile = profile { - opt.set("profile", value: profile) - } - - try VIPSImage.call("rawsave", options: &opt) - } - - /// Write raw image to buffer - /// - /// - Parameters: - /// - keep: Which metadata to retain - /// - background: Background value - /// - pageHeight: Set page height for multipage save - /// - profile: Filename of ICC profile to embed - public func rawsave( - keep: VipsForeignKeep? = nil, - background: [Double]? = nil, - pageHeight: Int? = nil, - profile: String? = nil - ) throws -> VIPSBlob { - var opt = VIPSOption() - - let out: UnsafeMutablePointer?> = .allocate(capacity: 1) - out.initialize(to: nil) - defer { - out.deallocate() - } - - opt.set("in", value: self.image) - if let keep = keep { - opt.set("keep", value: keep) - } - if let background = background { - opt.set("background", value: background) - } - if let pageHeight = pageHeight { - opt.set("page_height", value: pageHeight) - } - if let profile = profile { - opt.set("profile", value: profile) - } - opt.set("buffer", value: out) - - try VIPSImage.call("rawsave_buffer", options: &opt) - - guard let vipsBlob = out.pointee else { - throw VIPSError("Failed to get buffer from rawsave_buffer") - } - - return VIPSBlob(vipsBlob) - } - - /// Write raw image to target - /// - /// - Parameters: - /// - target: Target to save to - /// - keep: Which metadata to retain - /// - background: Background value - /// - pageHeight: Set page height for multipage save - /// - profile: Filename of ICC profile to embed - public func rawsave( - target: VIPSTarget, - keep: VipsForeignKeep? = nil, - background: [Double]? = nil, - pageHeight: Int? = nil, - profile: String? = nil - ) throws { - var opt = VIPSOption() - - opt.set("in", value: self) - opt.set("target", value: target) - if let keep = keep { - opt.set("keep", value: keep) - } - if let background = background { - opt.set("background", value: background) - } - if let pageHeight = pageHeight { - opt.set("page_height", value: pageHeight) + try Self.call("rawload", options: &opt) } - if let profile = profile { - opt.set("profile", value: profile) - } - - try VIPSImage.call("rawsave_target", options: &opt) } /// Load vips from file @@ -2741,8 +2815,8 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -2760,7 +2834,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("vipsload", options: &opt) + try Self.call("vipsload", options: &opt) } } @@ -2778,8 +2852,8 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("source", value: source) @@ -2797,78 +2871,8 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("vipsload_source", options: &opt) - } - } - - /// Save image to file in vips format - /// - /// - Parameters: - /// - filename: Filename to save to - /// - keep: Which metadata to retain - /// - background: Background value - /// - pageHeight: Set page height for multipage save - /// - profile: Filename of ICC profile to embed - public func vipssave( - filename: String, - keep: VipsForeignKeep? = nil, - background: [Double]? = nil, - pageHeight: Int? = nil, - profile: String? = nil - ) throws { - var opt = VIPSOption() - - opt.set("in", value: self) - opt.set("filename", value: filename) - if let keep = keep { - opt.set("keep", value: keep) - } - if let background = background { - opt.set("background", value: background) - } - if let pageHeight = pageHeight { - opt.set("page_height", value: pageHeight) - } - if let profile = profile { - opt.set("profile", value: profile) - } - - try VIPSImage.call("vipssave", options: &opt) - } - - /// Save image to target in vips format - /// - /// - Parameters: - /// - target: Target to save to - /// - keep: Which metadata to retain - /// - background: Background value - /// - pageHeight: Set page height for multipage save - /// - profile: Filename of ICC profile to embed - public func vipssave( - target: VIPSTarget, - keep: VipsForeignKeep? = nil, - background: [Double]? = nil, - pageHeight: Int? = nil, - profile: String? = nil - ) throws { - var opt = VIPSOption() - - opt.set("in", value: self) - opt.set("target", value: target) - if let keep = keep { - opt.set("keep", value: keep) - } - if let background = background { - opt.set("background", value: background) - } - if let pageHeight = pageHeight { - opt.set("page_height", value: pageHeight) + try Self.call("vipsload_source", options: &opt) } - if let profile = profile { - opt.set("profile", value: profile) - } - - try VIPSImage.call("vipssave_target", options: &opt) } } diff --git a/Sources/VIPS/Generated/Foreign/foreign_pdf.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_pdf.generated.swift index 66840a8..6fd6300 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_pdf.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_pdf.generated.swift @@ -8,7 +8,7 @@ import Cvips import CvipsShim -extension VIPSImage { +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Load pdf from file /// @@ -36,8 +36,8 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -73,7 +73,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("pdfload", options: &opt) + try Self.call("pdfload", options: &opt) } } @@ -104,10 +104,10 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { + ) throws -> Self { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage { out in + try Self { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -143,7 +143,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("pdfload_buffer", options: &opt) + try Self.call("pdfload_buffer", options: &opt) } } } @@ -176,7 +176,7 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { + ) throws -> Self { let blob = VIPSBlob(noCopy: buffer) return try pdfload( buffer: blob, @@ -219,8 +219,8 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("source", value: source) @@ -256,7 +256,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("pdfload_source", options: &opt) + try Self.call("pdfload_source", options: &opt) } } diff --git a/Sources/VIPS/Generated/Foreign/foreign_png.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_png.generated.swift index d89c02f..918b3de 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_png.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_png.generated.swift @@ -8,167 +8,7 @@ import Cvips import CvipsShim -extension VIPSImage { - - /// Load png from file - /// - /// - Parameters: - /// - filename: Filename to load from - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func pngload( - filename: String, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("filename", value: filename) - if let unlimited = unlimited { - opt.set("unlimited", value: unlimited) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("pngload", options: &opt) - } - } - - /// Load png from buffer - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func pngload( - buffer: VIPSBlob, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - // the operation will retain the blob - try buffer.withVipsBlob { blob in - try VIPSImage { out in - var opt = VIPSOption() - - opt.set("buffer", value: blob) - if let unlimited = unlimited { - opt.set("unlimited", value: unlimited) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("pngload_buffer", options: &opt) - } - } - } - - /// Load png from buffer without copying the data. The caller must ensure the buffer remains valid for - /// the lifetime of the returned image and all its descendants. - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func pngload( - unsafeBuffer buffer: UnsafeRawBufferPointer, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - let blob = VIPSBlob(noCopy: buffer) - return try pngload( - buffer: blob, - unlimited: unlimited, - memory: memory, - access: access, - failOn: failOn, - revalidate: revalidate - ) - } - - /// Load png from source - /// - /// - Parameters: - /// - source: Source to load from - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func pngload( - source: VIPSSource, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("source", value: source) - if let unlimited = unlimited { - opt.set("unlimited", value: unlimited) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("pngload_source", options: &opt) - } - } +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Save image to file as png /// @@ -242,7 +82,7 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("pngsave", options: &opt) + try Self.call("pngsave", options: &opt) } /// Save image to buffer as png @@ -321,7 +161,7 @@ extension VIPSImage { } opt.set("buffer", value: out) - try VIPSImage.call("pngsave_buffer", options: &opt) + try Self.call("pngsave_buffer", options: &opt) guard let vipsBlob = out.pointee else { throw VIPSError("Failed to get buffer from pngsave_buffer") @@ -402,7 +242,171 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("pngsave_target", options: &opt) + try Self.call("pngsave_target", options: &opt) + } + +} + +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { + + /// Load png from file + /// + /// - Parameters: + /// - filename: Filename to load from + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func pngload( + filename: String, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("filename", value: filename) + if let unlimited = unlimited { + opt.set("unlimited", value: unlimited) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("pngload", options: &opt) + } + } + + /// Load png from buffer + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func pngload( + buffer: VIPSBlob, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + // the operation will retain the blob + try buffer.withVipsBlob { blob in + try Self { out in + var opt = VIPSOption() + + opt.set("buffer", value: blob) + if let unlimited = unlimited { + opt.set("unlimited", value: unlimited) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("pngload_buffer", options: &opt) + } + } + } + + /// Load png from buffer without copying the data. The caller must ensure the buffer remains valid for + /// the lifetime of the returned image and all its descendants. + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func pngload( + unsafeBuffer buffer: UnsafeRawBufferPointer, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + let blob = VIPSBlob(noCopy: buffer) + return try pngload( + buffer: blob, + unlimited: unlimited, + memory: memory, + access: access, + failOn: failOn, + revalidate: revalidate + ) + } + + /// Load png from source + /// + /// - Parameters: + /// - source: Source to load from + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func pngload( + source: VIPSSource, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("source", value: source) + if let unlimited = unlimited { + opt.set("unlimited", value: unlimited) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("pngload_source", options: &opt) + } } } diff --git a/Sources/VIPS/Generated/Foreign/foreign_svg.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_svg.generated.swift index f74839c..6b40945 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_svg.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_svg.generated.swift @@ -8,7 +8,7 @@ import Cvips import CvipsShim -extension VIPSImage { +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Load svg with rsvg /// @@ -34,8 +34,8 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -68,7 +68,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("svgload", options: &opt) + try Self.call("svgload", options: &opt) } } @@ -97,10 +97,10 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { + ) throws -> Self { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage { out in + try Self { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -133,7 +133,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("svgload_buffer", options: &opt) + try Self.call("svgload_buffer", options: &opt) } } } @@ -164,7 +164,7 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { + ) throws -> Self { let blob = VIPSBlob(noCopy: buffer) return try svgload( buffer: blob, @@ -204,8 +204,8 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("source", value: source) @@ -238,7 +238,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("svgload_source", options: &opt) + try Self.call("svgload_source", options: &opt) } } diff --git a/Sources/VIPS/Generated/Foreign/foreign_tiff.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_tiff.generated.swift index cee2a11..8040ceb 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_tiff.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_tiff.generated.swift @@ -8,239 +8,7 @@ import Cvips import CvipsShim -extension VIPSImage { - - /// Load tiff from file - /// - /// - Parameters: - /// - filename: Filename to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - autorotate: Rotate image using orientation tag - /// - subifd: Subifd index - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func tiffload( - filename: String, - page: Int? = nil, - n: Int? = nil, - autorotate: Bool? = nil, - subifd: Int? = nil, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("filename", value: filename) - if let page = page { - opt.set("page", value: page) - } - if let n = n { - opt.set("n", value: n) - } - if let autorotate = autorotate { - opt.set("autorotate", value: autorotate) - } - if let subifd = subifd { - opt.set("subifd", value: subifd) - } - if let unlimited = unlimited { - opt.set("unlimited", value: unlimited) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("tiffload", options: &opt) - } - } - - /// Load tiff from buffer - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - autorotate: Rotate image using orientation tag - /// - subifd: Subifd index - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func tiffload( - buffer: VIPSBlob, - page: Int? = nil, - n: Int? = nil, - autorotate: Bool? = nil, - subifd: Int? = nil, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - // the operation will retain the blob - try buffer.withVipsBlob { blob in - try VIPSImage { out in - var opt = VIPSOption() - - opt.set("buffer", value: blob) - if let page = page { - opt.set("page", value: page) - } - if let n = n { - opt.set("n", value: n) - } - if let autorotate = autorotate { - opt.set("autorotate", value: autorotate) - } - if let subifd = subifd { - opt.set("subifd", value: subifd) - } - if let unlimited = unlimited { - opt.set("unlimited", value: unlimited) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("tiffload_buffer", options: &opt) - } - } - } - - /// Load tiff from buffer without copying the data. The caller must ensure the buffer remains valid for - /// the lifetime of the returned image and all its descendants. - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - autorotate: Rotate image using orientation tag - /// - subifd: Subifd index - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func tiffload( - unsafeBuffer buffer: UnsafeRawBufferPointer, - page: Int? = nil, - n: Int? = nil, - autorotate: Bool? = nil, - subifd: Int? = nil, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - let blob = VIPSBlob(noCopy: buffer) - return try tiffload( - buffer: blob, - page: page, - n: n, - autorotate: autorotate, - subifd: subifd, - unlimited: unlimited, - memory: memory, - access: access, - failOn: failOn, - revalidate: revalidate - ) - } - - /// Load tiff from source - /// - /// - Parameters: - /// - source: Source to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - autorotate: Rotate image using orientation tag - /// - subifd: Subifd index - /// - unlimited: Remove all denial of service limits - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func tiffload( - source: VIPSSource, - page: Int? = nil, - n: Int? = nil, - autorotate: Bool? = nil, - subifd: Int? = nil, - unlimited: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("source", value: source) - if let page = page { - opt.set("page", value: page) - } - if let n = n { - opt.set("n", value: n) - } - if let autorotate = autorotate { - opt.set("autorotate", value: autorotate) - } - if let subifd = subifd { - opt.set("subifd", value: subifd) - } - if let unlimited = unlimited { - opt.set("unlimited", value: unlimited) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("tiffload_source", options: &opt) - } - } +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Save image to tiff file /// @@ -374,7 +142,7 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("tiffsave", options: &opt) + try Self.call("tiffsave", options: &opt) } /// Save image to tiff buffer @@ -513,7 +281,7 @@ extension VIPSImage { } opt.set("buffer", value: out) - try VIPSImage.call("tiffsave_buffer", options: &opt) + try Self.call("tiffsave_buffer", options: &opt) guard let vipsBlob = out.pointee else { throw VIPSError("Failed to get buffer from tiffsave_buffer") @@ -654,7 +422,243 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("tiffsave_target", options: &opt) + try Self.call("tiffsave_target", options: &opt) + } + +} + +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { + + /// Load tiff from file + /// + /// - Parameters: + /// - filename: Filename to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - autorotate: Rotate image using orientation tag + /// - subifd: Subifd index + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func tiffload( + filename: String, + page: Int? = nil, + n: Int? = nil, + autorotate: Bool? = nil, + subifd: Int? = nil, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("filename", value: filename) + if let page = page { + opt.set("page", value: page) + } + if let n = n { + opt.set("n", value: n) + } + if let autorotate = autorotate { + opt.set("autorotate", value: autorotate) + } + if let subifd = subifd { + opt.set("subifd", value: subifd) + } + if let unlimited = unlimited { + opt.set("unlimited", value: unlimited) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("tiffload", options: &opt) + } + } + + /// Load tiff from buffer + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - autorotate: Rotate image using orientation tag + /// - subifd: Subifd index + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func tiffload( + buffer: VIPSBlob, + page: Int? = nil, + n: Int? = nil, + autorotate: Bool? = nil, + subifd: Int? = nil, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + // the operation will retain the blob + try buffer.withVipsBlob { blob in + try Self { out in + var opt = VIPSOption() + + opt.set("buffer", value: blob) + if let page = page { + opt.set("page", value: page) + } + if let n = n { + opt.set("n", value: n) + } + if let autorotate = autorotate { + opt.set("autorotate", value: autorotate) + } + if let subifd = subifd { + opt.set("subifd", value: subifd) + } + if let unlimited = unlimited { + opt.set("unlimited", value: unlimited) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("tiffload_buffer", options: &opt) + } + } + } + + /// Load tiff from buffer without copying the data. The caller must ensure the buffer remains valid for + /// the lifetime of the returned image and all its descendants. + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - autorotate: Rotate image using orientation tag + /// - subifd: Subifd index + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func tiffload( + unsafeBuffer buffer: UnsafeRawBufferPointer, + page: Int? = nil, + n: Int? = nil, + autorotate: Bool? = nil, + subifd: Int? = nil, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + let blob = VIPSBlob(noCopy: buffer) + return try tiffload( + buffer: blob, + page: page, + n: n, + autorotate: autorotate, + subifd: subifd, + unlimited: unlimited, + memory: memory, + access: access, + failOn: failOn, + revalidate: revalidate + ) + } + + /// Load tiff from source + /// + /// - Parameters: + /// - source: Source to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - autorotate: Rotate image using orientation tag + /// - subifd: Subifd index + /// - unlimited: Remove all denial of service limits + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func tiffload( + source: VIPSSource, + page: Int? = nil, + n: Int? = nil, + autorotate: Bool? = nil, + subifd: Int? = nil, + unlimited: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("source", value: source) + if let page = page { + opt.set("page", value: page) + } + if let n = n { + opt.set("n", value: n) + } + if let autorotate = autorotate { + opt.set("autorotate", value: autorotate) + } + if let subifd = subifd { + opt.set("subifd", value: subifd) + } + if let unlimited = unlimited { + opt.set("unlimited", value: unlimited) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("tiffload_source", options: &opt) + } } } diff --git a/Sources/VIPS/Generated/Foreign/foreign_webp.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_webp.generated.swift index b17e462..8e0b904 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_webp.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_webp.generated.swift @@ -8,203 +8,7 @@ import Cvips import CvipsShim -extension VIPSImage { - - /// Load webp from file - /// - /// - Parameters: - /// - filename: Filename to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - scale: Factor to scale by - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func webpload( - filename: String, - page: Int? = nil, - n: Int? = nil, - scale: Double? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("filename", value: filename) - if let page = page { - opt.set("page", value: page) - } - if let n = n { - opt.set("n", value: n) - } - if let scale = scale { - opt.set("scale", value: scale) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("webpload", options: &opt) - } - } - - /// Load webp from buffer - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - scale: Factor to scale by - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func webpload( - buffer: VIPSBlob, - page: Int? = nil, - n: Int? = nil, - scale: Double? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - // the operation will retain the blob - try buffer.withVipsBlob { blob in - try VIPSImage { out in - var opt = VIPSOption() - - opt.set("buffer", value: blob) - if let page = page { - opt.set("page", value: page) - } - if let n = n { - opt.set("n", value: n) - } - if let scale = scale { - opt.set("scale", value: scale) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("webpload_buffer", options: &opt) - } - } - } - - /// Load webp from buffer without copying the data. The caller must ensure the buffer remains valid for - /// the lifetime of the returned image and all its descendants. - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - scale: Factor to scale by - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - @inlinable - public static func webpload( - unsafeBuffer buffer: UnsafeRawBufferPointer, - page: Int? = nil, - n: Int? = nil, - scale: Double? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - let blob = VIPSBlob(noCopy: buffer) - return try webpload( - buffer: blob, - page: page, - n: n, - scale: scale, - memory: memory, - access: access, - failOn: failOn, - revalidate: revalidate - ) - } - - /// Load webp from source - /// - /// - Parameters: - /// - source: Source to load from - /// - page: First page to load - /// - n: Number of pages to load, -1 for all - /// - scale: Factor to scale by - /// - memory: Force open via memory - /// - access: Required access pattern for this file - /// - failOn: Error level to fail on - /// - revalidate: Don't use a cached result for this operation - public static func webpload( - source: VIPSSource, - page: Int? = nil, - n: Int? = nil, - scale: Double? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("source", value: source) - if let page = page { - opt.set("page", value: page) - } - if let n = n { - opt.set("n", value: n) - } - if let scale = scale { - opt.set("scale", value: scale) - } - if let memory = memory { - opt.set("memory", value: memory) - } - if let access = access { - opt.set("access", value: access) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) - } - opt.set("out", value: &out) - - try VIPSImage.call("webpload_source", options: &opt) - } - } +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Save as webp /// @@ -308,7 +112,7 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("webpsave", options: &opt) + try Self.call("webpsave", options: &opt) } /// Save as webp @@ -417,7 +221,7 @@ extension VIPSImage { } opt.set("buffer", value: out) - try VIPSImage.call("webpsave_buffer", options: &opt) + try Self.call("webpsave_buffer", options: &opt) guard let vipsBlob = out.pointee else { throw VIPSError("Failed to get buffer from webpsave_buffer") @@ -525,7 +329,7 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("webpsave_mime", options: &opt) + try Self.call("webpsave_mime", options: &opt) } /// Save as webp @@ -630,7 +434,207 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("webpsave_target", options: &opt) + try Self.call("webpsave_target", options: &opt) + } + +} + +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { + + /// Load webp from file + /// + /// - Parameters: + /// - filename: Filename to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - scale: Factor to scale by + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func webpload( + filename: String, + page: Int? = nil, + n: Int? = nil, + scale: Double? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("filename", value: filename) + if let page = page { + opt.set("page", value: page) + } + if let n = n { + opt.set("n", value: n) + } + if let scale = scale { + opt.set("scale", value: scale) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("webpload", options: &opt) + } + } + + /// Load webp from buffer + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - scale: Factor to scale by + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func webpload( + buffer: VIPSBlob, + page: Int? = nil, + n: Int? = nil, + scale: Double? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + // the operation will retain the blob + try buffer.withVipsBlob { blob in + try Self { out in + var opt = VIPSOption() + + opt.set("buffer", value: blob) + if let page = page { + opt.set("page", value: page) + } + if let n = n { + opt.set("n", value: n) + } + if let scale = scale { + opt.set("scale", value: scale) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("webpload_buffer", options: &opt) + } + } + } + + /// Load webp from buffer without copying the data. The caller must ensure the buffer remains valid for + /// the lifetime of the returned image and all its descendants. + /// + /// - Parameters: + /// - buffer: Buffer to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - scale: Factor to scale by + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + @inlinable + public static func webpload( + unsafeBuffer buffer: UnsafeRawBufferPointer, + page: Int? = nil, + n: Int? = nil, + scale: Double? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + let blob = VIPSBlob(noCopy: buffer) + return try webpload( + buffer: blob, + page: page, + n: n, + scale: scale, + memory: memory, + access: access, + failOn: failOn, + revalidate: revalidate + ) + } + + /// Load webp from source + /// + /// - Parameters: + /// - source: Source to load from + /// - page: First page to load + /// - n: Number of pages to load, -1 for all + /// - scale: Factor to scale by + /// - memory: Force open via memory + /// - access: Required access pattern for this file + /// - failOn: Error level to fail on + /// - revalidate: Don't use a cached result for this operation + public static func webpload( + source: VIPSSource, + page: Int? = nil, + n: Int? = nil, + scale: Double? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("source", value: source) + if let page = page { + opt.set("page", value: page) + } + if let n = n { + opt.set("n", value: n) + } + if let scale = scale { + opt.set("scale", value: scale) + } + if let memory = memory { + opt.set("memory", value: memory) + } + if let access = access { + opt.set("access", value: access) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + if let revalidate = revalidate { + opt.set("revalidate", value: revalidate) + } + opt.set("out", value: &out) + + try Self.call("webpload_source", options: &opt) + } } } diff --git a/Sources/VIPS/Generated/arithmetic.generated.swift b/Sources/VIPS/Generated/arithmetic.generated.swift index f9eaa0a..cee8e50 100644 --- a/Sources/VIPS/Generated/arithmetic.generated.swift +++ b/Sources/VIPS/Generated/arithmetic.generated.swift @@ -7,65 +7,65 @@ import Cvips -extension VIPSImage { +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Transform float lab to signed short - public func Lab2LabS() throws -> VIPSImage { - return try VIPSImage { out in + public func Lab2LabS() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("Lab2LabS", options: &opt) + try Self.call("Lab2LabS", options: &opt) } } /// Unpack a labq image to short lab - public func LabQ2LabS() throws -> VIPSImage { - return try VIPSImage { out in + public func LabQ2LabS() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("LabQ2LabS", options: &opt) + try Self.call("LabQ2LabS", options: &opt) } } /// Transform signed short lab to float - public func LabS2Lab() throws -> VIPSImage { - return try VIPSImage { out in + public func LabS2Lab() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("LabS2Lab", options: &opt) + try Self.call("LabS2Lab", options: &opt) } } /// Transform short lab to labq coding - public func LabS2LabQ() throws -> VIPSImage { - return try VIPSImage { out in + public func LabS2LabQ() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("LabS2LabQ", options: &opt) + try Self.call("LabS2LabQ", options: &opt) } } /// Absolute value of an image - public func abs() throws -> VIPSImage { - return try VIPSImage { out in + public func abs() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("abs", options: &opt) + try Self.call("abs", options: &opt) } } @@ -73,28 +73,28 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand image argument - public func add(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func add(_ rhs: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) opt.set("right", value: rhs) opt.set("out", value: &out) - try VIPSImage.call("add", options: &opt) + try Self.call("add", options: &opt) } } #if SHIM_VIPS_VERSION_8_16 /// Append an alpha channel - public func addalpha() throws -> VIPSImage { - return try VIPSImage { out in + public func addalpha() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("addalpha", options: &opt) + try Self.call("addalpha", options: &opt) } } #endif @@ -108,7 +108,7 @@ extension VIPSImage { opt.set("in", value: self.image) opt.set("out", value: &out) - try VIPSImage.call("avg", options: &opt) + try Self.call("avg", options: &opt) return out } @@ -118,8 +118,9 @@ extension VIPSImage { /// - Parameters: /// - `right`: Right-hand image argument /// - boolean: Boolean to perform - public func boolean(_ rhs: VIPSImage, boolean: VipsOperationBoolean) throws -> VIPSImage { - return try VIPSImage { out in + public func boolean(_ rhs: some VIPSImageProtocol, boolean: VipsOperationBoolean) throws -> Self + { + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) @@ -127,7 +128,7 @@ extension VIPSImage { opt.set("boolean", value: boolean) opt.set("out", value: &out) - try VIPSImage.call("boolean", options: &opt) + try Self.call("boolean", options: &opt) } } @@ -136,8 +137,8 @@ extension VIPSImage { /// - Parameters: /// - boolean: Boolean to perform /// - c: Array of constants - public func booleanConst(boolean: VipsOperationBoolean, c: [Double]) throws -> VIPSImage { - return try VIPSImage { out in + public func booleanConst(boolean: VipsOperationBoolean, c: [Double]) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -145,7 +146,7 @@ extension VIPSImage { opt.set("c", value: c) opt.set("out", value: &out) - try VIPSImage.call("boolean_const", options: &opt) + try Self.call("boolean_const", options: &opt) } } @@ -153,15 +154,15 @@ extension VIPSImage { /// /// - Parameters: /// - cmplx: Complex to perform - public func complex(cmplx: VipsOperationComplex) throws -> VIPSImage { - return try VIPSImage { out in + public func complex(cmplx: VipsOperationComplex) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("cmplx", value: cmplx) opt.set("out", value: &out) - try VIPSImage.call("complex", options: &opt) + try Self.call("complex", options: &opt) } } @@ -170,8 +171,9 @@ extension VIPSImage { /// - Parameters: /// - `right`: Right-hand image argument /// - cmplx: Binary complex operation to perform - public func complex2(_ rhs: VIPSImage, cmplx: VipsOperationComplex2) throws -> VIPSImage { - return try VIPSImage { out in + public func complex2(_ rhs: some VIPSImageProtocol, cmplx: VipsOperationComplex2) throws -> Self + { + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) @@ -179,7 +181,7 @@ extension VIPSImage { opt.set("cmplx", value: cmplx) opt.set("out", value: &out) - try VIPSImage.call("complex2", options: &opt) + try Self.call("complex2", options: &opt) } } @@ -187,15 +189,15 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand image argument - public func complexform(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func complexform(_ rhs: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) opt.set("right", value: rhs) opt.set("out", value: &out) - try VIPSImage.call("complexform", options: &opt) + try Self.call("complexform", options: &opt) } } @@ -203,15 +205,15 @@ extension VIPSImage { /// /// - Parameters: /// - `get`: Complex to perform - public func complexget(`get`: VipsOperationComplexget) throws -> VIPSImage { - return try VIPSImage { out in + public func complexget(`get`: VipsOperationComplexget) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("get", value: `get`) opt.set("out", value: &out) - try VIPSImage.call("complexget", options: &opt) + try Self.call("complexget", options: &opt) } } @@ -224,7 +226,7 @@ extension VIPSImage { opt.set("in", value: self.image) opt.set("out", value: &out) - try VIPSImage.call("deviate", options: &opt) + try Self.call("deviate", options: &opt) return out } @@ -233,27 +235,27 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand image argument - public func divide(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func divide(_ rhs: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) opt.set("right", value: rhs) opt.set("out", value: &out) - try VIPSImage.call("divide", options: &opt) + try Self.call("divide", options: &opt) } } /// Invert an image - public func invert() throws -> VIPSImage { - return try VIPSImage { out in + public func invert() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("invert", options: &opt) + try Self.call("invert", options: &opt) } } @@ -261,8 +263,8 @@ extension VIPSImage { /// /// - Parameters: /// - size: LUT size to generate - public func invertlut(size: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func invertlut(size: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -271,7 +273,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("invertlut", options: &opt) + try Self.call("invertlut", options: &opt) } } @@ -279,15 +281,15 @@ extension VIPSImage { /// /// - Parameters: /// - math: Math to perform - public func math(_ math: VipsOperationMath) throws -> VIPSImage { - return try VIPSImage { out in + public func math(_ math: VipsOperationMath) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("math", value: math) opt.set("out", value: &out) - try VIPSImage.call("math", options: &opt) + try Self.call("math", options: &opt) } } @@ -296,8 +298,8 @@ extension VIPSImage { /// - Parameters: /// - `right`: Right-hand image argument /// - math2: Math to perform - public func math2(_ rhs: VIPSImage, math2: VipsOperationMath2) throws -> VIPSImage { - return try VIPSImage { out in + public func math2(_ rhs: some VIPSImageProtocol, math2: VipsOperationMath2) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) @@ -305,7 +307,7 @@ extension VIPSImage { opt.set("math2", value: math2) opt.set("out", value: &out) - try VIPSImage.call("math2", options: &opt) + try Self.call("math2", options: &opt) } } @@ -314,8 +316,8 @@ extension VIPSImage { /// - Parameters: /// - math2: Math to perform /// - c: Array of constants - public func math2Const(math2: VipsOperationMath2, c: [Double]) throws -> VIPSImage { - return try VIPSImage { out in + public func math2Const(math2: VipsOperationMath2, c: [Double]) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -323,19 +325,19 @@ extension VIPSImage { opt.set("c", value: c) opt.set("out", value: &out) - try VIPSImage.call("math2_const", options: &opt) + try Self.call("math2_const", options: &opt) } } /// Invert a matrix - public func matrixinvert() throws -> VIPSImage { - return try VIPSImage { out in + public func matrixinvert() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("matrixinvert", options: &opt) + try Self.call("matrixinvert", options: &opt) } } @@ -343,15 +345,15 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Second matrix to multiply - public func matrixmultiply(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func matrixmultiply(_ rhs: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) opt.set("right", value: rhs) opt.set("out", value: &out) - try VIPSImage.call("matrixmultiply", options: &opt) + try Self.call("matrixmultiply", options: &opt) } } @@ -370,7 +372,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("max", options: &opt) + try Self.call("max", options: &opt) return out } @@ -379,15 +381,15 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand image argument - public func maxpair(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func maxpair(_ rhs: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) opt.set("right", value: rhs) opt.set("out", value: &out) - try VIPSImage.call("maxpair", options: &opt) + try Self.call("maxpair", options: &opt) } } @@ -406,7 +408,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("min", options: &opt) + try Self.call("min", options: &opt) return out } @@ -415,15 +417,15 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand image argument - public func minpair(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func minpair(_ rhs: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) opt.set("right", value: rhs) opt.set("out", value: &out) - try VIPSImage.call("minpair", options: &opt) + try Self.call("minpair", options: &opt) } } @@ -431,15 +433,15 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand image argument - public func multiply(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func multiply(_ rhs: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) opt.set("right", value: rhs) opt.set("out", value: &out) - try VIPSImage.call("multiply", options: &opt) + try Self.call("multiply", options: &opt) } } @@ -448,8 +450,8 @@ extension VIPSImage { /// /// - Parameters: /// - maxAlpha: Maximum value of alpha channel - public func premultiply(maxAlpha: Double? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func premultiply(maxAlpha: Double? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -458,7 +460,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("premultiply", options: &opt) + try Self.call("premultiply", options: &opt) } } #endif @@ -468,10 +470,10 @@ extension VIPSImage { /// - Parameters: /// - `right`: Right-hand image argument /// - relational: Relational to perform - public func relational(_ rhs: VIPSImage, relational: VipsOperationRelational) throws - -> VIPSImage + public func relational(_ rhs: some VIPSImageProtocol, relational: VipsOperationRelational) + throws -> Self { - return try VIPSImage { out in + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) @@ -479,7 +481,7 @@ extension VIPSImage { opt.set("relational", value: relational) opt.set("out", value: &out) - try VIPSImage.call("relational", options: &opt) + try Self.call("relational", options: &opt) } } @@ -488,10 +490,8 @@ extension VIPSImage { /// - Parameters: /// - relational: Relational to perform /// - c: Array of constants - public func relationalConst(relational: VipsOperationRelational, c: [Double]) throws - -> VIPSImage - { - return try VIPSImage { out in + public func relationalConst(relational: VipsOperationRelational, c: [Double]) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -499,7 +499,7 @@ extension VIPSImage { opt.set("c", value: c) opt.set("out", value: &out) - try VIPSImage.call("relational_const", options: &opt) + try Self.call("relational_const", options: &opt) } } @@ -507,15 +507,15 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand image argument - public func remainder(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func remainder(_ rhs: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) opt.set("right", value: rhs) opt.set("out", value: &out) - try VIPSImage.call("remainder", options: &opt) + try Self.call("remainder", options: &opt) } } @@ -523,7 +523,7 @@ extension VIPSImage { /// /// - Parameters: /// - value: Constant value - public func remainder(_ value: Double) throws -> VIPSImage { + public func remainder(_ value: Double) throws -> Self { return try remainderConst(c: [value]) } @@ -531,7 +531,7 @@ extension VIPSImage { /// /// - Parameters: /// - value: Constant value - public func remainder(_ value: Int) throws -> VIPSImage { + public func remainder(_ value: Int) throws -> Self { return try remainderConst(c: [Double(value)]) } @@ -539,15 +539,15 @@ extension VIPSImage { /// /// - Parameters: /// - c: Array of constants - public func remainderConst(c: [Double]) throws -> VIPSImage { - return try VIPSImage { out in + public func remainderConst(c: [Double]) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("c", value: c) opt.set("out", value: &out) - try VIPSImage.call("remainder_const", options: &opt) + try Self.call("remainder_const", options: &opt) } } @@ -555,39 +555,39 @@ extension VIPSImage { /// /// - Parameters: /// - round: Rounding operation to perform - public func round(_ round: VipsOperationRound) throws -> VIPSImage { - return try VIPSImage { out in + public func round(_ round: VipsOperationRound) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("round", value: round) opt.set("out", value: &out) - try VIPSImage.call("round", options: &opt) + try Self.call("round", options: &opt) } } /// Unit vector of pixel - public func sign() throws -> VIPSImage { - return try VIPSImage { out in + public func sign() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("sign", options: &opt) + try Self.call("sign", options: &opt) } } /// Find many image stats - public func stats() throws -> VIPSImage { - return try VIPSImage { out in + public func stats() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("stats", options: &opt) + try Self.call("stats", options: &opt) } } @@ -595,30 +595,15 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand image argument - public func subtract(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func subtract(_ rhs: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) opt.set("right", value: rhs) opt.set("out", value: &out) - try VIPSImage.call("subtract", options: &opt) - } - } - - /// Sum an array of images - /// - /// - Parameters: - /// - `in`: Array of input images - public static func sum(_ `in`: [VIPSImage]) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("in", value: `in`) - opt.set("out", value: &out) - - try VIPSImage.call("sum", options: &opt) + try Self.call("subtract", options: &opt) } } @@ -628,10 +613,8 @@ extension VIPSImage { /// - Parameters: /// - maxAlpha: Maximum value of alpha channel /// - alphaBand: Unpremultiply with this alpha - public func unpremultiply(maxAlpha: Double? = nil, alphaBand: Int? = nil) throws - -> VIPSImage - { - return try VIPSImage { out in + public func unpremultiply(maxAlpha: Double? = nil, alphaBand: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -643,7 +626,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("unpremultiply", options: &opt) + try Self.call("unpremultiply", options: &opt) } } #endif @@ -652,7 +635,7 @@ extension VIPSImage { /// /// - Parameters: /// - rhs: Right-hand input image - public func equal(_ rhs: VIPSImage) throws -> VIPSImage { + public func equal(_ rhs: some VIPSImageProtocol) throws -> Self { return try relational(rhs, relational: .equal) } @@ -660,7 +643,7 @@ extension VIPSImage { /// /// - Parameters: /// - value: Constant value - public func equal(_ value: Double) throws -> VIPSImage { + public func equal(_ value: Double) throws -> Self { return try relationalConst(relational: .equal, c: [value]) } @@ -668,7 +651,7 @@ extension VIPSImage { /// /// - Parameters: /// - rhs: Right-hand input image - public func notequal(_ rhs: VIPSImage) throws -> VIPSImage { + public func notequal(_ rhs: some VIPSImageProtocol) throws -> Self { return try relational(rhs, relational: .noteq) } @@ -676,7 +659,7 @@ extension VIPSImage { /// /// - Parameters: /// - value: Constant value - public func notequal(_ value: Double) throws -> VIPSImage { + public func notequal(_ value: Double) throws -> Self { return try relationalConst(relational: .noteq, c: [value]) } @@ -684,7 +667,7 @@ extension VIPSImage { /// /// - Parameters: /// - rhs: Right-hand input image - public func less(_ rhs: VIPSImage) throws -> VIPSImage { + public func less(_ rhs: some VIPSImageProtocol) throws -> Self { return try relational(rhs, relational: .less) } @@ -692,7 +675,7 @@ extension VIPSImage { /// /// - Parameters: /// - value: Constant value - public func less(_ value: Double) throws -> VIPSImage { + public func less(_ value: Double) throws -> Self { return try relationalConst(relational: .less, c: [value]) } @@ -700,7 +683,7 @@ extension VIPSImage { /// /// - Parameters: /// - rhs: Right-hand input image - public func lesseq(_ rhs: VIPSImage) throws -> VIPSImage { + public func lesseq(_ rhs: some VIPSImageProtocol) throws -> Self { return try relational(rhs, relational: .lesseq) } @@ -708,7 +691,7 @@ extension VIPSImage { /// /// - Parameters: /// - value: Constant value - public func lesseq(_ value: Double) throws -> VIPSImage { + public func lesseq(_ value: Double) throws -> Self { return try relationalConst(relational: .lesseq, c: [value]) } @@ -716,7 +699,7 @@ extension VIPSImage { /// /// - Parameters: /// - rhs: Right-hand input image - public func more(_ rhs: VIPSImage) throws -> VIPSImage { + public func more(_ rhs: some VIPSImageProtocol) throws -> Self { return try relational(rhs, relational: .more) } @@ -724,7 +707,7 @@ extension VIPSImage { /// /// - Parameters: /// - value: Constant value - public func more(_ value: Double) throws -> VIPSImage { + public func more(_ value: Double) throws -> Self { return try relationalConst(relational: .more, c: [value]) } @@ -732,7 +715,7 @@ extension VIPSImage { /// /// - Parameters: /// - rhs: Right-hand input image - public func moreeq(_ rhs: VIPSImage) throws -> VIPSImage { + public func moreeq(_ rhs: some VIPSImageProtocol) throws -> Self { return try relational(rhs, relational: .moreeq) } @@ -740,7 +723,7 @@ extension VIPSImage { /// /// - Parameters: /// - value: Constant value - public func moreeq(_ value: Double) throws -> VIPSImage { + public func moreeq(_ value: Double) throws -> Self { return try relationalConst(relational: .moreeq, c: [value]) } @@ -748,7 +731,7 @@ extension VIPSImage { /// /// - Parameters: /// - rhs: Right-hand input image - public func andimage(_ rhs: VIPSImage) throws -> VIPSImage { + public func andimage(_ rhs: some VIPSImageProtocol) throws -> Self { return try boolean(rhs, boolean: .and) } @@ -756,7 +739,7 @@ extension VIPSImage { /// /// - Parameters: /// - rhs: Right-hand input image - public func orimage(_ rhs: VIPSImage) throws -> VIPSImage { + public func orimage(_ rhs: some VIPSImageProtocol) throws -> Self { return try boolean(rhs, boolean: .or) } @@ -764,7 +747,7 @@ extension VIPSImage { /// /// - Parameters: /// - rhs: Right-hand input image - public func eorimage(_ rhs: VIPSImage) throws -> VIPSImage { + public func eorimage(_ rhs: some VIPSImageProtocol) throws -> Self { return try boolean(rhs, boolean: .eor) } @@ -772,7 +755,7 @@ extension VIPSImage { /// /// - Parameters: /// - amount: Number of bits to shift - public func lshift(_ amount: Int) throws -> VIPSImage { + public func lshift(_ amount: Int) throws -> Self { return try booleanConst(boolean: .lshift, c: [Double(amount)]) } @@ -780,8 +763,27 @@ extension VIPSImage { /// /// - Parameters: /// - amount: Number of bits to shift - public func rshift(_ amount: Int) throws -> VIPSImage { + public func rshift(_ amount: Int) throws -> Self { return try booleanConst(boolean: .rshift, c: [Double(amount)]) } } + +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { + + /// Sum an array of images + /// + /// - Parameters: + /// - `in`: Array of input images + public static func sum(_ `in`: [VIPSImage]) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("in", value: `in`) + opt.set("out", value: &out) + + try Self.call("sum", options: &opt) + } + } + +} diff --git a/Sources/VIPS/Generated/colour.generated.swift b/Sources/VIPS/Generated/colour.generated.swift index e6b0ec6..23bc628 100644 --- a/Sources/VIPS/Generated/colour.generated.swift +++ b/Sources/VIPS/Generated/colour.generated.swift @@ -7,89 +7,89 @@ import Cvips -extension VIPSImage { +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Transform lch to cmc - public func CMC2LCh() throws -> VIPSImage { - return try VIPSImage { out in + public func CMC2LCh() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("CMC2LCh", options: &opt) + try Self.call("CMC2LCh", options: &opt) } } /// Transform cmyk to xyz - public func CMYK2XYZ() throws -> VIPSImage { - return try VIPSImage { out in + public func CMYK2XYZ() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("CMYK2XYZ", options: &opt) + try Self.call("CMYK2XYZ", options: &opt) } } /// Transform hsv to srgb - public func HSV2sRGB() throws -> VIPSImage { - return try VIPSImage { out in + public func HSV2sRGB() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("HSV2sRGB", options: &opt) + try Self.call("HSV2sRGB", options: &opt) } } /// Transform lch to cmc - public func LCh2CMC() throws -> VIPSImage { - return try VIPSImage { out in + public func LCh2CMC() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("LCh2CMC", options: &opt) + try Self.call("LCh2CMC", options: &opt) } } /// Transform lch to lab - public func LCh2Lab() throws -> VIPSImage { - return try VIPSImage { out in + public func LCh2Lab() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("LCh2Lab", options: &opt) + try Self.call("LCh2Lab", options: &opt) } } /// Transform lab to lch - public func Lab2LCh() throws -> VIPSImage { - return try VIPSImage { out in + public func Lab2LCh() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("Lab2LCh", options: &opt) + try Self.call("Lab2LCh", options: &opt) } } /// Transform float lab to labq coding - public func Lab2LabQ() throws -> VIPSImage { - return try VIPSImage { out in + public func Lab2LabQ() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("Lab2LabQ", options: &opt) + try Self.call("Lab2LabQ", options: &opt) } } @@ -97,8 +97,8 @@ extension VIPSImage { /// /// - Parameters: /// - temp: Color temperature - public func Lab2XYZ(temp: [Double]? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func Lab2XYZ(temp: [Double]? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -107,43 +107,43 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("Lab2XYZ", options: &opt) + try Self.call("Lab2XYZ", options: &opt) } } /// Unpack a labq image to float lab - public func LabQ2Lab() throws -> VIPSImage { - return try VIPSImage { out in + public func LabQ2Lab() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("LabQ2Lab", options: &opt) + try Self.call("LabQ2Lab", options: &opt) } } /// Convert a labq image to srgb - public func LabQ2sRGB() throws -> VIPSImage { - return try VIPSImage { out in + public func LabQ2sRGB() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("LabQ2sRGB", options: &opt) + try Self.call("LabQ2sRGB", options: &opt) } } /// Transform xyz to cmyk - public func XYZ2CMYK() throws -> VIPSImage { - return try VIPSImage { out in + public func XYZ2CMYK() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("XYZ2CMYK", options: &opt) + try Self.call("XYZ2CMYK", options: &opt) } } @@ -151,8 +151,8 @@ extension VIPSImage { /// /// - Parameters: /// - temp: Colour temperature - public func XYZ2Lab(temp: [Double]? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func XYZ2Lab(temp: [Double]? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -161,43 +161,43 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("XYZ2Lab", options: &opt) + try Self.call("XYZ2Lab", options: &opt) } } /// Transform xyz to yxy - public func XYZ2Yxy() throws -> VIPSImage { - return try VIPSImage { out in + public func XYZ2Yxy() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("XYZ2Yxy", options: &opt) + try Self.call("XYZ2Yxy", options: &opt) } } /// Transform xyz to scrgb - public func XYZ2scRGB() throws -> VIPSImage { - return try VIPSImage { out in + public func XYZ2scRGB() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("XYZ2scRGB", options: &opt) + try Self.call("XYZ2scRGB", options: &opt) } } /// Transform yxy to xyz - public func Yxy2XYZ() throws -> VIPSImage { - return try VIPSImage { out in + public func Yxy2XYZ() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("Yxy2XYZ", options: &opt) + try Self.call("Yxy2XYZ", options: &opt) } } @@ -207,9 +207,9 @@ extension VIPSImage { /// - space: Destination color space /// - sourceSpace: Source color space public func colourspace(space: VipsInterpretation, sourceSpace: VipsInterpretation? = nil) - throws -> VIPSImage + throws -> Self { - return try VIPSImage { out in + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -219,19 +219,19 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("colourspace", options: &opt) + try Self.call("colourspace", options: &opt) } } /// False-color an image - public func falsecolour() throws -> VIPSImage { - return try VIPSImage { out in + public func falsecolour() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("falsecolour", options: &opt) + try Self.call("falsecolour", options: &opt) } } @@ -249,8 +249,8 @@ extension VIPSImage { blackPointCompensation: Bool? = nil, outputProfile: String? = nil, depth: Int? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -271,7 +271,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("icc_export", options: &opt) + try Self.call("icc_export", options: &opt) } } @@ -289,8 +289,8 @@ extension VIPSImage { blackPointCompensation: Bool? = nil, embedded: Bool? = nil, inputProfile: String? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -311,7 +311,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("icc_import", options: &opt) + try Self.call("icc_import", options: &opt) } } @@ -333,8 +333,8 @@ extension VIPSImage { embedded: Bool? = nil, inputProfile: String? = nil, depth: Int? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -359,43 +359,43 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("icc_transform", options: &opt) + try Self.call("icc_transform", options: &opt) } } /// Label regions in an image - public func labelregions() throws -> VIPSImage { - return try VIPSImage { out in + public func labelregions() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("mask", value: &out) - try VIPSImage.call("labelregions", options: &opt) + try Self.call("labelregions", options: &opt) } } /// Transform srgb to hsv - public func sRGB2HSV() throws -> VIPSImage { - return try VIPSImage { out in + public func sRGB2HSV() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("sRGB2HSV", options: &opt) + try Self.call("sRGB2HSV", options: &opt) } } /// Convert an srgb image to scrgb - public func sRGB2scRGB() throws -> VIPSImage { - return try VIPSImage { out in + public func sRGB2scRGB() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("sRGB2scRGB", options: &opt) + try Self.call("sRGB2scRGB", options: &opt) } } @@ -403,8 +403,8 @@ extension VIPSImage { /// /// - Parameters: /// - depth: Output device space depth in bits - public func scRGB2BW(depth: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func scRGB2BW(depth: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -413,19 +413,19 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("scRGB2BW", options: &opt) + try Self.call("scRGB2BW", options: &opt) } } /// Transform scrgb to xyz - public func scRGB2XYZ() throws -> VIPSImage { - return try VIPSImage { out in + public func scRGB2XYZ() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("scRGB2XYZ", options: &opt) + try Self.call("scRGB2XYZ", options: &opt) } } @@ -433,8 +433,8 @@ extension VIPSImage { /// /// - Parameters: /// - depth: Output device space depth in bits - public func scRGB2sRGB(depth: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func scRGB2sRGB(depth: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -443,10 +443,14 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("scRGB2sRGB", options: &opt) + try Self.call("scRGB2sRGB", options: &opt) } } +} + +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { + /// Make an image where pixel values are coordinates /// /// - Parameters: @@ -461,8 +465,8 @@ extension VIPSImage { csize: Int? = nil, dsize: Int? = nil, esize: Int? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -478,7 +482,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("xyz", options: &opt) + try Self.call("xyz", options: &opt) } } diff --git a/Sources/VIPS/Generated/conversion.generated.swift b/Sources/VIPS/Generated/conversion.generated.swift index 3fb456d..29a1160 100644 --- a/Sources/VIPS/Generated/conversion.generated.swift +++ b/Sources/VIPS/Generated/conversion.generated.swift @@ -7,7 +7,7 @@ import Cvips -extension VIPSImage { +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Affine transform of an image /// @@ -33,8 +33,8 @@ extension VIPSImage { background: [Double]? = nil, premultiplied: Bool? = nil, extend: VipsExtend? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -68,86 +68,19 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("affine", options: &opt) - } - } - - /// Join an array of images - /// - /// - Parameters: - /// - `in`: Array of input images - /// - across: Number of images across grid - /// - shim: Pixels between images - /// - background: Colour for new pixels - /// - halign: Align on the left, centre or right - /// - valign: Align on the top, centre or bottom - /// - hspacing: Horizontal spacing between images - /// - vspacing: Vertical spacing between images - public static func arrayjoin( - _ `in`: [VIPSImage], - across: Int? = nil, - shim: Int? = nil, - background: [Double]? = nil, - halign: VipsAlign? = nil, - valign: VipsAlign? = nil, - hspacing: Int? = nil, - vspacing: Int? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("in", value: `in`) - if let across = across { - opt.set("across", value: across) - } - if let shim = shim { - opt.set("shim", value: shim) - } - if let background = background { - opt.set("background", value: background) - } - if let halign = halign { - opt.set("halign", value: halign) - } - if let valign = valign { - opt.set("valign", value: valign) - } - if let hspacing = hspacing { - opt.set("hspacing", value: hspacing) - } - if let vspacing = vspacing { - opt.set("vspacing", value: vspacing) - } - opt.set("out", value: &out) - - try VIPSImage.call("arrayjoin", options: &opt) + try Self.call("affine", options: &opt) } } /// Autorotate image by exif tag - public func autorot() throws -> VIPSImage { - return try VIPSImage { out in + public func autorot() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("autorot", options: &opt) - } - } - - /// Bandwise join a set of images - /// - /// - Parameters: - /// - `in`: Array of input images - public static func bandjoin(_ `in`: [VIPSImage]) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("in", value: `in`) - opt.set("out", value: &out) - - try VIPSImage.call("bandjoin", options: &opt) + try Self.call("autorot", options: &opt) } } @@ -155,34 +88,15 @@ extension VIPSImage { /// /// - Parameters: /// - c: Array of constants to add - public func bandjoinConst(c: [Double]) throws -> VIPSImage { - return try VIPSImage { out in + public func bandjoinConst(c: [Double]) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("c", value: c) opt.set("out", value: &out) - try VIPSImage.call("bandjoin_const", options: &opt) - } - } - - /// Band-wise rank of a set of images - /// - /// - Parameters: - /// - `in`: Array of input images - /// - index: Select this band element from sorted list - public static func bandrank(_ `in`: [VIPSImage], index: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("in", value: `in`) - if let index = index { - opt.set("index", value: index) - } - opt.set("out", value: &out) - - try VIPSImage.call("bandrank", options: &opt) + try Self.call("bandjoin_const", options: &opt) } } @@ -191,8 +105,8 @@ extension VIPSImage { /// - Parameters: /// - format: Format to cast to /// - shift: Shift integer values up and down - public func cast(format: VipsBandFormat, shift: Bool? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func cast(format: VipsBandFormat, shift: Bool? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -202,47 +116,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("cast", options: &opt) - } - } - - /// Blend an array of images with an array of blend modes - /// - /// - Parameters: - /// - `in`: Array of input images - /// - mode: Array of VipsBlendMode to join with - /// - x: Array of x coordinates to join at - /// - y: Array of y coordinates to join at - /// - compositingSpace: Composite images in this colour space - /// - premultiplied: Images have premultiplied alpha - public static func composite( - _ `in`: [VIPSImage], - mode: [Int], - x: [Int]? = nil, - y: [Int]? = nil, - compositingSpace: VipsInterpretation? = nil, - premultiplied: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("in", value: `in`) - opt.set("mode", value: mode) - if let x = x { - opt.set("x", value: x) - } - if let y = y { - opt.set("y", value: y) - } - if let compositingSpace = compositingSpace { - opt.set("compositing_space", value: compositingSpace) - } - if let premultiplied = premultiplied { - opt.set("premultiplied", value: premultiplied) - } - opt.set("out", value: &out) - - try VIPSImage.call("composite", options: &opt) + try Self.call("cast", options: &opt) } } @@ -256,14 +130,14 @@ extension VIPSImage { /// - compositingSpace: Composite images in this colour space /// - premultiplied: Images have premultiplied alpha public func composite2( - overlay: VIPSImage, + overlay: some VIPSImageProtocol, mode: VipsBlendMode, x: Int? = nil, y: Int? = nil, compositingSpace: VipsInterpretation? = nil, premultiplied: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("base", value: self) @@ -283,7 +157,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("composite2", options: &opt) + try Self.call("composite2", options: &opt) } } @@ -311,8 +185,8 @@ extension VIPSImage { yres: Double? = nil, xoffset: Int? = nil, yoffset: Int? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -348,7 +222,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("copy", options: &opt) + try Self.call("copy", options: &opt) } } @@ -359,8 +233,8 @@ extension VIPSImage { /// - top: Top edge of extract area /// - width: Width of extract area /// - height: Height of extract area - public func crop(`left`: Int, top: Int, width: Int, height: Int) throws -> VIPSImage { - return try VIPSImage { out in + public func crop(`left`: Int, top: Int, width: Int, height: Int) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("input", value: self) @@ -370,7 +244,7 @@ extension VIPSImage { opt.set("height", value: height) opt.set("out", value: &out) - try VIPSImage.call("crop", options: &opt) + try Self.call("crop", options: &opt) } } @@ -390,8 +264,8 @@ extension VIPSImage { height: Int, extend: VipsExtend? = nil, background: [Double]? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -407,7 +281,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("embed", options: &opt) + try Self.call("embed", options: &opt) } } @@ -418,8 +292,8 @@ extension VIPSImage { /// - top: Top edge of extract area /// - width: Width of extract area /// - height: Height of extract area - public func extractArea(`left`: Int, top: Int, width: Int, height: Int) throws -> VIPSImage { - return try VIPSImage { out in + public func extractArea(`left`: Int, top: Int, width: Int, height: Int) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("input", value: self) @@ -429,7 +303,7 @@ extension VIPSImage { opt.set("height", value: height) opt.set("out", value: &out) - try VIPSImage.call("extract_area", options: &opt) + try Self.call("extract_area", options: &opt) } } @@ -438,8 +312,8 @@ extension VIPSImage { /// - Parameters: /// - band: Band to extract /// - n: Number of bands to extract - public func extractBand(_ band: Int, n: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func extractBand(_ band: Int, n: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -449,7 +323,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("extract_band", options: &opt) + try Self.call("extract_band", options: &opt) } } @@ -457,15 +331,15 @@ extension VIPSImage { /// /// - Parameters: /// - direction: Direction to flip image - public func flip(direction: VipsDirection) throws -> VIPSImage { - return try VIPSImage { out in + public func flip(direction: VipsDirection) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("direction", value: direction) opt.set("out", value: &out) - try VIPSImage.call("flip", options: &opt) + try Self.call("flip", options: &opt) } } @@ -475,8 +349,8 @@ extension VIPSImage { /// - tileHeight: Chop into tiles this high /// - across: Number of tiles across /// - down: Number of tiles down - public func grid(tileHeight: Int, across: Int, down: Int) throws -> VIPSImage { - return try VIPSImage { out in + public func grid(tileHeight: Int, across: Int, down: Int) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -485,7 +359,7 @@ extension VIPSImage { opt.set("down", value: down) opt.set("out", value: &out) - try VIPSImage.call("grid", options: &opt) + try Self.call("grid", options: &opt) } } @@ -498,13 +372,13 @@ extension VIPSImage { /// - expand: Expand output to hold all of both inputs /// - background: Color for new pixels public func insert( - sub: VIPSImage, + sub: some VIPSImageProtocol, x: Int, y: Int, expand: Bool? = nil, background: [Double]? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("main", value: self) @@ -519,7 +393,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("insert", options: &opt) + try Self.call("insert", options: &opt) } } @@ -533,14 +407,14 @@ extension VIPSImage { /// - background: Colour for new pixels /// - align: Align on the low, centre or high coordinate edge public func join( - in2: VIPSImage, + in2: some VIPSImageProtocol, direction: VipsDirection, expand: Bool? = nil, shim: Int? = nil, background: [Double]? = nil, align: VipsAlign? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in1", value: self) @@ -560,7 +434,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("join", options: &opt) + try Self.call("join", options: &opt) } } @@ -568,15 +442,15 @@ extension VIPSImage { /// /// - Parameters: /// - m: Matrix of coefficients - public func recomb(m: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func recomb(m: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("m", value: m) opt.set("out", value: &out) - try VIPSImage.call("recomb", options: &opt) + try Self.call("recomb", options: &opt) } } @@ -592,8 +466,8 @@ extension VIPSImage { vshrink: Double, kernel: VipsKernel? = nil, gap: Double? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -607,7 +481,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("reduce", options: &opt) + try Self.call("reduce", options: &opt) } } @@ -618,9 +492,9 @@ extension VIPSImage { /// - kernel: Resampling kernel /// - gap: Reducing gap public func reduceh(hshrink: Double, kernel: VipsKernel? = nil, gap: Double? = nil) throws - -> VIPSImage + -> Self { - return try VIPSImage { out in + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -633,7 +507,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("reduceh", options: &opt) + try Self.call("reduceh", options: &opt) } } @@ -644,9 +518,9 @@ extension VIPSImage { /// - kernel: Resampling kernel /// - gap: Reducing gap public func reducev(vshrink: Double, kernel: VipsKernel? = nil, gap: Double? = nil) throws - -> VIPSImage + -> Self { - return try VIPSImage { out in + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -659,7 +533,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("reducev", options: &opt) + try Self.call("reducev", options: &opt) } } @@ -675,8 +549,8 @@ extension VIPSImage { kernel: VipsKernel? = nil, gap: Double? = nil, vscale: Double? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -692,7 +566,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("resize", options: &opt) + try Self.call("resize", options: &opt) } } @@ -700,15 +574,15 @@ extension VIPSImage { /// /// - Parameters: /// - angle: Angle to rotate image - public func rot(angle: VipsAngle) throws -> VIPSImage { - return try VIPSImage { out in + public func rot(angle: VipsAngle) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("angle", value: angle) opt.set("out", value: &out) - try VIPSImage.call("rot", options: &opt) + try Self.call("rot", options: &opt) } } @@ -716,8 +590,8 @@ extension VIPSImage { /// /// - Parameters: /// - angle: Angle to rotate image - public func rot45(angle: VipsAngle45? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func rot45(angle: VipsAngle45? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -726,7 +600,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("rot45", options: &opt) + try Self.call("rot45", options: &opt) } } @@ -748,8 +622,8 @@ extension VIPSImage { ody: Double? = nil, idx: Double? = nil, idy: Double? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -774,7 +648,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("rotate", options: &opt) + try Self.call("rotate", options: &opt) } } @@ -783,8 +657,8 @@ extension VIPSImage { /// - Parameters: /// - exp: Exponent for log scale /// - log: Log scale - public func scale(exp: Double? = nil, log: Bool? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func scale(exp: Double? = nil, log: Bool? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -796,7 +670,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("scale", options: &opt) + try Self.call("scale", options: &opt) } } @@ -806,8 +680,8 @@ extension VIPSImage { /// - hshrink: Horizontal shrink factor /// - vshrink: Vertical shrink factor /// - ceil: Round-up output dimensions - public func shrink(hshrink: Double, vshrink: Double, ceil: Bool? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func shrink(hshrink: Double, vshrink: Double, ceil: Bool? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -818,7 +692,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("shrink", options: &opt) + try Self.call("shrink", options: &opt) } } @@ -827,8 +701,8 @@ extension VIPSImage { /// - Parameters: /// - hshrink: Horizontal shrink factor /// - ceil: Round-up output dimensions - public func shrinkh(hshrink: Int, ceil: Bool? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func shrinkh(hshrink: Int, ceil: Bool? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -838,7 +712,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("shrinkh", options: &opt) + try Self.call("shrinkh", options: &opt) } } @@ -847,8 +721,8 @@ extension VIPSImage { /// - Parameters: /// - vshrink: Vertical shrink factor /// - ceil: Round-up output dimensions - public func shrinkv(vshrink: Int, ceil: Bool? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func shrinkv(vshrink: Int, ceil: Bool? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -858,7 +732,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("shrinkv", options: &opt) + try Self.call("shrinkv", options: &opt) } } @@ -882,8 +756,8 @@ extension VIPSImage { ody: Double? = nil, idx: Double? = nil, idy: Double? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -913,7 +787,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("similarity", options: &opt) + try Self.call("similarity", options: &opt) } } @@ -929,8 +803,8 @@ extension VIPSImage { height: Int, interesting: VipsInteresting? = nil, premultiplied: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("input", value: self) @@ -944,7 +818,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("smartcrop", options: &opt) + try Self.call("smartcrop", options: &opt) } } @@ -964,8 +838,8 @@ extension VIPSImage { access: VipsAccess? = nil, threaded: Bool? = nil, persistent: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -989,7 +863,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("tilecache", options: &opt) + try Self.call("tilecache", options: &opt) } } @@ -997,8 +871,8 @@ extension VIPSImage { /// /// - Parameters: /// - pageHeight: Height of each input page - public func transpose3d(pageHeight: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func transpose3d(pageHeight: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -1007,7 +881,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("transpose3d", options: &opt) + try Self.call("transpose3d", options: &opt) } } @@ -1016,8 +890,8 @@ extension VIPSImage { /// - Parameters: /// - x: Left edge of input in output /// - y: Top edge of input in output - public func wrap(x: Int? = nil, y: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func wrap(x: Int? = nil, y: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -1029,7 +903,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("wrap", options: &opt) + try Self.call("wrap", options: &opt) } } @@ -1038,8 +912,8 @@ extension VIPSImage { /// - Parameters: /// - xfac: Horizontal zoom factor /// - yfac: Vertical zoom factor - public func zoom(xfac: Int, yfac: Int) throws -> VIPSImage { - return try VIPSImage { out in + public func zoom(xfac: Int, yfac: Int) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("input", value: self) @@ -1047,7 +921,137 @@ extension VIPSImage { opt.set("yfac", value: yfac) opt.set("out", value: &out) - try VIPSImage.call("zoom", options: &opt) + try Self.call("zoom", options: &opt) + } + } + +} + +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { + + /// Join an array of images + /// + /// - Parameters: + /// - `in`: Array of input images + /// - across: Number of images across grid + /// - shim: Pixels between images + /// - background: Colour for new pixels + /// - halign: Align on the left, centre or right + /// - valign: Align on the top, centre or bottom + /// - hspacing: Horizontal spacing between images + /// - vspacing: Vertical spacing between images + public static func arrayjoin( + _ `in`: [VIPSImage], + across: Int? = nil, + shim: Int? = nil, + background: [Double]? = nil, + halign: VipsAlign? = nil, + valign: VipsAlign? = nil, + hspacing: Int? = nil, + vspacing: Int? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("in", value: `in`) + if let across = across { + opt.set("across", value: across) + } + if let shim = shim { + opt.set("shim", value: shim) + } + if let background = background { + opt.set("background", value: background) + } + if let halign = halign { + opt.set("halign", value: halign) + } + if let valign = valign { + opt.set("valign", value: valign) + } + if let hspacing = hspacing { + opt.set("hspacing", value: hspacing) + } + if let vspacing = vspacing { + opt.set("vspacing", value: vspacing) + } + opt.set("out", value: &out) + + try Self.call("arrayjoin", options: &opt) + } + } + + /// Bandwise join a set of images + /// + /// - Parameters: + /// - `in`: Array of input images + public static func bandjoin(_ `in`: [VIPSImage]) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("in", value: `in`) + opt.set("out", value: &out) + + try Self.call("bandjoin", options: &opt) + } + } + + /// Band-wise rank of a set of images + /// + /// - Parameters: + /// - `in`: Array of input images + /// - index: Select this band element from sorted list + public static func bandrank(_ `in`: [VIPSImage], index: Int? = nil) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("in", value: `in`) + if let index = index { + opt.set("index", value: index) + } + opt.set("out", value: &out) + + try Self.call("bandrank", options: &opt) + } + } + + /// Blend an array of images with an array of blend modes + /// + /// - Parameters: + /// - `in`: Array of input images + /// - mode: Array of VipsBlendMode to join with + /// - x: Array of x coordinates to join at + /// - y: Array of y coordinates to join at + /// - compositingSpace: Composite images in this colour space + /// - premultiplied: Images have premultiplied alpha + public static func composite( + _ `in`: [VIPSImage], + mode: [Int], + x: [Int]? = nil, + y: [Int]? = nil, + compositingSpace: VipsInterpretation? = nil, + premultiplied: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("in", value: `in`) + opt.set("mode", value: mode) + if let x = x { + opt.set("x", value: x) + } + if let y = y { + opt.set("y", value: y) + } + if let compositingSpace = compositingSpace { + opt.set("compositing_space", value: compositingSpace) + } + if let premultiplied = premultiplied { + opt.set("premultiplied", value: premultiplied) + } + opt.set("out", value: &out) + + try Self.call("composite", options: &opt) } } diff --git a/Sources/VIPS/Generated/convolution.generated.swift b/Sources/VIPS/Generated/convolution.generated.swift index 85681bf..646bcf8 100644 --- a/Sources/VIPS/Generated/convolution.generated.swift +++ b/Sources/VIPS/Generated/convolution.generated.swift @@ -7,15 +7,15 @@ import Cvips -extension VIPSImage { +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Canny edge detector /// /// - Parameters: /// - sigma: Sigma of Gaussian /// - precision: Convolve with this precision - public func canny(sigma: Double? = nil, precision: VipsPrecision? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func canny(sigma: Double? = nil, precision: VipsPrecision? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -27,7 +27,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("canny", options: &opt) + try Self.call("canny", options: &opt) } } @@ -39,12 +39,12 @@ extension VIPSImage { /// - layers: Use this many layers in approximation /// - cluster: Cluster lines closer than this in approximation public func conv( - mask: VIPSImage, + mask: some VIPSImageProtocol, precision: VipsPrecision? = nil, layers: Int? = nil, cluster: Int? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -60,7 +60,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("conv", options: &opt) + try Self.call("conv", options: &opt) } } @@ -70,9 +70,10 @@ extension VIPSImage { /// - mask: Input matrix image /// - layers: Use this many layers in approximation /// - cluster: Cluster lines closer than this in approximation - public func conva(mask: VIPSImage, layers: Int? = nil, cluster: Int? = nil) throws -> VIPSImage + public func conva(mask: some VIPSImageProtocol, layers: Int? = nil, cluster: Int? = nil) throws + -> Self { - return try VIPSImage { out in + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -85,7 +86,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("conva", options: &opt) + try Self.call("conva", options: &opt) } } @@ -94,8 +95,8 @@ extension VIPSImage { /// - Parameters: /// - mask: Input matrix image /// - layers: Use this many layers in approximation - public func convasep(mask: VIPSImage, layers: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func convasep(mask: some VIPSImageProtocol, layers: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -105,7 +106,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("convasep", options: &opt) + try Self.call("convasep", options: &opt) } } @@ -113,15 +114,15 @@ extension VIPSImage { /// /// - Parameters: /// - mask: Input matrix image - public func convf(mask: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func convf(mask: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("mask", value: mask) opt.set("out", value: &out) - try VIPSImage.call("convf", options: &opt) + try Self.call("convf", options: &opt) } } @@ -129,15 +130,15 @@ extension VIPSImage { /// /// - Parameters: /// - mask: Input matrix image - public func convi(mask: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func convi(mask: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("mask", value: mask) opt.set("out", value: &out) - try VIPSImage.call("convi", options: &opt) + try Self.call("convi", options: &opt) } } @@ -149,12 +150,12 @@ extension VIPSImage { /// - layers: Use this many layers in approximation /// - cluster: Cluster lines closer than this in approximation public func convsep( - mask: VIPSImage, + mask: some VIPSImageProtocol, precision: VipsPrecision? = nil, layers: Int? = nil, cluster: Int? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -170,7 +171,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("convsep", options: &opt) + try Self.call("convsep", options: &opt) } } @@ -181,9 +182,9 @@ extension VIPSImage { /// - minAmpl: Minimum amplitude of Gaussian /// - precision: Convolve with this precision public func gaussblur(sigma: Double, minAmpl: Double? = nil, precision: VipsPrecision? = nil) - throws -> VIPSImage + throws -> Self { - return try VIPSImage { out in + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -196,7 +197,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("gaussblur", options: &opt) + try Self.call("gaussblur", options: &opt) } } @@ -216,8 +217,8 @@ extension VIPSImage { y3: Double? = nil, m1: Double? = nil, m2: Double? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -241,19 +242,19 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("sharpen", options: &opt) + try Self.call("sharpen", options: &opt) } } /// Sobel edge detector - public func sobel() throws -> VIPSImage { - return try VIPSImage { out in + public func sobel() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("sobel", options: &opt) + try Self.call("sobel", options: &opt) } } diff --git a/Sources/VIPS/Generated/create.generated.swift b/Sources/VIPS/Generated/create.generated.swift index 8e428bb..42f9341 100644 --- a/Sources/VIPS/Generated/create.generated.swift +++ b/Sources/VIPS/Generated/create.generated.swift @@ -7,7 +7,23 @@ import Cvips -extension VIPSImage { +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { + + /// Build a look-up table + public func buildlut() throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("in", value: self) + opt.set("out", value: &out) + + try Self.call("buildlut", options: &opt) + } + } + +} + +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Make a black image /// @@ -15,8 +31,8 @@ extension VIPSImage { /// - width: Image width in pixels /// - height: Image height in pixels /// - bands: Number of bands in image - public static func black(width: Int, height: Int, bands: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public static func black(width: Int, height: Int, bands: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -26,19 +42,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("black", options: &opt) - } - } - - /// Build a look-up table - public func buildlut() throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("in", value: self) - opt.set("out", value: &out) - - try VIPSImage.call("buildlut", options: &opt) + try Self.call("black", options: &opt) } } @@ -50,9 +54,9 @@ extension VIPSImage { /// - uchar: Output an unsigned char image /// - factor: Maximum spatial frequency public static func eye(width: Int, height: Int, uchar: Bool? = nil, factor: Double? = nil) - throws -> VIPSImage + throws -> Self { - return try VIPSImage { out in + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -65,7 +69,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("eye", options: &opt) + try Self.call("eye", options: &opt) } } @@ -75,10 +79,8 @@ extension VIPSImage { /// - width: Image width in pixels /// - height: Image height in pixels /// - fractalDimension: Fractal dimension - public static func fractsurf(width: Int, height: Int, fractalDimension: Double) throws - -> VIPSImage - { - return try VIPSImage { out in + public static func fractsurf(width: Int, height: Int, fractalDimension: Double) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -86,7 +88,7 @@ extension VIPSImage { opt.set("fractal_dimension", value: fractalDimension) opt.set("out", value: &out) - try VIPSImage.call("fractsurf", options: &opt) + try Self.call("fractsurf", options: &opt) } } @@ -102,8 +104,8 @@ extension VIPSImage { minAmpl: Double, separable: Bool? = nil, precision: VipsPrecision? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("sigma", value: sigma) @@ -116,7 +118,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("gaussmat", options: &opt) + try Self.call("gaussmat", options: &opt) } } @@ -134,8 +136,8 @@ extension VIPSImage { sigma: Double? = nil, mean: Double? = nil, seed: Int? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -151,7 +153,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("gaussnoise", options: &opt) + try Self.call("gaussnoise", options: &opt) } } @@ -161,8 +163,8 @@ extension VIPSImage { /// - width: Image width in pixels /// - height: Image height in pixels /// - uchar: Output an unsigned char image - public static func grey(width: Int, height: Int, uchar: Bool? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public static func grey(width: Int, height: Int, uchar: Bool? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -172,7 +174,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("grey", options: &opt) + try Self.call("grey", options: &opt) } } @@ -183,9 +185,9 @@ extension VIPSImage { /// - ushort: Create a 16-bit LUT /// - size: Size of 16-bit LUT public static func identity(bands: Int? = nil, ushort: Bool? = nil, size: Int? = nil) throws - -> VIPSImage + -> Self { - return try VIPSImage { out in + return try Self { out in var opt = VIPSOption() if let bands = bands { @@ -199,7 +201,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("identity", options: &opt) + try Self.call("identity", options: &opt) } } @@ -215,8 +217,8 @@ extension VIPSImage { minAmpl: Double, separable: Bool? = nil, precision: VipsPrecision? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("sigma", value: sigma) @@ -229,7 +231,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("logmat", options: &opt) + try Self.call("logmat", options: &opt) } } @@ -255,8 +257,8 @@ extension VIPSImage { nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -278,7 +280,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("mask_butterworth", options: &opt) + try Self.call("mask_butterworth", options: &opt) } } @@ -308,8 +310,8 @@ extension VIPSImage { nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -333,7 +335,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("mask_butterworth_band", options: &opt) + try Self.call("mask_butterworth_band", options: &opt) } } @@ -361,8 +363,8 @@ extension VIPSImage { nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -385,7 +387,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("mask_butterworth_ring", options: &opt) + try Self.call("mask_butterworth_ring", options: &opt) } } @@ -407,8 +409,8 @@ extension VIPSImage { nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -428,7 +430,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("mask_fractal", options: &opt) + try Self.call("mask_fractal", options: &opt) } } @@ -452,8 +454,8 @@ extension VIPSImage { nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -474,7 +476,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("mask_gaussian", options: &opt) + try Self.call("mask_gaussian", options: &opt) } } @@ -502,8 +504,8 @@ extension VIPSImage { nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -526,7 +528,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("mask_gaussian_band", options: &opt) + try Self.call("mask_gaussian_band", options: &opt) } } @@ -552,8 +554,8 @@ extension VIPSImage { nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -575,7 +577,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("mask_gaussian_ring", options: &opt) + try Self.call("mask_gaussian_ring", options: &opt) } } @@ -597,8 +599,8 @@ extension VIPSImage { nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -618,7 +620,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("mask_ideal", options: &opt) + try Self.call("mask_ideal", options: &opt) } } @@ -644,8 +646,8 @@ extension VIPSImage { nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -667,7 +669,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("mask_ideal_band", options: &opt) + try Self.call("mask_ideal_band", options: &opt) } } @@ -691,8 +693,8 @@ extension VIPSImage { nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -713,7 +715,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("mask_ideal_ring", options: &opt) + try Self.call("mask_ideal_ring", options: &opt) } } @@ -731,8 +733,8 @@ extension VIPSImage { cellSize: Int? = nil, uchar: Bool? = nil, seed: Int? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -748,7 +750,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("perlin", options: &opt) + try Self.call("perlin", options: &opt) } } @@ -766,8 +768,8 @@ extension VIPSImage { uchar: Bool? = nil, hfreq: Double? = nil, vfreq: Double? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -783,7 +785,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("sines", options: &opt) + try Self.call("sines", options: &opt) } } @@ -813,8 +815,8 @@ extension VIPSImage { fontfile: String? = nil, rgba: Bool? = nil, wrap: VipsTextWrap? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("text", value: text) @@ -850,7 +852,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("text", options: &opt) + try Self.call("text", options: &opt) } } @@ -878,8 +880,8 @@ extension VIPSImage { S: Double? = nil, M: Double? = nil, H: Double? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() if let inMax = inMax { @@ -914,7 +916,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("tonelut", options: &opt) + try Self.call("tonelut", options: &opt) } } @@ -926,9 +928,9 @@ extension VIPSImage { /// - cellSize: Size of Worley cells /// - seed: Random number seed public static func worley(width: Int, height: Int, cellSize: Int? = nil, seed: Int? = nil) - throws -> VIPSImage + throws -> Self { - return try VIPSImage { out in + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -941,7 +943,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("worley", options: &opt) + try Self.call("worley", options: &opt) } } @@ -951,8 +953,8 @@ extension VIPSImage { /// - width: Image width in pixels /// - height: Image height in pixels /// - uchar: Output an unsigned char image - public static func zone(width: Int, height: Int, uchar: Bool? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public static func zone(width: Int, height: Int, uchar: Bool? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("width", value: width) @@ -962,7 +964,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("zone", options: &opt) + try Self.call("zone", options: &opt) } } diff --git a/Sources/VIPS/Generated/freqfilt.generated.swift b/Sources/VIPS/Generated/freqfilt.generated.swift index f5dfb9d..1a9ac05 100644 --- a/Sources/VIPS/Generated/freqfilt.generated.swift +++ b/Sources/VIPS/Generated/freqfilt.generated.swift @@ -7,33 +7,33 @@ import Cvips -extension VIPSImage { +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Frequency-domain filtering /// /// - Parameters: /// - mask: Input mask image - public func freqmult(mask: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func freqmult(mask: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("mask", value: mask) opt.set("out", value: &out) - try VIPSImage.call("freqmult", options: &opt) + try Self.call("freqmult", options: &opt) } } /// Forward fft - public func fwfft() throws -> VIPSImage { - return try VIPSImage { out in + public func fwfft() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("fwfft", options: &opt) + try Self.call("fwfft", options: &opt) } } @@ -41,8 +41,8 @@ extension VIPSImage { /// /// - Parameters: /// - real: Output only the real part of the transform - public func invfft(real: Bool? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func invfft(real: Bool? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -51,7 +51,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("invfft", options: &opt) + try Self.call("invfft", options: &opt) } } diff --git a/Sources/VIPS/Generated/histogram.generated.swift b/Sources/VIPS/Generated/histogram.generated.swift index 7a08637..4b3e187 100644 --- a/Sources/VIPS/Generated/histogram.generated.swift +++ b/Sources/VIPS/Generated/histogram.generated.swift @@ -7,17 +7,17 @@ import Cvips -extension VIPSImage { +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Form cumulative histogram - public func histCum() throws -> VIPSImage { - return try VIPSImage { out in + public func histCum() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("hist_cum", options: &opt) + try Self.call("hist_cum", options: &opt) } } @@ -30,7 +30,7 @@ extension VIPSImage { opt.set("in", value: self.image) opt.set("out", value: &out) - try VIPSImage.call("hist_entropy", options: &opt) + try Self.call("hist_entropy", options: &opt) return out } @@ -39,8 +39,8 @@ extension VIPSImage { /// /// - Parameters: /// - band: Equalise with this band - public func histEqual(band: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func histEqual(band: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -49,7 +49,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("hist_equal", options: &opt) + try Self.call("hist_equal", options: &opt) } } @@ -57,8 +57,8 @@ extension VIPSImage { /// /// - Parameters: /// - band: Find histogram of band - public func histFind(band: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func histFind(band: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -67,7 +67,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("hist_find", options: &opt) + try Self.call("hist_find", options: &opt) } } @@ -76,8 +76,10 @@ extension VIPSImage { /// - Parameters: /// - index: Index image /// - combine: Combine bins like this - public func histFindIndexed(index: VIPSImage, combine: VipsCombine? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func histFindIndexed(index: some VIPSImageProtocol, combine: VipsCombine? = nil) throws + -> Self + { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -87,7 +89,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("hist_find_indexed", options: &opt) + try Self.call("hist_find_indexed", options: &opt) } } @@ -95,8 +97,8 @@ extension VIPSImage { /// /// - Parameters: /// - bins: Number of bins in each dimension - public func histFindNdim(bins: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func histFindNdim(bins: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -105,7 +107,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("hist_find_ndim", options: &opt) + try Self.call("hist_find_ndim", options: &opt) } } @@ -118,7 +120,7 @@ extension VIPSImage { opt.set("in", value: self.image) opt.set("monotonic", value: &out) - try VIPSImage.call("hist_ismonotonic", options: &opt) + try Self.call("hist_ismonotonic", options: &opt) return out } @@ -129,8 +131,8 @@ extension VIPSImage { /// - width: Window width in pixels /// - height: Window height in pixels /// - maxSlope: Maximum slope (CLAHE) - public func histLocal(width: Int, height: Int, maxSlope: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func histLocal(width: Int, height: Int, maxSlope: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -141,7 +143,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("hist_local", options: &opt) + try Self.call("hist_local", options: &opt) } } @@ -149,39 +151,39 @@ extension VIPSImage { /// /// - Parameters: /// - ref: Reference histogram - public func histMatch(ref: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func histMatch(ref: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("ref", value: ref) opt.set("out", value: &out) - try VIPSImage.call("hist_match", options: &opt) + try Self.call("hist_match", options: &opt) } } /// Normalise histogram - public func histNorm() throws -> VIPSImage { - return try VIPSImage { out in + public func histNorm() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("hist_norm", options: &opt) + try Self.call("hist_norm", options: &opt) } } /// Plot histogram - public func histPlot() throws -> VIPSImage { - return try VIPSImage { out in + public func histPlot() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("hist_plot", options: &opt) + try Self.call("hist_plot", options: &opt) } } @@ -192,9 +194,9 @@ extension VIPSImage { /// - minRadius: Smallest radius to search for /// - maxRadius: Largest radius to search for public func houghCircle(scale: Int? = nil, minRadius: Int? = nil, maxRadius: Int? = nil) throws - -> VIPSImage + -> Self { - return try VIPSImage { out in + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -209,7 +211,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("hough_circle", options: &opt) + try Self.call("hough_circle", options: &opt) } } @@ -218,8 +220,8 @@ extension VIPSImage { /// - Parameters: /// - width: Horizontal size of parameter space /// - height: Vertical size of parameter space - public func houghLine(width: Int? = nil, height: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func houghLine(width: Int? = nil, height: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -231,7 +233,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("hough_line", options: &opt) + try Self.call("hough_line", options: &opt) } } @@ -239,27 +241,27 @@ extension VIPSImage { /// /// - Parameters: /// - in2: Second input image - public func phasecor(in2: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func phasecor(in2: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("in2", value: in2) opt.set("out", value: &out) - try VIPSImage.call("phasecor", options: &opt) + try Self.call("phasecor", options: &opt) } } /// Make displayable power spectrum - public func spectrum() throws -> VIPSImage { - return try VIPSImage { out in + public func spectrum() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("spectrum", options: &opt) + try Self.call("spectrum", options: &opt) } } diff --git a/Sources/VIPS/Generated/misc.generated.swift b/Sources/VIPS/Generated/misc.generated.swift index 848f825..6f35cfb 100644 --- a/Sources/VIPS/Generated/misc.generated.swift +++ b/Sources/VIPS/Generated/misc.generated.swift @@ -8,21 +8,21 @@ import Cvips import CvipsShim -extension VIPSImage { +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Boolean operation across image bands /// /// - Parameters: /// - boolean: Boolean to perform - public func bandbool(boolean: VipsOperationBoolean) throws -> VIPSImage { - return try VIPSImage { out in + public func bandbool(boolean: VipsOperationBoolean) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("boolean", value: boolean) opt.set("out", value: &out) - try VIPSImage.call("bandbool", options: &opt) + try Self.call("bandbool", options: &opt) } } @@ -30,8 +30,8 @@ extension VIPSImage { /// /// - Parameters: /// - factor: Fold by this factor - public func bandfold(factor: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func bandfold(factor: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -40,19 +40,19 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("bandfold", options: &opt) + try Self.call("bandfold", options: &opt) } } /// Band-wise average - public func bandmean() throws -> VIPSImage { - return try VIPSImage { out in + public func bandmean() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("bandmean", options: &opt) + try Self.call("bandmean", options: &opt) } } @@ -60,8 +60,8 @@ extension VIPSImage { /// /// - Parameters: /// - factor: Unfold by this factor - public func bandunfold(factor: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func bandunfold(factor: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -70,19 +70,19 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("bandunfold", options: &opt) + try Self.call("bandunfold", options: &opt) } } /// Byteswap an image - public func byteswap() throws -> VIPSImage { - return try VIPSImage { out in + public func byteswap() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("byteswap", options: &opt) + try Self.call("byteswap", options: &opt) } } @@ -90,15 +90,15 @@ extension VIPSImage { /// /// - Parameters: /// - cases: Array of case images - public func `case`(cases: [VIPSImage]) throws -> VIPSImage { - return try VIPSImage { out in + public func `case`(cases: [VIPSImage]) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("index", value: self) opt.set("cases", value: cases) opt.set("out", value: &out) - try VIPSImage.call("case", options: &opt) + try Self.call("case", options: &opt) } } @@ -107,8 +107,8 @@ extension VIPSImage { /// - Parameters: /// - min: Minimum value /// - max: Maximum value - public func clamp(min: Double? = nil, max: Double? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func clamp(min: Double? = nil, max: Double? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -120,7 +120,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("clamp", options: &opt) + try Self.call("clamp", options: &opt) } } @@ -135,15 +135,15 @@ extension VIPSImage { /// - layers: Use this many layers in approximation /// - cluster: Cluster lines closer than this in approximation public func compass( - mask: VIPSImage, + mask: some VIPSImageProtocol, times: Int? = nil, angle: VipsAngle45? = nil, combine: VipsCombine? = nil, precision: VipsPrecision? = nil, layers: Int? = nil, cluster: Int? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -168,7 +168,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("compass", options: &opt) + try Self.call("compass", options: &opt) } } @@ -176,15 +176,15 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand input image - public func dE00(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func dE00(_ rhs: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) opt.set("right", value: rhs) opt.set("out", value: &out) - try VIPSImage.call("dE00", options: &opt) + try Self.call("dE00", options: &opt) } } @@ -192,15 +192,15 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand input image - public func dE76(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func dE76(_ rhs: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) opt.set("right", value: rhs) opt.set("out", value: &out) - try VIPSImage.call("dE76", options: &opt) + try Self.call("dE76", options: &opt) } } @@ -208,15 +208,15 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand input image - public func dECMC(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func dECMC(_ rhs: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) opt.set("right", value: rhs) opt.set("out", value: &out) - try VIPSImage.call("dECMC", options: &opt) + try Self.call("dECMC", options: &opt) } } @@ -224,27 +224,27 @@ extension VIPSImage { /// /// - Parameters: /// - ref: Input reference image - public func fastcor(ref: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func fastcor(ref: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("ref", value: ref) opt.set("out", value: &out) - try VIPSImage.call("fastcor", options: &opt) + try Self.call("fastcor", options: &opt) } } /// Fill image zeros with nearest non-zero pixel - public func fillNearest() throws -> VIPSImage { - return try VIPSImage { out in + public func fillNearest() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("fill_nearest", options: &opt) + try Self.call("fill_nearest", options: &opt) } } @@ -275,7 +275,7 @@ extension VIPSImage { } opt.set("left", value: &out) - try VIPSImage.call("find_trim", options: &opt) + try Self.call("find_trim", options: &opt) return out } @@ -285,8 +285,8 @@ extension VIPSImage { /// - Parameters: /// - background: Background value /// - maxAlpha: Maximum value of alpha channel - public func flatten(background: [Double]? = nil, maxAlpha: Double? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func flatten(background: [Double]? = nil, maxAlpha: Double? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -298,19 +298,19 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("flatten", options: &opt) + try Self.call("flatten", options: &opt) } } /// Transform float rgb to radiance coding - public func float2rad() throws -> VIPSImage { - return try VIPSImage { out in + public func float2rad() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("float2rad", options: &opt) + try Self.call("float2rad", options: &opt) } } @@ -318,8 +318,8 @@ extension VIPSImage { /// /// - Parameters: /// - exponent: Gamma factor - public func gamma(exponent: Double? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func gamma(exponent: Double? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -328,7 +328,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("gamma", options: &opt) + try Self.call("gamma", options: &opt) } } @@ -351,7 +351,7 @@ extension VIPSImage { } opt.set("out_array", value: &out) - try VIPSImage.call("getpoint", options: &opt) + try Self.call("getpoint", options: &opt) guard let out else { throw VIPSError("getpoint: no output") @@ -372,8 +372,8 @@ extension VIPSImage { /// - Parameters: /// - gamma: Image gamma /// - intOutput: Integer output - public func globalbalance(gamma: Double? = nil, intOutput: Bool? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func globalbalance(gamma: Double? = nil, intOutput: Bool? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -385,7 +385,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("globalbalance", options: &opt) + try Self.call("globalbalance", options: &opt) } } @@ -403,8 +403,8 @@ extension VIPSImage { height: Int, extend: VipsExtend? = nil, background: [Double]? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -419,7 +419,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("gravity", options: &opt) + try Self.call("gravity", options: &opt) } } @@ -429,8 +429,12 @@ extension VIPSImage { /// - in1: Source for TRUE pixels /// - in2: Source for FALSE pixels /// - blend: Blend smoothly between then and else parts - public func ifthenelse(in1: VIPSImage, in2: VIPSImage, blend: Bool? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func ifthenelse( + in1: some VIPSImageProtocol, + in2: some VIPSImageProtocol, + blend: Bool? = nil + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("cond", value: self) @@ -441,7 +445,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("ifthenelse", options: &opt) + try Self.call("ifthenelse", options: &opt) } } @@ -457,8 +461,8 @@ extension VIPSImage { access: VipsAccess? = nil, threaded: Bool? = nil, persistent: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -476,7 +480,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("linecache", options: &opt) + try Self.call("linecache", options: &opt) } } @@ -485,8 +489,8 @@ extension VIPSImage { /// - Parameters: /// - lut: Look-up table image /// - band: Apply one-band lut to this band of in - public func maplut(lut: VIPSImage, band: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func maplut(lut: some VIPSImageProtocol, band: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -496,7 +500,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("maplut", options: &opt) + try Self.call("maplut", options: &opt) } } @@ -517,7 +521,7 @@ extension VIPSImage { /// - search: Search to improve tie-points /// - interpolate: Interpolate pixels with this public func match( - sec: VIPSImage, + sec: some VIPSImageProtocol, xr1: Int, yr1: Int, xs1: Int, @@ -530,8 +534,8 @@ extension VIPSImage { harea: Int? = nil, search: Bool? = nil, interpolate: VIPSInterpolate? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("ref", value: self) @@ -558,7 +562,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("match", options: &opt) + try Self.call("match", options: &opt) } } @@ -578,8 +582,8 @@ extension VIPSImage { top: Int? = nil, width: Int? = nil, height: Int? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -599,7 +603,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("measure", options: &opt) + try Self.call("measure", options: &opt) } } @@ -612,13 +616,13 @@ extension VIPSImage { /// - dy: Vertical displacement from sec to ref /// - mblend: Maximum blend size public func merge( - sec: VIPSImage, + sec: some VIPSImageProtocol, direction: VipsDirection, dx: Int, dy: Int, mblend: Int? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("ref", value: self) @@ -631,7 +635,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("merge", options: &opt) + try Self.call("merge", options: &opt) } } @@ -649,7 +653,7 @@ extension VIPSImage { /// - mblend: Maximum blend size /// - bandno: Band to search for features on public func mosaic( - sec: VIPSImage, + sec: some VIPSImageProtocol, direction: VipsDirection, xref: Int, yref: Int, @@ -659,8 +663,8 @@ extension VIPSImage { harea: Int? = nil, mblend: Int? = nil, bandno: Int? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("ref", value: self) @@ -684,7 +688,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("mosaic", options: &opt) + try Self.call("mosaic", options: &opt) } } @@ -707,7 +711,7 @@ extension VIPSImage { /// - interpolate: Interpolate pixels with this /// - mblend: Maximum blend size public func mosaic1( - sec: VIPSImage, + sec: some VIPSImageProtocol, direction: VipsDirection, xr1: Int, yr1: Int, @@ -722,8 +726,8 @@ extension VIPSImage { search: Bool? = nil, interpolate: VIPSInterpolate? = nil, mblend: Int? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("ref", value: self) @@ -754,7 +758,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("mosaic1", options: &opt) + try Self.call("mosaic1", options: &opt) } } @@ -762,8 +766,8 @@ extension VIPSImage { /// /// - Parameters: /// - band: Band to msb - public func msb(band: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func msb(band: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -772,7 +776,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("msb", options: &opt) + try Self.call("msb", options: &opt) } } @@ -789,32 +793,32 @@ extension VIPSImage { opt.set("percent", value: percent) opt.set("threshold", value: &out) - try VIPSImage.call("percent", options: &opt) + try Self.call("percent", options: &opt) return out } /// Prewitt edge detector - public func prewitt() throws -> VIPSImage { - return try VIPSImage { out in + public func prewitt() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("prewitt", options: &opt) + try Self.call("prewitt", options: &opt) } } /// Unpack radiance coding to float rgb - public func rad2float() throws -> VIPSImage { - return try VIPSImage { out in + public func rad2float() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("rad2float", options: &opt) + try Self.call("rad2float", options: &opt) } } @@ -823,8 +827,8 @@ extension VIPSImage { /// - Parameters: /// - oldStr: Search for this string /// - newStr: And swap for this string - public func remosaic(oldStr: String, newStr: String) throws -> VIPSImage { - return try VIPSImage { out in + public func remosaic(oldStr: String, newStr: String) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -832,7 +836,7 @@ extension VIPSImage { opt.set("new_str", value: newStr) opt.set("out", value: &out) - try VIPSImage.call("remosaic", options: &opt) + try Self.call("remosaic", options: &opt) } } @@ -841,8 +845,8 @@ extension VIPSImage { /// - Parameters: /// - across: Repeat this many times horizontally /// - down: Repeat this many times vertically - public func replicate(across: Int, down: Int) throws -> VIPSImage { - return try VIPSImage { out in + public func replicate(across: Int, down: Int) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -850,73 +854,28 @@ extension VIPSImage { opt.set("down", value: down) opt.set("out", value: &out) - try VIPSImage.call("replicate", options: &opt) + try Self.call("replicate", options: &opt) } } /// Scharr edge detector - public func scharr() throws -> VIPSImage { - return try VIPSImage { out in + public func scharr() throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("out", value: &out) - try VIPSImage.call("scharr", options: &opt) + try Self.call("scharr", options: &opt) } } - #if SHIM_VIPS_VERSION_8_17 - /// Create an sdf image - /// - /// - Parameters: - /// - width: Image width in pixels - /// - height: Image height in pixels - /// - shape: SDF shape to create - /// - r: Radius - /// - a: Point a - /// - b: Point b - /// - corners: Corner radii - public static func sdf( - width: Int, - height: Int, - shape: VipsSdfShape, - r: Double? = nil, - a: [Double]? = nil, - b: [Double]? = nil, - corners: [Double]? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("width", value: width) - opt.set("height", value: height) - opt.set("shape", value: shape) - if let r = r { - opt.set("r", value: r) - } - if let a = a { - opt.set("a", value: a) - } - if let b = b { - opt.set("b", value: b) - } - if let corners = corners { - opt.set("corners", value: corners) - } - opt.set("out", value: &out) - - try VIPSImage.call("sdf", options: &opt) - } - } - #endif - /// Check sequential access /// /// - Parameters: /// - tileHeight: Tile height in pixels - public func sequential(tileHeight: Int? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func sequential(tileHeight: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -925,7 +884,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("sequential", options: &opt) + try Self.call("sequential", options: &opt) } } @@ -933,15 +892,15 @@ extension VIPSImage { /// /// - Parameters: /// - ref: Input reference image - public func spcor(ref: VIPSImage) throws -> VIPSImage { - return try VIPSImage { out in + public func spcor(ref: some VIPSImageProtocol) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) opt.set("ref", value: ref) opt.set("out", value: &out) - try VIPSImage.call("spcor", options: &opt) + try Self.call("spcor", options: &opt) } } @@ -961,8 +920,8 @@ extension VIPSImage { b: Double? = nil, m0: Double? = nil, a: Double? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -982,7 +941,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("stdif", options: &opt) + try Self.call("stdif", options: &opt) } } @@ -992,8 +951,8 @@ extension VIPSImage { /// - xfac: Horizontal subsample factor /// - yfac: Vertical subsample factor /// - point: Point sample - public func subsample(xfac: Int, yfac: Int, point: Bool? = nil) throws -> VIPSImage { - return try VIPSImage { out in + public func subsample(xfac: Int, yfac: Int, point: Bool? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("input", value: self) @@ -1004,22 +963,71 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("subsample", options: &opt) + try Self.call("subsample", options: &opt) } } +} + +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { + + #if SHIM_VIPS_VERSION_8_17 + /// Create an sdf image + /// + /// - Parameters: + /// - width: Image width in pixels + /// - height: Image height in pixels + /// - shape: SDF shape to create + /// - r: Radius + /// - a: Point a + /// - b: Point b + /// - corners: Corner radii + public static func sdf( + width: Int, + height: Int, + shape: VipsSdfShape, + r: Double? = nil, + a: [Double]? = nil, + b: [Double]? = nil, + corners: [Double]? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("width", value: width) + opt.set("height", value: height) + opt.set("shape", value: shape) + if let r = r { + opt.set("r", value: r) + } + if let a = a { + opt.set("a", value: a) + } + if let b = b { + opt.set("b", value: b) + } + if let corners = corners { + opt.set("corners", value: corners) + } + opt.set("out", value: &out) + + try Self.call("sdf", options: &opt) + } + } + #endif + /// Find the index of the first non-zero pixel in tests /// /// - Parameters: /// - tests: Table of images to test - public static func `switch`(tests: [VIPSImage]) throws -> VIPSImage { - return try VIPSImage { out in + public static func `switch`(tests: [VIPSImage]) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("tests", value: tests) opt.set("out", value: &out) - try VIPSImage.call("switch", options: &opt) + try Self.call("switch", options: &opt) } } diff --git a/Sources/VIPS/Generated/morphology.generated.swift b/Sources/VIPS/Generated/morphology.generated.swift index 2c92c5d..799e5e9 100644 --- a/Sources/VIPS/Generated/morphology.generated.swift +++ b/Sources/VIPS/Generated/morphology.generated.swift @@ -7,7 +7,7 @@ import Cvips -extension VIPSImage { +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Count lines in an image /// @@ -22,7 +22,7 @@ extension VIPSImage { opt.set("direction", value: direction) opt.set("nolines", value: &out) - try VIPSImage.call("countlines", options: &opt) + try Self.call("countlines", options: &opt) return out } @@ -32,8 +32,8 @@ extension VIPSImage { /// - Parameters: /// - mask: Input matrix image /// - morph: Morphological operation to perform - public func morph(mask: VIPSImage, morph: VipsOperationMorphology) throws -> VIPSImage { - return try VIPSImage { out in + public func morph(mask: some VIPSImageProtocol, morph: VipsOperationMorphology) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -41,7 +41,7 @@ extension VIPSImage { opt.set("morph", value: morph) opt.set("out", value: &out) - try VIPSImage.call("morph", options: &opt) + try Self.call("morph", options: &opt) } } @@ -51,8 +51,8 @@ extension VIPSImage { /// - width: Window width in pixels /// - height: Window height in pixels /// - index: Select pixel at index - public func rank(width: Int, height: Int, index: Int) throws -> VIPSImage { - return try VIPSImage { out in + public func rank(width: Int, height: Int, index: Int) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -61,7 +61,7 @@ extension VIPSImage { opt.set("index", value: index) opt.set("out", value: &out) - try VIPSImage.call("rank", options: &opt) + try Self.call("rank", options: &opt) } } diff --git a/Sources/VIPS/Generated/resample.generated.swift b/Sources/VIPS/Generated/resample.generated.swift index 36d3300..a9e38cd 100644 --- a/Sources/VIPS/Generated/resample.generated.swift +++ b/Sources/VIPS/Generated/resample.generated.swift @@ -8,7 +8,7 @@ import Cvips import CvipsShim -extension VIPSImage { +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { /// Resample with a map image /// @@ -19,13 +19,13 @@ extension VIPSImage { /// - premultiplied: Images have premultiplied alpha /// - extend: How to generate the extra pixels public func mapim( - index: VIPSImage, + index: some VIPSImageProtocol, interpolate: VIPSInterpolate? = nil, background: [Double]? = nil, premultiplied: Bool? = nil, extend: VipsExtend? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -44,7 +44,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("mapim", options: &opt) + try Self.call("mapim", options: &opt) } } @@ -53,9 +53,10 @@ extension VIPSImage { /// - Parameters: /// - coeff: Coefficient matrix /// - interpolate: Interpolate values with this - public func quadratic(coeff: VIPSImage, interpolate: VIPSInterpolate? = nil) throws -> VIPSImage + public func quadratic(coeff: some VIPSImageProtocol, interpolate: VIPSInterpolate? = nil) throws + -> Self { - return try VIPSImage { out in + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -65,10 +66,77 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("quadratic", options: &opt) + try Self.call("quadratic", options: &opt) } } + /// Generate thumbnail from image + /// + /// - Parameters: + /// - width: Size to this width + /// - height: Size to this height + /// - size: Only upsize, only downsize, or both + /// - noRotate: Don't use orientation tags to rotate image upright + /// - crop: Reduce to fill target rectangle, then crop + /// - linear: Reduce in linear light + /// - inputProfile: Fallback input profile + /// - outputProfile: Fallback output profile + /// - intent: Rendering intent + /// - failOn: Error level to fail on + public func thumbnailImage( + width: Int, + height: Int? = nil, + size: VipsSize? = nil, + noRotate: Bool? = nil, + crop: VipsInteresting? = nil, + linear: Bool? = nil, + inputProfile: String? = nil, + outputProfile: String? = nil, + intent: VipsIntent? = nil, + failOn: VipsFailOn? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() + + opt.set("in", value: self) + opt.set("width", value: width) + if let height = height { + opt.set("height", value: height) + } + if let size = size { + opt.set("size", value: size) + } + if let noRotate = noRotate { + opt.set("no_rotate", value: noRotate) + } + if let crop = crop { + opt.set("crop", value: crop) + } + if let linear = linear { + opt.set("linear", value: linear) + } + if let inputProfile = inputProfile { + opt.set("input_profile", value: inputProfile) + } + if let outputProfile = outputProfile { + opt.set("output_profile", value: outputProfile) + } + if let intent = intent { + opt.set("intent", value: intent) + } + if let failOn = failOn { + opt.set("fail_on", value: failOn) + } + opt.set("out", value: &out) + + try Self.call("thumbnail_image", options: &opt) + } + } + +} + +extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ { + /// Generate thumbnail from file /// /// - Parameters: @@ -95,8 +163,8 @@ extension VIPSImage { outputProfile: String? = nil, intent: VipsIntent? = nil, failOn: VipsFailOn? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -130,7 +198,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("thumbnail", options: &opt) + try Self.call("thumbnail", options: &opt) } } @@ -163,10 +231,10 @@ extension VIPSImage { outputProfile: String? = nil, intent: VipsIntent? = nil, failOn: VipsFailOn? = nil - ) throws -> VIPSImage { + ) throws -> Self { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage { out in + try Self { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -203,74 +271,11 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("thumbnail_buffer", options: &opt) + try Self.call("thumbnail_buffer", options: &opt) } } } - /// Generate thumbnail from image - /// - /// - Parameters: - /// - width: Size to this width - /// - height: Size to this height - /// - size: Only upsize, only downsize, or both - /// - noRotate: Don't use orientation tags to rotate image upright - /// - crop: Reduce to fill target rectangle, then crop - /// - linear: Reduce in linear light - /// - inputProfile: Fallback input profile - /// - outputProfile: Fallback output profile - /// - intent: Rendering intent - /// - failOn: Error level to fail on - public func thumbnailImage( - width: Int, - height: Int? = nil, - size: VipsSize? = nil, - noRotate: Bool? = nil, - crop: VipsInteresting? = nil, - linear: Bool? = nil, - inputProfile: String? = nil, - outputProfile: String? = nil, - intent: VipsIntent? = nil, - failOn: VipsFailOn? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in - var opt = VIPSOption() - - opt.set("in", value: self) - opt.set("width", value: width) - if let height = height { - opt.set("height", value: height) - } - if let size = size { - opt.set("size", value: size) - } - if let noRotate = noRotate { - opt.set("no_rotate", value: noRotate) - } - if let crop = crop { - opt.set("crop", value: crop) - } - if let linear = linear { - opt.set("linear", value: linear) - } - if let inputProfile = inputProfile { - opt.set("input_profile", value: inputProfile) - } - if let outputProfile = outputProfile { - opt.set("output_profile", value: outputProfile) - } - if let intent = intent { - opt.set("intent", value: intent) - } - if let failOn = failOn { - opt.set("fail_on", value: failOn) - } - opt.set("out", value: &out) - - try VIPSImage.call("thumbnail_image", options: &opt) - } - } - /// Generate thumbnail from source /// /// - Parameters: @@ -299,8 +304,8 @@ extension VIPSImage { outputProfile: String? = nil, intent: VipsIntent? = nil, failOn: VipsFailOn? = nil - ) throws -> VIPSImage { - return try VIPSImage { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("source", value: source) @@ -337,7 +342,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("thumbnail_source", options: &opt) + try Self.call("thumbnail_source", options: &opt) } } diff --git a/Tests/VIPSTests/TestSetup.swift b/Tests/VIPSTests/TestSetup.swift index 1891b6d..2d2d6ae 100644 --- a/Tests/VIPSTests/TestSetup.swift +++ b/Tests/VIPSTests/TestSetup.swift @@ -21,14 +21,14 @@ enum TestSetup { struct VIPSTestScopeProvider: TestScoping { func provideScope(for test: Test, testCase: Test.Case?, performing function: @Sendable () async throws -> Void) async throws { // Ensure VIPS is started before running any test - TestSetup.ensureSetup() - //try VIPS.start() + //TestSetup.ensureSetup() + try VIPS.start() // Run the test try await function() // stop vips - //VIPS.shutdown() + VIPS.shutdown() } } diff --git a/tools/generate-swift-wrappers.py b/tools/generate-swift-wrappers.py index a6e1306..39da304 100644 --- a/tools/generate-swift-wrappers.py +++ b/tools/generate-swift-wrappers.py @@ -43,7 +43,7 @@ GValue.gdouble_type: 'Double', GValue.gstr_type: 'String', GValue.refstr_type: 'String', - GValue.image_type: 'VIPSImage', + GValue.image_type: 'some VIPSImageProtocol', GValue.source_type: 'VIPSSource', GValue.target_type: 'VIPSTarget', GValue.guint64_type: 'UInt64', @@ -257,7 +257,7 @@ def generate_simple_const_overloads(base_operation_name, const_operation_name): result.append(" ///") result.append(" /// - Parameters:") result.append(" /// - value: Constant value") - result.append(f" public func {func_name}(_ value: Double) throws -> VIPSImage {{") + result.append(f" public func {func_name}(_ value: Double) throws -> Self {{") result.append(f" return try {snake_to_camel(const_operation_name)}(c: [value])") result.append(" }") overloads.append("\n".join(result)) @@ -268,7 +268,7 @@ def generate_simple_const_overloads(base_operation_name, const_operation_name): result.append(" ///") result.append(" /// - Parameters:") result.append(" /// - value: Constant value") - result.append(f" public func {func_name}(_ value: Int) throws -> VIPSImage {{") + result.append(f" public func {func_name}(_ value: Int) throws -> Self {{") result.append(f" return try {snake_to_camel(const_operation_name)}(c: [Double(value)])") result.append(" }") overloads.append("\n".join(result)) @@ -372,11 +372,11 @@ def generate_const_overload(base_operation_name, const_operation_name): if params: signature += f", {', '.join(params)}" - signature += ") throws -> VIPSImage {" + signature += ") throws -> Self {" result.append(signature) # Generate method body - call the const operation directly - result.append(" return try VIPSImage { out in") + result.append(" return try Self { out in") result.append(" var opt = VIPSOption()") result.append("") result.append(f' opt.set("{const_intro.member_x}", value: self)') @@ -393,7 +393,7 @@ def generate_const_overload(base_operation_name, const_operation_name): result.append(' opt.set("out", value: &out)') result.append("") - result.append(f' try VIPSImage.call("{const_operation_name}", options: &opt)') + result.append(f' try Self.call("{const_operation_name}", options: &opt)') result.append(" }") result.append(" }") @@ -493,7 +493,7 @@ def generate_unsafe_buffer_overload(operation_name): swift_type = get_swift_type(details['type']) params.append(f"{param_name}: {swift_type}? = nil") - signature += f"{', '.join(params)}) throws -> VIPSImage {{" + signature += f"{', '.join(params)}) throws -> Self {{" # Add @inlinable decorator result.append(" @inlinable") @@ -622,7 +622,7 @@ def generate_collection_uint8_overload(operation_name): swift_type = get_swift_type(details['type']) params.append(f"{param_name}: {swift_type}? = nil") - signature += f"{', '.join(params)}) throws -> VIPSImage {{" + signature += f"{', '.join(params)}) throws -> Self {{" # Add @inlinable decorator result.append(" @inlinable") @@ -782,7 +782,7 @@ def generate_vipsblob_overload(operation_name): params.append(f"{param_name}: {swift_type} = nil") signature += ", ".join(params) - signature += ") throws -> VIPSImage {" + signature += ") throws -> Self {" # Add @inlinable decorator result.append(" @inlinable") @@ -793,7 +793,7 @@ def generate_vipsblob_overload(operation_name): result.append(" // the operation will retain the blob") result.append(f" try {blob_param_swift}.withVipsBlob {{ blob in") - result.append(" try VIPSImage { out in") + result.append(" try Self { out in") result.append(" var opt = VIPSOption()") result.append("") result.append(f' opt.set("{blob_param_name}", value: blob)') @@ -821,7 +821,7 @@ def generate_vipsblob_overload(operation_name): result.append(' opt.set("out", value: &out)') result.append("") - result.append(f' try VIPSImage.call("{operation_name}", options: &opt)') + result.append(f' try Self.call("{operation_name}", options: &opt)') result.append(" }") result.append(" }") result.append(" }") @@ -972,7 +972,7 @@ def generate_swift_operation(operation_name): # Add return type if has_image_output: - signature += " -> VIPSImage" + signature += " -> Self" elif has_output: # Handle other output types output_type = get_swift_type(intro.details[required_output[0]]['type']) @@ -1014,11 +1014,11 @@ def generate_swift_operation(operation_name): blob_param_swift = swiftize_param(blob_param_name) result.append(f" // the operation will retain the blob") result.append(f" try {blob_param_swift}.withVipsBlob {{ blob in") - result.append(" try VIPSImage { out in") + result.append(" try Self { out in") result.append(" var opt = VIPSOption()") result.append("") else: - result.append(" return try VIPSImage { out in") + result.append(" return try Self { out in") result.append(" var opt = VIPSOption()") result.append("") elif has_output and not has_image_output: @@ -1126,7 +1126,7 @@ def generate_swift_operation(operation_name): pass result.append("") - result.append(f' try VIPSImage.call("{operation_name}", options: &opt)') + result.append(f' try Self.call("{operation_name}", options: &opt)') if has_image_output: # Check if we have blob parameters that need special closing @@ -1302,6 +1302,15 @@ def write_category_file(category, operations, output_dir): has_buffer_operations = True break + # Separate operations into static and instance methods + static_operations = [] + instance_operations = [] + for nickname, code in operations: + if 'public static func' in code: + static_operations.append((nickname, code)) + else: + instance_operations.append((nickname, code)) + with open(filepath, 'w') as f: f.write("//\n") f.write(f"// {filename}\n") @@ -1313,20 +1322,35 @@ def write_category_file(category, operations, output_dir): if has_buffer_operations: f.write("import CvipsShim\n") f.write("\n") - f.write("extension VIPSImage {\n\n") - - for nickname, code in operations: - f.write(code) - f.write("\n\n") - - # Add convenience methods for relational operations in the arithmetic category - if category == 'Arithmetic': - relational_methods = generate_relational_convenience_methods() - if relational_methods: - f.write(relational_methods) + + # Write instance methods in protocol extension + if instance_operations: + f.write("extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ {\n\n") + + for nickname, code in instance_operations: + f.write(code) f.write("\n\n") - - f.write("}\n") + + # Add convenience methods for relational operations in the arithmetic category + if category == 'Arithmetic': + relational_methods = generate_relational_convenience_methods() + if relational_methods: + f.write(relational_methods) + f.write("\n\n") + + f.write("}\n") + + # Write static methods in concrete class extension + if static_operations: + if instance_operations: + f.write("\n") + f.write("extension VIPSImageProtocol where Self: ~Copyable /*, Self: ~Escapable */ {\n\n") + + for nickname, code in static_operations: + f.write(code) + f.write("\n\n") + + f.write("}\n") # Format the file using swift format try: @@ -1359,7 +1383,7 @@ def generate_relational_convenience_methods(): methods.append(f" ///") methods.append(f" /// - Parameters:") methods.append(f" /// - rhs: Right-hand input image") - methods.append(f" public func {method_name}(_ rhs: VIPSImage) throws -> VIPSImage {{") + methods.append(f" public func {method_name}(_ rhs: some VIPSImageProtocol) throws -> Self {{") methods.append(f" return try relational(rhs, relational: .{enum_value})") methods.append(f" }}") methods.append(f"") @@ -1369,7 +1393,7 @@ def generate_relational_convenience_methods(): methods.append(f" ///") methods.append(f" /// - Parameters:") methods.append(f" /// - value: Constant value") - methods.append(f" public func {method_name}(_ value: Double) throws -> VIPSImage {{") + methods.append(f" public func {method_name}(_ value: Double) throws -> Self {{") methods.append(f" return try relationalConst(relational: .{enum_value}, c: [value])") methods.append(f" }}") methods.append(f"") @@ -1386,7 +1410,7 @@ def generate_relational_convenience_methods(): methods.append(f" ///") methods.append(f" /// - Parameters:") methods.append(f" /// - rhs: Right-hand input image") - methods.append(f" public func {method_name}(_ rhs: VIPSImage) throws -> VIPSImage {{") + methods.append(f" public func {method_name}(_ rhs: some VIPSImageProtocol) throws -> Self {{") methods.append(f" return try boolean(rhs, boolean: .{enum_value})") methods.append(f" }}") methods.append(f"") @@ -1402,7 +1426,7 @@ def generate_relational_convenience_methods(): methods.append(f" ///") methods.append(f" /// - Parameters:") methods.append(f" /// - amount: Number of bits to shift") - methods.append(f" public func {method_name}(_ amount: Int) throws -> VIPSImage {{") + methods.append(f" public func {method_name}(_ amount: Int) throws -> Self {{") methods.append(f" return try booleanConst(boolean: .{enum_value}, c: [Double(amount)])") methods.append(f" }}") methods.append(f"") From ed3fafc98cc104d0da0c234d2d41ba075811ce81 Mon Sep 17 00:00:00 2001 From: Tobias Haeberle Date: Sat, 15 Nov 2025 13:38:26 +0100 Subject: [PATCH 05/14] adds "takingOwnership" method --- Sources/VIPS/Core/VIPSImage.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/VIPS/Core/VIPSImage.swift b/Sources/VIPS/Core/VIPSImage.swift index e2092a4..63a2dfe 100644 --- a/Sources/VIPS/Core/VIPSImage.swift +++ b/Sources/VIPS/Core/VIPSImage.swift @@ -1176,3 +1176,8 @@ public struct UnownedVIPSImageRef: VIPSImageProtocol, ~Escapable { } } +extension VIPSImage { + public convenience init(takingOwnership imgRef: UnownedVIPSImageRef) { + self.init(g_object_ref(imgRef.ptr)) + } +} \ No newline at end of file From 3a7cf1aa6f46a9377a92ed7ede084af18e229d18 Mon Sep 17 00:00:00 2001 From: Tobias Haeberle Date: Sat, 15 Nov 2025 13:43:28 +0100 Subject: [PATCH 06/14] updated docs --- docs/VIPS.md | 1302 +++++++++++++++++++++++++------------------------- 1 file changed, 661 insertions(+), 641 deletions(-) diff --git a/docs/VIPS.md b/docs/VIPS.md index 4364acf..88e7862 100644 --- a/docs/VIPS.md +++ b/docs/VIPS.md @@ -11,9 +11,9 @@ | class | `VIPSInterpolate` | | class | `VIPSTarget` | | class | `VIPSTargetCustom` | +| struct | `UnownedVIPSImageRef` | | struct | `UnownedVIPSObjectRef` | | struct | `VIPSError` | -| struct | `VIPSImageRef` | | struct | `VIPSObjectRef` | | struct | `VIPSOption` | | struct | `Whence` | @@ -55,8 +55,6 @@ public class VIPSObject: PointerWrapper, VIPSObjectProtocol { public var type: GType { get } public required init(_ ptr: UnsafeMutableRawPointer) - - public func unref() } ``` @@ -174,7 +172,7 @@ public final class VIPSBlob: Collection, ExpressibleByArrayLiteral, Sendable, Se #### class VIPSImage ```swift -public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjectProtocol { +public final class VIPSImage: PointerWrapper, VIPSImageProtocol, VIPSObjectProtocol { /// A structure representing the dimensions of an image. public struct Size { /// The height of the image in pixels. @@ -280,6 +278,8 @@ public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjec /// - Returns: The height of each page in pixels public var pageHeight: Int { get } + public var ptr: UnsafeMutableRawPointer! + /// The scale factor for matrix images. /// /// Matrix images can have an optional scale field for use by integer @@ -331,195 +331,14 @@ public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjec /// - Returns: The vertical resolution in pixels per millimeter public var yres: Double { get } - /// Bandwise join a set of images - /// - public static func bandjoin(_ in: [VIPSImage]) throws -> VIPSImage - - /// Band-wise rank of a set of images - /// - public static func bandrank(_ in: [VIPSImage], index: Int? = nil) throws -> VIPSImage - - /// Make a black image - /// - public static func black(width: Int, height: Int, bands: Int? = nil) throws -> VIPSImage - - /// Make an image showing the eye's spatial response - /// - public static func eye(width: Int, height: Int, uchar: Bool? = nil, factor: Double? = nil) throws -> VIPSImage - - /// Make a fractal surface - /// - public static func fractsurf(width: Int, height: Int, fractalDimension: Double) throws -> VIPSImage - - /// Make a gaussnoise image - /// - public static func gaussnoise(width: Int, height: Int, sigma: Double? = nil, mean: Double? = nil, seed: Int? = nil) throws -> VIPSImage - - /// Make a grey ramp image - /// - public static func grey(width: Int, height: Int, uchar: Bool? = nil) throws -> VIPSImage - - /// Make a 1d image where pixel values are indexes - /// - public static func identity(bands: Int? = nil, ushort: Bool? = nil, size: Int? = nil) throws -> VIPSImage - - /// Make a butterworth filter - /// - public static func maskButterworth(width: Int, height: Int, order: Double, frequencyCutoff: Double, amplitudeCutoff: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage - - /// Make a butterworth_band filter - /// - public static func maskButterworthBand(width: Int, height: Int, order: Double, frequencyCutoffX: Double, frequencyCutoffY: Double, radius: Double, amplitudeCutoff: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage - - /// Make a butterworth ring filter - /// - public static func maskButterworthRing(width: Int, height: Int, order: Double, frequencyCutoff: Double, amplitudeCutoff: Double, ringwidth: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage - - /// Make fractal filter - /// - public static func maskFractal(width: Int, height: Int, fractalDimension: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage - - /// Make a gaussian filter - /// - public static func maskGaussian(width: Int, height: Int, frequencyCutoff: Double, amplitudeCutoff: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage - - /// Make a gaussian filter - /// - public static func maskGaussianBand(width: Int, height: Int, frequencyCutoffX: Double, frequencyCutoffY: Double, radius: Double, amplitudeCutoff: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage - - /// Make a gaussian ring filter - /// - public static func maskGaussianRing(width: Int, height: Int, frequencyCutoff: Double, amplitudeCutoff: Double, ringwidth: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage - - /// Make an ideal filter - /// - public static func maskIdeal(width: Int, height: Int, frequencyCutoff: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage - - /// Make an ideal band filter - /// - public static func maskIdealBand(width: Int, height: Int, frequencyCutoffX: Double, frequencyCutoffY: Double, radius: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage - - /// Make an ideal ring filter - /// - public static func maskIdealRing(width: Int, height: Int, frequencyCutoff: Double, ringwidth: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> VIPSImage - - /// Create an empty matrix image - public static func matrix(width: Int, height: Int) throws -> VIPSImage - - /// Create a matrix image from a double array - public static func matrix(width: Int, height: Int, data: [Double]) throws -> VIPSImage - - /// Make a perlin noise image - /// - public static func perlin(width: Int, height: Int, cellSize: Int? = nil, uchar: Bool? = nil, seed: Int? = nil) throws -> VIPSImage - - /// Load named icc profile - /// - public static func profileLoad(name: String) throws -> VIPSBlob - - /// Make a 2d sine wave - /// - public static func sines(width: Int, height: Int, uchar: Bool? = nil, hfreq: Double? = nil, vfreq: Double? = nil) throws -> VIPSImage - - /// Sum an array of images - /// - public static func sum(_ in: [VIPSImage]) throws -> VIPSImage - - /// Find the index of the first non-zero pixel in tests - /// - public static func `switch`(tests: [VIPSImage]) throws -> VIPSImage - - /// Build a look-up table - /// - public static func tonelut(inMax: Int? = nil, outMax: Int? = nil, Lb: Double? = nil, Lw: Double? = nil, Ps: Double? = nil, Pm: Double? = nil, Ph: Double? = nil, S: Double? = nil, M: Double? = nil, H: Double? = nil) throws -> VIPSImage - - /// Make a worley noise image - /// - public static func worley(width: Int, height: Int, cellSize: Int? = nil, seed: Int? = nil) throws -> VIPSImage - - /// Make an image where pixel values are coordinates - /// - public static func xyz(width: Int, height: Int, csize: Int? = nil, dsize: Int? = nil, esize: Int? = nil) throws -> VIPSImage - - /// Make a zone plate - /// - public static func zone(width: Int, height: Int, uchar: Bool? = nil) throws -> VIPSImage - - /// Transform lch to cmc - public func CMC2LCh() throws -> VIPSImage - - /// Transform cmyk to xyz - public func CMYK2XYZ() throws -> VIPSImage - - /// Transform hsv to srgb - public func HSV2sRGB() throws -> VIPSImage - - /// Transform lch to cmc - public func LCh2CMC() throws -> VIPSImage - - /// Transform lch to lab - public func LCh2Lab() throws -> VIPSImage - - /// Transform lab to lch - public func Lab2LCh() throws -> VIPSImage - - /// Transform float lab to labq coding - public func Lab2LabQ() throws -> VIPSImage - - /// Transform float lab to signed short - public func Lab2LabS() throws -> VIPSImage - - /// Transform cielab to xyz - /// - public func Lab2XYZ(temp: [Double]? = nil) throws -> VIPSImage - - /// Unpack a labq image to float lab - public func LabQ2Lab() throws -> VIPSImage - - /// Unpack a labq image to short lab - public func LabQ2LabS() throws -> VIPSImage - - /// Convert a labq image to srgb - public func LabQ2sRGB() throws -> VIPSImage - - /// Transform signed short lab to float - public func LabS2Lab() throws -> VIPSImage - - /// Transform short lab to labq coding - public func LabS2LabQ() throws -> VIPSImage - - /// Transform xyz to cmyk - public func XYZ2CMYK() throws -> VIPSImage - - /// Transform xyz to lab - /// - public func XYZ2Lab(temp: [Double]? = nil) throws -> VIPSImage - - /// Transform xyz to yxy - public func XYZ2Yxy() throws -> VIPSImage - - /// Transform xyz to scrgb - public func XYZ2scRGB() throws -> VIPSImage - - /// Transform yxy to xyz - public func Yxy2XYZ() throws -> VIPSImage - - /// Absolute value of an image - public func abs() throws -> VIPSImage - /// Calculate arccosine of image values (result in degrees) public func acos() throws -> VIPSImage /// Calculate inverse hyperbolic cosine of image values public func acosh() throws -> VIPSImage - /// Add two images - /// - public func add(_ rhs: VIPSImage) throws -> VIPSImage - - /// Bitwise AND of two images - /// - public func andimage(_ rhs: VIPSImage) throws -> VIPSImage + /// Bitwise AND of image with a constant (integer overload) + public func andimage(_ value: Int) throws -> VIPSImage /// Calculate arcsine of image values (result in degrees) public func asin() throws -> VIPSImage @@ -536,17 +355,11 @@ public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjec /// Calculate inverse hyperbolic tangent of image values public func atanh() throws -> VIPSImage - /// Autorotate image by exif tag - public func autorot() throws -> VIPSImage - /// Same as `autorot()` /// /// See: `VIPSImage.autorot()` public func autorotate() throws -> VIPSImage - /// Find image average - public func avg() throws -> Double - /// Perform bitwise AND operation across bands. /// /// Reduces multiple bands to a single band by performing bitwise AND @@ -565,16 +378,8 @@ public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjec /// - Throws: `VIPSError` if the operation fails public func bandeor() throws -> VIPSImage - /// Fold up x axis into bands - /// - public func bandfold(factor: Int? = nil) throws -> VIPSImage - - /// Append a constant band to an image - /// - public func bandjoinConst(c: [Double]) throws -> VIPSImage - - /// Band-wise average - public func bandmean() throws -> VIPSImage + /// See VIPSImage.bandjoin(`in`:) + public func bandjoin(_ other: [VIPSImage]) throws -> VIPSImage /// Perform bitwise OR operation across bands. /// @@ -585,33 +390,11 @@ public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjec /// - Throws: `VIPSError` if the operation fails public func bandor() throws -> VIPSImage - /// Unfold image bands into x axis - /// - public func bandunfold(factor: Int? = nil) throws -> VIPSImage - - /// Build a look-up table - public func buildlut() throws -> VIPSImage - - /// Byteswap an image - public func byteswap() throws -> VIPSImage - - /// Use pixel values to pick cases from an array of images - /// - public func `case`(cases: [VIPSImage]) throws -> VIPSImage - public func ceil() throws -> VIPSImage - /// Clamp values of an image - /// - public func clamp(min: Double? = nil, max: Double? = nil) throws -> VIPSImage - /// Create a complex image from real and imaginary parts public func complex(_ imaginary: VIPSImage) throws -> VIPSImage - /// Form a complex image from two real images - /// - public func complexform(_ rhs: VIPSImage) throws -> VIPSImage - /// Get the concurrency hint for this image. /// /// This returns the suggested level of parallelism for operations on this @@ -625,65 +408,14 @@ public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjec /// Calculate complex conjugate public func conj() throws -> VIPSImage - /// Approximate integer convolution - /// - public func conva(mask: VIPSImage, layers: Int? = nil, cluster: Int? = nil) throws -> VIPSImage - - /// Approximate separable integer convolution - /// - public func convasep(mask: VIPSImage, layers: Int? = nil) throws -> VIPSImage - - /// Float convolution operation - /// - public func convf(mask: VIPSImage) throws -> VIPSImage - - /// Int convolution operation - /// - public func convi(mask: VIPSImage) throws -> VIPSImage - - /// This function allocates memory, renders image into it, builds a new image - /// around the memory area, and returns that. - /// - /// If the image is already a simple area of memory, it just refs image and - /// returns it. - public func copyMemory() throws -> VIPSImage - /// Calculate cosine of image values (in degrees) public func cos() throws -> VIPSImage /// Calculate hyperbolic cosine of image values public func cosh() throws -> VIPSImage - /// Extract an area from an image - /// - public func crop(left: Int, top: Int, width: Int, height: Int) throws -> VIPSImage - - /// Calculate de00 - /// - public func dE00(_ rhs: VIPSImage) throws -> VIPSImage - - /// Calculate de76 - /// - public func dE76(_ rhs: VIPSImage) throws -> VIPSImage - - /// Calculate decmc - /// - public func dECMC(_ rhs: VIPSImage) throws -> VIPSImage - - /// Find image standard deviation - public func deviate() throws -> Double - - /// Divide two images - /// - public func divide(_ rhs: VIPSImage) throws -> VIPSImage - - /// Bitwise XOR of two images - /// - public func eorimage(_ rhs: VIPSImage) throws -> VIPSImage - - /// Test for equality - /// - public func equal(_ value: Double) throws -> VIPSImage + /// Bitwise XOR of image with a constant (integer overload) + public func eorimage(_ value: Int) throws -> VIPSImage /// Calculate e^x for each pixel public func exp() throws -> VIPSImage @@ -691,48 +423,8 @@ public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjec /// Calculate 10^x for each pixel public func exp10() throws -> VIPSImage - /// Extract an area from an image - /// - public func extractArea(left: Int, top: Int, width: Int, height: Int) throws -> VIPSImage - - /// Extract band from an image - /// - public func extractBand(_ band: Int, n: Int? = nil) throws -> VIPSImage - - /// False-color an image - public func falsecolour() throws -> VIPSImage - - /// Fast correlation - /// - public func fastcor(ref: VIPSImage) throws -> VIPSImage - - /// Fill image zeros with nearest non-zero pixel - public func fillNearest() throws -> VIPSImage - - /// Search an image for non-edge areas - /// - public func findTrim(threshold: Double? = nil, background: [Double]? = nil, lineArt: Bool? = nil) throws -> Int - - /// Flatten alpha out of an image - /// - public func flatten(background: [Double]? = nil, maxAlpha: Double? = nil) throws -> VIPSImage - - /// Transform float rgb to radiance coding - public func float2rad() throws -> VIPSImage - public func floor() throws -> VIPSImage - /// Frequency-domain filtering - /// - public func freqmult(mask: VIPSImage) throws -> VIPSImage - - /// Forward fft - public func fwfft() throws -> VIPSImage - - /// Gamma an image - /// - public func gamma(exponent: Double? = nil) throws -> VIPSImage - /// Get the value of the pixel at the specified coordinates. /// /// This is a convenience method that calls the main `getpoint` implementation. @@ -741,77 +433,18 @@ public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjec /// public func getpoint(_ x: Int, _ y: Int) throws -> [Double] - /// Read a point from an image - /// - public func getpoint(x: Int, y: Int, unpackComplex: Bool? = nil) throws -> [Double] + /// Extract imaginary part of complex image + public func imag() throws -> VIPSImage - /// Global balance an image mosaic - /// - public func globalbalance(gamma: Double? = nil, intOutput: Bool? = nil) throws -> VIPSImage + public init(_ ptr: UnsafeMutableRawPointer) - /// Grid an image + /// Creates a new image by loading the given data /// - public func grid(tileHeight: Int, across: Int, down: Int) throws -> VIPSImage - - /// Form cumulative histogram - public func histCum() throws -> VIPSImage - - /// Estimate image entropy - public func histEntropy() throws -> Double - - /// Histogram equalisation + /// The image will reference the data from the blob. /// - public func histEqual(band: Int? = nil) throws -> VIPSImage + public convenience init(blob: VIPSBlob, loader: String? = nil, options: String? = nil) throws - /// Find image histogram - /// - public func histFind(band: Int? = nil) throws -> VIPSImage - - /// Find n-dimensional image histogram - /// - public func histFindNdim(bins: Int? = nil) throws -> VIPSImage - - /// Test for monotonicity - public func histIsmonotonic() throws -> Bool - - /// Local histogram equalisation - /// - public func histLocal(width: Int, height: Int, maxSlope: Int? = nil) throws -> VIPSImage - - /// Match two histograms - /// - public func histMatch(ref: VIPSImage) throws -> VIPSImage - - /// Normalise histogram - public func histNorm() throws -> VIPSImage - - /// Plot histogram - public func histPlot() throws -> VIPSImage - - /// Find hough circle transform - /// - public func houghCircle(scale: Int? = nil, minRadius: Int? = nil, maxRadius: Int? = nil) throws -> VIPSImage - - /// Find hough line transform - /// - public func houghLine(width: Int? = nil, height: Int? = nil) throws -> VIPSImage - - /// Ifthenelse an image - /// - public func ifthenelse(in1: VIPSImage, in2: VIPSImage, blend: Bool? = nil) throws -> VIPSImage - - /// Extract imaginary part of complex image - public func imag() throws -> VIPSImage - - public required init(_ ptr: UnsafeMutableRawPointer) - - /// Creates a new image by loading the given data - /// - /// The image will reference the data from the blob. - /// - public convenience init(blob: VIPSBlob, loader: String? = nil, options: String? = nil) throws - - /// Creates a new image by loading the given data. + /// Creates a new image by loading the given data. /// /// The image will NOT copy the data into its own memory. /// You need to ensure that the data remains valid for the lifetime of the image and all its descendants. @@ -829,7 +462,9 @@ public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjec /// This function creates a VipsImage from a memory area. The memory area must be a simple array, /// for example RGBRGBRGB, left-to-right, top-to-bottom. The memory will be copied into the image. /// - public init(data: some Collection, width: Int, height: Int, bands: Int) throws + public convenience init(data: some Collection, width: Int, height: Int, bands: Int) throws + + public convenience init(takingOwnership imgRef: UnownedVIPSImageRef) /// Creates a new image by loading the given data /// @@ -848,33 +483,7 @@ public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjec /// into the image. You must ensure the memory remains valid for the lifetime of the image and all /// its descendants. Use the copy variant if you are unsure about memory management. /// - public init(unsafeData buffer: UnsafeBufferPointer, width: Int, height: Int, bands: Int) throws - - /// Insert image @sub into @main at @x, @y - /// - public func insert(sub: VIPSImage, x: Int, y: Int, expand: Bool? = nil, background: [Double]? = nil) throws -> VIPSImage - - /// Invert an image - public func invert() throws -> VIPSImage - - /// Build an inverted look-up table - /// - public func invertlut(size: Int? = nil) throws -> VIPSImage - - /// Inverse fft - /// - public func invfft(real: Bool? = nil) throws -> VIPSImage - - /// Label regions in an image - public func labelregions() throws -> VIPSImage - - /// Test for less than - /// - public func less(_ value: Double) throws -> VIPSImage - - /// Test for less than or equal - /// - public func lesseq(_ value: Double) throws -> VIPSImage + public convenience init(unsafeData buffer: UnsafeBufferPointer, width: Int, height: Int, bands: Int) throws public func linear(_ a: [Double], _ b: [Double], uchar: Bool? = nil) throws -> VIPSImage @@ -884,74 +493,11 @@ public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjec /// Calculate base-10 logarithm of each pixel public func log10() throws -> VIPSImage - /// Left shift - /// - public func lshift(_ amount: Int) throws -> VIPSImage - - /// Map an image though a lut - /// - public func maplut(lut: VIPSImage, band: Int? = nil) throws -> VIPSImage - - /// First-order match of two images - /// - public func match(sec: VIPSImage, xr1: Int, yr1: Int, xs1: Int, ys1: Int, xr2: Int, yr2: Int, xs2: Int, ys2: Int, hwindow: Int? = nil, harea: Int? = nil, search: Bool? = nil, interpolate: VIPSInterpolate? = nil) throws -> VIPSImage - - /// Invert an matrix - public func matrixinvert() throws -> VIPSImage - - /// Find image maximum - /// - public func max(size: Int? = nil) throws -> Double - - /// Maximum of a pair of images - /// - public func maxpair(_ rhs: VIPSImage) throws -> VIPSImage - - /// Measure a set of patches on a color chart - /// - public func measure(h: Int, v: Int, left: Int? = nil, top: Int? = nil, width: Int? = nil, height: Int? = nil) throws -> VIPSImage - - /// Find image minimum - /// - public func min(size: Int? = nil) throws -> Double - - /// Minimum of a pair of images - /// - public func minpair(_ rhs: VIPSImage) throws -> VIPSImage - - /// Test for greater than - /// - public func more(_ value: Double) throws -> VIPSImage - - /// Test for greater than or equal - /// - public func moreeq(_ value: Double) throws -> VIPSImage - - /// Pick most-significant byte from an image - /// - public func msb(band: Int? = nil) throws -> VIPSImage - - /// Multiply two images - /// - public func multiply(_ rhs: VIPSImage) throws -> VIPSImage - - public func new(_ colors: [Double]) throws -> VIPSImage - - /// Test for inequality - /// - public func notequal(_ value: Double) throws -> VIPSImage - - /// Bitwise OR of two images - /// - public func orimage(_ rhs: VIPSImage) throws -> VIPSImage - - /// Find threshold for percent of pixels - /// - public func percent(_ percent: Double) throws -> Int + /// Left shift with an image of shift amounts + public func lshift(_ shiftAmounts: VIPSImage) throws -> VIPSImage - /// Calculate phase correlation - /// - public func phasecor(in2: VIPSImage) throws -> VIPSImage + /// Bitwise OR of image with a constant (integer overload) + public func orimage(_ value: Int) throws -> VIPSImage /// Convert complex image to polar form public func polar() throws -> VIPSImage @@ -959,9 +505,6 @@ public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjec /// Raise image values to a constant power (integer overload) public func pow(_ exponent: Int) throws -> VIPSImage - /// Prewitt edge detector - public func prewitt() throws -> VIPSImage - /// Extract profiles from an image. /// /// Creates 1D profiles by averaging across rows and columns. @@ -980,99 +523,16 @@ public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjec /// - Throws: `VIPSError` if the operation fails public func project() throws -> (rows: VIPSImage, columns: VIPSImage) - /// Resample an image with a quadratic transform - /// - public func quadratic(coeff: VIPSImage, interpolate: VIPSInterpolate? = nil) throws -> VIPSImage - - /// Unpack radiance coding to float rgb - public func rad2float() throws -> VIPSImage - - /// Rank filter - /// - public func rank(width: Int, height: Int, index: Int) throws -> VIPSImage - /// Extract real part of complex image public func real() throws -> VIPSImage - /// Linear recombination with matrix - /// - public func recomb(m: VIPSImage) throws -> VIPSImage - /// Convert polar image to rectangular form public func rect() throws -> VIPSImage - /// Remainder after integer division of an image and a constant - /// - public func remainder(_ value: Int) throws -> VIPSImage - - /// Remainder after integer division of an image and a constant - /// - public func remainderConst(c: [Double]) throws -> VIPSImage - - /// Replicate an image - /// - public func replicate(across: Int, down: Int) throws -> VIPSImage - - /// Rotate an image by a number of degrees - /// - public func rotate(angle: Double, interpolate: VIPSInterpolate? = nil, background: [Double]? = nil, odx: Double? = nil, ody: Double? = nil, idx: Double? = nil, idy: Double? = nil) throws -> VIPSImage - public func round() throws -> VIPSImage - /// Right shift - /// - public func rshift(_ amount: Int) throws -> VIPSImage - - /// Transform srgb to hsv - public func sRGB2HSV() throws -> VIPSImage - - /// Convert an srgb image to scrgb - public func sRGB2scRGB() throws -> VIPSImage - - /// Convert scrgb to bw - /// - public func scRGB2BW(depth: Int? = nil) throws -> VIPSImage - - /// Transform scrgb to xyz - public func scRGB2XYZ() throws -> VIPSImage - - /// Convert an scrgb image to srgb - /// - public func scRGB2sRGB(depth: Int? = nil) throws -> VIPSImage - - /// Scale an image to uchar - /// - public func scale(exp: Double? = nil, log: Bool? = nil) throws -> VIPSImage - - /// Scharr edge detector - public func scharr() throws -> VIPSImage - - /// Check sequential access - /// - public func sequential(tileHeight: Int? = nil) throws -> VIPSImage - - /// Unsharp masking for print - /// - public func sharpen(sigma: Double? = nil, x1: Double? = nil, y2: Double? = nil, y3: Double? = nil, m1: Double? = nil, m2: Double? = nil) throws -> VIPSImage - - /// Shrink an image - /// - public func shrink(hshrink: Double, vshrink: Double, ceil: Bool? = nil) throws -> VIPSImage - - /// Shrink an image horizontally - /// - public func shrinkh(hshrink: Int, ceil: Bool? = nil) throws -> VIPSImage - - /// Shrink an image vertically - /// - public func shrinkv(vshrink: Int, ceil: Bool? = nil) throws -> VIPSImage - - /// Unit vector of pixel - public func sign() throws -> VIPSImage - - /// Similarity transform of an image - /// - public func similarity(scale: Double? = nil, angle: Double? = nil, interpolate: VIPSInterpolate? = nil, background: [Double]? = nil, odx: Double? = nil, ody: Double? = nil, idx: Double? = nil, idy: Double? = nil) throws -> VIPSImage + /// Right shift with an image of shift amounts + public func rshift(_ shiftAmounts: VIPSImage) throws -> VIPSImage /// Calculate sine of image values (in degrees) public func sin() throws -> VIPSImage @@ -1080,48 +540,15 @@ public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjec /// Calculate hyperbolic sine of image values public func sinh() throws -> VIPSImage - /// Sobel edge detector - public func sobel() throws -> VIPSImage - - /// Spatial correlation - /// - public func spcor(ref: VIPSImage) throws -> VIPSImage - - /// Make displayable power spectrum - public func spectrum() throws -> VIPSImage - - /// Find many image stats - public func stats() throws -> VIPSImage - - /// Statistical difference - /// - public func stdif(width: Int, height: Int, s0: Double? = nil, b: Double? = nil, m0: Double? = nil, a: Double? = nil) throws -> VIPSImage - - /// Subsample an image - /// - public func subsample(xfac: Int, yfac: Int, point: Bool? = nil) throws -> VIPSImage - - /// Subtract two images - /// - public func subtract(_ rhs: VIPSImage) throws -> VIPSImage - /// Calculate tangent of image values (in degrees) public func tan() throws -> VIPSImage /// Calculate hyperbolic tangent of image values public func tanh() throws -> VIPSImage - /// Transpose3d an image - /// - public func transpose3d(pageHeight: Int? = nil) throws -> VIPSImage - /// Raise a constant to the power of this image (integer overload) public func wop(_ base: Int) throws -> VIPSImage - /// Wrap image origin - /// - public func wrap(x: Int? = nil, y: Int? = nil) throws -> VIPSImage - /// Writes the image to a memory buffer in the specified format. /// /// This method writes the image to a memory buffer using a format determined by the suffix. @@ -1150,10 +577,6 @@ public class VIPSImage: VIPSObject, PointerWrapper, VIPSImageProtocol, VIPSObjec /// You can call the various save operations directly if you wish, see `jpegsave(target:)` for example. /// public func writeToTarget(suffix: String, target: VIPSTarget, quality: Int? = nil, options params: [String : Any] = [:], additionalOptions: String? = nil) throws - - /// Zoom an image - /// - public func zoom(xfac: Int, yfac: Int) throws -> VIPSImage } ``` @@ -1482,6 +905,16 @@ public final class VIPSTargetCustom: VIPSTarget, PointerWrapper, VIPSObjectProto } ``` +#### struct UnownedVIPSImageRef + +```swift +public struct UnownedVIPSImageRef: PointerWrapper, VIPSImageProtocol, VIPSObjectProtocol { + public var ptr: UnsafeMutableRawPointer! + + public init(_ ptr: UnsafeMutableRawPointer) +} +``` + #### struct UnownedVIPSObjectRef ```swift @@ -1489,10 +922,6 @@ public struct UnownedVIPSObjectRef: PointerWrapper, VIPSObjectProtocol { public let ptr: UnsafeMutableRawPointer! public init(_ ptr: UnsafeMutableRawPointer) - - public func ref() -> UnownedVIPSObjectRef - - public func unref() } ``` @@ -1529,16 +958,6 @@ public struct VIPSError: Error, Sendable, SendableMetatype { } ``` -#### struct VIPSImageRef - -```swift -public struct VIPSImageRef: PointerWrapper, VIPSImageProtocol, VIPSObjectProtocol { - public var ptr: UnsafeMutableRawPointer! - - public init(_ ptr: UnsafeMutableRawPointer) -} -``` - #### struct VIPSObjectRef ```swift @@ -1547,7 +966,7 @@ public struct VIPSObjectRef: PointerWrapper, VIPSObjectProtocol { public init(_ ptr: UnsafeMutableRawPointer) - public consuming func unref() + public init(borrowing ref: UnownedVIPSObjectRef) } ``` @@ -1627,7 +1046,7 @@ public enum VIPS { #### protocol PointerWrapper ```swift -public protocol PointerWrapper : ~Copyable { +public protocol PointerWrapper : ~Copyable, ~Escapable { public var ptr: UnsafeMutableRawPointer! { get } public init(_ ptr: UnsafeMutableRawPointer) @@ -1637,42 +1056,643 @@ public protocol PointerWrapper : ~Copyable { #### protocol VIPSImageProtocol ```swift -public protocol VIPSImageProtocol : VIPSObjectProtocol { +public protocol VIPSImageProtocol : VIPSObjectProtocol, ~Copyable, ~Escapable { public var bands: Int { get } public var height: Int { get } public var width: Int { get } -} -``` -#### protocol VIPSLoggingDelegate + /// Bandwise join a set of images + /// + public static func bandjoin(_ in: [VIPSImage]) throws -> Self -```swift -public protocol VIPSLoggingDelegate : AnyObject { - public func debug(_ message: String) + /// Band-wise rank of a set of images + /// + public static func bandrank(_ in: [VIPSImage], index: Int? = nil) throws -> Self - public func error(_ message: String) + /// Make a black image + /// + public static func black(width: Int, height: Int, bands: Int? = nil) throws -> Self - public func info(_ message: String) + /// Make an image showing the eye's spatial response + /// + public static func eye(width: Int, height: Int, uchar: Bool? = nil, factor: Double? = nil) throws -> Self - public func warning(_ message: String) -} -``` + /// Make a fractal surface + /// + public static func fractsurf(width: Int, height: Int, fractalDimension: Double) throws -> Self -#### protocol VIPSObjectProtocol + /// Make a gaussnoise image + /// + public static func gaussnoise(width: Int, height: Int, sigma: Double? = nil, mean: Double? = nil, seed: Int? = nil) throws -> Self -```swift -public protocol VIPSObjectProtocol : PointerWrapper, ~Copyable { - public var type: GType { get } + /// Make a grey ramp image + /// + public static func grey(width: Int, height: Int, uchar: Bool? = nil) throws -> Self - public func disconnect(signalHandler: Int) + /// Make a 1d image where pixel values are indexes + /// + public static func identity(bands: Int? = nil, ushort: Bool? = nil, size: Int? = nil) throws -> Self - public @discardableResult func onPreClose(_ handler: @escaping (UnownedVIPSObjectRef) -> Void) -> Int + /// Make a butterworth filter + /// + public static func maskButterworth(width: Int, height: Int, order: Double, frequencyCutoff: Double, amplitudeCutoff: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> Self - public func ref() -> Self + /// Make a butterworth_band filter + /// + public static func maskButterworthBand(width: Int, height: Int, order: Double, frequencyCutoffX: Double, frequencyCutoffY: Double, radius: Double, amplitudeCutoff: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> Self - public func unref() + /// Make a butterworth ring filter + /// + public static func maskButterworthRing(width: Int, height: Int, order: Double, frequencyCutoff: Double, amplitudeCutoff: Double, ringwidth: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> Self + + /// Make fractal filter + /// + public static func maskFractal(width: Int, height: Int, fractalDimension: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> Self + + /// Make a gaussian filter + /// + public static func maskGaussian(width: Int, height: Int, frequencyCutoff: Double, amplitudeCutoff: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> Self + + /// Make a gaussian filter + /// + public static func maskGaussianBand(width: Int, height: Int, frequencyCutoffX: Double, frequencyCutoffY: Double, radius: Double, amplitudeCutoff: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> Self + + /// Make a gaussian ring filter + /// + public static func maskGaussianRing(width: Int, height: Int, frequencyCutoff: Double, amplitudeCutoff: Double, ringwidth: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> Self + + /// Make an ideal filter + /// + public static func maskIdeal(width: Int, height: Int, frequencyCutoff: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> Self + + /// Make an ideal band filter + /// + public static func maskIdealBand(width: Int, height: Int, frequencyCutoffX: Double, frequencyCutoffY: Double, radius: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> Self + + /// Make an ideal ring filter + /// + public static func maskIdealRing(width: Int, height: Int, frequencyCutoff: Double, ringwidth: Double, uchar: Bool? = nil, nodc: Bool? = nil, reject: Bool? = nil, optical: Bool? = nil) throws -> Self + + /// Create an empty matrix image + public static func matrix(width: Int, height: Int) throws -> Self + + /// Create a matrix image from a double array + public static func matrix(width: Int, height: Int, data: [Double]) throws -> Self + + /// Make a perlin noise image + /// + public static func perlin(width: Int, height: Int, cellSize: Int? = nil, uchar: Bool? = nil, seed: Int? = nil) throws -> Self + + /// Load named icc profile + /// + public static func profileLoad(name: String) throws -> VIPSBlob + + /// Make a 2d sine wave + /// + public static func sines(width: Int, height: Int, uchar: Bool? = nil, hfreq: Double? = nil, vfreq: Double? = nil) throws -> Self + + /// Sum an array of images + /// + public static func sum(_ in: [VIPSImage]) throws -> Self + + /// Find the index of the first non-zero pixel in tests + /// + public static func `switch`(tests: [VIPSImage]) throws -> Self + + /// Build a look-up table + /// + public static func tonelut(inMax: Int? = nil, outMax: Int? = nil, Lb: Double? = nil, Lw: Double? = nil, Ps: Double? = nil, Pm: Double? = nil, Ph: Double? = nil, S: Double? = nil, M: Double? = nil, H: Double? = nil) throws -> Self + + /// Make a worley noise image + /// + public static func worley(width: Int, height: Int, cellSize: Int? = nil, seed: Int? = nil) throws -> Self + + /// Make an image where pixel values are coordinates + /// + public static func xyz(width: Int, height: Int, csize: Int? = nil, dsize: Int? = nil, esize: Int? = nil) throws -> Self + + /// Make a zone plate + /// + public static func zone(width: Int, height: Int, uchar: Bool? = nil) throws -> Self + + /// Transform lch to cmc + public func CMC2LCh() throws -> Self + + /// Transform cmyk to xyz + public func CMYK2XYZ() throws -> Self + + /// Transform hsv to srgb + public func HSV2sRGB() throws -> Self + + /// Transform lch to cmc + public func LCh2CMC() throws -> Self + + /// Transform lch to lab + public func LCh2Lab() throws -> Self + + /// Transform lab to lch + public func Lab2LCh() throws -> Self + + /// Transform float lab to labq coding + public func Lab2LabQ() throws -> Self + + /// Transform float lab to signed short + public func Lab2LabS() throws -> Self + + /// Transform cielab to xyz + /// + public func Lab2XYZ(temp: [Double]? = nil) throws -> Self + + /// Unpack a labq image to float lab + public func LabQ2Lab() throws -> Self + + /// Unpack a labq image to short lab + public func LabQ2LabS() throws -> Self + + /// Convert a labq image to srgb + public func LabQ2sRGB() throws -> Self + + /// Transform signed short lab to float + public func LabS2Lab() throws -> Self + + /// Transform short lab to labq coding + public func LabS2LabQ() throws -> Self + + /// Transform xyz to cmyk + public func XYZ2CMYK() throws -> Self + + /// Transform xyz to lab + /// + public func XYZ2Lab(temp: [Double]? = nil) throws -> Self + + /// Transform xyz to yxy + public func XYZ2Yxy() throws -> Self + + /// Transform xyz to scrgb + public func XYZ2scRGB() throws -> Self + + /// Transform yxy to xyz + public func Yxy2XYZ() throws -> Self + + /// Absolute value of an image + public func abs() throws -> Self + + /// Add two images + /// + public func add(_ rhs: some VIPSImageProtocol) throws -> Self + + /// Bitwise AND of two images + /// + public func andimage(_ rhs: some VIPSImageProtocol) throws -> Self + + /// Autorotate image by exif tag + public func autorot() throws -> Self + + /// Find image average + public func avg() throws -> Double + + /// Fold up x axis into bands + /// + public func bandfold(factor: Int? = nil) throws -> Self + + /// Append a constant band to an image + /// + public func bandjoinConst(c: [Double]) throws -> Self + + /// Band-wise average + public func bandmean() throws -> Self + + /// Unfold image bands into x axis + /// + public func bandunfold(factor: Int? = nil) throws -> Self + + /// Build a look-up table + public func buildlut() throws -> Self + + /// Byteswap an image + public func byteswap() throws -> Self + + /// Use pixel values to pick cases from an array of images + /// + public func `case`(cases: [VIPSImage]) throws -> Self + + /// Clamp values of an image + /// + public func clamp(min: Double? = nil, max: Double? = nil) throws -> Self + + /// Form a complex image from two real images + /// + public func complexform(_ rhs: some VIPSImageProtocol) throws -> Self + + /// Approximate integer convolution + /// + public func conva(mask: some VIPSImageProtocol, layers: Int? = nil, cluster: Int? = nil) throws -> Self + + /// Approximate separable integer convolution + /// + public func convasep(mask: some VIPSImageProtocol, layers: Int? = nil) throws -> Self + + /// Float convolution operation + /// + public func convf(mask: some VIPSImageProtocol) throws -> Self + + /// Int convolution operation + /// + public func convi(mask: some VIPSImageProtocol) throws -> Self + + /// This function allocates memory, renders image into it, builds a new image + /// around the memory area, and returns that. + /// + /// If the image is already a simple area of memory, it just refs image and + /// returns it. + public func copyMemory() throws -> Self + + /// Extract an area from an image + /// + public func crop(left: Int, top: Int, width: Int, height: Int) throws -> Self + + /// Calculate de00 + /// + public func dE00(_ rhs: some VIPSImageProtocol) throws -> Self + + /// Calculate de76 + /// + public func dE76(_ rhs: some VIPSImageProtocol) throws -> Self + + /// Calculate decmc + /// + public func dECMC(_ rhs: some VIPSImageProtocol) throws -> Self + + /// Find image standard deviation + public func deviate() throws -> Double + + /// Divide two images + /// + public func divide(_ rhs: some VIPSImageProtocol) throws -> Self + + /// Bitwise XOR of two images + /// + public func eorimage(_ rhs: some VIPSImageProtocol) throws -> Self + + /// Test for equality + /// + public func equal(_ value: Double) throws -> Self + + /// Extract an area from an image + /// + public func extractArea(left: Int, top: Int, width: Int, height: Int) throws -> Self + + /// Extract band from an image + /// + public func extractBand(_ band: Int, n: Int? = nil) throws -> Self + + /// False-color an image + public func falsecolour() throws -> Self + + /// Fast correlation + /// + public func fastcor(ref: some VIPSImageProtocol) throws -> Self + + /// Fill image zeros with nearest non-zero pixel + public func fillNearest() throws -> Self + + /// Search an image for non-edge areas + /// + public func findTrim(threshold: Double? = nil, background: [Double]? = nil, lineArt: Bool? = nil) throws -> Int + + /// Flatten alpha out of an image + /// + public func flatten(background: [Double]? = nil, maxAlpha: Double? = nil) throws -> Self + + /// Transform float rgb to radiance coding + public func float2rad() throws -> Self + + /// Frequency-domain filtering + /// + public func freqmult(mask: some VIPSImageProtocol) throws -> Self + + /// Forward fft + public func fwfft() throws -> Self + + /// Gamma an image + /// + public func gamma(exponent: Double? = nil) throws -> Self + + /// Read a point from an image + /// + public func getpoint(x: Int, y: Int, unpackComplex: Bool? = nil) throws -> [Double] + + /// Global balance an image mosaic + /// + public func globalbalance(gamma: Double? = nil, intOutput: Bool? = nil) throws -> Self + + /// Grid an image + /// + public func grid(tileHeight: Int, across: Int, down: Int) throws -> Self + + /// Form cumulative histogram + public func histCum() throws -> Self + + /// Estimate image entropy + public func histEntropy() throws -> Double + + /// Histogram equalisation + /// + public func histEqual(band: Int? = nil) throws -> Self + + /// Find image histogram + /// + public func histFind(band: Int? = nil) throws -> Self + + /// Find n-dimensional image histogram + /// + public func histFindNdim(bins: Int? = nil) throws -> Self + + /// Test for monotonicity + public func histIsmonotonic() throws -> Bool + + /// Local histogram equalisation + /// + public func histLocal(width: Int, height: Int, maxSlope: Int? = nil) throws -> Self + + /// Match two histograms + /// + public func histMatch(ref: some VIPSImageProtocol) throws -> Self + + /// Normalise histogram + public func histNorm() throws -> Self + + /// Plot histogram + public func histPlot() throws -> Self + + /// Find hough circle transform + /// + public func houghCircle(scale: Int? = nil, minRadius: Int? = nil, maxRadius: Int? = nil) throws -> Self + + /// Find hough line transform + /// + public func houghLine(width: Int? = nil, height: Int? = nil) throws -> Self + + /// Ifthenelse an image + /// + public func ifthenelse(in1: some VIPSImageProtocol, in2: some VIPSImageProtocol, blend: Bool? = nil) throws -> Self + + /// Insert image @sub into @main at @x, @y + /// + public func insert(sub: some VIPSImageProtocol, x: Int, y: Int, expand: Bool? = nil, background: [Double]? = nil) throws -> Self + + /// Invert an image + public func invert() throws -> Self + + /// Build an inverted look-up table + /// + public func invertlut(size: Int? = nil) throws -> Self + + /// Inverse fft + /// + public func invfft(real: Bool? = nil) throws -> Self + + /// Label regions in an image + public func labelregions() throws -> Self + + /// Test for less than + /// + public func less(_ value: Double) throws -> Self + + /// Test for less than or equal + /// + public func lesseq(_ value: Double) throws -> Self + + /// Left shift + /// + public func lshift(_ amount: Int) throws -> Self + + /// Map an image though a lut + /// + public func maplut(lut: some VIPSImageProtocol, band: Int? = nil) throws -> Self + + /// First-order match of two images + /// + public func match(sec: some VIPSImageProtocol, xr1: Int, yr1: Int, xs1: Int, ys1: Int, xr2: Int, yr2: Int, xs2: Int, ys2: Int, hwindow: Int? = nil, harea: Int? = nil, search: Bool? = nil, interpolate: VIPSInterpolate? = nil) throws -> Self + + /// Invert a matrix + public func matrixinvert() throws -> Self + + /// Multiply two matrices + /// + public func matrixmultiply(_ rhs: some VIPSImageProtocol) throws -> Self + + /// Find image maximum + /// + public func max(size: Int? = nil) throws -> Double + + /// Maximum of a pair of images + /// + public func maxpair(_ rhs: some VIPSImageProtocol) throws -> Self + + /// Measure a set of patches on a color chart + /// + public func measure(h: Int, v: Int, left: Int? = nil, top: Int? = nil, width: Int? = nil, height: Int? = nil) throws -> Self + + /// Find image minimum + /// + public func min(size: Int? = nil) throws -> Double + + /// Minimum of a pair of images + /// + public func minpair(_ rhs: some VIPSImageProtocol) throws -> Self + + /// Test for greater than + /// + public func more(_ value: Double) throws -> Self + + /// Test for greater than or equal + /// + public func moreeq(_ value: Double) throws -> Self + + /// Pick most-significant byte from an image + /// + public func msb(band: Int? = nil) throws -> Self + + /// Multiply two images + /// + public func multiply(_ rhs: some VIPSImageProtocol) throws -> Self + + public func new(_ colors: [Double]) throws -> Self + + /// Test for inequality + /// + public func notequal(_ value: Double) throws -> Self + + /// Bitwise OR of two images + /// + public func orimage(_ rhs: some VIPSImageProtocol) throws -> Self + + /// Find threshold for percent of pixels + /// + public func percent(_ percent: Double) throws -> Int + + /// Calculate phase correlation + /// + public func phasecor(in2: some VIPSImageProtocol) throws -> Self + + /// Prewitt edge detector + public func prewitt() throws -> Self + + /// Resample an image with a quadratic transform + /// + public func quadratic(coeff: some VIPSImageProtocol, interpolate: VIPSInterpolate? = nil) throws -> Self + + /// Unpack radiance coding to float rgb + public func rad2float() throws -> Self + + /// Rank filter + /// + public func rank(width: Int, height: Int, index: Int) throws -> Self + + /// Linear recombination with matrix + /// + public func recomb(m: some VIPSImageProtocol) throws -> Self + + /// Remainder after integer division of an image and a constant + /// + public func remainder(_ value: Int) throws -> Self + + /// Remainder after integer division of an image and a constant + /// + public func remainderConst(c: [Double]) throws -> Self + + /// Rebuild an mosaiced image + /// + public func remosaic(oldStr: String, newStr: String) throws -> Self + + /// Replicate an image + /// + public func replicate(across: Int, down: Int) throws -> Self + + /// Rotate an image by a number of degrees + /// + public func rotate(angle: Double, interpolate: VIPSInterpolate? = nil, background: [Double]? = nil, odx: Double? = nil, ody: Double? = nil, idx: Double? = nil, idy: Double? = nil) throws -> Self + + /// Right shift + /// + public func rshift(_ amount: Int) throws -> Self + + /// Transform srgb to hsv + public func sRGB2HSV() throws -> Self + + /// Convert an srgb image to scrgb + public func sRGB2scRGB() throws -> Self + + /// Convert scrgb to bw + /// + public func scRGB2BW(depth: Int? = nil) throws -> Self + + /// Transform scrgb to xyz + public func scRGB2XYZ() throws -> Self + + /// Convert scrgb to srgb + /// + public func scRGB2sRGB(depth: Int? = nil) throws -> Self + + /// Scale an image to uchar + /// + public func scale(exp: Double? = nil, log: Bool? = nil) throws -> Self + + /// Scharr edge detector + public func scharr() throws -> Self + + /// Check sequential access + /// + public func sequential(tileHeight: Int? = nil) throws -> Self + + /// Unsharp masking for print + /// + public func sharpen(sigma: Double? = nil, x1: Double? = nil, y2: Double? = nil, y3: Double? = nil, m1: Double? = nil, m2: Double? = nil) throws -> Self + + /// Shrink an image + /// + public func shrink(hshrink: Double, vshrink: Double, ceil: Bool? = nil) throws -> Self + + /// Shrink an image horizontally + /// + public func shrinkh(hshrink: Int, ceil: Bool? = nil) throws -> Self + + /// Shrink an image vertically + /// + public func shrinkv(vshrink: Int, ceil: Bool? = nil) throws -> Self + + /// Unit vector of pixel + public func sign() throws -> Self + + /// Similarity transform of an image + /// + public func similarity(scale: Double? = nil, angle: Double? = nil, interpolate: VIPSInterpolate? = nil, background: [Double]? = nil, odx: Double? = nil, ody: Double? = nil, idx: Double? = nil, idy: Double? = nil) throws -> Self + + /// Sobel edge detector + public func sobel() throws -> Self + + /// Spatial correlation + /// + public func spcor(ref: some VIPSImageProtocol) throws -> Self + + /// Make displayable power spectrum + public func spectrum() throws -> Self + + /// Find many image stats + public func stats() throws -> Self + + /// Statistical difference + /// + public func stdif(width: Int, height: Int, s0: Double? = nil, b: Double? = nil, m0: Double? = nil, a: Double? = nil) throws -> Self + + /// Subsample an image + /// + public func subsample(xfac: Int, yfac: Int, point: Bool? = nil) throws -> Self + + /// Subtract two images + /// + public func subtract(_ rhs: some VIPSImageProtocol) throws -> Self + + /// Transpose3d an image + /// + public func transpose3d(pageHeight: Int? = nil) throws -> Self + + /// Wrap image origin + /// + public func wrap(x: Int? = nil, y: Int? = nil) throws -> Self + + /// Zoom an image + /// + public func zoom(xfac: Int, yfac: Int) throws -> Self +} +``` + +#### protocol VIPSLoggingDelegate + +```swift +public protocol VIPSLoggingDelegate : AnyObject { + public func debug(_ message: String) + + public func error(_ message: String) + + public func info(_ message: String) + + public func warning(_ message: String) +} +``` + +#### protocol VIPSObjectProtocol + +```swift +public protocol VIPSObjectProtocol : PointerWrapper, ~Copyable, ~Escapable { + public var type: GType { get } + + public func disconnect(signalHandler: Int) + + public @discardableResult func onPreClose(_ handler: @escaping (UnownedVIPSObjectRef) -> Void) -> Int } ``` @@ -1733,4 +1753,4 @@ public typealias VipsPrecision = Cvips.VipsPrecision public typealias VipsSize = Cvips.VipsSize ``` - + From 41199425a997361f58c8bbc34d30d4a801f5ef75 Mon Sep 17 00:00:00 2001 From: Tobias Haeberle Date: Sat, 15 Nov 2025 14:07:18 +0100 Subject: [PATCH 07/14] "fix" tests --- Tests/VIPSTests/ArithmeticOperationsTests.swift | 2 +- Tests/VIPSTests/TestSetup.swift | 16 +++++++++++----- Tests/VIPSTests/VIPSTests.swift | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Tests/VIPSTests/ArithmeticOperationsTests.swift b/Tests/VIPSTests/ArithmeticOperationsTests.swift index 9ff0efa..f33b00f 100644 --- a/Tests/VIPSTests/ArithmeticOperationsTests.swift +++ b/Tests/VIPSTests/ArithmeticOperationsTests.swift @@ -5,7 +5,7 @@ import Testing @testable import VIPS extension VIPSTests { - @Suite(.vips, .serialized) + @Suite(.serialized, .vips,) struct ArithmeticOperationsTests { // MARK: - Basic Arithmetic with Different Formats diff --git a/Tests/VIPSTests/TestSetup.swift b/Tests/VIPSTests/TestSetup.swift index 2d2d6ae..0e25c62 100644 --- a/Tests/VIPSTests/TestSetup.swift +++ b/Tests/VIPSTests/TestSetup.swift @@ -21,14 +21,17 @@ enum TestSetup { struct VIPSTestScopeProvider: TestScoping { func provideScope(for test: Test, testCase: Test.Case?, performing function: @Sendable () async throws -> Void) async throws { // Ensure VIPS is started before running any test - //TestSetup.ensureSetup() + TestSetup.ensureSetup() + /*print("!!!!!! START") try VIPS.start() - + defer { + VIPS.shutdown() + print("!!!!!! SHUTDOWN") + }*/ // Run the test try await function() - // stop vips - VIPS.shutdown() + } } @@ -37,7 +40,10 @@ struct VIPSTestTrait: SuiteTrait, TestTrait { typealias TestScopeProvider = VIPSTestScopeProvider func scopeProvider(for test: Test, testCase: Test.Case?) -> VIPSTestScopeProvider? { - return VIPSTestScopeProvider() + if test.isSuite || testCase == nil { + return VIPSTestScopeProvider() + } + return nil } } diff --git a/Tests/VIPSTests/VIPSTests.swift b/Tests/VIPSTests/VIPSTests.swift index 69421dc..063fc6a 100644 --- a/Tests/VIPSTests/VIPSTests.swift +++ b/Tests/VIPSTests/VIPSTests.swift @@ -4,7 +4,7 @@ import Testing @testable import VIPS -@Suite(.vips, .serialized) +@Suite(.serialized) struct VIPSTests { @Suite(.vips, .serialized) From c89dcb65ce035d4e8ed820a25d962707214eea2a Mon Sep 17 00:00:00 2001 From: Tobias Haeberle Date: Sat, 15 Nov 2025 15:23:55 +0100 Subject: [PATCH 08/14] update libvips --- .github/workflows/build-and-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index bd0b097..015e5f2 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: swift-version: ["6.2.1"] - vips-version: ["8.15.5", "8.16.1", "8.17.2"] + vips-version: ["8.15.5", "8.16.1", "8.17.3"] container: image: ghcr.io/t089/swift-vips-builder:swift-${{ matrix.swift-version }}-vips-${{ matrix.vips-version }} From 6dde6d59050cbf5c13d2c04800dbf4aec0be6f8d Mon Sep 17 00:00:00 2001 From: Tobias Haeberle Date: Sun, 16 Nov 2025 21:32:44 +0100 Subject: [PATCH 09/14] make tests run in parallel again --- Tests/VIPSTests/ArithmeticOperationsTests.swift | 2 +- Tests/VIPSTests/ArithmeticTests.swift | 2 +- Tests/VIPSTests/ConversionTests.swift | 2 +- Tests/VIPSTests/CoreTests.swift | 2 +- Tests/VIPSTests/CreateTests.swift | 2 +- Tests/VIPSTests/ForeignTests.swift | 2 +- Tests/VIPSTests/HistogramTests.swift | 2 +- Tests/VIPSTests/ResampleTests.swift | 2 +- Tests/VIPSTests/VIPSBlobTests.swift | 2 +- Tests/VIPSTests/VIPSSourceCustomTests.swift | 2 +- Tests/VIPSTests/VIPSTargetCustomTests.swift | 2 +- Tests/VIPSTests/VIPSTests.swift | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Tests/VIPSTests/ArithmeticOperationsTests.swift b/Tests/VIPSTests/ArithmeticOperationsTests.swift index f33b00f..f6853fa 100644 --- a/Tests/VIPSTests/ArithmeticOperationsTests.swift +++ b/Tests/VIPSTests/ArithmeticOperationsTests.swift @@ -5,7 +5,7 @@ import Testing @testable import VIPS extension VIPSTests { - @Suite(.serialized, .vips,) + @Suite(.vips) struct ArithmeticOperationsTests { // MARK: - Basic Arithmetic with Different Formats diff --git a/Tests/VIPSTests/ArithmeticTests.swift b/Tests/VIPSTests/ArithmeticTests.swift index 5807cd1..a266ff5 100644 --- a/Tests/VIPSTests/ArithmeticTests.swift +++ b/Tests/VIPSTests/ArithmeticTests.swift @@ -5,7 +5,7 @@ import Testing @testable import VIPS extension VIPSTests { - @Suite(.vips, .serialized) + @Suite(.vips) struct ArithmeticTests { // MARK: - Basic Arithmetic Operations diff --git a/Tests/VIPSTests/ConversionTests.swift b/Tests/VIPSTests/ConversionTests.swift index 0083fc8..dd8fa7b 100644 --- a/Tests/VIPSTests/ConversionTests.swift +++ b/Tests/VIPSTests/ConversionTests.swift @@ -5,7 +5,7 @@ import Testing @testable import VIPS extension VIPSTests { - @Suite(.vips, .serialized) + @Suite(.vips) struct ConversionTests { // MARK: - Type Casting Operations diff --git a/Tests/VIPSTests/CoreTests.swift b/Tests/VIPSTests/CoreTests.swift index 7fc093a..b942c2c 100644 --- a/Tests/VIPSTests/CoreTests.swift +++ b/Tests/VIPSTests/CoreTests.swift @@ -6,7 +6,7 @@ import Testing @testable import VIPS extension VIPSTests { - @Suite(.vips, .serialized) + @Suite(.vips) struct CoreTests { var testPath: String { diff --git a/Tests/VIPSTests/CreateTests.swift b/Tests/VIPSTests/CreateTests.swift index ca99c89..35a7857 100644 --- a/Tests/VIPSTests/CreateTests.swift +++ b/Tests/VIPSTests/CreateTests.swift @@ -4,7 +4,7 @@ import Testing import Foundation extension VIPSTests { -@Suite(.vips, .serialized) +@Suite(.vips) struct CreateTests { // MARK: - Basic Image Creation diff --git a/Tests/VIPSTests/ForeignTests.swift b/Tests/VIPSTests/ForeignTests.swift index e81c13f..20afec8 100644 --- a/Tests/VIPSTests/ForeignTests.swift +++ b/Tests/VIPSTests/ForeignTests.swift @@ -5,7 +5,7 @@ import Testing @testable import VIPS extension VIPSTests { - @Suite(.vips, .serialized) + @Suite(.vips) struct ForeignTests { // MARK: - JPEG Format Tests diff --git a/Tests/VIPSTests/HistogramTests.swift b/Tests/VIPSTests/HistogramTests.swift index cc58c80..4adacb0 100644 --- a/Tests/VIPSTests/HistogramTests.swift +++ b/Tests/VIPSTests/HistogramTests.swift @@ -10,7 +10,7 @@ private func histogramSum(_ histogram: VIPSImage) throws -> Double { } extension VIPSTests { - @Suite(.vips, .serialized) + @Suite(.vips) struct HistogramTests { // MARK: - Basic Histogram Operations diff --git a/Tests/VIPSTests/ResampleTests.swift b/Tests/VIPSTests/ResampleTests.swift index 1e7f135..90c79ee 100644 --- a/Tests/VIPSTests/ResampleTests.swift +++ b/Tests/VIPSTests/ResampleTests.swift @@ -5,7 +5,7 @@ import Testing @testable import VIPS extension VIPSTests { - @Suite(.vips, .serialized) + @Suite(.vips) struct ResampleTests { // MARK: - Basic Resize Operations diff --git a/Tests/VIPSTests/VIPSBlobTests.swift b/Tests/VIPSTests/VIPSBlobTests.swift index 6464382..30b29b2 100644 --- a/Tests/VIPSTests/VIPSBlobTests.swift +++ b/Tests/VIPSTests/VIPSBlobTests.swift @@ -3,7 +3,7 @@ import Testing import VIPS extension VIPSTests { - @Suite(.vips, .serialized) + @Suite(.vips) struct VIPSBlobTests { @Test func testCreateFromArray() { diff --git a/Tests/VIPSTests/VIPSSourceCustomTests.swift b/Tests/VIPSTests/VIPSSourceCustomTests.swift index e01b921..6a4be0f 100644 --- a/Tests/VIPSTests/VIPSSourceCustomTests.swift +++ b/Tests/VIPSTests/VIPSSourceCustomTests.swift @@ -3,7 +3,7 @@ import Testing extension VIPSTests { - @Suite(.vips, .serialized) + @Suite(.vips) struct VIPSSourceCustomTests { @Test diff --git a/Tests/VIPSTests/VIPSTargetCustomTests.swift b/Tests/VIPSTests/VIPSTargetCustomTests.swift index 5d8bf63..00d9fe1 100644 --- a/Tests/VIPSTests/VIPSTargetCustomTests.swift +++ b/Tests/VIPSTests/VIPSTargetCustomTests.swift @@ -2,7 +2,7 @@ import Testing import VIPS extension VIPSTests { - @Suite(.vips, .serialized) + @Suite(.vips) struct VIPSTargetCustomTests { @Test diff --git a/Tests/VIPSTests/VIPSTests.swift b/Tests/VIPSTests/VIPSTests.swift index 063fc6a..ac1ef55 100644 --- a/Tests/VIPSTests/VIPSTests.swift +++ b/Tests/VIPSTests/VIPSTests.swift @@ -7,7 +7,7 @@ import Testing @Suite(.serialized) struct VIPSTests { - @Suite(.vips, .serialized) + @Suite(.vips) struct SomeTests { var testPath: String { testUrl.path From d79b438534c203eb98a3c563050ec17bad9232e3 Mon Sep 17 00:00:00 2001 From: Tobias Date: Sat, 15 Nov 2025 20:11:14 +0000 Subject: [PATCH 10/14] chore(setup): add post-install script for required packages Co-authored-by: terragon-labs[bot] --- post-install.sh | 1 + 1 file changed, 1 insertion(+) create mode 100644 post-install.sh diff --git a/post-install.sh b/post-install.sh new file mode 100644 index 0000000..0c06ba7 --- /dev/null +++ b/post-install.sh @@ -0,0 +1 @@ +apt-get -y install gnupg2 libcurl4-openssl-dev libxml2-dev libncurses-dev libz3-dev pkg-config \ No newline at end of file From 22fb1daff39acd07bfb7009f68febe74a7574481 Mon Sep 17 00:00:00 2001 From: Tobias Date: Sat, 15 Nov 2025 20:21:46 +0000 Subject: [PATCH 11/14] feat(vips): add backward compatibility shim for VipsSaveable enum In libvips 8.17.0, VipsSaveable was removed and replaced with VipsForeignSaveable flags. This commit adds a compatibility shim in CvipsShim to provide the old VipsSaveable enum by mapping to the new VipsForeignSaveable flags, ensuring code depending on the old enum continues to function with libvips 8.17+. Co-authored-by: terragon-labs[bot] --- Sources/CvipsShim/include/CvipsShim.h | 32 +++++++++++++++++++ Sources/VIPS/Foreign/Enums/VipsSaveable.swift | 3 ++ 2 files changed, 35 insertions(+) diff --git a/Sources/CvipsShim/include/CvipsShim.h b/Sources/CvipsShim/include/CvipsShim.h index 31655f5..cce6d36 100644 --- a/Sources/CvipsShim/include/CvipsShim.h +++ b/Sources/CvipsShim/include/CvipsShim.h @@ -106,4 +106,36 @@ gboolean shim_vips_source_is_pipe(VipsSource *source); #endif #endif +// VipsSaveable backwards compatibility shim for libvips 8.17+ +// In 8.17.0, VipsSaveable was removed and replaced with VipsForeignSaveable (a flags enum) +// This shim provides the old enum for backwards compatibility +#if defined(SHIM_VIPS_VERSION_8_17) +// Check if VipsSaveable doesn't exist (it was removed in 8.17.x) +#ifndef VIPS_TYPE_SAVEABLE + +/** + * VipsSaveable: + * @VIPS_SAVEABLE_MONO: 1 band (eg. CSV) + * @VIPS_SAVEABLE_RGB: 1 or 3 bands (eg. PPM) + * @VIPS_SAVEABLE_RGBA: 1, 2, 3 or 4 bands (eg. PNG) + * @VIPS_SAVEABLE_RGBA_ONLY: 3 or 4 bands (eg. WEBP) + * @VIPS_SAVEABLE_RGB_CMYK: 1, 3 or 4 bands (eg. JPEG) + * @VIPS_SAVEABLE_ANY: any number of bands (eg. TIFF) + * + * Backwards compatibility shim for the old VipsSaveable enum. + * Maps to appropriate VipsForeignSaveable flags. + */ +typedef enum { + VIPS_SAVEABLE_MONO = VIPS_FOREIGN_SAVEABLE_MONO, + VIPS_SAVEABLE_RGB = VIPS_FOREIGN_SAVEABLE_RGB, + VIPS_SAVEABLE_RGBA = VIPS_FOREIGN_SAVEABLE_RGB | VIPS_FOREIGN_SAVEABLE_ALPHA, + VIPS_SAVEABLE_RGBA_ONLY = VIPS_FOREIGN_SAVEABLE_RGB | VIPS_FOREIGN_SAVEABLE_ALPHA, + VIPS_SAVEABLE_RGB_CMYK = VIPS_FOREIGN_SAVEABLE_RGB | VIPS_FOREIGN_SAVEABLE_CMYK, + VIPS_SAVEABLE_ANY = VIPS_FOREIGN_SAVEABLE_ALL, + VIPS_SAVEABLE_LAST = VIPS_FOREIGN_SAVEABLE_ALL + 1 +} VipsSaveable; + +#endif +#endif + #endif /* C_vips_shim_h */ diff --git a/Sources/VIPS/Foreign/Enums/VipsSaveable.swift b/Sources/VIPS/Foreign/Enums/VipsSaveable.swift index 327cea7..f40d365 100644 --- a/Sources/VIPS/Foreign/Enums/VipsSaveable.swift +++ b/Sources/VIPS/Foreign/Enums/VipsSaveable.swift @@ -1,6 +1,9 @@ import Cvips import CvipsShim +// VipsSaveable was removed in libvips 8.17.0 and replaced with VipsForeignSaveable. +// CvipsShim provides a backwards compatibility layer for 8.17+ that recreates +// the old VipsSaveable enum by mapping to VipsForeignSaveable flags. extension VipsSaveable { public static var mono: Self { VIPS_SAVEABLE_MONO } public static var rgb: Self { VIPS_SAVEABLE_RGB } From 5a2332dd0690d87210515ab94c6d54b0eae43981 Mon Sep 17 00:00:00 2001 From: Tobias Date: Sat, 15 Nov 2025 20:55:57 +0000 Subject: [PATCH 12/14] fix(vips): improve VipsSaveable shim detection for libvips 8.17+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed the shim detection from checking VIPS_TYPE_SAVEABLE (a GObject type macro that may not be reliable) to checking for VIPS_SAVEABLE_MONO constant directly. This ensures the shim is properly activated when VipsSaveable doesn't exist in libvips 8.17.x. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Sources/CvipsShim/include/CvipsShim.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Sources/CvipsShim/include/CvipsShim.h b/Sources/CvipsShim/include/CvipsShim.h index cce6d36..f489d82 100644 --- a/Sources/CvipsShim/include/CvipsShim.h +++ b/Sources/CvipsShim/include/CvipsShim.h @@ -109,9 +109,8 @@ gboolean shim_vips_source_is_pipe(VipsSource *source); // VipsSaveable backwards compatibility shim for libvips 8.17+ // In 8.17.0, VipsSaveable was removed and replaced with VipsForeignSaveable (a flags enum) // This shim provides the old enum for backwards compatibility -#if defined(SHIM_VIPS_VERSION_8_17) -// Check if VipsSaveable doesn't exist (it was removed in 8.17.x) -#ifndef VIPS_TYPE_SAVEABLE +#if defined(SHIM_VIPS_VERSION_8_17) && !defined(VIPS_SAVEABLE_MONO) +// VipsSaveable doesn't exist in 8.17.x, so we recreate it /** * VipsSaveable: @@ -135,7 +134,6 @@ typedef enum { VIPS_SAVEABLE_LAST = VIPS_FOREIGN_SAVEABLE_ALL + 1 } VipsSaveable; -#endif #endif #endif /* C_vips_shim_h */ From 2ec015e8336fba9e3e368f644a573e2cc00cadd3 Mon Sep 17 00:00:00 2001 From: Tobias Date: Sat, 15 Nov 2025 21:01:40 +0000 Subject: [PATCH 13/14] feat(vips): add backward compatibility shim for VipsSaveable enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libvips 8.17.0 removed VipsSaveable and replaced it with VipsForeignSaveable (a flags enum with different semantics). This creates a compatibility layer: - CvipsShim defines the old enum values for libvips 8.17+ by mapping to VipsForeignSaveable flags - Swift code uses a typealias to UInt32 that works with both the native enum (pre-8.17) and our shim constants (8.17+) - Provides convenient static properties for all VipsSaveable values This ensures the wrapper works across libvips versions 8.15 through 8.17+ without breaking changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Sources/VIPS/Foreign/Enums/VipsSaveable.swift | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Sources/VIPS/Foreign/Enums/VipsSaveable.swift b/Sources/VIPS/Foreign/Enums/VipsSaveable.swift index f40d365..57db0f0 100644 --- a/Sources/VIPS/Foreign/Enums/VipsSaveable.swift +++ b/Sources/VIPS/Foreign/Enums/VipsSaveable.swift @@ -2,14 +2,18 @@ import Cvips import CvipsShim // VipsSaveable was removed in libvips 8.17.0 and replaced with VipsForeignSaveable. -// CvipsShim provides a backwards compatibility layer for 8.17+ that recreates -// the old VipsSaveable enum by mapping to VipsForeignSaveable flags. -extension VipsSaveable { - public static var mono: Self { VIPS_SAVEABLE_MONO } - public static var rgb: Self { VIPS_SAVEABLE_RGB } - public static var rgba: Self { VIPS_SAVEABLE_RGBA } - public static var rgbaOnly: Self { VIPS_SAVEABLE_RGBA_ONLY } - public static var rgbCmyk: Self { VIPS_SAVEABLE_RGB_CMYK } - public static var any: Self { VIPS_SAVEABLE_ANY } - public static var last: Self { VIPS_SAVEABLE_LAST } +// CvipsShim provides the VipsSaveable typedef and constants for 8.17+. +// We use a Swift typealias that works with both the native enum (pre-8.17) +// and our shim typedef (8.17+), since both are compatible with UInt32/CInt. + +public typealias VipsSaveable = UInt32 + +public extension VipsSaveable { + static var mono: Self { VIPS_SAVEABLE_MONO.rawValue } + static var rgb: Self { VIPS_SAVEABLE_RGB.rawValue } + static var rgba: Self { VIPS_SAVEABLE_RGBA.rawValue } + static var rgbaOnly: Self { VIPS_SAVEABLE_RGBA_ONLY.rawValue } + static var rgbCmyk: Self { VIPS_SAVEABLE_RGB_CMYK.rawValue } + static var any: Self { VIPS_SAVEABLE_ANY.rawValue } + static var last: Self { VIPS_SAVEABLE_LAST.rawValue } } \ No newline at end of file From b01505dfab4986281f21a4ddb6b3c82e2abd6146 Mon Sep 17 00:00:00 2001 From: Tobias Date: Sat, 15 Nov 2025 21:09:20 +0000 Subject: [PATCH 14/14] fix(vips): use shim functions for VipsSaveable constants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Swift cannot import C macros that use bitwise OR operators. libvips 8.17.3 added backwards compatibility macros for VipsSaveable, but they use OR operations which Swift rejects with "structure not supported". Solution: CvipsShim now provides inline getter functions that: - Work across all libvips versions (8.15.x through 8.17.x+) - Return the native enum values for pre-8.17 - Use the backwards compat macros if available in 8.17.3+ - Fall back to VipsForeignSaveable flags for 8.17.0-8.17.2 Swift code simply calls these functions to get the constant values, avoiding any direct dependency on enums or macros. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Sources/CvipsShim/include/CvipsShim.h | 120 ++++++++++++++---- Sources/VIPS/Foreign/Enums/VipsSaveable.swift | 21 +-- 2 files changed, 103 insertions(+), 38 deletions(-) diff --git a/Sources/CvipsShim/include/CvipsShim.h b/Sources/CvipsShim/include/CvipsShim.h index f489d82..863d6fb 100644 --- a/Sources/CvipsShim/include/CvipsShim.h +++ b/Sources/CvipsShim/include/CvipsShim.h @@ -106,34 +106,98 @@ gboolean shim_vips_source_is_pipe(VipsSource *source); #endif #endif -// VipsSaveable backwards compatibility shim for libvips 8.17+ -// In 8.17.0, VipsSaveable was removed and replaced with VipsForeignSaveable (a flags enum) -// This shim provides the old enum for backwards compatibility -#if defined(SHIM_VIPS_VERSION_8_17) && !defined(VIPS_SAVEABLE_MONO) -// VipsSaveable doesn't exist in 8.17.x, so we recreate it - -/** - * VipsSaveable: - * @VIPS_SAVEABLE_MONO: 1 band (eg. CSV) - * @VIPS_SAVEABLE_RGB: 1 or 3 bands (eg. PPM) - * @VIPS_SAVEABLE_RGBA: 1, 2, 3 or 4 bands (eg. PNG) - * @VIPS_SAVEABLE_RGBA_ONLY: 3 or 4 bands (eg. WEBP) - * @VIPS_SAVEABLE_RGB_CMYK: 1, 3 or 4 bands (eg. JPEG) - * @VIPS_SAVEABLE_ANY: any number of bands (eg. TIFF) - * - * Backwards compatibility shim for the old VipsSaveable enum. - * Maps to appropriate VipsForeignSaveable flags. - */ -typedef enum { - VIPS_SAVEABLE_MONO = VIPS_FOREIGN_SAVEABLE_MONO, - VIPS_SAVEABLE_RGB = VIPS_FOREIGN_SAVEABLE_RGB, - VIPS_SAVEABLE_RGBA = VIPS_FOREIGN_SAVEABLE_RGB | VIPS_FOREIGN_SAVEABLE_ALPHA, - VIPS_SAVEABLE_RGBA_ONLY = VIPS_FOREIGN_SAVEABLE_RGB | VIPS_FOREIGN_SAVEABLE_ALPHA, - VIPS_SAVEABLE_RGB_CMYK = VIPS_FOREIGN_SAVEABLE_RGB | VIPS_FOREIGN_SAVEABLE_CMYK, - VIPS_SAVEABLE_ANY = VIPS_FOREIGN_SAVEABLE_ALL, - VIPS_SAVEABLE_LAST = VIPS_FOREIGN_SAVEABLE_ALL + 1 -} VipsSaveable; - +// VipsSaveable backwards compatibility shim +// In libvips 8.17.0, VipsSaveable was removed and replaced with VipsForeignSaveable. +// In 8.17.3+, they added macros for backwards compatibility, but Swift can't import +// macros that use bitwise OR operations. We provide simple constant getters that work +// across all versions: pre-8.17 (native enum), 8.17.0-8.17.2 (VipsForeignSaveable), +// and 8.17.3+ (backwards compat macros). + +// Getter functions that Swift can import +static inline int shim_VIPS_SAVEABLE_MONO(void) { +#if defined(SHIM_VIPS_VERSION_8_17) + // For 8.17+, use the macro if available, otherwise VipsForeignSaveable + #ifdef VIPS_SAVEABLE_MONO + return VIPS_SAVEABLE_MONO; + #else + return VIPS_FOREIGN_SAVEABLE_MONO; + #endif +#else + // For pre-8.17, use the native enum + return VIPS_SAVEABLE_MONO; +#endif +} + +static inline int shim_VIPS_SAVEABLE_RGB(void) { +#if defined(SHIM_VIPS_VERSION_8_17) + #ifdef VIPS_SAVEABLE_RGB + return VIPS_SAVEABLE_RGB; + #else + return VIPS_FOREIGN_SAVEABLE_MONO | VIPS_FOREIGN_SAVEABLE_RGB; + #endif +#else + return VIPS_SAVEABLE_RGB; +#endif +} + +static inline int shim_VIPS_SAVEABLE_RGBA(void) { +#if defined(SHIM_VIPS_VERSION_8_17) + #ifdef VIPS_SAVEABLE_RGBA + return VIPS_SAVEABLE_RGBA; + #else + return VIPS_FOREIGN_SAVEABLE_MONO | VIPS_FOREIGN_SAVEABLE_RGB | VIPS_FOREIGN_SAVEABLE_ALPHA; + #endif +#else + return VIPS_SAVEABLE_RGBA; +#endif +} + +static inline int shim_VIPS_SAVEABLE_RGBA_ONLY(void) { +#if defined(SHIM_VIPS_VERSION_8_17) + #ifdef VIPS_SAVEABLE_RGBA_ONLY + return VIPS_SAVEABLE_RGBA_ONLY; + #else + return VIPS_FOREIGN_SAVEABLE_RGB | VIPS_FOREIGN_SAVEABLE_ALPHA; + #endif +#else + return VIPS_SAVEABLE_RGBA_ONLY; +#endif +} + +static inline int shim_VIPS_SAVEABLE_RGB_CMYK(void) { +#if defined(SHIM_VIPS_VERSION_8_17) + #ifdef VIPS_SAVEABLE_RGB_CMYK + return VIPS_SAVEABLE_RGB_CMYK; + #else + return VIPS_FOREIGN_SAVEABLE_MONO | VIPS_FOREIGN_SAVEABLE_RGB | VIPS_FOREIGN_SAVEABLE_CMYK; + #endif +#else + return VIPS_SAVEABLE_RGB_CMYK; +#endif +} + +static inline int shim_VIPS_SAVEABLE_ANY(void) { +#if defined(SHIM_VIPS_VERSION_8_17) + #ifdef VIPS_SAVEABLE_ANY + return VIPS_SAVEABLE_ANY; + #else + return VIPS_FOREIGN_SAVEABLE_ALL; + #endif +#else + return VIPS_SAVEABLE_ANY; +#endif +} + +static inline int shim_VIPS_SAVEABLE_LAST(void) { +#if defined(SHIM_VIPS_VERSION_8_17) + #ifdef VIPS_SAVEABLE_LAST + return VIPS_SAVEABLE_LAST; + #else + return 99; + #endif +#else + return VIPS_SAVEABLE_LAST; #endif +} #endif /* C_vips_shim_h */ diff --git a/Sources/VIPS/Foreign/Enums/VipsSaveable.swift b/Sources/VIPS/Foreign/Enums/VipsSaveable.swift index 57db0f0..7681d02 100644 --- a/Sources/VIPS/Foreign/Enums/VipsSaveable.swift +++ b/Sources/VIPS/Foreign/Enums/VipsSaveable.swift @@ -2,18 +2,19 @@ import Cvips import CvipsShim // VipsSaveable was removed in libvips 8.17.0 and replaced with VipsForeignSaveable. -// CvipsShim provides the VipsSaveable typedef and constants for 8.17+. -// We use a Swift typealias that works with both the native enum (pre-8.17) -// and our shim typedef (8.17+), since both are compatible with UInt32/CInt. +// In 8.17.3+, they added backwards compatibility macros, but Swift can't import them +// because they use bitwise OR. CvipsShim provides getter functions that work across +// all libvips versions by checking for the native enum/macros and falling back to +// VipsForeignSaveable flags. public typealias VipsSaveable = UInt32 public extension VipsSaveable { - static var mono: Self { VIPS_SAVEABLE_MONO.rawValue } - static var rgb: Self { VIPS_SAVEABLE_RGB.rawValue } - static var rgba: Self { VIPS_SAVEABLE_RGBA.rawValue } - static var rgbaOnly: Self { VIPS_SAVEABLE_RGBA_ONLY.rawValue } - static var rgbCmyk: Self { VIPS_SAVEABLE_RGB_CMYK.rawValue } - static var any: Self { VIPS_SAVEABLE_ANY.rawValue } - static var last: Self { VIPS_SAVEABLE_LAST.rawValue } + static var mono: Self { UInt32(shim_VIPS_SAVEABLE_MONO()) } + static var rgb: Self { UInt32(shim_VIPS_SAVEABLE_RGB()) } + static var rgba: Self { UInt32(shim_VIPS_SAVEABLE_RGBA()) } + static var rgbaOnly: Self { UInt32(shim_VIPS_SAVEABLE_RGBA_ONLY()) } + static var rgbCmyk: Self { UInt32(shim_VIPS_SAVEABLE_RGB_CMYK()) } + static var any: Self { UInt32(shim_VIPS_SAVEABLE_ANY()) } + static var last: Self { UInt32(shim_VIPS_SAVEABLE_LAST()) } } \ No newline at end of file