Last active
November 1, 2024 14:46
-
-
Save ZevEisenberg/65e5b5c0ea6687de6e490d4b959756b7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import CoreGraphics.CGBase | |
import RealityKit | |
import UIKit | |
/// The ratio of the resource’s width to its height. | |
/// - `...1` is portrait | |
/// - `1` is square | |
/// - `1...` is landscape | |
public enum AspectRatio: Equatable, Comparable, Codable, Sendable { | |
case portrait(Double) | |
case square | |
case landscape(Double) | |
public init<Ratio: BinaryFloatingPoint>(_ ratio: Ratio) { | |
precondition(ratio > 0, "Aspect ratio must be positive") | |
if ratio < 1 { | |
self = .portrait(Double(ratio)) | |
} else if ratio > 1 { | |
self = .landscape(Double(ratio)) | |
} else { | |
self = .square | |
} | |
} | |
public init<Dimension: BinaryFloatingPoint>( | |
width: Dimension, | |
height: Dimension | |
) { | |
self.init(width / height) | |
} | |
public init<Dimension: BinaryInteger>( | |
width: Dimension, | |
height: Dimension | |
) { | |
self.init(width: Double(width), height: Double(height)) | |
} | |
public var ratio: Double { | |
switch self { | |
case .portrait(let value): | |
precondition(value < 1, "It's impossible to validate direct enum case construction, so make sure we got this right when we made it. Consider moving this enum into a 'guts' type, and making the public \(AspectRatio.self) type a struct.") | |
return value | |
case .square: | |
return 1 | |
case .landscape(let value): | |
precondition(value > 1, "It's impossible to validate direct enum case construction, so make sure we got this right when we made it. Consider moving this enum into a 'guts' type, and making the public \(AspectRatio.self) type a struct.") | |
return value | |
} | |
} | |
public func size(setting dimension: Dimension, to value: CGFloat) -> CGSize { | |
switch dimension { | |
case .width: | |
CGSize( | |
width: value, | |
height: value / ratio | |
) | |
case .height: | |
CGSize( | |
width: value * ratio, | |
height: value | |
) | |
} | |
} | |
public static func / (lhs: AspectRatio, rhs: AspectRatio) -> Double { | |
lhs.ratio / rhs.ratio | |
} | |
public static func / (lhs: AspectRatio, rhs: Double) -> AspectRatio { | |
AspectRatio(lhs.ratio / rhs) | |
} | |
} | |
public extension CGSize { | |
var aspectRatio: AspectRatio { | |
.init(width: width, height: height) | |
} | |
} | |
public extension UIImage { | |
var aspectRatio: AspectRatio { | |
size.aspectRatio | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import CoreGraphics.CGBase | |
import Testing | |
import UIKit | |
@Suite | |
struct AspectRatioTests { | |
@Test | |
func initWithNumber() { | |
#expect(AspectRatio(0.5) == .portrait(0.5)) | |
#expect(AspectRatio(1) == .square) | |
#expect(AspectRatio(1.5) == .landscape(1.5)) | |
} | |
@Test | |
func initWithDimensions() { | |
#expect(AspectRatio(width: 9, height: 16) == .portrait(0.5625)) | |
#expect(AspectRatio(width: 20, height: 20) == .square) | |
#expect(AspectRatio(width: 4, height: 3) == .landscape(1.3333333333333333)) | |
} | |
@Test | |
func ratio() { | |
#expect(AspectRatio(0.5).ratio == 0.5) | |
#expect(AspectRatio(1).ratio == 1) | |
#expect(AspectRatio(1.5).ratio == 1.5) | |
} | |
@Test | |
func divisionByAspectRatio() { | |
#expect(AspectRatio.square / .square == 1) | |
#expect(AspectRatio(0.5) / AspectRatio(width: 4, height: 3) == 0.375) | |
} | |
@Test | |
func divisionByDouble() { | |
#expect(AspectRatio.square / 2 == AspectRatio(0.5)) | |
#expect(AspectRatio(width: 16, height: 9) / 4 == AspectRatio(width: 4, height: 9)) | |
} | |
@Suite | |
struct GetSizeBySettingDimension { | |
@Test | |
func landscape() { | |
let sut = AspectRatio(width: 4, height: 3) | |
#expect(sut.size(setting: .width, to: 4) == CGSize(width: 4, height: 3)) | |
#expect(sut.size(setting: .width, to: 400) == CGSize(width: 400, height: 300)) | |
#expect(sut.size(setting: .width, to: 120) == CGSize(width: 120, height: 90)) | |
#expect(sut.size(setting: .height, to: 3) == CGSize(width: 4, height: 3)) | |
#expect(sut.size(setting: .height, to: 300) == CGSize(width: 400, height: 300)) | |
#expect(sut.size(setting: .height, to: 90) == CGSize(width: 120, height: 90)) | |
} | |
@Test | |
func portrait() { | |
let sut = AspectRatio(width: 8, height: 10) | |
#expect(sut.size(setting: .width, to: 8) == CGSize(width: 8, height: 10)) | |
#expect(sut.size(setting: .width, to: 80) == CGSize(width: 80, height: 100)) | |
#expect(sut.size(setting: .height, to: 10) == CGSize(width: 8, height: 10)) | |
#expect(sut.size(setting: .height, to: 100) == CGSize(width: 80, height: 100)) | |
} | |
} | |
@Test | |
func cgSize() { | |
#expect(CGSize(width: 16, height: 9).aspectRatio == AspectRatio(1.7777777777777777)) | |
} | |
@Test | |
@MainActor | |
func uiImage() { | |
let format = UIGraphicsImageRendererFormat() | |
format.scale = 2 | |
let size = CGSize(width: 40, height: 20) | |
let image = UIGraphicsImageRenderer( | |
size: size, | |
format: format | |
).image { _ in | |
UIColor.blue.setFill() | |
UIRectFill(CGRect(origin: .zero, size: size)) | |
} | |
#expect(image.aspectRatio == AspectRatio(2)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment