Ignite/utilities/filmgrain/transfer.go
DataHoarder c533ad4386
Some checks failed
continuous-integration/drone/push Build is failing
Added ISO Noise table generator for libaom
2023-11-03 18:52:15 +01:00

142 lines
3.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}