142 lines
3.9 KiB
Go
142 lines
3.9 KiB
Go
package filmgrain
|
||
|
||
import (
|
||
"math"
|
||
"strings"
|
||
)
|
||
|
||
//TODO: transfer function type?
|
||
|
||
func GetTransferFunction(kind string) *TransferFunction {
|
||
switch strings.ToLower(kind) {
|
||
case "bt470m":
|
||
//gamma22
|
||
return &TransferFunction{
|
||
ToLinear: func(in float64) (linear float64) {
|
||
return math.Pow(in, 2.2)
|
||
},
|
||
FromLinear: func(linear float64) (out float64) {
|
||
return math.Pow(linear, 1/2.2)
|
||
},
|
||
MidTone: 0.18,
|
||
}
|
||
case "bt470bg":
|
||
//gamma28
|
||
return &TransferFunction{
|
||
ToLinear: func(in float64) (linear float64) {
|
||
return math.Pow(in, 2.8)
|
||
},
|
||
FromLinear: func(linear float64) (out float64) {
|
||
return math.Pow(linear, 1/2.8)
|
||
},
|
||
MidTone: 0.18,
|
||
}
|
||
case "bt601", "bt709", "bt2020":
|
||
//TODO: are bt601 and bt709 the same?
|
||
//bt2020. same as bt709, just defined more precise. Can use the same for all
|
||
const beta = 0.018053968510807
|
||
const alpha = 1 + 5.5*beta
|
||
return &TransferFunction{
|
||
ToLinear: func(in float64) (linear float64) {
|
||
if in < 0.081 {
|
||
return in / 4.5
|
||
} else {
|
||
return math.Pow((in+(alpha-1))/alpha, 1/0.45)
|
||
}
|
||
},
|
||
FromLinear: func(linear float64) (out float64) {
|
||
if linear < beta {
|
||
return 4.5 * linear
|
||
} else {
|
||
return alpha*math.Pow(linear, 0.45) - (alpha - 1)
|
||
}
|
||
},
|
||
//TODO: check this for bt2020?
|
||
MidTone: 0.18,
|
||
}
|
||
case "srgb":
|
||
//sRGB
|
||
return &TransferFunction{
|
||
ToLinear: func(in float64) (linear float64) {
|
||
if in <= 0.04045 {
|
||
return in / 12.92
|
||
} else {
|
||
return math.Pow((in+0.055)/1.055, 2.4)
|
||
}
|
||
},
|
||
FromLinear: func(linear float64) (out float64) {
|
||
if linear <= 0.0031308 {
|
||
return 12.92 * linear
|
||
} else {
|
||
return 1.055*math.Pow(linear, 1/2.4) - 0.055
|
||
}
|
||
},
|
||
MidTone: 0.18,
|
||
}
|
||
case "smpte2084", "pq":
|
||
//pq
|
||
const PqM1 = 2610. / 16384
|
||
const PqM2 = 128 * 2523. / 4096
|
||
const PqC1 = 3424. / 4096
|
||
const PqC2 = 32 * 2413. / 4096
|
||
const PqC3 = 32 * 2392. / 4096
|
||
return &TransferFunction{
|
||
ToLinear: func(in float64) (linear float64) {
|
||
pq_pow_inv_m2 := math.Pow(in, 1./PqM2)
|
||
return math.Pow(max(0, pq_pow_inv_m2-PqC1)/(PqC2-PqC3*pq_pow_inv_m2),
|
||
1./PqM1)
|
||
},
|
||
FromLinear: func(linear float64) (out float64) {
|
||
linear_pow_m1 := math.Pow(linear, PqM1)
|
||
return math.Pow((PqC1+PqC2*linear_pow_m1)/(1+PqC3*linear_pow_m1),
|
||
PqM2)
|
||
},
|
||
MidTone: 26. / 1000,
|
||
}
|
||
case "hlg":
|
||
//hlg
|
||
// Note: it is perhaps debatable whether “linear” for HLG should be scene light
|
||
// or display light. Here, it is implemented in terms of display light assuming
|
||
// a nominal peak display luminance of 1000 cd/m², hence the system γ of 1.2. To
|
||
// make it scene light instead, the OOTF (math.Pow(x, 1.2)) and its inverse should
|
||
// be removed from the functions below, and the TransferFunction.MidTone should be replaced
|
||
// with math.Pow(26. / 1000, 1 / 1.2).
|
||
const HlgA = 0.17883277
|
||
const HlgB = 0.28466892
|
||
const HlgC = 0.55991073
|
||
return &TransferFunction{
|
||
ToLinear: func(in float64) (linear float64) {
|
||
// EOTF = OOTF ∘ OETF⁻¹
|
||
if in <= 0.5 {
|
||
linear = in * in / 3
|
||
} else {
|
||
linear = (math.Exp((in-HlgC)/HlgA) + HlgB) / 12
|
||
}
|
||
return math.Pow(linear, 1.2)
|
||
},
|
||
FromLinear: func(linear float64) (out float64) {
|
||
// EOTF⁻¹ = OETF ∘ OOTF⁻¹
|
||
linear = math.Pow(linear, 1./1.2)
|
||
if linear <= (1. / 12) {
|
||
return math.Sqrt(3 * linear)
|
||
} else {
|
||
return HlgA*math.Log(12*linear-HlgB) + HlgC
|
||
}
|
||
},
|
||
MidTone: 26. / 1000,
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
type TransferFunction struct {
|
||
ToLinear func(in float64) (linear float64)
|
||
FromLinear func(linear float64) (out float64)
|
||
|
||
// MidTone In linear output light. This would typically be 0.18 for SDR (this matches
|
||
// the definition of Standard Output Sensitivity from ISO 12232:2019), but in
|
||
// HDR, we certainly do not want to consider 18% of the maximum output a
|
||
// “mid-tone”, as it would be e.g. 1800 cd/m² for SMPTE ST 2084 (PQ).
|
||
MidTone float64
|
||
}
|