diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index b7ed5af..015e5f2 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -13,8 +13,8 @@ jobs: strategy: fail-fast: false matrix: - swift-version: ["6.2.0", "6.1.3"] - vips-version: ["8.15.5", "8.16.1", "8.17.2"] + swift-version: ["6.2.1"] + 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 }} 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/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/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/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+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..63a2dfe 100644 --- a/Sources/VIPS/Core/VIPSImage.swift +++ b/Sources/VIPS/Core/VIPSImage.swift @@ -1,14 +1,29 @@ import Cvips import CvipsShim -open class VIPSImage { +public typealias VIPSProgress = Cvips.VipsProgress - internal var other: Any? = nil +public final class VIPSImage: VIPSImageProtocol { + public var ptr: UnsafeMutableRawPointer! - @usableFromInline - private(set) var image: UnsafeMutablePointer + public var image: UnsafeMutablePointer! { + return self.ptr.assumingMemoryBound(to: VipsImage.self) + } + + public init(_ ptr: UnsafeMutableRawPointer) { + self.ptr = ptr + } + + public init(_ image: UnsafeMutablePointer) { + self.ptr = UnsafeMutableRawPointer(image) + } + + deinit { + guard let ptr else { return } + g_object_unref(ptr) + } - func withVipsImage(_ body: (UnsafeMutablePointer) -> R) -> R { + public func withVipsImage(_ body: (UnsafeMutablePointer) -> R) -> R { return body(self.image) } @@ -16,7 +31,7 @@ open class VIPSImage { /// 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, @@ -47,12 +62,13 @@ open class VIPSImage { } if let maybe = maybe { - self.image = maybe + self.init(maybe) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.init(image) } } @@ -68,7 +84,7 @@ open class VIPSImage { /// - 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, @@ -97,12 +113,13 @@ open class VIPSImage { } if let maybe = maybe { - self.image = maybe + self.init(maybe) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.init(image) } } @@ -118,7 +135,7 @@ open class VIPSImage { /// - 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, @@ -147,12 +164,13 @@ open class VIPSImage { } if let maybe = maybe { - self.image = maybe + self.init(maybe) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.init(image) } } @@ -168,7 +186,7 @@ open class VIPSImage { /// - 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, @@ -197,12 +215,13 @@ open class VIPSImage { } if let maybe = maybe { - self.image = maybe + self.init(maybe) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.init(image) } } @@ -218,7 +237,7 @@ open class VIPSImage { /// - 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, @@ -247,12 +266,13 @@ open class VIPSImage { } if let maybe = maybe { - self.image = maybe + self.init(maybe) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.init(image) } } @@ -268,7 +288,7 @@ open class VIPSImage { /// - 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, @@ -297,12 +317,13 @@ open class VIPSImage { } if let maybe = maybe { - self.image = maybe + self.init(maybe) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.init(image) } } @@ -318,7 +339,7 @@ open class VIPSImage { /// - 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, @@ -347,12 +368,13 @@ open class VIPSImage { } if let maybe = maybe { - self.image = maybe + self.init(maybe) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.init(image) } } @@ -368,7 +390,7 @@ open class VIPSImage { /// - 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, @@ -397,12 +419,13 @@ open class VIPSImage { } if let maybe = maybe { - self.image = maybe + self.init(maybe) } else { - self.image = try Array(data) + let image = try Array(data) .withUnsafeBufferPointer { buffer in try createImage(from: .init(buffer)) } + self.init(image) } } @@ -421,7 +444,7 @@ open class VIPSImage { /// - 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, @@ -440,7 +463,7 @@ open class VIPSImage { throw VIPSError() } - self.image = image + self.init(image) } /// Creates a VIPSImage from a memory area containing signed 8-bit integer data. @@ -458,7 +481,7 @@ open class VIPSImage { /// - 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, @@ -477,7 +500,7 @@ open class VIPSImage { throw VIPSError() } - self.image = image + self.init(image) } /// Creates a VIPSImage from a memory area containing unsigned 16-bit integer data. @@ -495,7 +518,7 @@ open class VIPSImage { /// - 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, @@ -514,7 +537,7 @@ open class VIPSImage { throw VIPSError() } - self.image = image + self.init(image) } /// Creates a VIPSImage from a memory area containing signed 16-bit integer data. @@ -532,7 +555,7 @@ open class VIPSImage { /// - 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, @@ -551,7 +574,7 @@ open class VIPSImage { throw VIPSError() } - self.image = image + self.init(image) } /// Creates a VIPSImage from a memory area containing unsigned 32-bit integer data. @@ -569,7 +592,7 @@ open class VIPSImage { /// - 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, @@ -588,7 +611,7 @@ open class VIPSImage { throw VIPSError() } - self.image = image + self.init(image) } /// Creates a VIPSImage from a memory area containing signed 32-bit integer data. @@ -606,7 +629,7 @@ open class VIPSImage { /// - 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, @@ -625,7 +648,7 @@ open class VIPSImage { throw VIPSError() } - self.image = image + self.init(image) } /// Creates a VIPSImage from a memory area containing 32-bit floating point data. @@ -643,7 +666,7 @@ open class VIPSImage { /// - 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, @@ -662,7 +685,7 @@ open class VIPSImage { throw VIPSError() } - self.image = image + self.init(image) } /// Creates a VIPSImage from a memory area containing 64-bit floating point data. @@ -680,7 +703,7 @@ open class VIPSImage { /// - 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, @@ -699,7 +722,7 @@ open class VIPSImage { throw VIPSError() } - self.image = image + self.init(image) } /// Creates a new image by loading the given data. @@ -711,7 +734,7 @@ open class VIPSImage { /// - 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 @@ -731,7 +754,7 @@ open class VIPSImage { 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) @@ -748,7 +771,7 @@ open class VIPSImage { /// - 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 @@ -775,7 +798,7 @@ open class VIPSImage { 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) @@ -794,7 +817,7 @@ open class VIPSImage { /// - 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 @@ -804,16 +827,16 @@ 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 { 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) @@ -830,7 +853,7 @@ open class VIPSImage { /// - 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 @@ -840,8 +863,8 @@ open class VIPSImage { else { 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) @@ -879,13 +902,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 +918,15 @@ 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 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) + else { throw VIPSError(vips_error_buffer()) } - self.image = image + self.init(image) } public convenience init( @@ -911,7 +937,7 @@ open class VIPSImage { 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) @@ -919,33 +945,9 @@ 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) - image.initialize(to: nil) - defer { - image.deallocate() - } - try block(&image.pointee) - precondition(image.pointee != nil, "Image pointer cannot be nil after init.") - self.image = image.pointee! - self.other = other - } - - func withUnsafeMutablePointer(_ block: (inout UnsafeMutablePointer) throws -> (T)) - rethrows -> T - { - return try block(&self.image) - } +} - deinit { - g_object_unref(self.image) - } +extension VIPSImageProtocol where Self: ~Copyable, Self: ~Escapable { @usableFromInline static func call( @@ -959,7 +961,7 @@ open class VIPSImage { @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) @@ -975,9 +977,23 @@ open class VIPSImage { } } -extension VIPSImage { - public func new(_ colors: [Double]) throws -> VIPSImage { - try VIPSImage(self) { out in +extension VIPSImageProtocol where Self: ~Copyable { + @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 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() } @@ -991,7 +1007,7 @@ extension VIPSImage { /// - 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))" @@ -1013,7 +1029,7 @@ extension VIPSImage { throw VIPSError() } - return VIPSImage(image) + return Self(image) } /// Create an empty matrix image @@ -1021,12 +1037,12 @@ extension VIPSImage { /// - 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 @@ -1034,10 +1050,134 @@ 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 + public func copyMemory() throws -> Self { + return try Self { out in out = vips_image_copy_memory(self.image) if out == nil { throw VIPSError() } } } + +} + +public protocol VIPSImageProtocol: VIPSObjectProtocol, ~Escapable, ~Copyable { + init(_ image: UnsafeMutablePointer) + var image: UnsafeMutablePointer! { get } } + +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)) + } + + @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 + } + /* Compiler bug: + set { + 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) { + vips_image_set_progress(self.image, enabled ? .true : .false) + } + + @discardableResult + public func onPreeval(_ handler: @escaping (UnownedVIPSImageRef, VIPSProgress) -> Void) -> Int { + self.onProgress(signal: "preeval", handler: handler) + } + + @discardableResult + public func onEval(_ handler: @escaping (UnownedVIPSImageRef, VIPSProgress) -> Void) -> Int { + self.onProgress(signal: "eval", handler: handler) + } + + @discardableResult + public func onPosteval(_ handler: @escaping (UnownedVIPSImageRef, VIPSProgress) -> Void) -> Int { + self.onProgress(signal: "posteval", handler: handler) + } + + private func onProgress(signal: String, handler: @escaping (UnownedVIPSImageRef, 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<(UnownedVIPSImageRef, VIPSProgress), Void> + > + .fromOpaque(userData).takeUnretainedValue() + holder.closure((UnownedVIPSImageRef(imagePtr), progressPtr.pointee)) + } + let closureHolder = ClosureHolder<(UnownedVIPSImageRef, 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<(UnownedVIPSImageRef, VIPSProgress), Void> + > + .fromOpaque(userData) + .release() + } + } + ) + } +} + +public struct UnownedVIPSImageRef: VIPSImageProtocol, ~Escapable { + public var ptr: UnsafeMutableRawPointer! + + public init(_ image: UnsafeMutablePointer) { + self.ptr = UnsafeMutableRawPointer(image) + } + + public init(_ ptr: UnsafeMutableRawPointer) { + self.ptr = ptr + } + + public var image: UnsafeMutablePointer! { + return ptr.assumingMemoryBound(to: VipsImage.self) + } +} + +extension VIPSImage { + public convenience init(takingOwnership imgRef: UnownedVIPSImageRef) { + self.init(g_object_ref(imgRef.ptr)) + } +} \ No newline at end of file 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..59b9e13 100644 --- a/Sources/VIPS/Core/VIPSObject.swift +++ b/Sources/VIPS/Core/VIPSObject.swift @@ -1,25 +1,155 @@ 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 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, ~Escapable { + init(_ ptr: UnsafeMutableRawPointer) + var ptr: UnsafeMutableRawPointer! { get } +} + +public protocol VIPSObjectProtocol: PointerWrapper, ~Copyable, ~Escapable { + var object: UnsafeMutablePointer! { get } +} + +extension VIPSObjectProtocol where Self : ~Copyable, Self: ~Escapable { + 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 struct VIPSObjectRef: VIPSObjectProtocol, ~Copyable { + public let ptr: UnsafeMutableRawPointer! + + public init(_ ptr: UnsafeMutableRawPointer) { + self.ptr = ptr + } + + public init(borrowing ref: UnownedVIPSObjectRef) { + self.ptr = ref.ptr + g_object_ref(ref.ptr) + } + deinit { - guard let object = self.object else { return } - g_object_unref(object) + g_object_unref(self.ptr) + } +} + +public struct UnownedVIPSObjectRef: VIPSObjectProtocol, ~Escapable { + public let ptr: UnsafeMutableRawPointer! - self.object = nil + public init(_ ptr: UnsafeMutableRawPointer) { + self.ptr = 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/Sources/VIPS/Core/VIPSOption.swift b/Sources/VIPS/Core/VIPSOption.swift index 9a08542..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: VIPSObject) { + 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/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/Sources/VIPS/Foreign/Enums/VipsSaveable.swift b/Sources/VIPS/Foreign/Enums/VipsSaveable.swift deleted file mode 100644 index 327cea7..0000000 --- a/Sources/VIPS/Foreign/Enums/VipsSaveable.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Cvips -import CvipsShim - -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 } -} \ No newline at end of file diff --git a/Sources/VIPS/Generated/Foreign/foreign_gif.generated.swift b/Sources/VIPS/Generated/Foreign/foreign_gif.generated.swift index 85416f7..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(nil) { 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(nil) { 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([source]) { 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 /// @@ -199,6 +21,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 +35,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 +66,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) } @@ -255,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 @@ -268,6 +95,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 +108,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 +144,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) } @@ -329,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") @@ -349,6 +181,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 +195,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 +226,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) } @@ -405,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 7742b5a..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(nil) { 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(nil) { 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([source]) { 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 3930ea4..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(nil) { 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(nil) { 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([source]) { 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 dc59b1e..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(nil) { 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(nil) { 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([source]) { 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(nil) { 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([source]) { 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,195 +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 - /// - 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, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage(nil) { out in - var opt = VIPSOption() - - opt.set("filename", value: filename) - 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("jp2kload", options: &opt) - } - } - - /// Load jpeg2000 image - /// - /// - Parameters: - /// - buffer: Buffer to load from - /// - page: Load this page from the image - /// - 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, - 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(nil) { out in - var opt = VIPSOption() - - opt.set("buffer", value: blob) - 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("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 - /// - 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, - 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, - memory: memory, - access: access, - failOn: failOn, - revalidate: revalidate - ) - } - - /// Load jpeg2000 image - /// - /// - Parameters: - /// - source: Source to load from - /// - page: Load this page from the image - /// - 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, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage([source]) { out in - var opt = VIPSOption() - - opt.set("source", value: source) - 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("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) @@ -892,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 @@ -956,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") @@ -1022,191 +637,72 @@ 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(nil) { 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) + 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) + } + if let effort = effort { + opt.set("effort", value: effort) + } + if let lossless = lossless { + opt.set("lossless", value: lossless) + } + if let quality = quality { + opt.set("Q", value: quality) + } + 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("jxlsave", options: &opt) } - /// Load jpeg-xl image + /// Save image in jpeg-xl format /// /// - 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(nil) { 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([source]) { 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 @@ -1217,7 +713,6 @@ extension VIPSImage { /// - 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, @@ -1227,11 +722,16 @@ extension VIPSImage { background: [Double]? = nil, pageHeight: Int? = nil, profile: String? = nil - ) throws { + ) throws -> VIPSBlob { var opt = VIPSOption() - opt.set("in", value: self) - opt.set("filename", value: filename) + let out: UnsafeMutablePointer?> = .allocate(capacity: 1) + out.initialize(to: nil) + defer { + out.deallocate() + } + + opt.set("in", value: self.image) if let tier = tier { opt.set("tier", value: tier) } @@ -1259,13 +759,21 @@ extension VIPSImage { if let profile = profile { opt.set("profile", value: profile) } + opt.set("buffer", value: out) + + try Self.call("jxlsave_buffer", options: &opt) + + guard let vipsBlob = out.pointee else { + throw VIPSError("Failed to get buffer from jxlsave_buffer") + } - try VIPSImage.call("jxlsave", options: &opt) + return VIPSBlob(vipsBlob) } /// Save image in jpeg-xl format /// /// - Parameters: + /// - target: Target to save to /// - tier: Decode speed tier /// - distance: Target butteraugli distance /// - effort: Encoding effort @@ -1276,6 +784,7 @@ extension VIPSImage { /// - pageHeight: Set page height for multipage save /// - profile: Filename of ICC profile to embed public func jxlsave( + target: VIPSTarget, tier: Int? = nil, distance: Double? = nil, effort: Int? = nil, @@ -1285,16 +794,11 @@ extension VIPSImage { background: [Double]? = nil, pageHeight: Int? = nil, profile: String? = nil - ) throws -> VIPSBlob { + ) throws { var opt = VIPSOption() - let out: UnsafeMutablePointer?> = .allocate(capacity: 1) - out.initialize(to: nil) - defer { - out.deallocate() - } - - opt.set("in", value: self.image) + opt.set("in", value: self) + opt.set("target", value: target) if let tier = tier { opt.set("tier", value: tier) } @@ -1322,37 +826,30 @@ extension VIPSImage { if let profile = profile { opt.set("profile", value: profile) } - opt.set("buffer", value: out) - - try VIPSImage.call("jxlsave_buffer", options: &opt) - - guard let vipsBlob = out.pointee else { - throw VIPSError("Failed to get buffer from jxlsave_buffer") - } - return VIPSBlob(vipsBlob) + try Self.call("jxlsave_target", options: &opt) } - /// Save image in jpeg-xl format + /// Save file with imagemagick /// /// - Parameters: - /// - target: Target to save to - /// - tier: Decode speed tier - /// - distance: Target butteraugli distance - /// - effort: Encoding effort - /// - lossless: Enable lossless compression - /// - quality: Quality factor + /// - 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 jxlsave( - target: VIPSTarget, - tier: Int? = nil, - distance: Double? = nil, - effort: Int? = nil, - lossless: Bool? = nil, + 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, @@ -1361,21 +858,21 @@ extension VIPSImage { var opt = VIPSOption() opt.set("in", value: self) - opt.set("target", value: target) - if let tier = tier { - opt.set("tier", value: tier) + opt.set("filename", value: filename) + if let format = format { + opt.set("format", value: format) } - if let distance = distance { - opt.set("distance", value: distance) + if let quality = quality { + opt.set("quality", value: quality) } - if let effort = effort { - opt.set("effort", value: effort) + if let optimizeGifFrames = optimizeGifFrames { + opt.set("optimize_gif_frames", value: optimizeGifFrames) } - if let lossless = lossless { - opt.set("lossless", value: lossless) + if let optimizeGifTransparency = optimizeGifTransparency { + opt.set("optimize_gif_transparency", value: optimizeGifTransparency) } - if let quality = quality { - opt.set("Q", value: quality) + if let bitdepth = bitdepth { + opt.set("bitdepth", value: bitdepth) } if let keep = keep { opt.set("keep", value: keep) @@ -1390,66 +887,1007 @@ extension VIPSImage { opt.set("profile", value: profile) } - try VIPSImage.call("jxlsave_target", options: &opt) + try Self.call("magicksave", options: &opt) } - /// Load file with imagemagick7 + /// Save image to magick buffer /// /// - 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 -> VIPSImage { - return try VIPSImage(nil) { out in - var opt = VIPSOption() + /// - 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( + 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 -> VIPSBlob { + 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) + let out: UnsafeMutablePointer?> = .allocate(capacity: 1) + out.initialize(to: nil) + defer { + out.deallocate() + } + + opt.set("in", value: self.image) + 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) + } + opt.set("buffer", value: out) + + try Self.call("magicksave_buffer", options: &opt) + + guard let vipsBlob = out.pointee else { + throw VIPSError("Failed to get buffer from magicksave_buffer") + } + + return VIPSBlob(vipsBlob) + } + + /// Save image to matrix + /// + /// - 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 matrixsave( + 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("matrixsave", options: &opt) + } + + /// Save image to matrix + /// + /// - 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 matrixsave( + 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("matrixsave_target", 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 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 VIPSImage.call("magickload", options: &opt) + try Self.call("fitsload", options: &opt) } } - /// Load buffer with imagemagick7 + /// 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 - /// - 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 @@ -1457,25 +1895,21 @@ extension VIPSImage { /// - failOn: Error level to fail on /// - revalidate: Don't use a cached result for this operation @inlinable - public static func magickload( + public static func jxlload( 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 { + ) throws -> Self { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out 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) } @@ -1496,17 +1930,16 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("magickload_buffer", options: &opt) + try Self.call("jxlload_buffer", options: &opt) } } } - /// Load buffer with imagemagick7 without copying the data. The caller must ensure the buffer remains valid for + /// 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 - /// - 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 @@ -1514,20 +1947,18 @@ extension VIPSImage { /// - failOn: Error level to fail on /// - revalidate: Don't use a cached result for this operation @inlinable - public static func magickload( + public static func jxlload( 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 { + ) throws -> Self { let blob = VIPSBlob(noCopy: buffer) - return try magickload( + return try jxlload( buffer: blob, - density: density, page: page, n: n, memory: memory, @@ -1537,155 +1968,35 @@ extension VIPSImage { ) } - /// 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 - /// - /// - Parameters: - /// - 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( - 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 -> 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 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) - } - opt.set("buffer", value: out) - - try VIPSImage.call("magicksave_buffer", options: &opt) - - guard let vipsBlob = out.pointee else { - throw VIPSError("Failed to get buffer from magicksave_buffer") - } - - return VIPSBlob(vipsBlob) - } - - /// Load mat from file + /// Load jpeg-xl image /// /// - Parameters: - /// - filename: Filename to load from + /// - 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 matload( - filename: String, + 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(nil) { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() - opt.set("filename", value: filename) + 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) } @@ -1700,29 +2011,44 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("matload", options: &opt) + try Self.call("jxlload_source", options: &opt) } } - /// Load matrix + /// 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 matrixload( + 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 -> VIPSImage { - return try VIPSImage(nil) { out in + ) 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) } @@ -1737,118 +2063,103 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("matrixload", options: &opt) + try Self.call("magickload", options: &opt) } } - /// Load matrix + /// Load buffer with imagemagick7 /// /// - Parameters: - /// - source: Source to load from + /// - 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 - public static func matrixload( - source: VIPSSource, + @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 { - return try VIPSImage([source]) { 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: - /// - 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 matrixsave( - filename: String, - keep: VipsForeignKeep? = nil, - background: [Double]? = nil, - pageHeight: Int? = nil, - profile: String? = nil - ) throws { - var opt = VIPSOption() + ) throws -> Self { + // the operation will retain the blob + try buffer.withVipsBlob { blob in + try Self { out in + 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) - } + 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("matrixsave", options: &opt) + try Self.call("magickload_buffer", options: &opt) + } + } } - /// Save image to matrix + /// 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: - /// - 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 matrixsave( - 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 VIPSImage.call("matrixsave_target", options: &opt) + /// - 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 an openexr image + /// Load mat from file /// /// - Parameters: /// - filename: Filename to load from @@ -1856,14 +2167,14 @@ extension VIPSImage { /// - 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( + public static func matload( filename: String, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -1881,54 +2192,29 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("openexrload", options: &opt) + try Self.call("matload", options: &opt) } } - /// Load file with openslide + /// Load matrix /// /// - Parameters: /// - filename: Filename to load from - /// - level: Load this level from the file - /// - autocrop: Crop to image bounds - /// - associated: Load this associated image - /// - attachAssociated: Attach all associated images - /// - rgb: Output RGB (not RGBA) /// - 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 openslideload( + public static func matrixload( filename: String, - level: Int? = nil, - autocrop: Bool? = nil, - associated: String? = nil, - attachAssociated: Bool? = nil, - rgb: Bool? = nil, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("filename", value: filename) - if let level = level { - opt.set("level", value: level) - } - if let autocrop = autocrop { - opt.set("autocrop", value: autocrop) - } - if let associated = associated { - opt.set("associated", value: associated) - } - if let attachAssociated = attachAssociated { - opt.set("attach_associated", value: attachAssociated) - } - if let rgb = rgb { - opt.set("rgb", value: rgb) - } if let memory = memory { opt.set("memory", value: memory) } @@ -1943,54 +2229,29 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("openslideload", options: &opt) + try Self.call("matrixload", options: &opt) } } - /// Load source with openslide + /// Load matrix /// /// - Parameters: /// - source: Source to load from - /// - level: Load this level from the file - /// - autocrop: Crop to image bounds - /// - associated: Load this associated image - /// - attachAssociated: Attach all associated images - /// - rgb: Output RGB (not RGBA) /// - 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 openslideload( + public static func matrixload( source: VIPSSource, - level: Int? = nil, - autocrop: Bool? = nil, - associated: String? = nil, - attachAssociated: Bool? = nil, - rgb: Bool? = nil, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("source", value: source) - if let level = level { - opt.set("level", value: level) - } - if let autocrop = autocrop { - opt.set("autocrop", value: autocrop) - } - if let associated = associated { - opt.set("associated", value: associated) - } - if let attachAssociated = attachAssociated { - opt.set("attach_associated", value: attachAssociated) - } - if let rgb = rgb { - opt.set("rgb", value: rgb) - } if let memory = memory { opt.set("memory", value: memory) } @@ -2005,11 +2266,11 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("openslideload_source", options: &opt) + try Self.call("matrixload_source", options: &opt) } } - /// Load ppm from file + /// Load an openexr image /// /// - Parameters: /// - filename: Filename to load from @@ -2017,14 +2278,14 @@ extension VIPSImage { /// - 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 ppmload( + public static func openexrload( filename: String, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -2042,173 +2303,135 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("ppmload", options: &opt) + try Self.call("openexrload", options: &opt) } } - /// Load ppm base class + /// Load file with openslide /// /// - Parameters: - /// - source: Source to load from + /// - filename: Filename to load from + /// - level: Load this level from the file + /// - autocrop: Crop to image bounds + /// - associated: Load this associated image + /// - attachAssociated: Attach all associated images + /// - rgb: Output RGB (not RGBA) /// - 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 ppmload( - source: VIPSSource, + public static func openslideload( + filename: String, + level: Int? = nil, + autocrop: Bool? = nil, + associated: String? = nil, + attachAssociated: Bool? = nil, + rgb: Bool? = nil, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() - opt.set("source", value: source) - if let memory = memory { - opt.set("memory", value: memory) + opt.set("filename", value: filename) + if let level = level { + opt.set("level", value: level) } - if let access = access { - opt.set("access", value: access) + if let autocrop = autocrop { + opt.set("autocrop", value: autocrop) } - if let failOn = failOn { - opt.set("fail_on", value: failOn) + if let associated = associated { + opt.set("associated", value: associated) } - if let revalidate = revalidate { - opt.set("revalidate", value: revalidate) + if let attachAssociated = attachAssociated { + opt.set("attach_associated", value: attachAssociated) } - 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() + if let rgb = rgb { + opt.set("rgb", value: rgb) + } + 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) - 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("openslideload", options: &opt) } - - try VIPSImage.call("ppmsave_target", options: &opt) } - /// Load named icc profile + /// Load source with openslide /// /// - Parameters: - /// - name: Profile name - public static func profileLoad(name: String) throws -> VIPSBlob { - var opt = VIPSOption() - - let out: UnsafeMutablePointer?> = .allocate(capacity: 1) - out.initialize(to: nil) - defer { - out.deallocate() - } - - opt.set("name", value: name) - opt.set("profile", value: out) + /// - source: Source to load from + /// - level: Load this level from the file + /// - autocrop: Crop to image bounds + /// - associated: Load this associated image + /// - attachAssociated: Attach all associated images + /// - rgb: Output RGB (not RGBA) + /// - 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 openslideload( + source: VIPSSource, + level: Int? = nil, + autocrop: Bool? = nil, + associated: String? = nil, + attachAssociated: Bool? = nil, + rgb: Bool? = nil, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() - try VIPSImage.call("profile_load", options: &opt) + opt.set("source", value: source) + if let level = level { + opt.set("level", value: level) + } + if let autocrop = autocrop { + opt.set("autocrop", value: autocrop) + } + if let associated = associated { + opt.set("associated", value: associated) + } + if let attachAssociated = attachAssociated { + opt.set("attach_associated", value: attachAssociated) + } + if let rgb = rgb { + opt.set("rgb", value: rgb) + } + 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) - guard let vipsBlob = out.pointee else { - throw VIPSError("Failed to get buffer from profile_load") + try Self.call("openslideload_source", options: &opt) } - - return VIPSBlob(vipsBlob) } - /// Load a radiance image from a file + /// Load ppm from file /// /// - Parameters: /// - filename: Filename to load from @@ -2216,14 +2439,14 @@ extension VIPSImage { /// - 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( + public static func ppmload( filename: String, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -2241,11 +2464,11 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("radload", options: &opt) + try Self.call("ppmload", options: &opt) } } - /// Load rad from buffer + /// Load ppm from buffer /// /// - Parameters: /// - buffer: Buffer to load from @@ -2254,16 +2477,16 @@ extension VIPSImage { /// - failOn: Error level to fail on /// - revalidate: Don't use a cached result for this operation @inlinable - public static func radload( + public static func ppmload( buffer: VIPSBlob, memory: Bool? = nil, 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(nil) { out in + try Self { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -2281,12 +2504,12 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("radload_buffer", options: &opt) + try Self.call("ppmload_buffer", options: &opt) } } } - /// Load rad from buffer without copying the data. The caller must ensure the buffer remains valid for + /// 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: @@ -2296,15 +2519,15 @@ extension VIPSImage { /// - failOn: Error level to fail on /// - revalidate: Don't use a cached result for this operation @inlinable - public static func radload( + public static func ppmload( unsafeBuffer buffer: UnsafeRawBufferPointer, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { + ) throws -> Self { let blob = VIPSBlob(noCopy: buffer) - return try radload( + return try ppmload( buffer: blob, memory: memory, access: access, @@ -2313,7 +2536,7 @@ extension VIPSImage { ) } - /// Load rad from source + /// Load ppm from source /// /// - Parameters: /// - source: Source to load from @@ -2321,14 +2544,14 @@ extension VIPSImage { /// - 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( + public static func ppmload( source: VIPSSource, memory: Bool? = nil, access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("source", value: source) @@ -2346,123 +2569,175 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("radload_source", options: &opt) + try Self.call("ppmload_source", options: &opt) } } - /// Save image to radiance file + /// Load named icc profile /// /// - 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 { + /// - name: Profile name + public static func profileLoad(name: String) throws -> VIPSBlob { 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) + let out: UnsafeMutablePointer?> = .allocate(capacity: 1) + out.initialize(to: nil) + defer { + out.deallocate() } - if let profile = profile { - opt.set("profile", value: profile) + + opt.set("name", value: name) + opt.set("profile", value: out) + + try Self.call("profile_load", options: &opt) + + guard let vipsBlob = out.pointee else { + throw VIPSError("Failed to get buffer from profile_load") } - try VIPSImage.call("radsave", options: &opt) + return VIPSBlob(vipsBlob) } - /// Save image to radiance buffer + /// Load a radiance image from a file /// /// - 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() + /// - 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 radload( + filename: String, + memory: Bool? = nil, + access: VipsAccess? = nil, + failOn: VipsFailOn? = nil, + revalidate: Bool? = nil + ) throws -> Self { + return try Self { out in + var opt = VIPSOption() - let out: UnsafeMutablePointer?> = .allocate(capacity: 1) - out.initialize(to: nil) - defer { - out.deallocate() - } + 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) - 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) + try Self.call("radload", options: &opt) } - opt.set("buffer", value: out) + } + + /// Load rad 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 radload( + buffer: VIPSBlob, + 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() - try VIPSImage.call("radsave_buffer", options: &opt) + 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) - guard let vipsBlob = out.pointee else { - throw VIPSError("Failed to get buffer from radsave_buffer") + try Self.call("radload_buffer", options: &opt) + } } + } - return VIPSBlob(vipsBlob) + /// Load rad 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 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 @@ -2491,8 +2766,8 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -2522,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 @@ -2655,8 +2815,8 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage(nil) { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -2674,7 +2834,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("vipsload", options: &opt) + try Self.call("vipsload", options: &opt) } } @@ -2692,8 +2852,8 @@ extension VIPSImage { access: VipsAccess? = nil, failOn: VipsFailOn? = nil, revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage([source]) { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("source", value: source) @@ -2711,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 cd1c7bb..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(nil) { 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(nil) { 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([source]) { 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 95665f8..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(nil) { 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(nil) { 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([source]) { 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 3e40af5..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 /// @@ -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 + ) throws -> Self { + return try Self { 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) } @@ -58,7 +68,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("svgload", options: &opt) + try Self.call("svgload", options: &opt) } } @@ -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,14 +91,16 @@ 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 { + ) throws -> Self { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out in + try Self { 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) } @@ -113,7 +133,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("svgload_buffer", options: &opt) + try Self.call("svgload_buffer", options: &opt) } } } @@ -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,17 +158,21 @@ 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 { + ) throws -> Self { let blob = VIPSBlob(noCopy: buffer) return try svgload( buffer: blob, 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 + ) throws -> Self { + return try Self { 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) } @@ -202,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 5fc45cb..8040ceb 100644 --- a/Sources/VIPS/Generated/Foreign/foreign_tiff.generated.swift +++ b/Sources/VIPS/Generated/Foreign/foreign_tiff.generated.swift @@ -8,221 +8,7 @@ import Cvips import CvipsShim -extension VIPSImage { - - /// Load tiff from file - /// - /// - 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 - /// - 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, - subifd: Int? = nil, - n: Int? = nil, - autorotate: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage(nil) { 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 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 - /// - subifd: Subifd index - /// - n: Number of pages to load, -1 for all - /// - autorotate: Rotate image using orientation tag - /// - 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, - subifd: Int? = nil, - n: Int? = nil, - autorotate: 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(nil) { 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 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 - /// - subifd: Subifd index - /// - n: Number of pages to load, -1 for all - /// - autorotate: Rotate image using orientation tag - /// - 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, - subifd: Int? = nil, - n: Int? = nil, - autorotate: 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, - subifd: subifd, - n: n, - autorotate: autorotate, - memory: memory, - access: access, - failOn: failOn, - revalidate: revalidate - ) - } - - /// Load tiff from source - /// - /// - 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 - /// - 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, - subifd: Int? = nil, - n: Int? = nil, - autorotate: Bool? = nil, - memory: Bool? = nil, - access: VipsAccess? = nil, - failOn: VipsFailOn? = nil, - revalidate: Bool? = nil - ) throws -> VIPSImage { - return try VIPSImage([source]) { 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 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 /// @@ -356,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 @@ -495,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") @@ -636,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 2f3288a..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(nil) { 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(nil) { 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([source]) { 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 7f76bd3..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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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([self, rhs]) { 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(self) { 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([self, rhs]) { 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(self) { 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(self) { 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([self, rhs]) { 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([self, rhs]) { 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(self) { 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([self, rhs]) { 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(self) { 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(self) { 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(self) { 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([self, rhs]) { 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(self) { 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,35 @@ 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 an matrix - public func matrixinvert() throws -> VIPSImage { - return try VIPSImage(self) { out in + /// Invert a matrix + 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) + } + } + + /// Multiply two matrices + /// + /// - Parameters: + /// - `right`: Second matrix to multiply + 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 Self.call("matrixmultiply", options: &opt) } } @@ -354,7 +372,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("max", options: &opt) + try Self.call("max", options: &opt) return out } @@ -363,15 +381,15 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand image argument - public func maxpair(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { 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) } } @@ -390,7 +408,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("min", options: &opt) + try Self.call("min", options: &opt) return out } @@ -399,15 +417,15 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand image argument - public func minpair(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { 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) } } @@ -415,15 +433,15 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand image argument - public func multiply(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { 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) } } @@ -432,8 +450,8 @@ extension VIPSImage { /// /// - Parameters: /// - maxAlpha: Maximum value of alpha channel - public func premultiply(maxAlpha: Double? = nil) throws -> VIPSImage { - return try VIPSImage(self) { out in + public func premultiply(maxAlpha: Double? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -442,7 +460,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("premultiply", options: &opt) + try Self.call("premultiply", options: &opt) } } #endif @@ -452,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([self, rhs]) { out in + return try Self { out in var opt = VIPSOption() opt.set("left", value: self) @@ -463,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) } } @@ -472,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(self) { out in + public func relationalConst(relational: VipsOperationRelational, c: [Double]) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -483,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) } } @@ -491,15 +507,15 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand image argument - public func remainder(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { 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) } } @@ -507,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]) } @@ -515,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)]) } @@ -523,15 +539,15 @@ extension VIPSImage { /// /// - Parameters: /// - c: Array of constants - public func remainderConst(c: [Double]) throws -> VIPSImage { - return try VIPSImage(self) { 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) } } @@ -539,39 +555,39 @@ extension VIPSImage { /// /// - Parameters: /// - round: Rounding operation to perform - public func round(_ round: VipsOperationRound) throws -> VIPSImage { - return try VIPSImage(self) { 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(self) { 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(self) { 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) } } @@ -579,30 +595,15 @@ extension VIPSImage { /// /// - Parameters: /// - `right`: Right-hand image argument - public func subtract(_ rhs: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, rhs]) { 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([`in`]) { 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) } } @@ -612,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(self) { 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) @@ -627,7 +626,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("unpremultiply", options: &opt) + try Self.call("unpremultiply", options: &opt) } } #endif @@ -636,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) } @@ -644,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]) } @@ -652,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) } @@ -660,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]) } @@ -668,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) } @@ -676,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]) } @@ -684,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) } @@ -692,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]) } @@ -700,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) } @@ -708,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]) } @@ -716,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) } @@ -724,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]) } @@ -732,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) } @@ -740,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) } @@ -748,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) } @@ -756,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)]) } @@ -764,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 ab8c111..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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { out in + public func scRGB2BW(depth: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -413,28 +413,28 @@ 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(self) { 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) } } - /// 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 + 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(nil) { 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 c41f3cf..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([self, interpolate as Any]) { 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([`in`]) { 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(self) { 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([`in`]) { 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(self) { 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([`in`]) { 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(self) { 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([`in`]) { 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([self, overlay]) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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([self, sub]) { 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([self, in2]) { 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([self, m]) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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([self, interpolate as Any]) { 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(self) { 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(self) { 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(self) { 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(self) { 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([self, interpolate as Any]) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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 0017bd4..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(self) { 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([self, mask]) { 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([self, mask]) { 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([self, mask]) { 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([self, mask]) { 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([self, mask]) { 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([self, mask]) { 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(self) { 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(self) { 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(self) { 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 bb064a4..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(nil) { 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(self) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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(nil) { 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 0b34a04..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([self, mask]) { 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(self) { 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(self) { 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 7bea18e..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(self) { 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(self) { 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(self) { 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([self, index]) { 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(self) { 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(self) { 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([self, ref]) { 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(self) { 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(self) { 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(self) { 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(self) { 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([self, in2]) { 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(self) { 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 d51c31b..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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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([self, cases]) { 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(self) { 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([self, mask]) { 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([self, rhs]) { 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([self, rhs]) { 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([self, rhs]) { 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([self, ref]) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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(self) { 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([self, in1, in2]) { 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(self) { 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([self, lut]) { 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([self, sec, interpolate as Any]) { 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(self) { 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([self, sec]) { 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([self, sec]) { 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([self, sec, interpolate as Any]) { 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(self) { 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,50 @@ 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(self) { 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(self) { 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) + } + } + + /// Rebuild an mosaiced image + /// + /// - Parameters: + /// - oldStr: Search for this string + /// - newStr: And swap for this string + public func remosaic(oldStr: String, newStr: String) throws -> Self { + return try Self { 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 Self.call("remosaic", options: &opt) } } @@ -823,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(self) { out in + public func replicate(across: Int, down: Int) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -832,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(self) { 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(nil) { 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(self) { out in + public func sequential(tileHeight: Int? = nil) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -907,7 +884,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("sequential", options: &opt) + try Self.call("sequential", options: &opt) } } @@ -915,15 +892,15 @@ extension VIPSImage { /// /// - Parameters: /// - ref: Input reference image - public func spcor(ref: VIPSImage) throws -> VIPSImage { - return try VIPSImage([self, ref]) { 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) } } @@ -943,8 +920,8 @@ extension VIPSImage { b: Double? = nil, m0: Double? = nil, a: Double? = nil - ) throws -> VIPSImage { - return try VIPSImage(self) { out in + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("in", value: self) @@ -964,7 +941,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("stdif", options: &opt) + try Self.call("stdif", options: &opt) } } @@ -974,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(self) { 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) @@ -986,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([tests]) { 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 63330cc..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([self, mask]) { 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(self) { 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 f5d6e34..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([self, index, interpolate as Any]) { 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([self, coeff, interpolate as Any]) { 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: @@ -79,8 +147,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 +159,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 + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("filename", value: filename) @@ -116,11 +184,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) @@ -130,7 +198,7 @@ extension VIPSImage { } opt.set("out", value: &out) - try VIPSImage.call("thumbnail", options: &opt) + try Self.call("thumbnail", options: &opt) } } @@ -145,8 +213,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 +227,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 { + ) throws -> Self { // the operation will retain the blob try buffer.withVipsBlob { blob in - try VIPSImage(nil) { out in + try Self { out in var opt = VIPSOption() opt.set("buffer", value: blob) @@ -189,11 +257,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) @@ -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 - /// - importProfile: Fallback import profile - /// - exportProfile: Fallback export 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, - importProfile: String? = nil, - exportProfile: String? = nil, - intent: VipsIntent? = nil, - failOn: VipsFailOn? = nil - ) throws -> VIPSImage { - return try VIPSImage(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 importProfile = importProfile { - opt.set("import_profile", value: importProfile) - } - if let exportProfile = exportProfile { - opt.set("export_profile", value: exportProfile) - } - 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: @@ -282,8 +287,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 +300,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 + ) throws -> Self { + return try Self { out in var opt = VIPSOption() opt.set("source", value: source) @@ -323,11 +328,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) @@ -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/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/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..f6853fa 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) + 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..a266ff5 --- /dev/null +++ b/Tests/VIPSTests/ArithmeticTests.swift @@ -0,0 +1,432 @@ +import Cvips +import Foundation +import Testing + +@testable import VIPS + +extension VIPSTests { + @Suite(.vips) + 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..dd8fa7b --- /dev/null +++ b/Tests/VIPSTests/ConversionTests.swift @@ -0,0 +1,467 @@ +import Cvips +import Foundation +import Testing + +@testable import VIPS + +extension VIPSTests { + @Suite(.vips) + 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..b942c2c --- /dev/null +++ b/Tests/VIPSTests/CoreTests.swift @@ -0,0 +1,89 @@ +import Cvips +import CvipsShim +import Foundation +import Testing + +@testable import VIPS + +extension VIPSTests { + @Suite(.vips) + 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%") + } + } + + @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/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..35a7857 100644 --- a/Tests/VIPSTests/CreateGeneratedTests.swift +++ b/Tests/VIPSTests/CreateTests.swift @@ -3,8 +3,9 @@ import Cvips import Testing import Foundation +extension VIPSTests { @Suite(.vips) -struct CreateGeneratedTests { +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..20afec8 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) + 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..4adacb0 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) + 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..90c79ee 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) + 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..0e25c62 100644 --- a/Tests/VIPSTests/TestSetup.swift +++ b/Tests/VIPSTests/TestSetup.swift @@ -22,12 +22,16 @@ 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() - + /*print("!!!!!! START") + try VIPS.start() + defer { + VIPS.shutdown() + print("!!!!!! SHUTDOWN") + }*/ // 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 + } } @@ -36,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/VIPSBlobTests.swift b/Tests/VIPSTests/VIPSBlobTests.swift index cf6291e..30b29b2 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) + 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..6a4be0f --- /dev/null +++ b/Tests/VIPSTests/VIPSSourceCustomTests.swift @@ -0,0 +1,38 @@ +import VIPS +import Testing + + +extension VIPSTests { + @Suite(.vips) + 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..88e7862 --- /dev/null +++ b/docs/VIPS.md @@ -0,0 +1,1756 @@ +## Module `VIPS` + +### Table of Contents + +| Type | Name | +| --- | --- | +| class | `VIPSObject` | +| class | `VIPSBlob` | +| class | `VIPSImage` | +| struct | `VIPSImage.Size` | +| class | `VIPSInterpolate` | +| class | `VIPSTarget` | +| class | `VIPSTargetCustom` | +| struct | `UnownedVIPSImageRef` | +| struct | `UnownedVIPSObjectRef` | +| struct | `VIPSError` | +| struct | `VIPSObjectRef` | +| struct | `VIPSOption` | +| struct | `Whence` | +| enum | `VIPS` | +| protocol | `PointerWrapper` | +| protocol | `VIPSImageProtocol` | +| protocol | `VIPSLoggingDelegate` | +| protocol | `VIPSObjectProtocol` | +| 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: PointerWrapper, VIPSObjectProtocol { + public var ptr: UnsafeMutableRawPointer! + + public var type: GType { get } + + public required init(_ ptr: UnsafeMutableRawPointer) +} +``` + +#### 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 final class VIPSImage: PointerWrapper, VIPSImageProtocol, VIPSObjectProtocol { + /// 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 } + + public var ptr: UnsafeMutableRawPointer! + + /// 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 } + + /// 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 + + /// 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 + + /// 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 + + /// Same as `autorot()` + /// + /// See: `VIPSImage.autorot()` + public func autorotate() throws -> VIPSImage + + /// 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 + + /// See VIPSImage.bandjoin(`in`:) + public func bandjoin(_ other: [VIPSImage]) 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 + + public func ceil() throws -> VIPSImage + + /// Create a complex image from real and imaginary parts + public func complex(_ imaginary: 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 + + /// Calculate cosine of image values (in degrees) + public func cos() throws -> VIPSImage + + /// Calculate hyperbolic cosine of image values + public func cosh() 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 + + /// Calculate 10^x for each pixel + public func exp10() throws -> VIPSImage + + public func floor() 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] + + /// Extract imaginary part of complex image + public func imag() throws -> VIPSImage + + public 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. + /// + /// 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 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 + /// + /// 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 convenience init(unsafeData buffer: UnsafeBufferPointer, width: Int, height: Int, bands: Int) throws + + 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 with an image of shift amounts + public func lshift(_ shiftAmounts: 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 + + /// Raise image values to a constant power (integer overload) + public func pow(_ exponent: Int) 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) + + /// Extract real part of complex image + public func real() throws -> VIPSImage + + /// Convert polar image to rectangular form + public func rect() throws -> VIPSImage + + public func round() 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 + + /// Calculate hyperbolic sine of image values + public func sinh() 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 + + /// Raise a constant to the power of this image (integer overload) + public func wop(_ base: Int) 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 +} +``` + +#### class VIPSInterpolate + +```swift +public class VIPSInterpolate: VIPSObject, PointerWrapper, VIPSObjectProtocol { + 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, PointerWrapper, VIPSObjectProtocol { + /// 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 + + public required init(_ ptr: UnsafeMutableRawPointer) + + /// 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 + + public func read(into span: inout OutputRawSpan) throws + + /// 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 + + /// 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. + /// 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, 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. + /// 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 @discardableResult func onEnd(_ handler: @escaping () -> Int) -> 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 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. + /// + /// 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 @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. + /// + /// 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 @discardableResult func onUnsafeWrite(_ handler: @escaping (UnsafeRawBufferPointer) -> Int) -> Int + + public @discardableResult func onWrite(_ handler: @escaping (RawSpan) -> Int) -> Int +} +``` + +#### struct UnownedVIPSImageRef + +```swift +public struct UnownedVIPSImageRef: PointerWrapper, VIPSImageProtocol, VIPSObjectProtocol { + public var ptr: UnsafeMutableRawPointer! + + public init(_ ptr: UnsafeMutableRawPointer) +} +``` + +#### struct UnownedVIPSObjectRef + +```swift +public struct UnownedVIPSObjectRef: PointerWrapper, VIPSObjectProtocol { + public let ptr: UnsafeMutableRawPointer! + + public init(_ ptr: UnsafeMutableRawPointer) +} +``` + +#### 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 VIPSObjectRef + +```swift +public struct VIPSObjectRef: PointerWrapper, VIPSObjectProtocol { + public let ptr: UnsafeMutableRawPointer! + + public init(_ ptr: UnsafeMutableRawPointer) + + public init(borrowing ref: UnownedVIPSObjectRef) +} +``` + +#### 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 PointerWrapper + +```swift +public protocol PointerWrapper : ~Copyable, ~Escapable { + public var ptr: UnsafeMutableRawPointer! { get } + + public init(_ ptr: UnsafeMutableRawPointer) +} +``` + +#### protocol VIPSImageProtocol + +```swift +public protocol VIPSImageProtocol : VIPSObjectProtocol, ~Copyable, ~Escapable { + public var bands: Int { get } + + public var height: Int { get } + + public var width: Int { get } + + /// Bandwise join a set of images + /// + public static func bandjoin(_ in: [VIPSImage]) throws -> Self + + /// Band-wise rank of a set of images + /// + public static func bandrank(_ in: [VIPSImage], index: Int? = nil) throws -> Self + + /// Make a black image + /// + public static func black(width: Int, height: Int, bands: Int? = nil) throws -> Self + + /// 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 + + /// Make a fractal surface + /// + public static func fractsurf(width: Int, height: Int, fractalDimension: Double) throws -> Self + + /// Make a gaussnoise image + /// + public static func gaussnoise(width: Int, height: Int, sigma: Double? = nil, mean: Double? = nil, seed: Int? = nil) throws -> Self + + /// Make a grey ramp image + /// + public static func grey(width: Int, height: Int, uchar: Bool? = nil) throws -> Self + + /// Make a 1d image where pixel values are indexes + /// + public static func identity(bands: Int? = nil, ushort: Bool? = nil, size: Int? = nil) throws -> Self + + /// 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 + + /// 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 + + /// 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 +} +``` + +#### 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 +``` + + diff --git a/tools/generate-swift-wrappers.py b/tools/generate-swift-wrappers.py index 2b3f48a..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(self) { 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") @@ -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 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,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 Self { 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 Self { out in") result.append(" var opt = VIPSOption()") result.append("") elif has_output and not has_image_output: @@ -1164,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 @@ -1340,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") @@ -1351,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: @@ -1397,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"") @@ -1407,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"") @@ -1424,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"") @@ -1440,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"")