Skip to content

Instantly share code, notes, and snippets.

@ZevEisenberg
Last active November 1, 2024 14:46
Show Gist options
  • Save ZevEisenberg/65e5b5c0ea6687de6e490d4b959756b7 to your computer and use it in GitHub Desktop.
Save ZevEisenberg/65e5b5c0ea6687de6e490d4b959756b7 to your computer and use it in GitHub Desktop.
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
}
}
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