Skip to content

Commit 2819398

Browse files
committed
CIEXYZ1931ColorSpace: fix PQ transfer function and minor tweaks
1 parent 3e55c33 commit 2819398

3 files changed

Lines changed: 43 additions & 42 deletions

File tree

Sources/SubstrateImage/Image+CGImage.swift

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,22 @@ extension Image {
2323
return CGColorSpace(name: CGColorSpace.displayP3)!
2424
case .linearSRGB:
2525
return CGColorSpace(name: isGrayscale ? CGColorSpace.linearGray : CGColorSpace.linearSRGB)!
26-
case .rec2020:
27-
if #available(macOS 12.0, iOS 15.1, *) {
28-
return CGColorSpace(name: CGColorSpace.itur_2020_sRGBGamma)!
29-
} else {
30-
break
31-
}
32-
case .rec709:
33-
return CGColorSpace(name: CGColorSpace.itur_709)!
3426
case .sRGB:
3527
return CGColorSpace(name: CGColorSpace.sRGB)!
3628
default:
29+
if cieSpace.primaries == .rec709, cieSpace.referenceWhite == .d65, cieSpace.eotf == .rec709 || cieSpace.eotf == .power(2.4) {
30+
return CGColorSpace(name: CGColorSpace.itur_709)!
31+
} else if cieSpace.primaries == .rec2020, cieSpace.referenceWhite == .d65 {
32+
if cieSpace.eotf == .rec709 || cieSpace.eotf == .power(2.4) {
33+
return CGColorSpace(name: CGColorSpace.itur_2020)!
34+
} else if cieSpace.eotf == .sRGB {
35+
if #available(macOS 12.0, iOS 15.1, *) {
36+
return CGColorSpace(name: CGColorSpace.itur_2020_sRGBGamma)!
37+
} else {
38+
break
39+
}
40+
}
41+
}
3742
break
3843
}
3944

@@ -145,41 +150,29 @@ extension Image where ComponentType: SIMDScalar {
145150
case CGColorSpace.displayP3:
146151
colorSpace = .cieRGB(colorSpace: .displayP3)
147152
case itur_2020_sRGBGamma:
148-
colorSpace = .cieRGB(colorSpace: .rec2020)
153+
colorSpace = .cieRGB(colorSpace: .rec2020(eotf: .sRGB))
149154
case linearITUR_2020:
150-
var cieSpace = CIEXYZ1931ColorSpace<Float>.rec2020
151-
cieSpace.eotf = .linear
152-
colorSpace = .cieRGB(colorSpace: cieSpace)
155+
colorSpace = .cieRGB(colorSpace: .rec2020(eotf: .linear))
153156
case CGColorSpace.itur_2020, CGColorSpace.extendedITUR_2020:
154-
var cieSpace = CIEXYZ1931ColorSpace<Float>.rec2020
155-
cieSpace.eotf = .power(2.4)
156-
colorSpace = .cieRGB(colorSpace: cieSpace)
157+
colorSpace = .cieRGB(colorSpace: .rec2020(eotf: .power(2.4)))
157158
case CGColorSpace.itur_709:
158-
colorSpace = .cieRGB(colorSpace: .rec709)
159+
colorSpace = .cieRGB(colorSpace: .rec709(eotf: .rec709))
159160
case itur709HLG:
160-
var cieSpace = CIEXYZ1931ColorSpace<Float>.rec709
161-
cieSpace.eotf = .hlg
162-
colorSpace = .cieRGB(colorSpace: cieSpace)
161+
colorSpace = .cieRGB(colorSpace: .rec709(eotf: .hlg))
163162
case CGColorSpace.displayP3_HLG:
164163
var cieSpace = CIEXYZ1931ColorSpace<Float>.displayP3
165164
cieSpace.eotf = .hlg
166165
colorSpace = .cieRGB(colorSpace: cieSpace)
167166
case CGColorSpace.itur_2100_HLG:
168-
var cieSpace = CIEXYZ1931ColorSpace<Float>.rec2020
169-
cieSpace.eotf = .hlg
170-
colorSpace = .cieRGB(colorSpace: cieSpace)
167+
colorSpace = .cieRGB(colorSpace: .rec2020(eotf: .hlg))
171168
case itur_709_PQ:
172-
var cieSpace = CIEXYZ1931ColorSpace<Float>.rec709
173-
cieSpace.eotf = .pq
174-
colorSpace = .cieRGB(colorSpace: cieSpace)
169+
colorSpace = .cieRGB(colorSpace: .rec709(eotf: .pq))
175170
case CGColorSpace.displayP3_PQ:
176171
var cieSpace = CIEXYZ1931ColorSpace<Float>.displayP3
177172
cieSpace.eotf = .pq
178173
colorSpace = .cieRGB(colorSpace: cieSpace)
179174
case CGColorSpace.itur_2100_PQ:
180-
var cieSpace = CIEXYZ1931ColorSpace<Float>.rec2020
181-
cieSpace.eotf = .pq
182-
colorSpace = .cieRGB(colorSpace: cieSpace)
175+
colorSpace = .cieRGB(colorSpace: .rec2020(eotf: .pq))
183176
default:
184177
if ComponentType.self == UInt8.self, let sRGB = cgImage.copy(colorSpace: CGColorSpace(name: CGColorSpace.sRGB)!) {
185178
cgImage = sRGB

Sources/SubstrateImageIO/ImageSaving.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ fileprivate extension LodePNGInfo {
361361
self.srgb_defined = 1
362362
self.srgb_intent = 1 // relative colorimetric
363363
case .cieRGB(let colorSpace):
364-
if colorSpace == .rec709 || colorSpace == .sRGB {
364+
if colorSpace == .rec709(eotf: .sRGB) || colorSpace == .sRGB {
365365
self.srgb_defined = 1
366366
self.srgb_intent = 1 // relative colorimetric
367367
} else {

Sources/SubstrateMath/Color.swift

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -384,8 +384,6 @@ public enum ColorTransferFunction<Scalar: BinaryFloatingPoint & Real> {
384384
case pq
385385
case hlg
386386

387-
public static var rec2020: ColorTransferFunction { return .rec709 }
388-
389387
@inlinable
390388
public var representativeGamma: Scalar? {
391389
switch self {
@@ -573,9 +571,9 @@ public enum ColorTransferFunction<Scalar: BinaryFloatingPoint & Real> {
573571
let c2: Scalar = 2413.0 / 128.0
574572
let c3: Scalar = 2392.0 / 128.0
575573

576-
let inverse = self.linearToEncoded(x)
577-
let numerator = max(Scalar.pow(inverse, 1.0 / m2) - c1, 0.0)
578-
let denominator = c2 - c3 * Scalar.pow(inverse, 1.0 / m2)
574+
let xGamma = Scalar.pow(x, 1.0 / m2)
575+
let numerator = max(xGamma - c1, 0.0)
576+
let denominator = c2 - c3 * xGamma
579577

580578
return 10_000.0 * Scalar.pow(numerator / denominator, 1.0 / m1)
581579
case .proPhoto:
@@ -600,7 +598,7 @@ public enum ColorTransferFunction<Scalar: BinaryFloatingPoint & Real> {
600598
// x = a * Scalar.log(12.0 * E - b) + c
601599
// (x - c) / a = log(12.0 * E - b)
602600
// exp((x - c) / a) + b = 12.0 * E
603-
return Scalar.exp((x - c) / a + b) / 12.0
601+
return (Scalar.exp((x - c) / a) + b) / 12.0
604602
}
605603
}
606604
}
@@ -660,6 +658,14 @@ public struct CIEXYZ1931ColorSpace<Scalar: BinaryFloatingPoint & Real & SIMDScal
660658
blue: SIMD2(0.15000, 0.06000))
661659
}
662660

661+
@inlinable
662+
public static var rec709: Primaries {
663+
return Primaries(
664+
red: SIMD2(0.64000, 0.33000),
665+
green: SIMD2(0.30000, 0.60000),
666+
blue: SIMD2(0.15000, 0.06000))
667+
}
668+
663669
@inlinable
664670
public static var p3: Primaries {
665671
return Primaries(
@@ -802,10 +808,11 @@ public struct CIEXYZ1931ColorSpace<Scalar: BinaryFloatingPoint & Real & SIMDScal
802808
self.referenceWhite = referenceWhite
803809
}
804810

811+
// BT.1886 defines a gamma 2.4 EOTF. Rec.709 has its own OETF but that's only used for encoding directly to Rec.709 from a camera sensor.
805812
@inlinable
806-
public static var rec709: CIEXYZ1931ColorSpace {
813+
public static func rec709(eotf: ColorTransferFunction<Scalar> = .power(2.4)) -> CIEXYZ1931ColorSpace {
807814
return .init(primaries: .sRGB,
808-
eotf: .rec709,
815+
eotf: eotf,
809816
referenceWhite: .d65)
810817
}
811818

@@ -837,10 +844,11 @@ public struct CIEXYZ1931ColorSpace<Scalar: BinaryFloatingPoint & Real & SIMDScal
837844
referenceWhite: .d65)
838845
}
839846

847+
// BT.1886 defines a gamma 2.4 EOTF.
840848
@inlinable
841-
public static var rec2020: CIEXYZ1931ColorSpace {
849+
public static func rec2020(eotf: ColorTransferFunction<Scalar> = .power(2.4)) -> CIEXYZ1931ColorSpace {
842850
return .init(primaries: .rec2020,
843-
eotf: .rec2020,
851+
eotf: eotf,
844852
referenceWhite: .d65)
845853
}
846854

@@ -880,7 +888,7 @@ public struct CIEXYZ1931ColorSpace<Scalar: BinaryFloatingPoint & Real & SIMDScal
880888
}
881889

882890
@inlinable
883-
public static func arriLogC(el: ArriAlexaEl, usingSensorValues: Bool = false) -> CIEXYZ1931ColorSpace {
891+
public static func arriLogC(el: ArriAlexaEl = .el800, usingSensorValues: Bool = false) -> CIEXYZ1931ColorSpace {
884892
return .init(primaries: .arriWideGamutRGB,
885893
eotf: .arriLogC(usingSensorValues ? el.sensorSignalParameters : el.exposureValueParameters),
886894
referenceWhite: .d65)
@@ -982,9 +990,9 @@ extension CIEXYZ1931ColorSpace {
982990
case "Generic Grey Gamma 2.2 Profile":
983991
self = .init(primaries: .sRGB, eotf: .power(2.2), referenceWhite: .d65)
984992
case "Rec. ITU-R BT.2020-1":
985-
self = .rec2020
993+
self = .rec2020(eotf: .rec709)
986994
case "Rec. ITU-R BT.709-5":
987-
self = .rec709
995+
self = .rec709(eotf: .rec709)
988996
case "ROMM RGB: ISO 22028-2:2013":
989997
self = .proPhoto
990998
case "SMPTE RP 431-2-2007 DCI (P3)":

0 commit comments

Comments
 (0)