diff --git a/Sources/OpenSwiftUICore/View/Image/Image.swift b/Sources/OpenSwiftUICore/View/Image/Image.swift index 5dae0bab9..e3ecb2e80 100644 --- a/Sources/OpenSwiftUICore/View/Image/Image.swift +++ b/Sources/OpenSwiftUICore/View/Image/Image.swift @@ -2,100 +2,196 @@ // Image.swift // OpenSwiftUICore // -// Audited for 6.0.87 -// Status: WIP +// Audited for 6.5.4 +// Status: Blocked by ImageResolutionContext and View +// ID: BE2D783904D422377BBEBAC3C942583C (SwiftUICore) -#if canImport(CoreGraphics) -public import CoreGraphics -#endif +package import OpenAttributeGraphShims +// MARK: - Image + +/// A view that displays an image. +/// +/// Use an `Image` instance when you want to add images to your OpenSwiftUI app. +/// You can create images from many sources: +/// +/// * Image files in your app's asset library or bundle. Supported types include +/// PNG, JPEG, HEIC, and more. +/// * Instances of platform-specific image types, like +/// [UIImage](https://developer.apple.com/documentation/uikit/uiimage) and +/// [NSImage](https://developer.apple.com/documentation/appkit/nsimage). +/// * A bitmap stored in a Core Graphics +/// [CGImage](https://developer.apple.com/documentation/coregraphics/cgimage) +/// instance. +/// * System graphics from the SF Symbols set. +/// +/// The following example shows how to load an image from the app's asset +/// library or bundle and scale it to fit within its container: +/// +/// Image("Landscape_4") +/// .resizable() +/// .aspectRatio(contentMode: .fit) +/// Text("Water wheel") +/// +/// ![An image of a water wheel and its adjoining building, resized to fit the +/// width of an iPhone display. The words Water wheel appear under this +/// image.](Image-1.png) +/// +/// You can use methods on the `Image` type as well as +/// standard view modifiers to adjust the size of the image to fit your app's +/// interface. Here, the `Image` type's +/// ``Image/resizable(capInsets:resizingMode:)`` method scales the image to fit +/// the current view. Then, the +/// ``View/aspectRatio(_:contentMode:)`` view modifier adjusts +/// this resizing behavior to maintain the image's original aspect ratio, rather +/// than scaling the x- and y-axes independently to fill all four sides of the +/// view. The article +/// shows how to apply scaling, +/// clipping, and tiling to `Image` instances of different sizes. +/// +/// An `Image` is a late-binding token; the system resolves its actual value +/// only when it's about to use the image in an environment. +/// +/// ### Making images accessible +/// +/// To use an image as a control, use one of the initializers that takes a +/// `label` parameter. This allows the system's accessibility frameworks to use +/// the label as the name of the control for users who use features like +/// VoiceOver. For images that are only present for aesthetic reasons, use an +/// initializer with the `decorative` parameter; the accessibility systems +/// ignore these images. +@available(OpenSwiftUI_v1_0, *) @frozen public struct Image: Equatable, Sendable { - #if canImport(CoreGraphics) - // FIXME - public init(decorative: CGImage, scale: CGFloat, orientation: Image.Orientation) { + package var provider: AnyImageProviderBox + + package init

(_ provider: P) where P: ImageProvider { + self.provider = ImageProviderBox(base: provider) + } + + package func resolve(in context: ImageResolutionContext) -> Image.Resolved { + provider.resolve(in: context) + } + + package func resolveNamedImage(in context: ImageResolutionContext) -> Image.NamedResolved? { + provider.resolve(in: context) + } + + public static func == (lhs: Image, rhs: Image) -> Bool { + lhs.provider.isEqual(to: rhs.provider) + } +} + +// MARK: - ImageResolutionContext [WIP] + +package struct ImageResolutionContext { + package struct Options: OptionSet { + package let rawValue: UInt8 + + package init(rawValue: UInt8) { + self.rawValue = rawValue + } + + package static let inferSymbolRenderingMode: ImageResolutionContext.Options = .init(rawValue: 1 << 0) + + package static let isArchived: ImageResolutionContext.Options = .init(rawValue: 1 << 1) + + package static let useCatalogReferences: ImageResolutionContext.Options = .init(rawValue: 1 << 2) + + package static let animationsDisabled: ImageResolutionContext.Options = .init(rawValue: 1 << 3) + + package static let preservesVectors: ImageResolutionContext.Options = .init(rawValue: 1 << 4) + } + + package var environment: EnvironmentValues + +// package var symbolAnimator: RBSymbolAnimator? + + package var textStyle: Text.Style? + + package var transaction: OptionalAttribute + +// package var symbolRenderingMode: SymbolRenderingMode? + + package var allowedDynamicRange: Image.DynamicRange? + + package var options: ImageResolutionContext.Options + + package init( + environment: EnvironmentValues, + textStyle: Text.Style? = nil, + transaction: OptionalAttribute = .init() + ) { _openSwiftUIUnimplementedFailure() } - #endif - // FIXME - package enum Resolved {} +// package var effectiveAllowedDynamicRange: Image.DynamicRange? { +// _openSwiftUIUnimplementedFailure() +// } +} + +// MARK: - ImageProvider + +package protocol ImageProvider: Equatable { + func resolve(in context: ImageResolutionContext) -> Image.Resolved + + func resolveNamedImage(in context: ImageResolutionContext) -> Image.NamedResolved? +} + +// MARK: - Image + View [WIP] - package enum NamedResolved {} +package protocol ImageStyleProtocol { + static func _makeImageView(view: _GraphValue, inputs: _ViewInputs) -> _ViewOutputs } +@available(OpenSwiftUI_v1_0, *) +extension Image: View, UnaryView, PrimitiveView { + package struct Style: ViewInput { + package static let defaultValue: Stack = .empty + } + + nonisolated public static func _makeView(view: _GraphValue, inputs: _ViewInputs) -> _ViewOutputs { + _openSwiftUIUnimplementedFailure() + } +} + +// MARK: - ImageProviderBox + +@available(OpenSwiftUI_v1_0, *) +@usableFromInline +package class AnyImageProviderBox: @unchecked Sendable { + func resolve(in context: ImageResolutionContext) -> Image.Resolved { + _openSwiftUIBaseClassAbstractMethod() + } + + func resolveNamedImage(in context: ImageResolutionContext) -> Image.NamedResolved? { + _openSwiftUIBaseClassAbstractMethod() + } + + func isEqual(to other: AnyImageProviderBox) -> Bool { + _openSwiftUIBaseClassAbstractMethod() + } +} + +final package class ImageProviderBox: AnyImageProviderBox, @unchecked Sendable where Base: ImageProvider { + package let base: Base + + init(base: Base) { + self.base = base + } + + override func resolve(in context: ImageResolutionContext) -> Image.Resolved { + base.resolve(in: context) + } + + override func resolveNamedImage(in context: ImageResolutionContext) -> Image.NamedResolved? { + base.resolveNamedImage(in: context) + } -extension Image: PrimitiveView {} - -extension Image { - /// The orientation of an image. - /// - /// Many image formats such as JPEG include orientation metadata in the - /// image data. In other cases, you can specify image orientation - /// in code. Properly specifying orientation is often important both for - /// displaying the image and for certain kinds of image processing. - /// - /// In OpenSwiftUI, you provide an orientation value when initializing an - /// ``Image`` from an existing - /// [CGImage](https://developer.apple.com/documentation/coregraphics/cgimage). - @frozen - public enum Orientation: UInt8, CaseIterable, Hashable { - /// A value that indicates the original pixel data matches the image's - /// intended display orientation. - case up = 0 - - /// A value that indicates a horizontal flip of the image from the - /// orientation of its original pixel data. - case upMirrored = 2 - - /// A value that indicates a 180° rotation of the image from the - /// orientation of its original pixel data. - case down = 6 - - /// A value that indicates a vertical flip of the image from the - /// orientation of its original pixel data. - case downMirrored = 4 - - /// A value that indicates a 90° counterclockwise rotation from the - /// orientation of its original pixel data. - case left = 1 - - /// A value that indicates a 90° clockwise rotation and horizontal - /// flip of the image from the orientation of its original pixel - /// data. - case leftMirrored = 3 - - /// A value that indicates a 90° clockwise rotation of the image from - /// the orientation of its original pixel data. - case right = 7 - - /// A value that indicates a 90° counterclockwise rotation and - /// horizontal flip from the orientation of its original pixel data. - case rightMirrored = 5 - - /// Creates an image orientation from an EXIF orientation value. - /// - /// This initializer converts the standard EXIF orientation values (1-8) - /// to the corresponding `Image.Orientation` cases. - /// - /// - Parameter exifValue: An integer representing the EXIF orientation. - /// Valid values are 1 through 8, corresponding to the standard EXIF - /// orientation values. - /// - Returns: The corresponding orientation, or `nil` if the provided - /// value is not a valid EXIF orientation value. - @_spi(Private) - public init?(exifValue: Int) { - switch exifValue { - case 1: self = .up - case 2: self = .upMirrored - case 3: self = .down - case 4: self = .downMirrored - case 5: self = .leftMirrored - case 6: self = .right - case 7: self = .rightMirrored - case 8: self = .left - default: return nil - } + override func isEqual(to other: AnyImageProviderBox) -> Bool { + guard let other = (other as? ImageProviderBox) else { + return false } + return base == other.base } } diff --git a/Sources/OpenSwiftUICore/View/Image/ImageDynamicRange.swift b/Sources/OpenSwiftUICore/View/Image/ImageDynamicRange.swift new file mode 100644 index 000000000..d35690d5a --- /dev/null +++ b/Sources/OpenSwiftUICore/View/Image/ImageDynamicRange.swift @@ -0,0 +1,184 @@ +// +// ImageDynamicRange.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete +// ID: B0F5FD51133E70141176B7B8AC4E9712 (SwiftUICore) + +package import Foundation + +// MARK: - Image.DynamicRange + +@available(OpenSwiftUI_v5_0, *) +// @_spi_available(watchOS, introduced: 10.0) +extension Image { + public struct DynamicRange: Hashable, Sendable { + package enum Storage: UInt8, Hashable, Comparable { + case standard + case constrainedHigh + case high + + package static func < (lhs: Image.DynamicRange.Storage, rhs: Image.DynamicRange.Storage) -> Bool { + lhs.rawValue < rhs.rawValue + } + } + + package var storage: Image.DynamicRange.Storage + + package init(storage: Image.DynamicRange.Storage) { + self.storage = storage + } + + /// Restrict the image content dynamic range to the standard range. + public static let standard: Image.DynamicRange = .init(storage: .standard) + + /// Allow image content to use some extended range. This is + /// appropriate for placing HDR content next to SDR content. + public static let constrainedHigh: Image.DynamicRange = .init(storage: .constrainedHigh) + + /// Allow image content to use an unrestricted extended range. + public static let high: Image.DynamicRange = .init(storage: .high) + + package var maxHeadroom: Image.Headroom { + switch storage { + case .standard: .standard + case .constrainedHigh: .constrainedHigh + case .high: .high + } + } + } + + package struct Headroom: RawRepresentable, Comparable { + package let rawValue: CGFloat + + package init(rawValue: CGFloat) { + self.rawValue = rawValue + } + + package static func < (lhs: Image.Headroom, rhs: Image.Headroom) -> Bool { + lhs.rawValue < rhs.rawValue + } + + package static let standard: Image.Headroom = .init(rawValue: 1.0) + + package static let constrainedHigh: Image.Headroom = .init(rawValue: 2.0) + + package static let highHLG: Image.Headroom = .init(rawValue: 5.0) + + package static let high: Image.Headroom = .init(rawValue: 8.0) + } + + /// Returns a new image configured with the specified allowed + /// dynamic range. + /// + /// The following example enables HDR rendering for a specific + /// image view, assuming that the image has an HDR (ITU-R 2100) + /// color space and the output device supports it: + /// + /// Image("hdr-asset").allowedDynamicRange(.high) + /// + /// - Parameter range: the requested dynamic range, or nil to + /// restore the default allowed range. + /// + /// - Returns: a new image. + public func allowedDynamicRange(_ range: Image.DynamicRange?) -> Image { + Image( + DynamicRangeProvider( + base: self, + allowedDynamicRange: range + ) + ) + } +} + +// MARK: - DynamicRangeProvider + +private struct DynamicRangeProvider: ImageProvider { + var base: Image + + var allowedDynamicRange: Image.DynamicRange? + + func resolve(in context: ImageResolutionContext) -> Image.Resolved { + var context = context + if let allowedDynamicRange { + context.allowedDynamicRange = allowedDynamicRange + } + return base.resolve(in: context) + } + + func resolveNamedImage(in context: ImageResolutionContext) -> Image.NamedResolved? { + var context = context + if let allowedDynamicRange { + context.allowedDynamicRange = allowedDynamicRange + } + return base.resolveNamedImage(in: context) + } +} + +// MARK: - EnvironmentValues + DynamicRange + +private struct AllowedDynamicRangeKey : EnvironmentKey { + static var defaultValue: Image.DynamicRange? { nil } +} + +@available(OpenSwiftUI_v5_0, *) +// @_spi_available(watchOS, introduced: 10.0) +extension EnvironmentValues { + /// The allowed dynamic range for the view, or nil. + public var allowedDynamicRange: Image.DynamicRange? { + get { self[AllowedDynamicRangeKey.self] } + set { self[AllowedDynamicRangeKey.self] = newValue } + } +} + +struct MaxAllowedDynamicRangeKey: EnvironmentKey { + static var defaultValue: Image.DynamicRange? { nil } +} + +@_spi(Private) +@available(OpenSwiftUI_v6_0, *) +extension EnvironmentValues { + public var maxAllowedDynamicRange: Image.DynamicRange? { + get { self[MaxAllowedDynamicRangeKey.self] } + set { self[MaxAllowedDynamicRangeKey.self] = newValue } + } +} + +@available(iOS 17.0, macOS 14.0, tvOS 17.0, *) +@_spi_available(watchOS, introduced: 10.0) +extension View { + + /// Returns a new view configured with the specified allowed + /// dynamic range. + /// + /// The following example enables HDR rendering within a view + /// hierarchy: + /// + /// MyView().allowedDynamicRange(.high) + /// + /// - Parameter range: the requested dynamic range, or nil to + /// restore the default allowed range. + /// + /// - Returns: a new view. + @_alwaysEmitIntoClient + nonisolated + public func allowedDynamicRange(_ range: Image.DynamicRange?) -> some View { + return environment(\.allowedDynamicRange, range) + } +} + +// MARK: - DynamicRange + ProtobufEnum + +extension Image.DynamicRange: ProtobufEnum { + package var protobufValue: UInt { + UInt(storage.rawValue) + } + + package init?(protobufValue value: UInt) { + guard let storage = Image.DynamicRange.Storage(rawValue: UInt8(value)) else { + return nil + } + self.storage = storage + } +} diff --git a/Sources/OpenSwiftUICore/View/Image/ImageOrientation.swift b/Sources/OpenSwiftUICore/View/Image/ImageOrientation.swift new file mode 100644 index 000000000..e4a523dbe --- /dev/null +++ b/Sources/OpenSwiftUICore/View/Image/ImageOrientation.swift @@ -0,0 +1,188 @@ +// +// ImageOrientation.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete + +package import OpenCoreGraphicsShims + +// MARK: - Image.Orientation + +extension Image { + /// The orientation of an image. + /// + /// Many image formats such as JPEG include orientation metadata in the + /// image data. In other cases, you can specify image orientation + /// in code. Properly specifying orientation is often important both for + /// displaying the image and for certain kinds of image processing. + /// + /// In OpenSwiftUI, you provide an orientation value when initializing an + /// ``Image`` from an existing + /// [CGImage](https://developer.apple.com/documentation/coregraphics/cgimage). + @frozen + public enum Orientation: UInt8, CaseIterable, Hashable { + /// A value that indicates the original pixel data matches the image's + /// intended display orientation. + case up = 0 + + /// A value that indicates a horizontal flip of the image from the + /// orientation of its original pixel data. + case upMirrored = 2 + + /// A value that indicates a 180° rotation of the image from the + /// orientation of its original pixel data. + case down = 6 + + /// A value that indicates a vertical flip of the image from the + /// orientation of its original pixel data. + case downMirrored = 4 + + /// A value that indicates a 90° counterclockwise rotation from the + /// orientation of its original pixel data. + case left = 1 + + /// A value that indicates a 90° clockwise rotation and horizontal + /// flip of the image from the orientation of its original pixel + /// data. + case leftMirrored = 3 + + /// A value that indicates a 90° clockwise rotation of the image from + /// the orientation of its original pixel data. + case right = 7 + + /// A value that indicates a 90° counterclockwise rotation and + /// horizontal flip from the orientation of its original pixel data. + case rightMirrored = 5 + + /// Creates an image orientation from an EXIF orientation value. + /// + /// This initializer converts the standard EXIF orientation values (1-8) + /// to the corresponding `Image.Orientation` cases. + /// + /// - Parameter exifValue: An integer representing the EXIF orientation. + /// Valid values are 1 through 8, corresponding to the standard EXIF + /// orientation values. + /// - Returns: The corresponding orientation, or `nil` if the provided + /// value is not a valid EXIF orientation value. + @_spi(Private) + public init?(exifValue: Int) { + switch exifValue { + case 1: self = .up + case 2: self = .upMirrored + case 3: self = .down + case 4: self = .downMirrored + case 5: self = .leftMirrored + case 6: self = .right + case 7: self = .rightMirrored + case 8: self = .left + default: return nil + } + } + } +} + +extension Image.Orientation: ProtobufEnum {} + +// MARK: - CoreGraphics + Image.Orientation + +extension CGSize { + package func apply(_ orientation: Image.Orientation) -> CGSize { + switch orientation { + case .up, .upMirrored, .down, .downMirrored: + return self + case .left, .leftMirrored, .right, .rightMirrored: + return CGSize(width: height, height: width) + } + } + + package func unapply(_ orientation: Image.Orientation) -> CGSize { + apply(orientation) + } +} + +extension CGRect { + package func apply(_ orientation: Image.Orientation, in size: CGSize) -> CGRect { + switch orientation { + case .up: + self + case .upMirrored: + CGRect(x: size.width - x - width, y: y, width: width, height: height) + case .down: + CGRect(x: size.width - x - width, y: size.height - y - height, width: width, height: height) + case .downMirrored: + CGRect(x: x, y: size.height - y - height, width: width, height: height) + case .left: + CGRect(x: size.height - y, y: x, width: height, height: width) + case .leftMirrored: + CGRect(x: y - height, y: x, width: height, height: width) + case .right: + CGRect(x: y - height, y: size.width - x - width, width: height, height: width) + case .rightMirrored: + CGRect(x: size.height - y, y: size.width - x - width, width: height, height: width) + } + } + + package func unapply(_ orientation: Image.Orientation, in size: CGSize) -> CGRect { + switch orientation { + case .up: + self + case .upMirrored: + CGRect(x: size.width - x - width, y: y, width: width, height: height) + case .down: + CGRect(x: size.width - x - width, y: size.height - y - height, width: width, height: height) + case .downMirrored: + CGRect(x: x, y: size.height - y - height, width: width, height: height) + case .left: + CGRect(x: y, y: size.height - x, width: height, height: width) + case .leftMirrored: + CGRect(x: y, y: x + width, width: height, height: width) + case .right: + CGRect(x: size.width - y - height, y: x + width, width: height, height: width) + case .rightMirrored: + CGRect(x: size.width - y - height, y: size.height - x, width: height, height: width) + } + } +} + +extension CGAffineTransform { + package init(orientation: Image.Orientation, in size: CGSize) { + switch orientation { + case .up: + self = .identity + case .upMirrored: + self = CGAffineTransform(a: -1, b: 0, c: 0, d: 1, tx: size.width, ty: 0) + case .down: + self = CGAffineTransform(a: -1, b: 0, c: 0, d: -1, tx: size.width, ty: size.height) + case .downMirrored: + self = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: size.height) + case .left: + self = CGAffineTransform(a: 0, b: -1, c: 1, d: 0, tx: 0, ty: size.height) + case .leftMirrored: + self = CGAffineTransform(a: 0, b: 1, c: 1, d: 0, tx: 0, ty: 0) + case .right: + self = CGAffineTransform(a: 0, b: 1, c: -1, d: 0, tx: size.width, ty: 0) + case .rightMirrored: + self = CGAffineTransform(a: 0, b: -1, c: -1, d: 0, tx: size.width, ty: size.height) + } + } + + package init(orientation: Image.Orientation, in rect: CGRect) { + let orientationTransform = CGAffineTransform(orientation: orientation, in: rect.size) + let translateToOrigin = CGAffineTransform(translationX: rect.x, y: rect.y) + let translateBack = CGAffineTransform(translationX: -rect.x, y: -rect.y) + self = translateBack.concatenating(orientationTransform).concatenating(translateToOrigin) + } + + package mutating func apply(_ orientation: Image.Orientation, in size: CGSize) { + guard orientation != .up else { return } + let orientationTransform = CGAffineTransform(orientation: orientation, in: size) + self = orientationTransform.concatenating(self) + } + + package mutating func apply(_ orientation: Image.Orientation) { + guard orientation != .up else { return } + let orientationTransform = CGAffineTransform(orientation: orientation, in: CGSize(width: 1, height: 1)) + self = orientationTransform.concatenating(self) + } +} diff --git a/Sources/OpenSwiftUICore/View/Image/NamedImage.swift b/Sources/OpenSwiftUICore/View/Image/NamedImage.swift new file mode 100644 index 000000000..65eda8c9a --- /dev/null +++ b/Sources/OpenSwiftUICore/View/Image/NamedImage.swift @@ -0,0 +1,23 @@ +// +// NamedImage.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Empty +// ID: BBFAAB6E2E9787715FB41E2179A3B661 (SwiftUICore) + +public import OpenCoreGraphicsShims + +extension Image { + #if canImport(CoreGraphics) + // FIXME + public init(decorative: CGImage, scale: CGFloat, orientation: Image.Orientation) { + _openSwiftUIUnimplementedFailure() + } + #endif + + // FIXME + package enum Resolved {} + + package enum NamedResolved {} +} diff --git a/Sources/OpenSwiftUICore/View/Image/VectorImageLayer.swift b/Sources/OpenSwiftUICore/View/Image/VectorImageLayer.swift new file mode 100644 index 000000000..13ec1899e --- /dev/null +++ b/Sources/OpenSwiftUICore/View/Image/VectorImageLayer.swift @@ -0,0 +1,8 @@ +// +// VectorImageLayer.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Empty +// ID: 988D5168E40F7399F12C543D2EE9C5E9 (SwiftUICore) + diff --git a/Sources/OpenSwiftUISymbolDualTestsSupport/View/Image/ImageOrientationTestsStub.c b/Sources/OpenSwiftUISymbolDualTestsSupport/View/Image/ImageOrientationTestsStub.c new file mode 100644 index 000000000..aa79b415e --- /dev/null +++ b/Sources/OpenSwiftUISymbolDualTestsSupport/View/Image/ImageOrientationTestsStub.c @@ -0,0 +1,25 @@ +// +// ImageOrientationTestsStub.c +// OpenSwiftUISymbolDualTestsSupport + +#include "OpenSwiftUIBase.h" + +#if OPENSWIFTUI_TARGET_OS_DARWIN + +#import + +DEFINE_SL_STUB_SLF(OpenSwiftUITestStub_ImageOrientationInitWithExifValue, SwiftUI, $s7SwiftUI5ImageV11OrientationO9exifValueAESgSi_tcfC); + +DEFINE_SL_STUB_SLF(OpenSwiftUITestStub_CGSizeApplyImageOrientation, SwiftUI, $sSo6CGSizeV7SwiftUIE5applyyAbC5ImageV11OrientationOF); +DEFINE_SL_STUB_SLF(OpenSwiftUITestStub_CGSizeUnapplyImageOrientation, SwiftUI, $sSo6CGSizeV7SwiftUIE7unapplyyAbC5ImageV11OrientationOF); + +DEFINE_SL_STUB_SLF(OpenSwiftUITestStub_CGRectApplyImageOrientation, SwiftUI, $sSo6CGRectV7SwiftUIE5apply_2inAbC5ImageV11OrientationO_So6CGSizeVtF); +DEFINE_SL_STUB_SLF(OpenSwiftUITestStub_CGRectUnapplyImageOrientation, SwiftUI, $sSo6CGRectV7SwiftUIE7unapply_2inAbC5ImageV11OrientationO_So6CGSizeVtF); + +DEFINE_SL_STUB_SLF(OpenSwiftUITestStub_CGAffineTransformInitWithImageOrientationInSize, SwiftUI, $sSo17CGAffineTransformV7SwiftUIE11orientation2inAbC5ImageV11OrientationO_So6CGSizeVtcfC); +DEFINE_SL_STUB_SLF(OpenSwiftUITestStub_CGAffineTransformInitWithImageOrientationInRect, SwiftUI, $sSo17CGAffineTransformV7SwiftUIE11orientation2inAbC5ImageV11OrientationO_So6CGRectVtcfC); + +DEFINE_SL_STUB_SLF(OpenSwiftUITestStub_CGAffineTransformApplyImageOrientationWithSize, SwiftUI, $sSo17CGAffineTransformV7SwiftUIE5apply_2inyAC5ImageV11OrientationO_So6CGSizeVtF); +DEFINE_SL_STUB_SLF(OpenSwiftUITestStub_CGAffineTransformApplyImageOrientation, SwiftUI, $sSo17CGAffineTransformV7SwiftUIE5applyyyAC5ImageV11OrientationOF); + +#endif diff --git a/Sources/OpenSwiftUISymbolDualTestsSupport/View/Image/ImageTestsStub.c b/Sources/OpenSwiftUISymbolDualTestsSupport/View/Image/ImageTestsStub.c deleted file mode 100644 index 346773fd5..000000000 --- a/Sources/OpenSwiftUISymbolDualTestsSupport/View/Image/ImageTestsStub.c +++ /dev/null @@ -1,13 +0,0 @@ -// -// ImageTestsStub.c -// OpenSwiftUISymbolDualTestsSupport - -#include "OpenSwiftUIBase.h" - -#if OPENSWIFTUI_TARGET_OS_DARWIN - -#import - -DEFINE_SL_STUB_SLF(OpenSwiftUITestStub_ImageOrientationInitWithExifValue, SwiftUI, $s7SwiftUI5ImageV11OrientationO9exifValueAESgSi_tcfC); - -#endif diff --git a/Tests/OpenSwiftUICoreTests/View/Image/ImageOrientationTests.swift b/Tests/OpenSwiftUICoreTests/View/Image/ImageOrientationTests.swift new file mode 100644 index 000000000..8b95a9c2e --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/View/Image/ImageOrientationTests.swift @@ -0,0 +1,191 @@ +// +// ImageOrientationTests.swift +// OpenSwiftUICoreTests + +#if canImport(ImageIO) +import ImageIO +#endif +import OpenCoreGraphicsShims +@_spi(Private) import OpenSwiftUICore +import Testing + +struct ImageOrientationTests { + @Test(arguments: [ + (Image.Orientation.up, 0), + (Image.Orientation.upMirrored, 2), + (Image.Orientation.down, 6), + (Image.Orientation.downMirrored, 4), + (Image.Orientation.left, 1), + (Image.Orientation.leftMirrored, 3), + (Image.Orientation.right, 7), + (Image.Orientation.rightMirrored, 5), + ]) + func rawValue(_ orientation: Image.Orientation, _ expectedRawValue: Int) { + #expect(orientation.rawValue == expectedRawValue) + } + + @Test(arguments: [ + (0, Image.Orientation.up), + (1, Image.Orientation.left), + (2, Image.Orientation.upMirrored), + (3, Image.Orientation.leftMirrored), + (4, Image.Orientation.downMirrored), + (5, Image.Orientation.rightMirrored), + (6, Image.Orientation.down), + (7, Image.Orientation.right), + ]) + func initFromRawValue(_ rawValue: Int, _ expectedOrientation: Image.Orientation) { + #expect(Image.Orientation(rawValue: UInt8(rawValue)) == expectedOrientation) + } + + @Test(arguments: [ + (1, Image.Orientation.up), + (2, Image.Orientation.upMirrored), + (3, Image.Orientation.down), + (4, Image.Orientation.downMirrored), + (5, Image.Orientation.leftMirrored), + (6, Image.Orientation.right), + (7, Image.Orientation.rightMirrored), + (8, Image.Orientation.left), + ]) + func initFromExifValue(_ exifValue: Int, _ expectedOrientation: Image.Orientation) { + #expect(Image.Orientation(exifValue: exifValue) == expectedOrientation) + } + + #if canImport(ImageIO) + @Test(arguments: [ + (CGImagePropertyOrientation.up, Image.Orientation.up), + (CGImagePropertyOrientation.upMirrored, Image.Orientation.upMirrored), + (CGImagePropertyOrientation.down, Image.Orientation.down), + (CGImagePropertyOrientation.downMirrored, Image.Orientation.downMirrored), + (CGImagePropertyOrientation.leftMirrored, Image.Orientation.leftMirrored), + (CGImagePropertyOrientation.right, Image.Orientation.right), + (CGImagePropertyOrientation.rightMirrored, Image.Orientation.rightMirrored), + (CGImagePropertyOrientation.left, Image.Orientation.left), + ]) + func initFromCGImagePropertyOrientation( + _ cgImagePropertyOrientation: CGImagePropertyOrientation, + _ expectedOrientation: Image.Orientation + ) { + let orientation = Image.Orientation(exifValue: Int(cgImagePropertyOrientation.rawValue)) + #expect(orientation == expectedOrientation) + } + #endif +} + +// MARK: - CGSizeOrientationTests + +struct CGSizeOrientationTests { + private static let input: CGSize = CGSize(width: 100, height: 200) + + private static let reverse: CGSize = CGSize(width: 200, height: 100) + + @Test(arguments: [ + (Image.Orientation.up, input, input), + (Image.Orientation.upMirrored, input, input), + (Image.Orientation.down, input, input), + (Image.Orientation.downMirrored, input, input), + (Image.Orientation.left, input, reverse), + (Image.Orientation.leftMirrored, input, reverse), + (Image.Orientation.rightMirrored, input, reverse), + (Image.Orientation.right, input, reverse), + ]) + func apply(_ orientation: Image.Orientation, _ origin: CGSize, _ expected: CGSize) { + let result = origin.apply(orientation) + #expect(result == expected) + #expect(result.unapply(orientation) == origin) + } +} + +// MARK: - CGRectOrientationTests + +struct CGRectOrientationTests { + private static let input: CGRect = CGRect(x: 10, y: 20, width: 100, height: 200) + private static let size: CGSize = CGSize(width: 300, height: 400) + + @Test(arguments: [ + (Image.Orientation.up, input, size, CGRect(x: 10, y: 20, width: 100, height: 200)), + (Image.Orientation.upMirrored, input, size, CGRect(x: 190, y: 20, width: 100, height: 200)), + (Image.Orientation.down, input, size, CGRect(x: 190, y: 180, width: 100, height: 200)), + (Image.Orientation.downMirrored, input, size, CGRect(x: 10, y: 180, width: 100, height: 200)), + (Image.Orientation.left, input, size, CGRect(x: 380, y: 10, width: 200, height: 100)), + (Image.Orientation.leftMirrored, input, size, CGRect(x: -180, y: 10, width: 200, height: 100)), + (Image.Orientation.right, input, size, CGRect(x: -180, y: 190, width: 200, height: 100)), + (Image.Orientation.rightMirrored, input, size, CGRect(x: 380, y: 190, width: 200, height: 100)), + ]) + func apply(_ orientation: Image.Orientation, _ origin: CGRect, _ size: CGSize, _ expected: CGRect) { + let result = origin.apply(orientation, in: size) + #expect(result == expected) + #expect(result.unapply(orientation, in: size) == origin) + } +} + +// MARK: - CGAffineTransformOrientationTests + +struct CGAffineTransformOrientationTests { + private static let input: CGAffineTransform = CGAffineTransform(a: 2, b: 0.5, c: 0.5, d: 2, tx: 10, ty: 20) + private static let size: CGSize = CGSize(width: 100, height: 200) + private static let rect: CGRect = CGRect(x: 10, y: 20, width: 100, height: 200) + + @Test(arguments: [ + (Image.Orientation.up, size, CGAffineTransform(a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 0.0, ty: 0.0)), + (Image.Orientation.upMirrored, size, CGAffineTransform(a: -1.0, b: -0.0, c: 0.0, d: 1.0, tx: 100.0, ty: 0.0)), + (Image.Orientation.down, size, CGAffineTransform(a: -1.0, b: -0.0, c: -0.0, d: -1.0, tx: 100.0, ty: 200.0)), + (Image.Orientation.downMirrored, size, CGAffineTransform(a: 1.0, b: 0.0, c: -0.0, d: -1.0, tx: 0.0, ty: 200.0)), + (Image.Orientation.left, size, CGAffineTransform(a: -0.0, b: -1.0, c: 1.0, d: 0.0, tx: 0.0, ty: 200.0)), + (Image.Orientation.leftMirrored, size, CGAffineTransform(a: 0.0, b: 1.0, c: 1.0, d: 0.0, tx: 0.0, ty: 0.0)), + (Image.Orientation.right, size, CGAffineTransform(a: 0.0, b: 1.0, c: -1.0, d: -0.0, tx: 100.0, ty: 0.0)), + (Image.Orientation.rightMirrored, size, CGAffineTransform(a: -0.0, b: -1.0, c: -1.0, d: -0.0, tx: 100.0, ty: 200.0)), + ]) + func initWithOrientationInSize(_ orientation: Image.Orientation, _ size: CGSize, _ expected: CGAffineTransform) { + let result = CGAffineTransform(orientation: orientation, in: size) + #expect(result == expected) + } + + @Test(arguments: [ + (Image.Orientation.up, rect, CGAffineTransform(a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 0.0, ty: 0.0)), + (Image.Orientation.upMirrored, rect, CGAffineTransform(a: -1.0, b: 0.0, c: 0.0, d: 1.0, tx: 120.0, ty: 0.0)), + (Image.Orientation.down, rect, CGAffineTransform(a: -1.0, b: -0.0, c: -0.0, d: -1.0, tx: 120.0, ty: 240.0)), + (Image.Orientation.downMirrored, rect, CGAffineTransform(a: 1.0, b: 0.0, c: 0.0, d: -1.0, tx: 0.0, ty: 240.0)), + (Image.Orientation.left, rect, CGAffineTransform(a: 0.0, b: -1.0, c: 1.0, d: 0.0, tx: -10.0, ty: 230.0)), + (Image.Orientation.leftMirrored, rect, CGAffineTransform(a: 0.0, b: 1.0, c: 1.0, d: 0.0, tx: -10.0, ty: 10.0)), + (Image.Orientation.right, rect, CGAffineTransform(a: 0.0, b: 1.0, c: -1.0, d: 0.0, tx: 130.0, ty: 10.0)), + (Image.Orientation.rightMirrored, rect, CGAffineTransform(a: -0.0, b: -1.0, c: -1.0, d: -0.0, tx: 130.0, ty: 230.0)), + ]) + func initWithOrientationInRect(_ orientation: Image.Orientation, _ rect: CGRect, _ expected: CGAffineTransform) { + let result = CGAffineTransform(orientation: orientation, in: rect) + #expect(result == expected) + } + + @Test(arguments: [ + (Image.Orientation.up, input, size, CGAffineTransform(a: 2.0, b: 0.5, c: 0.5, d: 2.0, tx: 10.0, ty: 20.0)), + (Image.Orientation.upMirrored, input, size, CGAffineTransform(a: -2.0, b: -0.5, c: 0.5, d: 2.0, tx: 210.0, ty: 70.0)), + (Image.Orientation.down, input, size, CGAffineTransform(a: -2.0, b: -0.5, c: -0.5, d: -2.0, tx: 310.0, ty: 470.0)), + (Image.Orientation.downMirrored, input, size, CGAffineTransform(a: 2.0, b: 0.5, c: -0.5, d: -2.0, tx: 110.0, ty: 420.0)), + (Image.Orientation.left, input, size, CGAffineTransform(a: -0.5, b: -2.0, c: 2.0, d: 0.5, tx: 110.0, ty: 420.0)), + (Image.Orientation.leftMirrored, input, size, CGAffineTransform(a: 0.5, b: 2.0, c: 2.0, d: 0.5, tx: 10.0, ty: 20.0)), + (Image.Orientation.right, input, size, CGAffineTransform(a: 0.5, b: 2.0, c: -2.0, d: -0.5, tx: 210.0, ty: 70.0)), + (Image.Orientation.rightMirrored, input, size, CGAffineTransform(a: -0.5, b: -2.0, c: -2.0, d: -0.5, tx: 310.0, ty: 470.0)), + ]) + func applyWithSize(_ orientation: Image.Orientation, _ origin: CGAffineTransform, _ size: CGSize, _ expected: CGAffineTransform) { + var result = origin + result.apply(orientation, in: size) + #expect(result == expected) + } + + @Test(arguments: [ + (Image.Orientation.up, input, CGAffineTransform(a: 2.0, b: 0.5, c: 0.5, d: 2.0, tx: 10.0, ty: 20.0)), + (Image.Orientation.upMirrored, input, CGAffineTransform(a: -2.0, b: -0.5, c: 0.5, d: 2.0, tx: 12.0, ty: 20.5)), + (Image.Orientation.down, input, CGAffineTransform(a: -2.0, b: -0.5, c: -0.5, d: -2.0, tx: 12.5, ty: 22.5)), + (Image.Orientation.downMirrored, input, CGAffineTransform(a: 2.0, b: 0.5, c: -0.5, d: -2.0, tx: 10.5, ty: 22.0)), + (Image.Orientation.left, input, CGAffineTransform(a: -0.5, b: -2.0, c: 2.0, d: 0.5, tx: 10.5, ty: 22.0)), + (Image.Orientation.leftMirrored, input, CGAffineTransform(a: 0.5, b: 2.0, c: 2.0, d: 0.5, tx: 10.0, ty: 20.0)), + (Image.Orientation.right, input, CGAffineTransform(a: 0.5, b: 2.0, c: -2.0, d: -0.5, tx: 12.0, ty: 20.5)), + (Image.Orientation.rightMirrored, input, CGAffineTransform(a: -0.5, b: -2.0, c: -2.0, d: -0.5, tx: 12.5, ty: 22.5)), + ]) + func apply(_ orientation: Image.Orientation, _ origin: CGAffineTransform, _ expected: CGAffineTransform) { + var result = origin + result.apply(orientation) + #expect(result == expected) + } +} diff --git a/Tests/OpenSwiftUICoreTests/View/Image/ImageTests.swift b/Tests/OpenSwiftUICoreTests/View/Image/ImageTests.swift deleted file mode 100644 index 4d7f5c0db..000000000 --- a/Tests/OpenSwiftUICoreTests/View/Image/ImageTests.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// ImageTests.swift -// OpenSwiftUICoreTests - -#if canImport(ImageIO) -import ImageIO -#endif -@_spi(Private) import OpenSwiftUICore -import Testing - -struct ImageOrientationTests { - @Test(arguments: [ - (Image.Orientation.up, 0), - (Image.Orientation.upMirrored, 2), - (Image.Orientation.down, 6), - (Image.Orientation.downMirrored, 4), - (Image.Orientation.left, 1), - (Image.Orientation.leftMirrored, 3), - (Image.Orientation.right, 7), - (Image.Orientation.rightMirrored, 5), - ]) - func rawValue(_ orientation: Image.Orientation, _ expectedRawValue: Int) { - #expect(orientation.rawValue == expectedRawValue) - } - - @Test(arguments: [ - (0, Image.Orientation.up), - (1, Image.Orientation.left), - (2, Image.Orientation.upMirrored), - (3, Image.Orientation.leftMirrored), - (4, Image.Orientation.downMirrored), - (5, Image.Orientation.rightMirrored), - (6, Image.Orientation.down), - (7, Image.Orientation.right), - ]) - func initFromRawValue(_ rawValue: Int, _ expectedOrientation: Image.Orientation) { - #expect(Image.Orientation(rawValue: UInt8(rawValue)) == expectedOrientation) - } - - @Test(arguments: [ - (1, Image.Orientation.up), - (2, Image.Orientation.upMirrored), - (3, Image.Orientation.down), - (4, Image.Orientation.downMirrored), - (5, Image.Orientation.leftMirrored), - (6, Image.Orientation.right), - (7, Image.Orientation.rightMirrored), - (8, Image.Orientation.left), - ]) - func initFromExifValue(_ exifValue: Int, _ expectedOrientation: Image.Orientation) { - #expect(Image.Orientation(exifValue: exifValue) == expectedOrientation) - } - - #if canImport(ImageIO) - @Test(arguments: [ - (CGImagePropertyOrientation.up, Image.Orientation.up), - (CGImagePropertyOrientation.upMirrored, Image.Orientation.upMirrored), - (CGImagePropertyOrientation.down, Image.Orientation.down), - (CGImagePropertyOrientation.downMirrored, Image.Orientation.downMirrored), - (CGImagePropertyOrientation.leftMirrored, Image.Orientation.leftMirrored), - (CGImagePropertyOrientation.right, Image.Orientation.right), - (CGImagePropertyOrientation.rightMirrored, Image.Orientation.rightMirrored), - (CGImagePropertyOrientation.left, Image.Orientation.left), - ]) - func initFromCGImagePropertyOrientation( - _ cgImagePropertyOrientation: CGImagePropertyOrientation, - _ expectedOrientation: Image.Orientation - ) { - let orientation = Image.Orientation(exifValue: Int(cgImagePropertyOrientation.rawValue)) - #expect(orientation == expectedOrientation) - } - #endif -} diff --git a/Tests/OpenSwiftUISymbolDualTests/View/Image/ImageOrientationDualTests.swift b/Tests/OpenSwiftUISymbolDualTests/View/Image/ImageOrientationDualTests.swift new file mode 100644 index 000000000..d2649de55 --- /dev/null +++ b/Tests/OpenSwiftUISymbolDualTests/View/Image/ImageOrientationDualTests.swift @@ -0,0 +1,228 @@ +// +// ImageOrientationDualTests.swift +// OpenSwiftUISymbolDualTests + +#if canImport(SwiftUI, _underlyingVersion: 6.0.87) +import ImageIO +import SwiftUI +import Testing + +extension Image.Orientation { + @_silgen_name("OpenSwiftUITestStub_ImageOrientationInitWithExifValue") + init?(exifValue: Int) +} + +struct ImageOrientationDualTests { + @Test(arguments: [ + (Image.Orientation.up, 0), + (Image.Orientation.upMirrored, 2), + (Image.Orientation.down, 6), + (Image.Orientation.downMirrored, 4), + (Image.Orientation.left, 1), + (Image.Orientation.leftMirrored, 3), + (Image.Orientation.right, 7), + (Image.Orientation.rightMirrored, 5), + ]) + func rawValue(_ orientation: Image.Orientation, _ expectedRawValue: Int) { + #expect(orientation.rawValue == expectedRawValue) + } + + @Test(arguments: [ + (0, Image.Orientation.up), + (1, Image.Orientation.left), + (2, Image.Orientation.upMirrored), + (3, Image.Orientation.leftMirrored), + (4, Image.Orientation.downMirrored), + (5, Image.Orientation.rightMirrored), + (6, Image.Orientation.down), + (7, Image.Orientation.right), + ]) + func initFromRawValue(_ rawValue: Int, _ expectedOrientation: Image.Orientation) { + #expect(Image.Orientation(rawValue: UInt8(rawValue)) == expectedOrientation) + } + + @Test(arguments: [ + (1, Image.Orientation.up), + (2, Image.Orientation.upMirrored), + (3, Image.Orientation.down), + (4, Image.Orientation.downMirrored), + (5, Image.Orientation.leftMirrored), + (6, Image.Orientation.right), + (7, Image.Orientation.rightMirrored), + (8, Image.Orientation.left), + ]) + func initFromExifValue(_ exifValue: Int, _ expectedOrientation: Image.Orientation) { + #expect(Image.Orientation(exifValue: exifValue) == expectedOrientation) + } + + #if canImport(ImageIO) + @Test(arguments: [ + (CGImagePropertyOrientation.up, Image.Orientation.up), + (CGImagePropertyOrientation.upMirrored, Image.Orientation.upMirrored), + (CGImagePropertyOrientation.down, Image.Orientation.down), + (CGImagePropertyOrientation.downMirrored, Image.Orientation.downMirrored), + (CGImagePropertyOrientation.leftMirrored, Image.Orientation.leftMirrored), + (CGImagePropertyOrientation.right, Image.Orientation.right), + (CGImagePropertyOrientation.rightMirrored, Image.Orientation.rightMirrored), + (CGImagePropertyOrientation.left, Image.Orientation.left), + ]) + func initFromCGImagePropertyOrientation( + _ cgImagePropertyOrientation: CGImagePropertyOrientation, + _ expectedOrientation: Image.Orientation + ) { + let orientation = Image.Orientation(exifValue: Int(cgImagePropertyOrientation.rawValue)) + #expect(orientation == expectedOrientation) + } + #endif +} + +// MARK: - CGSizeOrientationDualTests + +extension CGSize { + @_silgen_name("OpenSwiftUITestStub_CGSizeApplyImageOrientation") + func apply(_ orientation: Image.Orientation) -> CGSize + + @_silgen_name("OpenSwiftUITestStub_CGSizeUnapplyImageOrientation") + func unapply(_ orientation: Image.Orientation) -> CGSize +} + +struct CGSizeOrientationDualTests { + private static let input: CGSize = CGSize(width: 100, height: 200) + + private static let reverse: CGSize = CGSize(width: 200, height: 100) + + @Test(arguments: [ + (Image.Orientation.up, input, input), + (Image.Orientation.upMirrored, input, input), + (Image.Orientation.down, input, input), + (Image.Orientation.downMirrored, input, input), + (Image.Orientation.left, input, reverse), + (Image.Orientation.leftMirrored, input, reverse), + (Image.Orientation.rightMirrored, input, reverse), + (Image.Orientation.right, input, reverse), + ]) + func apply(_ orientation: Image.Orientation, _ origin: CGSize, _ expected: CGSize) { + let result = origin.apply(orientation) + #expect(result == expected) + #expect(result.unapply(orientation) == origin) + } +} + +// MARK: - CGRectOrientationDualTests + +extension CGRect { + @_silgen_name("OpenSwiftUITestStub_CGRectApplyImageOrientation") + func apply(_ orientation: Image.Orientation, in size: CGSize) -> CGRect + + @_silgen_name("OpenSwiftUITestStub_CGRectUnapplyImageOrientation") + func unapply(_ orientation: Image.Orientation, in size: CGSize) -> CGRect +} + +struct CGRectOrientationDualTests { + private static let input: CGRect = CGRect(x: 10, y: 20, width: 100, height: 200) + private static let size: CGSize = CGSize(width: 300, height: 400) + + @Test(arguments: [ + (Image.Orientation.up, input, size, CGRect(x: 10, y: 20, width: 100, height: 200)), + (Image.Orientation.upMirrored, input, size, CGRect(x: 190, y: 20, width: 100, height: 200)), + (Image.Orientation.down, input, size, CGRect(x: 190, y: 180, width: 100, height: 200)), + (Image.Orientation.downMirrored, input, size, CGRect(x: 10, y: 180, width: 100, height: 200)), + (Image.Orientation.left, input, size, CGRect(x: 380, y: 10, width: 200, height: 100)), + (Image.Orientation.leftMirrored, input, size, CGRect(x: -180, y: 10, width: 200, height: 100)), + (Image.Orientation.right, input, size, CGRect(x: -180, y: 190, width: 200, height: 100)), + (Image.Orientation.rightMirrored, input, size, CGRect(x: 380, y: 190, width: 200, height: 100)), + ]) + func apply(_ orientation: Image.Orientation, _ origin: CGRect, _ size: CGSize, _ expected: CGRect) { + let result = origin.apply(orientation, in: size) + #expect(result == expected) + #expect(result.unapply(orientation, in: size) == origin) + } +} + +// MARK: - CGAffineTransformOrientationDualTests + +extension CGAffineTransform { + @_silgen_name("OpenSwiftUITestStub_CGAffineTransformInitWithImageOrientationInSize") + init(orientation: Image.Orientation, in size: CGSize) + + @_silgen_name("OpenSwiftUITestStub_CGAffineTransformInitWithImageOrientationInRect") + init(orientation: Image.Orientation, in rect: CGRect) + + @_silgen_name("OpenSwiftUITestStub_CGAffineTransformApplyImageOrientationWithSize") + mutating func apply(_ orientation: Image.Orientation, in size: CGSize) + + @_silgen_name("OpenSwiftUITestStub_CGAffineTransformApplyImageOrientation") + mutating func apply(_ orientation: Image.Orientation) +} + +// MARK: - CGAffineTransformOrientationDualTests + +struct CGAffineTransformOrientationDualTests { + private static let input: CGAffineTransform = CGAffineTransform(a: 2, b: 0.5, c: 0.5, d: 2, tx: 10, ty: 20) + private static let size: CGSize = CGSize(width: 100, height: 200) + private static let rect: CGRect = CGRect(x: 10, y: 20, width: 100, height: 200) + + @Test(arguments: [ + (Image.Orientation.up, size, CGAffineTransform(a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 0.0, ty: 0.0)), + (Image.Orientation.upMirrored, size, CGAffineTransform(a: -1.0, b: -0.0, c: 0.0, d: 1.0, tx: 100.0, ty: 0.0)), + (Image.Orientation.down, size, CGAffineTransform(a: -1.0, b: -0.0, c: -0.0, d: -1.0, tx: 100.0, ty: 200.0)), + (Image.Orientation.downMirrored, size, CGAffineTransform(a: 1.0, b: 0.0, c: -0.0, d: -1.0, tx: 0.0, ty: 200.0)), + (Image.Orientation.left, size, CGAffineTransform(a: -0.0, b: -1.0, c: 1.0, d: 0.0, tx: 0.0, ty: 200.0)), + (Image.Orientation.leftMirrored, size, CGAffineTransform(a: 0.0, b: 1.0, c: 1.0, d: 0.0, tx: 0.0, ty: 0.0)), + (Image.Orientation.right, size, CGAffineTransform(a: 0.0, b: 1.0, c: -1.0, d: -0.0, tx: 100.0, ty: 0.0)), + (Image.Orientation.rightMirrored, size, CGAffineTransform(a: -0.0, b: -1.0, c: -1.0, d: -0.0, tx: 100.0, ty: 200.0)), + ]) + func initWithOrientationInSize(_ orientation: Image.Orientation, _ size: CGSize, _ expected: CGAffineTransform) { + let result = CGAffineTransform(orientation: orientation, in: size) + #expect(result == expected) + } + + @Test(arguments: [ + (Image.Orientation.up, rect, CGAffineTransform(a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 0.0, ty: 0.0)), + (Image.Orientation.upMirrored, rect, CGAffineTransform(a: -1.0, b: 0.0, c: 0.0, d: 1.0, tx: 120.0, ty: 0.0)), + (Image.Orientation.down, rect, CGAffineTransform(a: -1.0, b: -0.0, c: -0.0, d: -1.0, tx: 120.0, ty: 240.0)), + (Image.Orientation.downMirrored, rect, CGAffineTransform(a: 1.0, b: 0.0, c: 0.0, d: -1.0, tx: 0.0, ty: 240.0)), + (Image.Orientation.left, rect, CGAffineTransform(a: 0.0, b: -1.0, c: 1.0, d: 0.0, tx: -10.0, ty: 230.0)), + (Image.Orientation.leftMirrored, rect, CGAffineTransform(a: 0.0, b: 1.0, c: 1.0, d: 0.0, tx: -10.0, ty: 10.0)), + (Image.Orientation.right, rect, CGAffineTransform(a: 0.0, b: 1.0, c: -1.0, d: 0.0, tx: 130.0, ty: 10.0)), + (Image.Orientation.rightMirrored, rect, CGAffineTransform(a: -0.0, b: -1.0, c: -1.0, d: -0.0, tx: 130.0, ty: 230.0)), + ]) + func initWithOrientationInRect(_ orientation: Image.Orientation, _ rect: CGRect, _ expected: CGAffineTransform) { + let result = CGAffineTransform(orientation: orientation, in: rect) + #expect(result == expected) + } + + @Test(arguments: [ + (Image.Orientation.up, input, size, CGAffineTransform(a: 2.0, b: 0.5, c: 0.5, d: 2.0, tx: 10.0, ty: 20.0)), + (Image.Orientation.upMirrored, input, size, CGAffineTransform(a: -2.0, b: -0.5, c: 0.5, d: 2.0, tx: 210.0, ty: 70.0)), + (Image.Orientation.down, input, size, CGAffineTransform(a: -2.0, b: -0.5, c: -0.5, d: -2.0, tx: 310.0, ty: 470.0)), + (Image.Orientation.downMirrored, input, size, CGAffineTransform(a: 2.0, b: 0.5, c: -0.5, d: -2.0, tx: 110.0, ty: 420.0)), + (Image.Orientation.left, input, size, CGAffineTransform(a: -0.5, b: -2.0, c: 2.0, d: 0.5, tx: 110.0, ty: 420.0)), + (Image.Orientation.leftMirrored, input, size, CGAffineTransform(a: 0.5, b: 2.0, c: 2.0, d: 0.5, tx: 10.0, ty: 20.0)), + (Image.Orientation.right, input, size, CGAffineTransform(a: 0.5, b: 2.0, c: -2.0, d: -0.5, tx: 210.0, ty: 70.0)), + (Image.Orientation.rightMirrored, input, size, CGAffineTransform(a: -0.5, b: -2.0, c: -2.0, d: -0.5, tx: 310.0, ty: 470.0)), + ]) + func applyWithSize(_ orientation: Image.Orientation, _ origin: CGAffineTransform, _ size: CGSize, _ expected: CGAffineTransform) { + var result = origin + result.apply(orientation, in: size) + #expect(result == expected) + } + + @Test(arguments: [ + (Image.Orientation.up, input, CGAffineTransform(a: 2.0, b: 0.5, c: 0.5, d: 2.0, tx: 10.0, ty: 20.0)), + (Image.Orientation.upMirrored, input, CGAffineTransform(a: -2.0, b: -0.5, c: 0.5, d: 2.0, tx: 12.0, ty: 20.5)), + (Image.Orientation.down, input, CGAffineTransform(a: -2.0, b: -0.5, c: -0.5, d: -2.0, tx: 12.5, ty: 22.5)), + (Image.Orientation.downMirrored, input, CGAffineTransform(a: 2.0, b: 0.5, c: -0.5, d: -2.0, tx: 10.5, ty: 22.0)), + (Image.Orientation.left, input, CGAffineTransform(a: -0.5, b: -2.0, c: 2.0, d: 0.5, tx: 10.5, ty: 22.0)), + (Image.Orientation.leftMirrored, input, CGAffineTransform(a: 0.5, b: 2.0, c: 2.0, d: 0.5, tx: 10.0, ty: 20.0)), + (Image.Orientation.right, input, CGAffineTransform(a: 0.5, b: 2.0, c: -2.0, d: -0.5, tx: 12.0, ty: 20.5)), + (Image.Orientation.rightMirrored, input, CGAffineTransform(a: -0.5, b: -2.0, c: -2.0, d: -0.5, tx: 12.5, ty: 22.5)), + ]) + func apply(_ orientation: Image.Orientation, _ origin: CGAffineTransform, _ expected: CGAffineTransform) { + var result = origin + result.apply(orientation) + #expect(result == expected) + } +} + +#endif diff --git a/Tests/OpenSwiftUISymbolDualTests/View/Image/ImageTests.swift b/Tests/OpenSwiftUISymbolDualTests/View/Image/ImageTests.swift deleted file mode 100644 index 628339a34..000000000 --- a/Tests/OpenSwiftUISymbolDualTests/View/Image/ImageTests.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// ImageTests.swift -// OpenSwiftUISymbolDualTests - -#if canImport(SwiftUI, _underlyingVersion: 6.0.87) -import ImageIO -import SwiftUI -import Testing - -extension Image.Orientation { - @_silgen_name("OpenSwiftUITestStub_ImageOrientationInitWithExifValue") - init?(exifValue: Int) -} - -struct ImageOrientationTests { - @Test(arguments: [ - (Image.Orientation.up, 0), - (Image.Orientation.upMirrored, 2), - (Image.Orientation.down, 6), - (Image.Orientation.downMirrored, 4), - (Image.Orientation.left, 1), - (Image.Orientation.leftMirrored, 3), - (Image.Orientation.right, 7), - (Image.Orientation.rightMirrored, 5), - ]) - func rawValue(_ orientation: Image.Orientation, _ expectedRawValue: Int) { - #expect(orientation.rawValue == expectedRawValue) - } - - @Test(arguments: [ - (0, Image.Orientation.up), - (1, Image.Orientation.left), - (2, Image.Orientation.upMirrored), - (3, Image.Orientation.leftMirrored), - (4, Image.Orientation.downMirrored), - (5, Image.Orientation.rightMirrored), - (6, Image.Orientation.down), - (7, Image.Orientation.right), - ]) - func initFromRawValue(_ rawValue: Int, _ expectedOrientation: Image.Orientation) { - #expect(Image.Orientation(rawValue: UInt8(rawValue)) == expectedOrientation) - } - - @Test(arguments: [ - (1, Image.Orientation.up), - (2, Image.Orientation.upMirrored), - (3, Image.Orientation.down), - (4, Image.Orientation.downMirrored), - (5, Image.Orientation.leftMirrored), - (6, Image.Orientation.right), - (7, Image.Orientation.rightMirrored), - (8, Image.Orientation.left), - ]) - func initFromExifValue(_ exifValue: Int, _ expectedOrientation: Image.Orientation) { - #expect(Image.Orientation(exifValue: exifValue) == expectedOrientation) - } - - #if canImport(ImageIO) - @Test(arguments: [ - (CGImagePropertyOrientation.up, Image.Orientation.up), - (CGImagePropertyOrientation.upMirrored, Image.Orientation.upMirrored), - (CGImagePropertyOrientation.down, Image.Orientation.down), - (CGImagePropertyOrientation.downMirrored, Image.Orientation.downMirrored), - (CGImagePropertyOrientation.leftMirrored, Image.Orientation.leftMirrored), - (CGImagePropertyOrientation.right, Image.Orientation.right), - (CGImagePropertyOrientation.rightMirrored, Image.Orientation.rightMirrored), - (CGImagePropertyOrientation.left, Image.Orientation.left), - ]) - func initFromCGImagePropertyOrientation( - _ cgImagePropertyOrientation: CGImagePropertyOrientation, - _ expectedOrientation: Image.Orientation - ) { - let orientation = Image.Orientation(exifValue: Int(cgImagePropertyOrientation.rawValue)) - #expect(orientation == expectedOrientation) - } - #endif -} -#endif