Ignite/utilities/filmgrain/isotable.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

161 lines
4.7 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 (
"errors"
"fmt"
"math"
"strings"
)
func CreatePhotonNoiseTable(width, height int, iso float64, transferFunction *TransferFunction) ([]byte, error) {
if transferFunction == nil {
return nil, errors.New("unknown transfer function")
}
// Assumes a daylight-like spectrum.
// https://www.strollswithmydog.com/effective-quantum-efficiency-of-sensor/#:~:text=11%2C260%20photons/um%5E2/lx-s
const kPhotonsPerLxSPerUm2 = 11260
// Order of magnitude for cameras in the 2010-2020 decade, taking the CFA into
// account.
const kEffectiveQuantumEfficiency = 0.20
// Also reasonable values for current cameras. The read noise is typically
// higher than this at low ISO settings but it matters less there.
const kPhotoResponseNonUniformity = 0.005
const kInputReferredReadNoise = 1.5
// Focal plane exposure for a mid-tone (typically a 18% reflectance card), in
// lx·s.
midToneExposure := 10 / float64(iso)
// In microns. Assumes a 35mm sensor (36mm × 24mm).
pixelAreaUm2 := float64((36000 * 24000) / (width * height))
midToneElectronsPerPixel := kEffectiveQuantumEfficiency *
kPhotonsPerLxSPerUm2 *
midToneExposure * pixelAreaUm2
maxElectronsPerPixel :=
midToneElectronsPerPixel / transferFunction.MidTone
var filmGrain struct {
// 8 bit values
scalingPointsY [14][2]int
numYPoints int // value: 0..14
// 8 bit values
scalingPointsCb [10][2]int
numCbPoints int // value: 0..10
// 8 bit values
scalingPointsCr [10][2]int
numCrPoints int // value: 0..10
arCoeffLag int // values: 0..3
// 8 bit values
arCoeffsY [24]int
arCoeffsCb [25]int
arCoeffsCr [25]int
}
filmGrain.numYPoints = 14
filmGrain.numCbPoints = 0
filmGrain.numCrPoints = 0
filmGrain.arCoeffLag = 0
for i := 0; i < filmGrain.numYPoints; i++ {
x := float64(i) / float64(filmGrain.numYPoints-1)
linear := transferFunction.ToLinear(x)
electronsPerPixel := maxElectronsPerPixel * linear
// Quadrature sum of the relevant sources of noise, in electrons rms. Photon
// shot noise is math.Sqrt(electrons) so we can skip the square root and the
// squaring.
// https://en.wikipedia.org/wiki/Addition_in_quadrature
// https://doi.org/10.1117/3.725073
noiseInElectrons :=
math.Sqrt(kInputReferredReadNoise*kInputReferredReadNoise +
electronsPerPixel +
(kPhotoResponseNonUniformity * kPhotoResponseNonUniformity *
electronsPerPixel * electronsPerPixel))
linearNoise := noiseInElectrons / maxElectronsPerPixel
linearRangeStart := max(0., linear-2*linearNoise)
linearRangeEnd := min(1., linear+2*linearNoise)
tfSlope :=
(transferFunction.FromLinear(linearRangeEnd) -
transferFunction.FromLinear(
linearRangeStart)) /
(linearRangeEnd - linearRangeStart)
encodedNoise := linearNoise * tfSlope
x = math.Round(255 * x)
encodedNoise = min(255., math.Round(255*7.88*encodedNoise))
filmGrain.scalingPointsY[i][0] = int(x)
filmGrain.scalingPointsY[i][1] = int(encodedNoise)
}
var lines []string
lines = append(lines, "filmgrn1")
lines = append(lines, fmt.Sprintf("E %d %d %d %d %d", 0 /*start_time*/, math.MaxInt64 /*end_time*/, 1 /*apply_grain*/, 7391 /*seed*/, 1 /*update_parameters*/))
lines = append(lines, fmt.Sprintf("\tp %d %d %d %d %d %d %d %d %d %d %d %d",
0 /*ar_coeff_lag*/, 6 /*ar_coeff_shift*/, 0, /*grain_scale_shift*/
8 /*scaling_shift*/, 0, /*chroma_scaling_from_luma*/
1 /*overlap_flag*/, 0 /*cb_mult*/, 0, /*cb_luma_mult*/
0 /*cb_offset*/, 0 /*cr_mult*/, 0, /*cr_luma_mult*/
0 /*cr_offset*/))
{
line := fmt.Sprintf("\tsY %d ", filmGrain.numYPoints)
for i := 0; i < filmGrain.numYPoints; i++ {
line += fmt.Sprintf(" %d %d", filmGrain.scalingPointsY[i][0],
filmGrain.scalingPointsY[i][1])
}
lines = append(lines, line)
}
{
line := fmt.Sprintf("\tsCb %d", filmGrain.numCbPoints)
for i := 0; i < filmGrain.numCbPoints; i++ {
line += fmt.Sprintf(" %d %d", filmGrain.scalingPointsCb[i][0],
filmGrain.scalingPointsCb[i][1])
}
lines = append(lines, line)
}
{
line := fmt.Sprintf("\tsCr %d", filmGrain.numCrPoints)
for i := 0; i < filmGrain.numCrPoints; i++ {
line += fmt.Sprintf(" %d %d", filmGrain.scalingPointsCr[i][0],
filmGrain.scalingPointsCr[i][1])
}
lines = append(lines, line)
}
n := 2 * filmGrain.arCoeffLag * (filmGrain.arCoeffLag + 1)
{
line := "\tcY"
for i := 0; i < n; i++ {
line += fmt.Sprintf(" %d", filmGrain.arCoeffsY[i])
}
lines = append(lines, line)
}
{
line := "\tcCb"
for i := 0; i <= n; i++ {
line += fmt.Sprintf(" %d", filmGrain.arCoeffsCb[i])
}
lines = append(lines, line)
}
{
line := "\tcCr"
for i := 0; i <= n; i++ {
line += fmt.Sprintf(" %d", filmGrain.arCoeffsCr[i])
}
lines = append(lines, line)
}
return []byte(strings.Join(lines, "\n") + "\n"), nil
}