diff --git a/build/unix/Makefile.am b/build/unix/Makefile.am
index fe652c8..8880a0f 100644
--- a/build/unix/Makefile.am
+++ b/build/unix/Makefile.am
@@ -166,6 +166,7 @@ commonsrc = \
../../src/fmtcl/InterlacingType.h \
../../src/fmtcl/KernelData.cpp \
../../src/fmtcl/KernelData.h \
+ ../../src/fmtcl/LumMatch.h \
../../src/fmtcl/Mat3.h \
../../src/fmtcl/Mat3.hpp \
../../src/fmtcl/Mat4.h \
@@ -242,6 +243,7 @@ commonsrc = \
../../src/fmtcl/TransOpFilmStream.h \
../../src/fmtcl/TransOpHlg.cpp \
../../src/fmtcl/TransOpHlg.h \
+ ../../src/fmtcl/TransOpInterface.cpp \
../../src/fmtcl/TransOpInterface.h \
../../src/fmtcl/TransOpLinPow.cpp \
../../src/fmtcl/TransOpLinPow.h \
diff --git a/build/win/common/common.vcxproj b/build/win/common/common.vcxproj
index 8793e99..9788f01 100644
--- a/build/win/common/common.vcxproj
+++ b/build/win/common/common.vcxproj
@@ -205,6 +205,7 @@
+
@@ -360,6 +361,7 @@
+
diff --git a/build/win/common/common.vcxproj.filters b/build/win/common/common.vcxproj.filters
index 78beb69..fafd5a9 100644
--- a/build/win/common/common.vcxproj.filters
+++ b/build/win/common/common.vcxproj.filters
@@ -204,6 +204,9 @@
fmtcl
+
+ fmtcl
+
fmtcl
@@ -695,6 +698,9 @@
fmtcl
+
+ fmtcl
+
diff --git a/doc/fmtconv.html b/doc/fmtconv.html
index 4ee0cfe..496cd33 100644
--- a/doc/fmtconv.html
+++ b/doc/fmtconv.html
@@ -195,16 +195,24 @@ Colorspace conversions
c = c.fmtc.transfer (transs="1886", transd="srgb")
c = c.fmtc.bitdepth (bits=8)
-Displaying a UHDTV HDR content encoded with BT.2100 ICtCp-PQ on a standard
-computer display:
+Displaying a UHDTV HDR content encoded with BT.2100
+ICT CP -PQ on a standard computer display.
+Conversion to sRGB (SDR) is done by reducing the contrast from an arbitrary
+factor and clipping the pixel values at the end.
+Obviously this is a poor solution, a tone mapping algorithm would be more
+suited for this task.
+With PQ, the linear values go way above 1.0 which is 203 cd/m2
+for the reference white, so even if we reduce the contrast, we convert data
+to float to prevent information loss during the subsequent color conversion
+steps (this is optional):
-c = core.fmtc.resample (clip=c, css="444")
-c = core.fmtc.matrix (clip=c, mats="ICtCp_PQ", matd="rgb")
-c = core.fmtc.transfer (clip=c, transs="2084", transd="linear")
-c = core.fmtc.matrix (clip=c, mats="lms", matd="rgb")
-c = core.fmtc.primaries (clip=c, prims="2100", primd="srgb")
-c = core.fmtc.transfer (clip=c, transs="linear", transd="srgb")
-c = core.fmtc.bitdepth (clip=c, bits=8)
+c = c.fmtc.resample (css="444")
+c = c.fmtc.matrix (mats="ICtCp_PQ", matd="rgb")
+c = c.fmtc.transfer (transs="2084", transd="linear", cont=0.2, flt=True)
+c = c.fmtc.matrix (mats="lms", matd="rgb")
+c = c.fmtc.primaries (prims="2100", primd="srgb")
+c = c.fmtc.transfer (transs="linear", transd="srgb")
+c = c.fmtc.bitdepth (bits=8)
Examples for Avisynth+
@@ -1603,20 +1611,28 @@ transfer
fmtc.transfer (
- clip : clip ;
- transs : data[] : opt;
- transd : data[] : opt;
- cont : float : opt;
- gcor : float : opt;
- bits : int : opt;
- flt : int : opt;
- fulls : int : opt; (True)
- fulld : int : opt; (True)
- logceis : int : opt; (800)
- logceid : int : opt; (800)
- cpuopt : int : opt; (-1)
- blacklvl : float : opt; (0)
- planes : float[]: opt; (3)
+ clip : clip ;
+ transs : data[] : opt;
+ transd : data[] : opt;
+ cont : float : opt;
+ gcor : float : opt;
+ bits : int : opt;
+ flt : int : opt;
+ fulls : int : opt; (True)
+ fulld : int : opt; (True)
+ logceis : int : opt; (800)
+ logceid : int : opt; (800)
+ cpuopt : int : opt; (-1)
+ blacklvl: float : opt; (0)
+ sceneref: int : opt; (undefined)
+ lb : float : opt;
+ lw : float : opt;
+ lw_s : float : opt; (lw)
+ lw_d : float : opt; (lw)
+ ambient : float : opt;
+ match : int : opt; (1)
+ gy : int : opt;
+ debug : int : opt; (0)
)
fmtc_transfer (
clip c,
@@ -1632,7 +1648,15 @@ transfer
int logceid (800),
int cpuopt (-1),
float blacklvl (0),
- arrayf planes (3)
+ bool sceneref (undefined),
+ float lb (undefined),
+ float lw (undefined),
+ float lw_s (lw),
+ float lw_d (lw),
+ float ambient (undefined),
+ int match (1),
+ bool gy (undefined),
+ int debug (0)
)
@@ -1641,24 +1665,50 @@ transfer
video signal to convert between linear and gamma-corrected modes.
The function offers four conversions in a row; all are optional:
-an electro-optical transfer function (EOTF) or an inverse opto-electrical transfer function (OETF−1 ) converting the signal to linear,
+an electro-optical transfer function (EOTF) or an inverse opto-electrical transfer function (OETF−1 ) converting the signal from gamma-compressed to linear light (scene or display),
a contrast adjustement,
a gamma correction operating on the linear signal,
and an opto-electrical transfer function (OETF) or an inverse electro-optical transfer function (EOTF−1 ) to encode the signal back to a non-linear
domain.
+It is possible to specify the linear step as scene-referred or
+display-referred.
+The latter is the default.
+By default, the function tries to match the reference white levels between
+the transfer functions by automatically scaling the linear light values.
+In the linear range, reference white is generally associated to the normalised
+value 1.0, peak white might be much higher.
+This is important to remember when dealing with HDR content, especially when
+using integer data types which do not allow overflow (normalised values above
+1.0).
+An additional contrast step might be required to fit the content into the
+desired range, when linear values are used as input or output.
+Please refer to the curve table below to
+check the luminance information.
+This function is for simple conversions only, it doesn’t do sophisticated
+tone mapping.
As input, the function accepts only RGB and grayscale colorspaces.
As output, the data type can be changed while the colorspace is kept.
Only 16-bit integer and 32-bit float are supported.
Use bitdepth
to properly convert the output to a lower
bitdepth.
-The signal is clipped depending on the transfer specification or the domain
-requirement of the functions.
+The signal may be clipped depending on the transfer specification or the
+domain requirement of the functions.
The function sets the _ColorRange
and _Transfer
frame properties.
Avisynth+ Colorspaces with an alpha channel are
supported too, but the channel is left untouched.
+
+
+Processing diagram for the display-referred mode.
+
+
+
+
+Processing diagram for the scene-referred mode
+
+
Parameters
clip
@@ -1672,47 +1722,48 @@ Parameters
transs, transd
Transfer characteristics for input and output, respectively.
-The characteristic may be an OETF (opto-electronic transfer function), an
-EOTF (eletro-optical transfer function), or just remain unspecified.
+The characteristic may be an OETF, an EOTF, or just remain unspecified.
It is direct or inverted according to where it is applied, output or input.
The intermediate state is assumed linear light.
The curve set is the same as the list in ISO/IEC 23008-2 (HEVC), with a few
additions from various camera manufacturers or NLE systems.
Most curves map their value from the 0–1 range to 0–1, but some are for
high dynamic range or wide gamut signals and locate their value for peak white
-much higher.
-
-Value Linear range Type Description
-"709"
0…1 OETF ITU-R BT.709
-"470m"
0…1 ITU-R BT.470-6 System M, FCC (assumed display gamma 2.2) Actually uses the same curve as IEC 61966-2-1.
-"470bg"
0…1 ITU-R BT.470-6 System B, G (assumed display gamma 2.8)
-"601"
0…1 OETF ITU-R BT.601
-"240"
0…1 SMPTE 240M
-"linear" ""
Unspecified Linear (bypass)
-"log100"
0…1 Logarithmic transfer characteristic (100:1 range)
-"log316"
0…1 Logarithmic transfer characteristic (100*Sqrt(10):1 range)
-"61966-2-4"
Unspecified OETF IEC 61966-2-4, xvYCC. Same as BT.709, but with an extended range, including negative values.
-"1361"
−0.25…1.33 ITU-R BT.1361 extended colour gamut system
-"61966-2-1" "srgb" "sycc"
0…1 EOTF IEC 61966-2-1, sRGB or sYCC
-"2020_10"
0…1 ITU-R BT.2020 for 10-bit system
-"2020_12" "2020"
0…1 ITU-R BT.2020 for 12-bit system
-"2084"
0…1 EOTF SMPTE ST 2084 for 10, 12, 14 and 16-bit systems ITU-R BT.2100-2 PQ (perceptual quantization) Linear 1.0 is peak white and corresponds to a display luminance level of 10 000 cd/m2 .
-"428"
0…1 SMPTE ST 428-1 Linear 1.0 is peak white and corresponds to a display luminance level of 48 cd/m2 .
-"hlg"
0…1 OETF ITU-R BT.2100 HLG (hybrid log-gamma), ARIB STD-B67
-"1886"
0…1 EOTF ITU-R BT.1886. Intended to mimicing a CRT display curve.
-"1886a"
0…1 EOTF ITU-R BT.1886, alternative approximation
-"filmstream"
0…1 OETF? Thomson FilmStream Linear 1.0 is the sensor clipping level, corresponding to 3840 on a linear 12-bit scale.
-"slog"
−0.006…10 OETF? Sony S-Log Linear 1.0 is the reference white, peak white is at 10.0.
-"slog2"
−0.0085…14.13 OETF? Sony S-Log 2 Linear 1.0 is the reference white, peak white is at 14.13.
-"slog3"
0…38.421 OETF? Sony S-Log3.
-"logc2"
Unspecified OETF? Arri Log C Alexa 2.x, linear scene exposure Peak white is 57.45 linear. The negative part of the range allows coding sensor noise. logceis and logceid set the Exposure Index (EI).
-"logc3"
Unspecified OETF? Arri Log C Alexa 3.x, linear scene exposure Peak white is 55.08 linear. The negative part of the range allows coding sensor noise. logceis and logceid set the Exposure Index (EI).
-"canonlog"
0…8.00903 OETF Canon-Log Peak white is 8.00903 in linear scale and 1.08676 in compressed scale.
-"adobergb"
0…1 Adobe RGB (1998 and Wide Gamut)
-"romm"
0…1 ProPhoto, ROMM
-"acescc"
−65504…65504 ACEScc. Values are actually bounded to the ACES 16-bit float range.
-"erimm"
0…316.2 OETF ERIMM
-"vlog"
0…1 Panasonic V-Log
+much higher.
+Generally, display-referred linear scales are mapped to a known range in
+cd/m2 , whereas scene-referred linear scales are arbitrary.
+
+Value Linear range Type Range Description
+"709"
0…1 OETF SDR ITU-R BT.709
+"470m"
0…1 SDR ITU-R BT.470-6 System M, FCC (assumed display gamma 2.2) Actually uses the same curve as IEC 61966-2-1.
+"470bg"
0…1 SDR ITU-R BT.470-6 System B, G (assumed display gamma 2.8)
+"601"
0…1 OETF SDR ITU-R BT.601
+"240"
0…1 SDR SMPTE 240M
+"linear" ""
Unspecified Linear (bypass)
+"log100"
0…1 Logarithmic transfer characteristic (100:1 range)
+"log316"
0…1 Logarithmic transfer characteristic (100√10:1 range)
+"61966-2-4"
Unspecified OETF SDR IEC 61966-2-4, xvYCC. Same as BT.709, but with an extended range, including negative values.
+"1361"
−0.25…1.33 ITU-R BT.1361 extended colour gamut system
+"61966-2-1" "srgb" "sycc"
0…1 EOTF SDR IEC 61966-2-1, sRGB or sYCC
+"2020_10"
0…1 SDR ITU-R BT.2020 for 10-bit system
+"2020_12" "2020"
0…1 SDR ITU-R BT.2020 for 12-bit system
+"pq" "2084"
0…1 EOTF HDR SMPTE ST 2084 for 10, 12, 14 and 16-bit systems ITU-R BT.2100-2 PQ (perceptual quantization) Linear 1.0 is peak white and corresponds to a display luminance level of 10 000 cd/m2 . Reference white is 203 cd/m2 .
+"428" "428-1"
0…1 SDR SMPTE ST 428-1 Linear 48 / 52.37 corresponds to a reference display luminance level of 48 cd/m2 .
+"hlg"
0…1 OETF HDR ITU-R BT.2100 HLG (hybrid log-gamma), ARIB STD-B67 Reference white is at 75 % of the coding range, giving 203 cd/m2 when peak white is set to 1000 cd/m2 . Set lw to specify explicitely the peak white luminance.
+"1886"
0…1 EOTF SDR ITU-R BT.1886. Intended to mimicing a CRT display curve. Peak white is 100 cd/m2 .
+"1886a"
0…1 EOTF SDR ITU-R BT.1886, alternative approximation
+"filmstream"
0…1 OETF Thomson FilmStream Linear 1.0 is the sensor clipping level, corresponding to 3840 on a linear 12-bit scale.
+"slog"
−0.006…10 OETF Sony S-Log Linear 1.0 is the reference white, peak white is at 10.0.
+"slog2"
−0.0085…14.13 OETF Sony S-Log 2 Linear 1.0 is the reference white, peak white is at 14.13.
+"slog3"
0…38.421 OETF Sony S-Log3.
+"logc2"
Unspecified OETF Arri Log C Alexa 2.x, linear scene exposure Peak white is 57.45 linear. The negative part of the range allows coding sensor noise. logceis and logceid set the Exposure Index (EI).
+"logc3"
Unspecified OETF Arri Log C Alexa 3.x, linear scene exposure Peak white is 55.08 linear. The negative part of the range allows coding sensor noise. logceis and logceid set the Exposure Index (EI).
+"canonlog"
0…8.00903 OETF Canon-Log Peak white is 8.00903 in linear scale and 1.08676 in compressed scale.
+"adobergb"
0…1 SDR Adobe RGB (1998 and Wide Gamut). Reference white is 160 cd/m2 .
+"romm"
0…1 SDR ProPhoto, ROMM. Reference white is 142 cd/m2 .
+"acescc"
−65504…65504 ACEScc. Values are actually bounded to the ACES 16-bit float range.
+"erimm"
0…316.2 OETF ERIMM
+"vlog"
0…1 Panasonic V-Log
cont
@@ -1725,8 +1776,10 @@ Parameters
gcor
Optional gamma correction to apply to the linear signal.
-This is a power value, 1 is neutral.
-Comes after contrast adjustment.
+This is a power value, 1 is a bypass.
+Comes after contrast adjustment.
+In display-referred mode, the invariant (neutral) point of the correction
+is the reference white.
bits
Sets the output bitdepth.
@@ -1759,6 +1812,8 @@
Parameters
10: limit to AVX2.
blacklvl
+This parameter is deprecated, please use lb and lw
+instead.
Black level value (linear range) for the electro-optical transfer function.
It shifts and stretches the transfer curve in order to make the black value in
gamma-encoded range match the specified level in linear range.
@@ -1766,24 +1821,78 @@
Parameters
combined with a slight contrast reduction not to alter the white.
The parameter should be ≥ 0.
There is no specific unit, it’s just a value from the target linear range,
-generally in 0–1.
-
-planes
-This array decribes how each plane should be processed.
-It’s similar to the y , u and v parameters
-in Masktools 2.
+generally in 0–1.
+Internally, it is converted to lb , so it uses lw as
+reference.
+If the latter is not specified by the user, it uses 100 cd/m2 , even
+if the transfer function is HDR.
+Ignored when the linear light is scene-referred.
+
+sceneref
+Indicates that the linear light step is scene-referred.
+When set to False
, the linear light is display-referred.
+This helps removing ambiguity when BT.2100 transfer functions (PQ and HLG)
+are used.
+Direct or inverse OOTFs are switched as required.
+
+lb
+Indicate the black level for the source transfer function, in
+cd/m2 .
+This parameters is taken into account when display-referred transfer
+functions are used.
+The EOTF (source) function uses lb as brightness setting.
+
+lw, lws, lwd
+Indicate the peak white levels in cd/m2 .
+lws is for the source transfer function, and lwd
+for the destination one.
+These parameters are taken into account when display-referred transfer
+functions are used.
+Minimum lw value is 0.1 cd/m2 .
+System gamma may be changed according to the lw parameter.
+Unless specified, HDR functions use a peak white of 1000 cd/m2 .
+Similarly, SDR and other functions use 100 cd/m2 by default.
+It is strongly recommended to manually set lw when using the HLG
+curve, otherwise the automatic LW estimation could result in
+something unexpected.
+
+ambient
+Default ambient luminance for display-referred transfer functions, in
+cd/m2 .
+This adapts automatically the OOTF system gamma.
+
+match
+Selects the luminance matching between the transfer functions.
-−65535 to +0.5 All the pixels of the plane will be set to −x (the opposite of the specified value). The range depends on the output data type. Remember, in floating-point YUV, the chroma planes range from −0.5 to +0.5.
-1 The plane will not be processed. This means that the content of the output plane is pure garbage.
-2 The plane of the input clip will be copied and possibly cropped. Range (full or TV) conversions are ignored.
-3 The plane will be processed.
+0 No specific match. Linear values are not scaled.
+1 Matches the reference white. 1 is the reference white level.
+2 Tries to match the display luminance.
-Avisynth+ The alpha channel processing is
-disabled and replaced with a straight copy from the source.
-The parameter value can also be a string, like in the
-fmtc_bitdepth
function: "all"
, ""
, or a
-combination of "0"
, "1"
, "2"
,
-"r"
, "g"
and "b"
.
+
Display luminance matching is available only for display-referred
+transfers.
+It is automatically disabled when sceneref is True
.
+Linear scale in luminance match is at least 100 cd/m2 , but the
+exact value is unspecified and depends on the involved transfer functions.
+
+gy
+Overrides default gamma processing.
+When set to False
, gamma is applied on each component separately.
+Processing is fast, but possibly distorts the colors.
+When set to True
, gamma is applied to the luminance only, leaving
+the colors undistorted.
+When the parameter is unspecified, the gamma processing is selected depending
+on the transfer functions.
+More specifically, the HLG OOTF curve requires applying the gamma on luminance.
+Other curves use standard gamma processing.
+
+debug
+When this parameter is not null, a property is attached to the processed
+frames.
+It contains a text string with debugging information about light levels and
+system gamma used at each stage of the processing.
+The name of the property is "FmtcTransferDbg"
followed with the
+parameter value, so multiple calls to transfer
can be checked
+simultaneously.
@@ -1833,6 +1942,10 @@ V) Changelog
resample
: added top-left chroma location.
resample
/Avisynth+: interlacing detection now uses the global stream information when the frame properties are not available.
resample
/Avisynth+: fixed the planes parameter.
+transfer
: Linear light can now be display- or scene-referred. Added sceneref , lw , lws , lwd , lb , ambiant and deprecated blacklvl .
+transfer
: By default, automatically matches the reference white levels for source and destination transfer curves. This may cause some backward incompatibilities. match parameter added.
+transfer
: Removed the planes parameter introduced in r24 because planes are no longer independent of each other.
+transfer
: debug provides information about the transfer operation and levels as a frame property.
transfer
: BT.470M characteristic is now a pure power curve, instead of a copy of the sRGB curve.
transfer
/Avisynth+: fixed flt + bits combination that was ignored. Thanks to DTL for the report.
transfer
/Avisynth+: fixed case sensitivity to transs and transd .
diff --git a/doc/transfer-diag-display.svg b/doc/transfer-diag-display.svg
new file mode 100644
index 0000000..701111e
--- /dev/null
+++ b/doc/transfer-diag-display.svg
@@ -0,0 +1,676 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/transfer-diag-scene.svg b/doc/transfer-diag-scene.svg
new file mode 100644
index 0000000..94b997c
--- /dev/null
+++ b/doc/transfer-diag-scene.svg
@@ -0,0 +1,639 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/fmtc/Transfer.cpp b/src/fmtc/Transfer.cpp
index a164b1a..71d8fba 100644
--- a/src/fmtc/Transfer.cpp
+++ b/src/fmtc/Transfer.cpp
@@ -29,6 +29,8 @@ To Public License, Version 2, as published by Sam Hocevar. See
#include "fmtc/CpuOpt.h"
#include "fmtc/Transfer.h"
#include "fmtc/fnc.h"
+#include "fmtcl/TransModel.h"
+#include "fmtcl/TransOpLogC.h"
#include "fmtcl/TransUtil.h"
#include "fstb/fnc.h"
#include "vsutl/fnc.h"
@@ -54,28 +56,12 @@ Transfer::Transfer (const ::VSMap &in, ::VSMap &out, void * /*user_data_ptr*/, :
, _clip_src_sptr (vsapi.propGetNode (&in, "clip", 0, 0), vsapi)
, _vi_in (*_vsapi.getVideoInfo (_clip_src_sptr.get ()))
, _vi_out (_vi_in)
-, _sse2_flag (false)
-, _avx2_flag (false)
, _transs (get_arg_str (in, out, "transs", ""))
, _transd (get_arg_str (in, out, "transd", ""))
, _contrast (get_arg_flt (in, out, "cont", 1))
, _gcor (get_arg_flt (in, out, "gcor", 1))
-, _lvl_black (get_arg_flt (in, out, "blacklvl", 0))
, _full_range_src_flag (get_arg_int (in, out, "fulls", 1) != 0)
, _full_range_dst_flag (get_arg_int (in, out, "fulld", 1) != 0)
-, _curve_s (fmtcl::TransCurve_UNDEF)
-, _curve_d (fmtcl::TransCurve_UNDEF)
-, _logc_ei_s (fmtcl::TransOpLogC::ExpIdx_800)
-, _logc_ei_d (fmtcl::TransOpLogC::ExpIdx_800)
-#if defined (_MSC_VER)
-#pragma warning (push)
-#pragma warning (disable : 4355)
-#endif // 'this': used in base member initializer list
-, _plane_processor (vsapi, *this, "transfer", true)
-#if defined (_MSC_VER)
-#pragma warning (pop)
-#endif
-, _model_uptr ()
{
fstb::conv_to_lower_case (_transs);
fstb::conv_to_lower_case (_transd);
@@ -155,10 +141,88 @@ Transfer::Transfer (const ::VSMap &in, ::VSMap &out, void * /*user_data_ptr*/, :
{
throw_inval_arg ("invalid gcor value.");
}
- if (_lvl_black < 0)
+
+ const bool scene_flag = (get_arg_int (in, out, "sceneref", 0) != 0);
+
+ bool lw_def_flag = false;
+ bool lws_def_flag = false;
+ double lw = get_arg_flt (in, out, "lw" , 0 , 0, &lw_def_flag);
+ double lws = get_arg_flt (in, out, "lws", lw, 0, &lws_def_flag);
+ double lwd = get_arg_flt (in, out, "lwd", lw);
+ if (lws > 0 && lws < fmtcl::TransModel::_min_luminance)
+ {
+ throw_inval_arg ("lws must be >= 0.1.");
+ }
+ if (lwd > 0 && lwd < fmtcl::TransModel::_min_luminance)
+ {
+ throw_inval_arg ("lwd must be >= 0.1.");
+ }
+
+ // Lb Lw mess
+ bool bl_def_flag = false; // lvl_black
+ bool lb_def_flag = false;
+ double lvl_black =
+ get_arg_flt (in, out, "blacklvl", 0, 0, &bl_def_flag);
+ double lb = get_arg_flt (in, out, "lb", 0, 0, &lb_def_flag);
+ if (lvl_black < 0)
{
throw_inval_arg ("invalid blacklvl value.");
}
+ if (lw > 0 && lb >= lws)
+ {
+ throw_inval_arg ("invalid lb/lw combination.");
+ }
+ if ( bl_def_flag
+ && lb_def_flag
+ && (lws_def_flag || lw_def_flag))
+ {
+ throw_inval_arg (
+ "you can define at most two of these parameters: "
+ "blacklvl, lb and (lw or lws)."
+ );
+ }
+ else if (! lb_def_flag)
+ {
+ if (lw_def_flag || lws_def_flag)
+ {
+ lb = lvl_black * lws;
+ }
+ else
+ {
+ lb = lvl_black * 100;
+ }
+ }
+
+ const double lamb = get_arg_flt (in, out, "ambient", 5);
+ if (lamb < fmtcl::TransModel::_min_luminance)
+ {
+ throw_inval_arg ("ambient luminance must be >= 0.1.");
+ }
+
+ const auto match = fmtcl::LumMatch (
+ get_arg_int (in, out, "match", fmtcl::LumMatch_REF_WHITE)
+ );
+ if (match < 0 || match >= fmtcl::LumMatch_NBR_ELT)
+ {
+ throw_inval_arg ("invalid match value.");
+ }
+
+ const int gy_val = get_arg_int (in, out, "gy", -1);
+ const auto gy_proc =
+ (gy_val == 0) ? fmtcl::TransModel::GyProc::OFF
+ : (gy_val == 1) ? fmtcl::TransModel::GyProc::ON
+ : fmtcl::TransModel::GyProc::UNDEF;
+
+ const int dbg = get_arg_int (in, out, "debug", 0);
+ if (dbg < 0)
+ {
+ throw_inval_arg ("debug must be >= 0.");
+ }
+ _dbg_flag = (dbg > 0);
+ if (_dbg_flag)
+ {
+ _dbg_name = fmtcl::TransUtil::gen_degub_prop_name (dbg);
+ }
// Finally...
const fmtcl::PicFmt src_fmt =
@@ -168,7 +232,7 @@ Transfer::Transfer (const ::VSMap &in, ::VSMap &out, void * /*user_data_ptr*/, :
_model_uptr = std::make_unique (
dst_fmt, _curve_d, _logc_ei_d,
src_fmt, _curve_s, _logc_ei_s,
- _contrast, _gcor, _lvl_black,
+ _contrast, _gcor, lb, lws, lwd, lamb, scene_flag, match, gy_proc,
_sse2_flag, _avx2_flag
);
}
@@ -177,16 +241,17 @@ Transfer::Transfer (const ::VSMap &in, ::VSMap &out, void * /*user_data_ptr*/, :
void Transfer::init_filter (::VSMap &in, ::VSMap &out, ::VSNode &node, ::VSCore &core)
{
- fstb::unused (core);
+ fstb::unused (in, out, core);
_vsapi.setVideoInfo (&_vi_out, 1, &node);
- _plane_processor.set_filter (in, out, _vi_out);
}
const ::VSFrameRef * Transfer::get_frame (int n, int activation_reason, void * &frame_data_ptr, ::VSFrameContext &frame_ctx, ::VSCore &core)
{
+ fstb::unused (frame_data_ptr);
+
assert (n >= 0);
::VSFrameRef * dst_ptr = 0;
@@ -209,30 +274,29 @@ const ::VSFrameRef * Transfer::get_frame (int n, int activation_reason, void * &
const int h = _vsapi.getFrameHeight (&src, 0);
dst_ptr = _vsapi.newVideoFrame (_vi_out.format, w, h, &src, &core);
- const int ret_val = _plane_processor.process_frame (
- *dst_ptr, n, frame_data_ptr, frame_ctx, core, _clip_src_sptr
- );
+ const auto pa { build_mat_proc (_vsapi, *dst_ptr, src) };
+ _model_uptr->process_frame (pa);
- if (ret_val == 0)
- {
- // Output frame properties
- ::VSMap & dst_prop = *(_vsapi.getFramePropsRW (dst_ptr));
+ // Output frame properties
+ ::VSMap & dst_prop = *(_vsapi.getFramePropsRW (dst_ptr));
- const int cr_val = (_full_range_dst_flag) ? 0 : 1;
- _vsapi.propSetInt (&dst_prop, "_ColorRange", cr_val, ::paReplace);
+ const int cr_val = (_full_range_dst_flag) ? 0 : 1;
+ _vsapi.propSetInt (&dst_prop, "_ColorRange", cr_val, ::paReplace);
- int transfer = fmtcl::TransCurve_UNSPECIFIED;
- if (_curve_d >= 0 && _curve_d <= fmtcl::TransCurve_ISO_RANGE_LAST)
- {
- transfer = _curve_d;
- }
- _vsapi.propSetInt (&dst_prop, "_Transfer", transfer, ::paReplace);
+ int transfer = fmtcl::TransCurve_UNSPECIFIED;
+ if (_curve_d >= 0 && _curve_d <= fmtcl::TransCurve_ISO_RANGE_LAST)
+ {
+ transfer = _curve_d;
}
+ _vsapi.propSetInt (&dst_prop, "_Transfer", transfer, ::paReplace);
- if (ret_val != 0)
+ if (_dbg_flag)
{
- _vsapi.freeFrame (dst_ptr);
- dst_ptr = 0;
+ const std::string & txt = _model_uptr->get_debug_text ();
+ _vsapi.propSetData (
+ &dst_prop, _dbg_name.c_str (),
+ txt.c_str (), int (txt.length () + 1), ::paReplace
+ );
}
}
@@ -245,44 +309,6 @@ const ::VSFrameRef * Transfer::get_frame (int n, int activation_reason, void * &
-int Transfer::do_process_plane (::VSFrameRef &dst, int n, int plane_index, void *frame_data_ptr, ::VSFrameContext &frame_ctx, ::VSCore &core, const vsutl::NodeRefSPtr &src_node1_sptr, const vsutl::NodeRefSPtr &src_node2_sptr, const vsutl::NodeRefSPtr &src_node3_sptr)
-{
- fstb::unused (frame_data_ptr, core, src_node2_sptr, src_node3_sptr);
- assert (src_node1_sptr.get () != 0);
-
- int ret_val = 0;
-
- const vsutl::PlaneProcMode proc_mode =
- _plane_processor.get_mode (plane_index);
-
- if (proc_mode == vsutl::PlaneProcMode_PROCESS)
- {
- vsutl::FrameRefSPtr src_sptr (
- _vsapi.getFrameFilter (n, src_node1_sptr.get (), &frame_ctx),
- _vsapi
- );
- const ::VSFrameRef & src = *src_sptr;
-
- const int w = _vsapi.getFrameWidth (&src, plane_index);
- const int h = _vsapi.getFrameHeight (&src, plane_index);
-
- const uint8_t* data_src_ptr = _vsapi.getReadPtr (&src, plane_index);
- const int stride_src = _vsapi.getStride (&src, plane_index);
- uint8_t * data_dst_ptr = _vsapi.getWritePtr (&dst, plane_index);
- const int stride_dst = _vsapi.getStride (&dst, plane_index);
-
- _model_uptr->process_plane (
- { data_dst_ptr, stride_dst },
- { data_src_ptr, stride_src },
- w, h
- );
- }
-
- return (ret_val);
-}
-
-
-
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtc/Transfer.h b/src/fmtc/Transfer.h
index b416e58..2c86734 100644
--- a/src/fmtc/Transfer.h
+++ b/src/fmtc/Transfer.h
@@ -35,8 +35,6 @@ To Public License, Version 2, as published by Sam Hocevar. See
#include "fmtcl/TransOpLogC.h"
#include "vsutl/FilterBase.h"
#include "vsutl/NodeRefSPtr.h"
-#include "vsutl/PlaneProcCbInterface.h"
-#include "vsutl/PlaneProcessor.h"
#include "vswrap.h"
#include
@@ -50,7 +48,6 @@ namespace fmtc
class Transfer
: public vsutl::FilterBase
-, public vsutl::PlaneProcCbInterface
{
/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
@@ -71,9 +68,6 @@ class Transfer
protected:
- // vsutl::PlaneProcCbInterface
- virtual int do_process_plane (::VSFrameRef &dst, int n, int plane_index, void *frame_data_ptr, ::VSFrameContext &frame_ctx, ::VSCore &core, const vsutl::NodeRefSPtr &src_node1_sptr, const vsutl::NodeRefSPtr &src_node2_sptr, const vsutl::NodeRefSPtr &src_node3_sptr);
-
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
@@ -91,30 +85,29 @@ class Transfer
_vi_in; // Input. Must be declared after _clip_src_sptr because of initialisation order.
::VSVideoInfo _vi_out; // Output. Must be declared after _vi_in.
- bool _sse2_flag;
- bool _avx2_flag;
+ bool _sse2_flag = false;
+ bool _avx2_flag = false;
std::string _transs;
std::string _transd;
- double _contrast;
- double _gcor;
- double _lvl_black;
- bool _full_range_src_flag;
- bool _full_range_dst_flag;
+ double _contrast = 1;
+ double _gcor = 1;
+ bool _full_range_src_flag = true;
+ bool _full_range_dst_flag = true;
fmtcl::TransCurve
- _curve_s;
+ _curve_s = fmtcl::TransCurve_UNDEF;
fmtcl::TransCurve
- _curve_d;
+ _curve_d = fmtcl::TransCurve_UNDEF;
fmtcl::TransOpLogC::ExpIdx // Exposure Index for the Arri Log C curves
- _logc_ei_s;
+ _logc_ei_s = fmtcl::TransOpLogC::ExpIdx_800;
fmtcl::TransOpLogC::ExpIdx
- _logc_ei_d;
-
- vsutl::PlaneProcessor
- _plane_processor;
+ _logc_ei_d = fmtcl::TransOpLogC::ExpIdx_800;
std::unique_ptr
_model_uptr;
+ bool _dbg_flag = false;
+ std::string _dbg_name; // Property name
+
/*\\\ FORBIDDEN MEMBER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcavs/Transfer.cpp b/src/fmtcavs/Transfer.cpp
index 8fb2a63..af880ad 100644
--- a/src/fmtcavs/Transfer.cpp
+++ b/src/fmtcavs/Transfer.cpp
@@ -29,6 +29,7 @@ To Public License, Version 2, as published by Sam Hocevar. See
#include "fmtcavs/function_names.h"
#include "fmtcavs/FmtAvs.h"
#include "fmtcavs/Transfer.h"
+#include "fmtcl/TransModel.h"
#include "fmtcl/TransOpLogC.h"
#include "fmtcl/TransUtil.h"
#include "fstb/fnc.h"
@@ -88,42 +89,40 @@ Transfer::Transfer (::IScriptEnvironment &env, const ::AVSValue &args)
}
// Output format is validated.
- fmt_dst.conv_to_vi (vi);
-
- // Configures the plane processor
- _plane_proc_uptr =
- std::make_unique (vi, *this, false);
- _plane_proc_uptr->set_dst_clip_info (avsutl::PlaneProcessor::ClipType_NORMAL);
- _plane_proc_uptr->set_clip_info (
- avsutl::PlaneProcessor::ClipIdx_SRC1,
- _clip_src_sptr,
- avsutl::PlaneProcessor::ClipType_NORMAL
- );
- set_masktools_planes_param (
- *_plane_proc_uptr, env, args [Param_PLANES], fmtcavs_TRANSFER ", planes"
- );
-
- // Alpha plane is copied instead of processed
- const int nbr_proc_planes = fmt_dst.get_nbr_comp_non_alpha ();
- if (nbr_proc_planes < vi.NumComponents ())
+ if (fmt_dst.conv_to_vi (vi) != 0)
{
- const double mode = _plane_proc_uptr->get_proc_mode (nbr_proc_planes);
- if (mode == double (avsutl::PlaneProcMode_PROCESS))
- {
- _plane_proc_uptr->set_proc_mode (
- nbr_proc_planes, avsutl::PlaneProcMode_COPY1
- );
- }
+ env.ThrowError (fmtcavs_TRANSFER ": illegal output colorspace.");
}
+ // Alpha plane processing, if any
+ _proc_alpha_uptr = std::make_unique (
+ fmt_dst, fmt_src, vi.width, vi.height, cpu_opt
+ );
+
// Other parameters
std::string transs = args [Param_TRANSS ].AsString ("");
std::string transd = args [Param_TRANSD ].AsString ("");
const double contrast = args [Param_CONT ].AsFloat (1);
const double gcor = args [Param_GCOR ].AsFloat (1);
- const double lvl_black = args [Param_BLACKLVL].AsFloat (0);
+ double lvl_black = args [Param_BLACKLVL].AsFloat (0);
+ double lb = args [Param_LB ].AsFloat (0);
+ const double lw = args [Param_LW ].AsFloat (0);
+ const double lws = args [Param_LWS ].AsDblDef (lw);
+ const double lwd = args [Param_LWD ].AsDblDef (lw);
+ const double lamb = args [Param_AMBIENT ].AsFloat (5);
+ const bool scene_flag = args [Param_SCENEREF].AsBool (false);
const int logc_ei_raw_s = args [Param_LOGCEIS].AsInt (800);
const int logc_ei_raw_d = args [Param_LOGCEID].AsInt (800);
+ const auto match = fmtcl::LumMatch (
+ args [Param_MATCH].AsInt (fmtcl::LumMatch_REF_WHITE)
+ );
+ const int dbg = args [Param_DEBUG ].AsInt (0);
+ const bool gydef_flag = args [Param_GY ].Defined ();
+ const bool gy_flag = args [Param_GY ].AsBool (false);
+ const auto gy_proc =
+ (! gydef_flag) ? fmtcl::TransModel::GyProc::UNDEF
+ : gy_flag ? fmtcl::TransModel::GyProc::ON
+ : fmtcl::TransModel::GyProc::OFF;
fstb::conv_to_lower_case (transs);
fstb::conv_to_lower_case (transd);
@@ -158,10 +157,66 @@ Transfer::Transfer (::IScriptEnvironment &env, const ::AVSValue &args)
{
env.ThrowError (fmtcavs_TRANSFER ": invalid gcor value.");
}
+
+ if (lws > 0 && lws < fmtcl::TransModel::_min_luminance)
+ {
+ env.ThrowError (fmtcavs_TRANSFER ": lws must be 0 or >= 0.1.");
+ }
+ if (lwd > 0 && lwd < fmtcl::TransModel::_min_luminance)
+ {
+ env.ThrowError (fmtcavs_TRANSFER ": lwd must be 0 or >= 0.1.");
+ }
+
+ // Lb Lw mess
if (lvl_black < 0)
{
env.ThrowError (fmtcavs_TRANSFER ": invalid blacklvl value.");
}
+ if (lws > 0 && lb >= lws)
+ {
+ env.ThrowError (fmtcavs_TRANSFER ": invalid lb/lws combination.");
+ }
+ if ( args [Param_BLACKLVL].Defined ()
+ && args [Param_LB ].Defined ()
+ && (args [Param_LWS].Defined () || args [Param_LW].Defined ()))
+ {
+ env.ThrowError (fmtcavs_TRANSFER
+ ": you can define at most two of these parameters:\n"
+ "blacklvl, lb and (lw or lws)."
+ );
+ }
+ else if (! args [Param_LB].Defined ())
+ {
+ if (args [Param_LW].Defined () || args [Param_LWS].Defined ())
+ {
+ lb = lvl_black * lws;
+ }
+ else
+ {
+ lb = lvl_black * 100;
+ }
+ }
+
+ if (lamb < fmtcl::TransModel::_min_luminance)
+ {
+ env.ThrowError (fmtcavs_TRANSFER ": ambient luminance must be > 0.1.");
+ }
+
+ if (match < 0 || match >= fmtcl::LumMatch_NBR_ELT)
+ {
+ env.ThrowError (fmtcavs_TRANSFER ": invalid match value.");
+ }
+
+ if (dbg < 0)
+ {
+ env.ThrowError (fmtcavs_TRANSFER ": debug must be >= 0.");
+ }
+ _dbg_flag = (dbg > 0);
+ if (_dbg_flag)
+ {
+ _dbg_name = fmtcl::TransUtil::gen_degub_prop_name (dbg);
+ }
+
// Finally...
const fmtcl::PicFmt src_picfmt =
@@ -171,7 +226,7 @@ Transfer::Transfer (::IScriptEnvironment &env, const ::AVSValue &args)
_model_uptr = std::make_unique (
dst_picfmt, _curve_d, logc_ei_d,
src_picfmt, _curve_s, logc_ei_s,
- contrast, gcor, lvl_black,
+ contrast, gcor, lb, lws, lwd, lamb, scene_flag, match, gy_proc,
sse2_flag, avx2_flag
);
}
@@ -183,7 +238,11 @@ ::PVideoFrame __stdcall Transfer::GetFrame (int n, ::IScriptEnvironment *env_ptr
::PVideoFrame src_sptr = _clip_src_sptr->GetFrame (n, env_ptr);
::PVideoFrame dst_sptr = build_new_frame (*env_ptr, vi, &src_sptr);
- _plane_proc_uptr->process_frame (dst_sptr, n, *env_ptr, nullptr);
+ const auto pa { build_mat_proc (vi, dst_sptr, _vi_src, src_sptr) };
+ _model_uptr->process_frame (pa);
+
+ // Alpha plane now
+ _proc_alpha_uptr->process_plane (dst_sptr, src_sptr);
// Frame properties
if (supports_props ())
@@ -203,6 +262,15 @@ ::PVideoFrame __stdcall Transfer::GetFrame (int n, ::IScriptEnvironment *env_ptr
env_ptr->propSetInt (
props_ptr, "_Transfer", transfer, ::PROPAPPENDMODE_REPLACE
);
+
+ if (_dbg_flag)
+ {
+ const std::string & txt = _model_uptr->get_debug_text ();
+ env_ptr->propSetData (
+ props_ptr, _dbg_name.c_str (),
+ txt.c_str (), int (txt.length () + 1), ::PROPAPPENDMODE_REPLACE
+ );
+ }
}
return dst_sptr;
@@ -214,32 +282,6 @@ ::PVideoFrame __stdcall Transfer::GetFrame (int n, ::IScriptEnvironment *env_ptr
-void Transfer::do_process_plane (::PVideoFrame &dst_sptr, int n, ::IScriptEnvironment &env, int plane_index, int plane_id, void *ctx_ptr)
-{
- fstb::unused (plane_index, ctx_ptr);
- assert (plane_index < _max_nbr_planes_proc);
-
- ::PVideoFrame src_sptr = _clip_src_sptr->GetFrame (n, &env);
-
- uint8_t * data_dst_ptr = dst_sptr->GetWritePtr (plane_id);
- const int stride_dst = dst_sptr->GetPitch (plane_id);
- const uint8_t* data_src_ptr = src_sptr->GetReadPtr (plane_id);
- const int stride_src = src_sptr->GetPitch (plane_id);
- const int w = _plane_proc_uptr->get_width (
- dst_sptr, plane_id, avsutl::PlaneProcessor::ClipIdx_DST
- );
- const int h = _plane_proc_uptr->get_height (dst_sptr, plane_id);
-
- // Standard channel
- _model_uptr->process_plane (
- { data_dst_ptr, stride_dst },
- { data_src_ptr, stride_src },
- w, h
- );
-}
-
-
-
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcavs/Transfer.h b/src/fmtcavs/Transfer.h
index 69c42cd..3833c6c 100644
--- a/src/fmtcavs/Transfer.h
+++ b/src/fmtcavs/Transfer.h
@@ -23,9 +23,8 @@ To Public License, Version 2, as published by Sam Hocevar. See
/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
-#include "avsutl/PlaneProcCbInterface.h"
-#include "avsutl/PlaneProcessor.h"
#include "avsutl/VideoFilterBase.h"
+#include "fmtcavs/ProcAlpha.h"
#include "fmtcl/TransCurve.h"
#include "fmtcl/TransModel.h"
@@ -40,7 +39,6 @@ namespace fmtcavs
class Transfer
: public avsutl::VideoFilterBase
-, public avsutl::PlaneProcCbInterface
{
/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
@@ -64,7 +62,15 @@ class Transfer
Param_LOGCEID, // 10
Param_CPUOPT,
Param_BLACKLVL,
- Param_PLANES,
+ Param_SCENEREF,
+ Param_LB,
+ Param_LW,
+ Param_LWS,
+ Param_LWD,
+ Param_AMBIENT,
+ Param_MATCH,
+ Param_GY, // 20
+ Param_DEBUG,
Param_NBR_ELT,
};
@@ -82,25 +88,22 @@ class Transfer
protected:
- // PlaneProcCbInterface
- void do_process_plane (::PVideoFrame &dst_sptr, int n, ::IScriptEnvironment &env, int plane_index, int plane_id, void *ctx_ptr) override;
-
- static constexpr int _max_nbr_planes_proc = 3;
-
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
private:
+ static constexpr int _max_nbr_planes_proc = 3;
+
FmtAvs get_output_colorspace (::IScriptEnvironment &env, const ::AVSValue &args, const FmtAvs &fmt_src);
::PClip _clip_src_sptr;
const ::VideoInfo
_vi_src;
- std::unique_ptr
- _plane_proc_uptr;
+ std::unique_ptr
+ _proc_alpha_uptr;
std::unique_ptr
_model_uptr;
@@ -112,6 +115,9 @@ class Transfer
fmtcl::TransCurve
_curve_d = fmtcl::TransCurve_INVALID;
+ bool _dbg_flag = false;
+ std::string _dbg_name; // Property name
+
/*\\\ FORBIDDEN MEMBER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/GammaY.cpp b/src/fmtcl/GammaY.cpp
index 014e030..c325874 100644
--- a/src/fmtcl/GammaY.cpp
+++ b/src/fmtcl/GammaY.cpp
@@ -214,16 +214,16 @@ GammaY::Op::Op (double gamma, double alpha)
-double GammaY::Op::operator () (double x) const
+double GammaY::Op::do_convert (double x) const
{
return (x == 0) ? 0 : _alpha * pow (fabs (x), _gamma - 1);
}
-double GammaY::Op::get_max () const
+TransOpInterface::LinInfo GammaY::Op::do_get_info () const
{
- return _alpha;
+ return { Type::UNDEF, Range::UNDEF, _alpha, 0 };
}
diff --git a/src/fmtcl/GammaY.h b/src/fmtcl/GammaY.h
index 4eca119..e9f158b 100644
--- a/src/fmtcl/GammaY.h
+++ b/src/fmtcl/GammaY.h
@@ -93,8 +93,9 @@ class GammaY
{
public:
explicit Op (double gamma, double alpha);
- double operator () (double x) const override;
- double get_max () const override;
+ protected:
+ double do_convert (double x) const override;
+ LinInfo do_get_info () const override;
private:
double _gamma = 1;
double _alpha = 1;
diff --git a/src/fmtcl/LumMatch.h b/src/fmtcl/LumMatch.h
new file mode 100644
index 0000000..0808846
--- /dev/null
+++ b/src/fmtcl/LumMatch.h
@@ -0,0 +1,60 @@
+/*****************************************************************************
+
+ LumMatch.h
+ Author: Laurent de Soras, 2021
+
+--- Legal stuff ---
+
+This program is free software. It comes without any warranty, to
+the extent permitted by applicable law. You can redistribute it
+and/or modify it under the terms of the Do What The Fuck You Want
+To Public License, Version 2, as published by Sam Hocevar. See
+http://www.wtfpl.net/ for more details.
+
+*Tab=3***********************************************************************/
+
+
+
+#pragma once
+#if ! defined (fmtcl_LumMatch_HEADER_INCLUDED)
+#define fmtcl_LumMatch_HEADER_INCLUDED
+
+
+
+/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+
+
+
+namespace fmtcl
+{
+
+
+
+enum LumMatch
+{
+
+ LumMatch_UNDEF = -1,
+
+ LumMatch_NONE = 0,
+ LumMatch_REF_WHITE,
+ LumMatch_LUMINANCE,
+
+ LumMatch_NBR_ELT
+
+}; // enum LumMatch
+
+
+
+} // namespace fmtcl
+
+
+
+//#include "fmtcl/LumMatch.hpp"
+
+
+
+#endif // fmtcl_LumMatch_HEADER_INCLUDED
+
+
+
+/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransModel.cpp b/src/fmtcl/TransModel.cpp
index 6f0d6e2..af6c46c 100644
--- a/src/fmtcl/TransModel.cpp
+++ b/src/fmtcl/TransModel.cpp
@@ -24,10 +24,13 @@ To Public License, Version 2, as published by Sam Hocevar. See
/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+#include "fmtcl/TransCst.h"
#include "fmtcl/TransModel.h"
#include "fmtcl/TransOpAffine.h"
+#include "fmtcl/TransOpBypass.h"
#include "fmtcl/TransOpCompose.h"
#include "fmtcl/TransOpContrast.h"
+#include "fmtcl/TransOpLinPow.h"
#include "fmtcl/TransOpLogC.h"
#include "fmtcl/TransOpPow.h"
#include "fstb/fnc.h"
@@ -46,7 +49,12 @@ namespace fmtcl
-TransModel::TransModel (PicFmt dst_fmt, TransCurve curve_d, TransOpLogC::ExpIdx logc_ei_d, PicFmt src_fmt, TransCurve curve_s, TransOpLogC::ExpIdx logc_ei_s, double contrast, double gcor, double lvl_black, bool sse2_flag, bool avx2_flag)
+constexpr int TransModel::_nbr_planes;
+constexpr double TransModel::_min_luminance;
+
+
+
+TransModel::TransModel (PicFmt dst_fmt, TransCurve curve_d, TransOpLogC::ExpIdx logc_ei_d, PicFmt src_fmt, TransCurve curve_s, TransOpLogC::ExpIdx logc_ei_s, double contrast, double gcor, double lb, double lws, double lwd, double lamb, bool scene_flag, LumMatch match, GyProc gy_proc, bool sse2_flag, bool avx2_flag)
{
assert (dst_fmt.is_valid ());
assert (TransCurve_is_valid (curve_d));
@@ -58,11 +66,22 @@ TransModel::TransModel (PicFmt dst_fmt, TransCurve curve_d, TransOpLogC::ExpIdx
assert (logc_ei_s < TransOpLogC::ExpIdx_NBR_ELT);
assert (contrast > 0);
assert (gcor > 0);
- assert (lvl_black >= 0);
+ assert (lb >= 0);
+ assert (lws > _min_luminance || lws <= 0);
+ assert (lwd > _min_luminance || lwd <= 0);
+ assert (lamb > _min_luminance);
+ assert (match >= 0);
+ assert (match < LumMatch_NBR_ELT);
+
+ char txt_0 [127+1];
+ // Creates the requested transfer curves
OpSPtr op_s = TransUtil::conv_curve_to_op (curve_s, true , logc_ei_s);
OpSPtr op_d = TransUtil::conv_curve_to_op (curve_d, false, logc_ei_d);
+ const auto s_info = op_s->get_info ();
+ const auto d_info = op_d->get_info ();
+
// Linear or log LUT?
bool loglut_flag = false;
if ( SplFmt_is_float (src_fmt._sf)
@@ -72,7 +91,8 @@ TransModel::TransModel (PicFmt dst_fmt, TransCurve curve_d, TransOpLogC::ExpIdx
// Actually we could just use the log LUT for all the curves...?
// 10 bits per stop + interpolation should be enough for all of them.
// What about the speed?
- if ( curve_d == TransCurve_470BG
+ if ( curve_d == TransCurve_470M
+ || curve_d == TransCurve_470BG
|| curve_d == TransCurve_LINEAR
|| curve_d == TransCurve_61966_2_4
|| curve_d == TransCurve_2084
@@ -101,80 +121,359 @@ TransModel::TransModel (PicFmt dst_fmt, TransCurve curve_d, TransOpLogC::ExpIdx
}
}
- // Black level
- const double lw = op_s->get_max ();
- if (lvl_black > 0 && lvl_black < lw)
+ _dbg_txt += "LUT = ";
+ _dbg_txt += ((loglut_flag) ? "log" : "lin");
+
+ // We try to estimate lw in cd/m^2 if not provided by the user
+ if (! scene_flag)
{
+ estimate_lw (lws, s_info);
+ estimate_lw (lwd, d_info);
+
+ fstb::snprintf4all (txt_0, sizeof (txt_0),
+ ", lw_s = %.1f cd/m2, lw_d = %.1f cd/m2", lws, lwd
+ );
+ _dbg_txt += txt_0;
+ }
+
+ bool gammay_flag = false; // HLG OOTF or OOTF^-1 (GammaY module)
+
+ // Power-gain paths:
+ // HLG OOTF : xsl = a_src * xi ^ g_src
+ // User correction: xld = (a_lin * xsl) ^ g_lin
+ // HLG OOTF^-1 : xo = (a_dst * xld) ^ g_dst
+ // Source and destination gamma and gain are applied in different orders,
+ // depending if they belong to the OOTF or OOTF^-1 (reversed steps)
+ // Direct : Fd = scale * E ^ gamma
+ // Inverse: E = (1/scale) ^ (1/gamma) * Fd ^ (1/gamma)
+ double g_src = 1;
+ double a_src = 1;
+ double g_lin = gcor;
+ double a_lin = contrast;
+ double g_dst = 1;
+ double a_dst = 1;
+
+ // Reference white for each function
+ double wref_s = s_info._wref;
+ double wref_d = d_info._wref;
+ assert (wref_s > 0);
+ assert (wref_d > 0);
+
+ // Scene-referred
+ if (scene_flag)
+ {
+ if (curve_s == TransCurve_2084)
+ {
+ const auto op_ootf_inv = build_pq_ootf_inv ();
+ op_s = compose (op_s, op_ootf_inv);
+ }
+ if (curve_d == TransCurve_2084)
+ {
+ const auto op_ootf = build_pq_ootf ();
+ op_d = compose (op_ootf, op_d);
+ }
+ }
+
+ // Display-referred
+ else
+ {
+ if (curve_s == TransCurve_HLG)
+ {
+ // OOTF
+ gammay_flag = true;
+ const double g_sys = TransUtil::compute_hlg_gamma (lws, lamb);
+ g_src = g_sys;
+ wref_s = pow (wref_s, g_sys);
+ fstb::snprintf4all (txt_0, sizeof (txt_0),
+ ", sys_gam_s = %.3f", g_sys
+ );
+ _dbg_txt += txt_0;
+ }
+
+ if (curve_d == TransCurve_HLG)
+ {
+ // OOTF^-1
+ gammay_flag = true;
+ const double g_sys = TransUtil::compute_hlg_gamma (lwd, lamb);
+ g_dst = 1 / g_sys;
+ wref_d = pow (wref_d, g_sys);
+ fstb::snprintf4all (txt_0, sizeof (txt_0),
+ ", sys_gam_d = %.3f", g_sys
+ );
+ _dbg_txt += txt_0;
+ }
+ }
+
+ fstb::snprintf4all (txt_0, sizeof (txt_0),
+ ", ref_white_s = %.4f, ref_white_d = %.4f", wref_s, wref_d
+ );
+ _dbg_txt += txt_0;
+
+ auto scale_cdm2_s = s_info._scale_cdm2;
+ auto scale_cdm2_d = d_info._scale_cdm2;
+ if (match == LumMatch_LUMINANCE)
+ {
+ // Luminance match is available only for display-referred signals
+ if (scene_flag)
+ {
+ match = LumMatch_NONE;
+ }
+
+ else
+ {
+ // If we don't know the scale, use the peak white value.
+ if (scale_cdm2_s <= 0)
+ {
+ scale_cdm2_s = lws;
+ }
+ if (scale_cdm2_d <= 0)
+ {
+ scale_cdm2_d = lwd;
+ }
+
+ // Scales must be specified for both transfer functions
+ if (scale_cdm2_s <= 0 || scale_cdm2_d <= 0)
+ {
+ match = LumMatch_NONE;
+ }
+ }
+ }
+
+ // Matching
+ if (match == LumMatch_REF_WHITE)
+ {
+ // Reference white matching
+ a_src = 1 / wref_s;
+ a_dst = wref_d;
+ }
+ else if (match == LumMatch_LUMINANCE)
+ {
+ assert (! scene_flag);
+ assert (scale_cdm2_s > 0);
+ assert (scale_cdm2_d > 0);
+
+ // Tries to avoid overflows when using integer, either as temporary for
+ // the GammaY stage (if any), either as linear input or output when one
+ // of the transfer functions is bypassed.
+ double norm = 100.0; // 1.0 -> 100 cd/m^2
+ norm = std::max (norm, scale_cdm2_s);
+ norm = std::max (norm, scale_cdm2_d);
+ norm = std::max (norm, lws);
+ norm = std::max (norm, lwd);
+ norm = std::max (norm, s_info._wpeak_cdm2);
+ norm = std::max (norm, d_info._wpeak_cdm2);
+
+ a_src = scale_cdm2_s / norm;
+ a_dst = norm / scale_cdm2_d;
+ }
+
+ _dbg_txt += ", match = ";
+ switch (match)
+ {
+ case LumMatch_NONE: _dbg_txt += "none"; break;
+ case LumMatch_REF_WHITE: _dbg_txt += "ref.white"; break;
+ case LumMatch_LUMINANCE: _dbg_txt += "luminance"; break;
+ default: _dbg_txt += "\?\?\?"; break;
+ }
+
+ fstb::snprintf4all (txt_0, sizeof (txt_0),
+ ", scale_s = %.4f, scale_d = %.4f", a_src, a_dst
+ );
+ _dbg_txt += txt_0;
+
+ // Global gamma and gain:
+ // xo = gain * pow (xi, gamma)
+ // With:
+ // xo = pow (a_dst * pow (a_lin * a_src * pow (xi, g_src), g_lin), g_dst)
+ // = pow (a_dst * pow (a_lin * a_src, g_lin), g_dst) * pow (xi, g_src * g_lin * g_dst)
+ const double gamma = g_src * g_lin * g_dst;
+ const double gain = pow (a_dst * pow (a_lin * a_src, g_lin), g_dst);
+
+ // Black level. Operates at the very beginning of the chain, on
+ // the EOTF. We assume here that the linear range is display-referred.
+ if (! scene_flag && lb > 0 && lb < lws)
+ {
+ // Scale to convert the lw and lb values in relative values (curve range)
+ auto lum_scale = s_info._scale_cdm2;
+ if (curve_s == TransCurve_HLG)
+ {
+ lum_scale = lws;
+ }
+ else if (lum_scale <= 0)
+ {
+ lum_scale = 100;
+ }
+ assert (lum_scale > 0);
+
/*
Black level (brightness) and contrast settings as defined
in ITU-R BT.1886, and called "b":
- L = a' * fi (V + b')
+ L = a' * EOTF (V + b')
With:
- fi = EOTF (gamma to linear)
L = Lb for V = 0
L = Lw for V = Vmax
For power functions, could be rewritten as:
- L = fi (a * V + b)
+ L = EOTF (a * V + b)
Substitution:
- Lb = fi ( b)
- Lw = fi (a * Vmax + b)
-
- Then, given:
- f = OETF (linear to gamma)
+ Lb = EOTF ( b)
+ Lw = EOTF (a * Vmax + b)
We get:
- f (Lb) = b
- f (Lw) = a * Vmax + b
+ EOTF^-1 (Lb) = b
+ EOTF^-1 (Lw) = a * Vmax + b
- b = f (Lb)
- a = (f (Lw) - f (Lb)) / Vmax
+ b = EOTF^-1 (Lb)
+ a = (EOTF^-1 (Lw) - EOTF^-1 (Lb)) / Vmax
*/
- auto oetf =
+ auto eotf_inv =
TransUtil::conv_curve_to_op (curve_s, false, logc_ei_s);
- const double lwg = (*oetf) (lw );
- const double lbg = (*oetf) (lvl_black);
+ const double lwg = (*eotf_inv) (lws / lum_scale);
+ const double lbg = (*eotf_inv) (lb / lum_scale);
const double vmax = lwg;
const double a = (lwg - lbg) / vmax;
const double b = lbg;
- auto op_a = std::make_shared (a, b);
- op_s = std::make_shared (op_a, op_s);
+ auto op_a = std::make_shared (a, b);
+ op_s = compose (op_a, op_s);
+ }
+
+ // Operator for the linear-light part when GammaY is not used
+ OpSPtr op_l;
+
+ // Do we really need a GammaY stage?
+ if (fstb::is_eq (gamma, 1.0))
+ {
+ gammay_flag = false;
+ }
+
+ // User setting override
+ gammay_flag =
+ ((gammay_flag && gy_proc != GyProc::OFF) || gy_proc == GyProc::ON);
+
+ if (! gammay_flag)
+ {
+ // Gamma correction
+ if (! fstb::is_eq (gamma, 1.0))
+ {
+ auto op_g =
+ std::make_shared (true, gamma, 1, 1e6);
+ op_l = compose (op_l, op_g);
+ }
+
+ // Contrast
+ if (! fstb::is_eq (gain, 1.0))
+ {
+ auto op_c =
+ std::make_shared (gain);
+ op_l = compose (op_l, op_c);
+ }
}
- // Gamma correction
- if (! fstb::is_eq (gcor, 1.0))
+ // Finds the processing scheme
+ if (! gammay_flag)
+ {
+ _proc_mode = Proc::DIRECT;
+ op_s = compose (compose (op_s, op_l), op_d);
+ op_d.reset ();
+ }
+ else
{
- auto op_g =
- std::make_shared (true, gcor, 1, 1e6);
- op_d = std::make_shared (op_g, op_d);
+ const bool s_flag =
+ (dynamic_cast (op_s.get ()) == nullptr);
+ const bool d_flag =
+ (dynamic_cast (op_d.get ()) == nullptr);
+
+ if (s_flag && d_flag)
+ {
+ _proc_mode = Proc::SGD;
+ }
+ else if (s_flag)
+ {
+ _proc_mode = Proc::SG;
+ op_d.reset ();
+ assert (dst_fmt._full_flag || dst_fmt._sf == SplFmt_FLOAT);
+ }
+ else if (d_flag)
+ {
+ _proc_mode = Proc::GD;
+ op_s.reset ();
+ assert (src_fmt._full_flag || src_fmt._sf == SplFmt_FLOAT);
+ }
+ else
+ {
+ // Case not handled
+ assert (false);
+ }
}
- // Contrast
- if (! fstb::is_eq (contrast, 1.0))
+ _dbg_txt += ", proc = ";
+ switch (_proc_mode)
{
- auto op_c =
- std::make_shared (contrast);
- op_d = std::make_shared (op_c, op_d);
+ case Proc::DIRECT: _dbg_txt += "direct"; break;
+ case Proc::SG: _dbg_txt += "src+gamma"; break;
+ case Proc::GD: _dbg_txt += "gamma+dst"; break;
+ case Proc::SGD: _dbg_txt += "src+gamma+dst"; break;
}
// LUTify
- auto op_f = std::make_shared (op_s, op_d);
+ const int bps_s = src_fmt._res >> 3;
+ const int bps_d = dst_fmt._res >> 3;
+ _max_len = _max_seg_len / std::max (bps_s, bps_d);
- _lut_uptr = std::make_unique (
- *op_f, loglut_flag,
- src_fmt._sf, src_fmt._res, src_fmt._full_flag,
- dst_fmt._sf, dst_fmt._res, dst_fmt._full_flag,
- sse2_flag, avx2_flag
- );
+ if (op_s.get () != nullptr)
+ {
+ const bool fulld_flag = (dst_fmt._full_flag || gammay_flag);
+ _lut_s_uptr = std::make_unique (
+ *op_s, loglut_flag,
+ src_fmt._sf, src_fmt._res, src_fmt._full_flag,
+ dst_fmt._sf, dst_fmt._res, fulld_flag,
+ sse2_flag, avx2_flag
+ );
+ src_fmt = dst_fmt;
+ }
+ if (gammay_flag)
+ {
+ _gamma_y_uptr = std::make_unique (
+ src_fmt._sf, src_fmt._res,
+ dst_fmt._sf, dst_fmt._res,
+ gamma, gain,
+ sse2_flag, avx2_flag
+ );
+ src_fmt = dst_fmt;
+ }
+ if (op_d.get () != nullptr)
+ {
+ const bool fulls_flag = (src_fmt._full_flag || gammay_flag);
+ _lut_d_uptr = std::make_unique (
+ *op_d, loglut_flag,
+ src_fmt._sf, src_fmt._res, fulls_flag,
+ dst_fmt._sf, dst_fmt._res, dst_fmt._full_flag,
+ sse2_flag, avx2_flag
+ );
+ src_fmt = dst_fmt;
+ }
+}
+
+
+
+const std::string & TransModel::get_debug_text () const noexcept
+{
+ return _dbg_txt;
}
-void TransModel::process_plane (const Plane <> &dst, const PlaneRO <> &src, int w, int h) const noexcept
+void TransModel::process_frame (const ProcComp3Arg &arg) const noexcept
{
- _lut_uptr->process_plane (dst, src, w, h);
+ switch (_proc_mode)
+ {
+ case Proc::DIRECT: process_frame_direct (arg); break;
+ case Proc::SG: process_frame_sg (arg); break;
+ case Proc::GD: process_frame_gd (arg); break;
+ case Proc::SGD: process_frame_sgd (arg); break;
+ }
}
@@ -187,6 +486,292 @@ void TransModel::process_plane (const Plane <> &dst, const PlaneRO <> &src, int
+void TransModel::process_frame_direct (const ProcComp3Arg &arg) const noexcept
+{
+ assert (_lut_s_uptr.get () != nullptr);
+
+ for (int p_idx = 0; p_idx < _nbr_planes; ++p_idx)
+ {
+ _lut_s_uptr->process_plane (
+ arg._dst [p_idx], arg._src [p_idx], arg._w, arg._h
+ );
+ }
+}
+
+
+
+void TransModel::process_frame_sg (const ProcComp3Arg &arg) const noexcept
+{
+ assert (_lut_s_uptr.get () != nullptr);
+ assert (_gamma_y_uptr.get () != nullptr);
+
+ alignas (64) SegArray tmp_seg;
+
+ auto line_src = arg._src;
+ auto line_dst = arg._dst;
+ const Frame <> tmp {
+ Plane <> { tmp_seg [0].data (), 0 },
+ Plane <> { tmp_seg [1].data (), 0 },
+ Plane <> { tmp_seg [2].data (), 0 }
+ };
+
+ for (int y = 0; y < arg._h; ++y)
+ {
+ auto src = line_src;
+ auto dst = line_dst;
+
+ for (int x = 0; x < arg._w; x += _max_len)
+ {
+ const int work_w = std::min (arg._w - x, _max_len);
+
+ for (int p_idx = 0; p_idx < _nbr_planes; ++p_idx)
+ {
+ _lut_s_uptr->process_plane (tmp [p_idx], src [p_idx], work_w, 1);
+ }
+
+ _gamma_y_uptr->process_plane (dst, tmp, work_w, 1);
+
+ src.step_pix (_max_seg_len);
+ dst.step_pix (_max_seg_len);
+ }
+
+ line_src.step_line ();
+ line_dst.step_line ();
+ }
+}
+
+
+
+void TransModel::process_frame_gd (const ProcComp3Arg &arg) const noexcept
+{
+ assert (_gamma_y_uptr.get () != nullptr);
+ assert (_lut_d_uptr.get () != nullptr);
+
+ alignas (64) SegArray tmp_seg;
+
+ auto line_src = arg._src;
+ auto line_dst = arg._dst;
+ const Frame <> tmp {
+ Plane <> { tmp_seg [0].data (), 0 },
+ Plane <> { tmp_seg [1].data (), 0 },
+ Plane <> { tmp_seg [2].data (), 0 }
+ };
+
+ for (int y = 0; y < arg._h; ++y)
+ {
+ auto src = line_src;
+ auto dst = line_dst;
+
+ for (int x = 0; x < arg._w; x += _max_len)
+ {
+ const int work_w = std::min (arg._w - x, _max_len);
+
+ _gamma_y_uptr->process_plane (tmp, src, work_w, 1);
+
+ for (int p_idx = 0; p_idx < _nbr_planes; ++p_idx)
+ {
+ _lut_d_uptr->process_plane (dst [p_idx], tmp [p_idx], work_w, 1);
+ }
+
+ src.step_pix (_max_seg_len);
+ dst.step_pix (_max_seg_len);
+ }
+
+ line_src.step_line ();
+ line_dst.step_line ();
+ }
+}
+
+
+
+void TransModel::process_frame_sgd (const ProcComp3Arg &arg) const noexcept
+{
+ assert (_lut_s_uptr.get () != nullptr);
+ assert (_gamma_y_uptr.get () != nullptr);
+ assert (_lut_d_uptr.get () != nullptr);
+
+ alignas (64) SegArray tmp_seg_s;
+ alignas (64) SegArray tmp_seg_d;
+
+ auto line_src = arg._src;
+ auto line_dst = arg._dst;
+ const Frame <> tmp_s {
+ Plane <> { tmp_seg_s [0].data (), 0 },
+ Plane <> { tmp_seg_s [1].data (), 0 },
+ Plane <> { tmp_seg_s [2].data (), 0 }
+ };
+ const Frame <> tmp_d {
+ Plane <> { tmp_seg_d [0].data (), 0 },
+ Plane <> { tmp_seg_d [1].data (), 0 },
+ Plane <> { tmp_seg_d [2].data (), 0 }
+ };
+
+ for (int y = 0; y < arg._h; ++y)
+ {
+ auto src = line_src;
+ auto dst = line_dst;
+
+ for (int x = 0; x < arg._w; x += _max_len)
+ {
+ const int work_w = std::min (arg._w - x, _max_len);
+
+ for (int p_idx = 0; p_idx < _nbr_planes; ++p_idx)
+ {
+ _lut_s_uptr->process_plane (tmp_s [p_idx], src [p_idx], work_w, 1);
+ }
+
+ _gamma_y_uptr->process_plane (tmp_d, tmp_s, work_w, 1);
+
+ for (int p_idx = 0; p_idx < _nbr_planes; ++p_idx)
+ {
+ _lut_d_uptr->process_plane (dst [p_idx], tmp_d [p_idx], work_w, 1);
+ }
+
+ src.step_pix (_max_seg_len);
+ dst.step_pix (_max_seg_len);
+ }
+
+ line_src.step_line ();
+ line_dst.step_line ();
+ }
+}
+
+
+
+void TransModel::estimate_lw (double &lw, const TransOpInterface::LinInfo &info)
+{
+ if (lw <= 0)
+ {
+ if (info._wpeak_cdm2 > 0)
+ {
+ lw = info._wpeak_cdm2;
+ }
+ else
+ {
+ if (info._range == TransOpInterface::Range::HDR)
+ {
+ lw = 1000;
+ }
+ else if (info._range == TransOpInterface::Range::SDR)
+ {
+ lw = 100;
+ }
+ else
+ {
+ lw = 100; // Default value
+ }
+ }
+ }
+}
+
+
+
+TransModel::OpSPtr TransModel::compose (OpSPtr op_1_sptr, OpSPtr op_2_sptr)
+{
+ assert (op_1_sptr.get () != nullptr || op_2_sptr.get () != nullptr);
+
+ return
+ (op_2_sptr.get () == nullptr) ? op_1_sptr
+ : (op_1_sptr.get () == nullptr) ? op_2_sptr
+ : std::make_shared (op_1_sptr, op_2_sptr);
+}
+
+
+
+// OOTF_PQ = [0-1] -> *range -> OETF_709 -> EOTF_1886 -> [0-10000]
+// range = 59.5208
+TransModel::OpSPtr TransModel::build_pq_ootf ()
+{
+ // Linear range, no unit
+ const double range_709_10k = compute_pq_sceneref_range_709 ();
+ auto op_gain_pre =
+ std::make_shared (range_709_10k);
+
+ auto op_709 = std::make_shared (
+ false,
+ TransCst::_bt709_alpha,
+ TransCst::_bt709_beta,
+ TransCst::_bt709_power,
+ TransCst::_bt709_slope,
+ 0, range_709_10k
+ );
+
+ // Subsequent EOTF_PQ takes 0-10000 cd/m2 in range [0-1].
+ // EOTF_1886 assumes range [0-1] is 100 cd/m2
+ const double gain_post = TransCst::_bt2100_pq_lw / TransCst::_bt1886_lw;
+ const double alpha = pow (gain_post, 1 / TransCst::_bt1886_gamma);
+ auto op_1886 = std::make_shared (
+ true, TransCst::_bt1886_gamma,
+ alpha, alpha, TransCst::_bt1886_lw
+ );
+
+ auto op_ootf =
+ compose (op_gain_pre, compose (op_709, op_1886));
+
+ return op_ootf;
+}
+
+
+
+// OOTF_PQ^-1 = [0-10000] -> EOTF_1886^-1 -> OETF_709^-1 -> /range -> [0-1]
+// range = 59.5208
+TransModel::OpSPtr TransModel::build_pq_ootf_inv ()
+{
+ // Preceding EOTF_PQ outputs 0-10000 cd/m2 in range [0-1].
+ // EOTF_1886^-1 assumes range [0-1] is 100 cd/m2
+ const double gain_pre = TransCst::_bt2100_pq_lw / TransCst::_bt1886_lw;
+ const double alpha = pow (gain_pre, 1 / TransCst::_bt1886_gamma);
+ auto op_1886i = std::make_shared (
+ false, TransCst::_bt1886_gamma,
+ alpha, alpha, TransCst::_bt1886_lw
+ );
+
+ // Linear range, no unit
+ const double range_709_10k = compute_pq_sceneref_range_709 ();
+ auto op_709i = std::make_shared (
+ true,
+ TransCst::_bt709_alpha,
+ TransCst::_bt709_beta,
+ TransCst::_bt709_power,
+ TransCst::_bt709_slope,
+ 0, range_709_10k
+ );
+
+ auto op_gain_post =
+ std::make_shared (1 / range_709_10k);
+
+ auto op_ootf_inv =
+ compose (compose (op_1886i, op_709i), op_gain_post);
+
+ return op_ootf_inv;
+}
+
+
+
+// Linear range for the PQ scene signal at the input of the BT.709 OETF
+// 10000 cd/m^2 -> EOTF_1886^-1 -> OETF_709^-1 -> upper bound of the range
+double TransModel::compute_pq_sceneref_range_709 ()
+{
+ constexpr double lw_ratio = TransCst::_bt2100_pq_lw / TransCst::_bt1886_lw;
+
+ // EOTF_1886^-1 output
+ const double ecode =
+ pow (lw_ratio, 1 / TransCst::_bt1886_gamma);
+
+ // OETF_709^-1 output
+ const double range = pow (
+ (ecode + (TransCst::_bt709_alpha - 1)) / TransCst::_bt709_alpha,
+ 1 / TransCst::_bt709_power
+ );
+
+ // Validity check. Exact value is 59.520834...
+ assert (range > 59.520 && range < 59.521);
+
+ return range;
+}
+
+
+
} // namespace fmtcl
diff --git a/src/fmtcl/TransModel.h b/src/fmtcl/TransModel.h
index 3de9813..f88c0b8 100644
--- a/src/fmtcl/TransModel.h
+++ b/src/fmtcl/TransModel.h
@@ -25,7 +25,10 @@ To Public License, Version 2, as published by Sam Hocevar. See
#include "fmtcl/Frame.h"
#include "fmtcl/FrameRO.h"
+#include "fmtcl/GammaY.h"
+#include "fmtcl/LumMatch.h"
#include "fmtcl/PicFmt.h"
+#include "fmtcl/ProcComp3Arg.h"
#include "fmtcl/TransCurve.h"
#include "fmtcl/TransLut.h"
#include "fmtcl/TransOpLogC.h"
@@ -49,11 +52,26 @@ class TransModel
public:
+ static constexpr int _nbr_planes = ProcComp3Arg::_nbr_planes;
+
+ // Minimum accepted luminance in cd/m^2 for peak luminance or ambient
+ // surround luminance. Always > 0.
+ static constexpr double _min_luminance = 0.1;
+
typedef TransUtil::OpSPtr OpSPtr;
- explicit TransModel (PicFmt dst_fmt, TransCurve curve_d, TransOpLogC::ExpIdx logc_ei_d, PicFmt src_fmt, TransCurve curve_s, TransOpLogC::ExpIdx logc_ei_s, double contrast, double gcor, double lvl_black, bool sse2_flag, bool avx2_flag);
+ enum class GyProc
+ {
+ UNDEF = -1,
+ OFF = 0,
+ ON
+ };
+
+ explicit TransModel (PicFmt dst_fmt, TransCurve curve_d, TransOpLogC::ExpIdx logc_ei_d, PicFmt src_fmt, TransCurve curve_s, TransOpLogC::ExpIdx logc_ei_s, double contrast, double gcor, double lb, double lws, double lwd, double lamb, bool scene_flag, LumMatch match, GyProc gy_proc, bool sse2_flag, bool avx2_flag);
- void process_plane (const Plane <> &dst, const PlaneRO <> &src, int w, int h) const noexcept;
+ const std::string &
+ get_debug_text () const noexcept;
+ void process_frame (const ProcComp3Arg &arg) const noexcept;
@@ -67,8 +85,44 @@ class TransModel
private:
+ // Maximum segment length in bytes. Multiple of 64.
+ static constexpr int _max_seg_len = 4096;
+
+ enum class Proc
+ {
+ DIRECT = 0, // LUT S only
+ SG, // LUT S -> Gamma Y
+ GD, // Gamma Y -> LUT D
+ SGD // LUT S -> Gamma Y -> LUT D
+ };
+
+ typedef std::array Segment;
+ typedef std::array SegArray;
+
+ void process_frame_direct (const ProcComp3Arg &arg) const noexcept;
+ void process_frame_sg (const ProcComp3Arg &arg) const noexcept;
+ void process_frame_gd (const ProcComp3Arg &arg) const noexcept;
+ void process_frame_sgd (const ProcComp3Arg &arg) const noexcept;
+
+ static void estimate_lw (double &lw, const TransOpInterface::LinInfo &info);
+ static OpSPtr compose (OpSPtr op_1_sptr, OpSPtr op_2_sptr);
+ static OpSPtr build_pq_ootf ();
+ static OpSPtr build_pq_ootf_inv ();
+ static double compute_pq_sceneref_range_709 ();
+
+ Proc _proc_mode = Proc::DIRECT;
+ int _max_len = 0; // Pixels
+
+ // At least one of these functions must be populated
std::unique_ptr
- _lut_uptr;
+ _lut_s_uptr;
+ std::unique_ptr
+ _gamma_y_uptr;
+ std::unique_ptr
+ _lut_d_uptr;
+
+ // Contains debugging information about what is done
+ std::string _dbg_txt;
diff --git a/src/fmtcl/TransOp2084.cpp b/src/fmtcl/TransOp2084.cpp
index 1aec072..2cf5a1a 100644
--- a/src/fmtcl/TransOp2084.cpp
+++ b/src/fmtcl/TransOp2084.cpp
@@ -49,8 +49,12 @@ TransOp2084::TransOp2084 (bool inv_flag)
+/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+
+
+
// Linear values are absolute. 1 is 10000 cd/m2.
-double TransOp2084::operator () (double x) const
+double TransOp2084::do_convert (double x) const
{
x = fstb::limit (x, 0.0, 1.0);
double y = x;
@@ -93,7 +97,16 @@ double TransOp2084::operator () (double x) const
-/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+TransOpInterface::LinInfo TransOp2084::do_get_info () const
+{
+ constexpr double w_peak = 10'000.0; // cd/m^2
+ constexpr double w_ref = 203.152145938; // cd/m^2
+ // w_ref = 1000 * EOTF_HLG (0.75)
+ // = 1000 * OOTF_HLG (OETF_HLG^-1 (0.75))
+ // = 1000 * (0.264962560421 ^ 1.2)
+
+ return { Type::EOTF, Range::HDR, 1.0, w_ref / w_peak, w_peak, w_peak };
+}
diff --git a/src/fmtcl/TransOp2084.h b/src/fmtcl/TransOp2084.h
index 9bed0a6..afcd973 100644
--- a/src/fmtcl/TransOp2084.h
+++ b/src/fmtcl/TransOp2084.h
@@ -47,16 +47,16 @@ class TransOp2084
explicit TransOp2084 (bool inv_flag);
virtual ~TransOp2084 () {}
- // TransOpInterface
- virtual double operator () (double x) const;
- virtual double get_max () const { return (1.0); }
-
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
protected:
+ // TransOpInterface
+ double do_convert (double x) const override;
+ LinInfo do_get_info () const override;
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpAcesCc.cpp b/src/fmtcl/TransOpAcesCc.cpp
index 4960e4a..4942190 100644
--- a/src/fmtcl/TransOpAcesCc.cpp
+++ b/src/fmtcl/TransOpAcesCc.cpp
@@ -47,7 +47,11 @@ TransOpAcesCc::TransOpAcesCc (bool inv_flag)
-double TransOpAcesCc::operator () (double x) const
+/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+
+
+
+double TransOpAcesCc::do_convert (double x) const
{
double y = x;
@@ -92,7 +96,10 @@ double TransOpAcesCc::operator () (double x) const
-/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+TransOpInterface::LinInfo TransOpAcesCc::do_get_info () const
+{
+ return { Type::UNDEF, Range::UNDEF, _max_val, 1.0, 0.0, 0.0 };
+}
@@ -100,6 +107,10 @@ double TransOpAcesCc::operator () (double x) const
+constexpr double TransOpAcesCc::_max_val;
+
+
+
} // namespace fmtcl
diff --git a/src/fmtcl/TransOpAcesCc.h b/src/fmtcl/TransOpAcesCc.h
index 4c667ec..b4f0410 100644
--- a/src/fmtcl/TransOpAcesCc.h
+++ b/src/fmtcl/TransOpAcesCc.h
@@ -47,16 +47,16 @@ class TransOpAcesCc
explicit TransOpAcesCc (bool inv_flag);
virtual ~TransOpAcesCc () {}
- // TransOpInterface
- virtual double operator () (double x) const;
- virtual double get_max () const { return (65504); }
-
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
protected:
+ // TransOpInterface
+ double do_convert (double x) const override;
+ LinInfo do_get_info () const override;
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpAffine.cpp b/src/fmtcl/TransOpAffine.cpp
index 7415ea1..3a3e4ff 100644
--- a/src/fmtcl/TransOpAffine.cpp
+++ b/src/fmtcl/TransOpAffine.cpp
@@ -49,14 +49,14 @@ TransOpAffine::TransOpAffine (double a, double b)
-double TransOpAffine::operator () (double x) const
-{
- return (x * _a + _b);
-}
+/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
-/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+double TransOpAffine::do_convert (double x) const
+{
+ return x * _a + _b;
+}
diff --git a/src/fmtcl/TransOpAffine.h b/src/fmtcl/TransOpAffine.h
index 78e044b..0f3875e 100644
--- a/src/fmtcl/TransOpAffine.h
+++ b/src/fmtcl/TransOpAffine.h
@@ -47,15 +47,16 @@ class TransOpAffine
explicit TransOpAffine (double a, double b);
virtual ~TransOpAffine () {}
- // TransOpInterface
- virtual double operator () (double x) const;
-
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
protected:
+ // TransOpInterface
+ double do_convert (double x) const override;
+ LinInfo do_get_info () const override { return _unbounded; }
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpBypass.h b/src/fmtcl/TransOpBypass.h
index 20a8d82..84fa6c5 100644
--- a/src/fmtcl/TransOpBypass.h
+++ b/src/fmtcl/TransOpBypass.h
@@ -47,15 +47,16 @@ class TransOpBypass
TransOpBypass () = default;
virtual ~TransOpBypass () {}
- // TransOpInterface
- virtual double operator () (double x) const { return (x); }
-
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
protected:
+ // TransOpInterface
+ double do_convert (double x) const override { return x; }
+ LinInfo do_get_info () const override { return _unbounded; }
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpCanonLog.cpp b/src/fmtcl/TransOpCanonLog.cpp
index e810253..c77e4a2 100644
--- a/src/fmtcl/TransOpCanonLog.cpp
+++ b/src/fmtcl/TransOpCanonLog.cpp
@@ -50,8 +50,12 @@ TransOpCanonLog::TransOpCanonLog (bool inv_flag)
+/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+
+
+
// 1.08676 is log peak white, at 8.00903 in linear scale.
-double TransOpCanonLog::operator () (double x) const
+double TransOpCanonLog::do_convert (double x) const
{
static constexpr double a = 10.1596;
static constexpr double b = 0.529136;
@@ -72,7 +76,10 @@ double TransOpCanonLog::operator () (double x) const
-/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+TransOpInterface::LinInfo TransOpCanonLog::do_get_info () const
+{
+ return { Type::OETF, Range::UNDEF, 8.00903, 1.0, 0.0, 0.0 };
+}
diff --git a/src/fmtcl/TransOpCanonLog.h b/src/fmtcl/TransOpCanonLog.h
index 9d95dc2..78ce54a 100644
--- a/src/fmtcl/TransOpCanonLog.h
+++ b/src/fmtcl/TransOpCanonLog.h
@@ -47,16 +47,16 @@ class TransOpCanonLog
explicit TransOpCanonLog (bool inv_flag);
virtual ~TransOpCanonLog () {}
- // TransOpInterface
- virtual double operator () (double x) const;
- virtual double get_max () const { return (8.00903); }
-
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
protected:
+ // TransOpInterface
+ double do_convert (double x) const override;
+ LinInfo do_get_info () const override;
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpCompose.h b/src/fmtcl/TransOpCompose.h
index d602a0e..1cbfd73 100644
--- a/src/fmtcl/TransOpCompose.h
+++ b/src/fmtcl/TransOpCompose.h
@@ -52,16 +52,16 @@ class TransOpCompose
TransOpCompose (OpSPtr op_1_sptr, OpSPtr op_2_sptr);
virtual ~TransOpCompose () {}
- // TransOpInterface
- virtual inline double
- operator () (double x) const;
-
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
protected:
+ // TransOpInterface
+ inline double do_convert (double x) const override;
+ LinInfo do_get_info () const override { return _unbounded; }
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpCompose.hpp b/src/fmtcl/TransOpCompose.hpp
index ed4b68e..77ef15d 100644
--- a/src/fmtcl/TransOpCompose.hpp
+++ b/src/fmtcl/TransOpCompose.hpp
@@ -45,7 +45,11 @@ TransOpCompose::TransOpCompose (OpSPtr op_1_sptr, OpSPtr op_2_sptr)
-double TransOpCompose::operator () (double x) const
+/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+
+
+
+double TransOpCompose::do_convert (double x) const
{
x = (*_op_1_sptr) (x);
x = (*_op_2_sptr) (x);
@@ -55,10 +59,6 @@ double TransOpCompose::operator () (double x) const
-/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
-
-
-
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpContrast.h b/src/fmtcl/TransOpContrast.h
index 8a8bce0..b5844bd 100644
--- a/src/fmtcl/TransOpContrast.h
+++ b/src/fmtcl/TransOpContrast.h
@@ -48,16 +48,16 @@ class TransOpContrast
TransOpContrast (double cont);
virtual ~TransOpContrast () {}
- // TransOpInterface
- virtual inline double
- operator () (double x) const;
-
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
protected:
+ // TransOpInterface
+ inline double do_convert (double x) const override;
+ LinInfo do_get_info () const override { return _unbounded; }
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpContrast.hpp b/src/fmtcl/TransOpContrast.hpp
index 9dab328..e92b285 100644
--- a/src/fmtcl/TransOpContrast.hpp
+++ b/src/fmtcl/TransOpContrast.hpp
@@ -43,14 +43,14 @@ TransOpContrast::TransOpContrast (double cont)
-double TransOpContrast::operator () (double x) const
-{
- return (x * _cont);
-}
+/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
-/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+double TransOpContrast::do_convert (double x) const
+{
+ return x * _cont;
+}
diff --git a/src/fmtcl/TransOpErimm.cpp b/src/fmtcl/TransOpErimm.cpp
index 23eb9b4..804fc47 100644
--- a/src/fmtcl/TransOpErimm.cpp
+++ b/src/fmtcl/TransOpErimm.cpp
@@ -53,7 +53,11 @@ TransOpErimm::TransOpErimm (bool inv_flag)
-double TransOpErimm::operator () (double x) const
+/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+
+
+
+double TransOpErimm::do_convert (double x) const
{
double y = x;
@@ -102,7 +106,10 @@ double TransOpErimm::operator () (double x) const
-/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+TransOpInterface::LinInfo TransOpErimm::do_get_info () const
+{
+ return { Type::OETF, Range::UNDEF, _eclip, 1.0, 0.0, 0.0 };
+}
diff --git a/src/fmtcl/TransOpErimm.h b/src/fmtcl/TransOpErimm.h
index 7d8f260..8a4aded 100644
--- a/src/fmtcl/TransOpErimm.h
+++ b/src/fmtcl/TransOpErimm.h
@@ -47,16 +47,16 @@ class TransOpErimm
explicit TransOpErimm (bool inv_flag);
virtual ~TransOpErimm () {}
- // TransOpInterface
- virtual double operator () (double x) const;
- virtual double get_max () const { return (_eclip); }
-
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
protected:
+ // TransOpInterface
+ double do_convert (double x) const override;
+ LinInfo do_get_info () const override;
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpFilmStream.cpp b/src/fmtcl/TransOpFilmStream.cpp
index 18d5061..444a3cd 100644
--- a/src/fmtcl/TransOpFilmStream.cpp
+++ b/src/fmtcl/TransOpFilmStream.cpp
@@ -49,8 +49,12 @@ TransOpFilmStream::TransOpFilmStream (bool inv_flag)
+/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+
+
+
// Linear 1 is the sensor clipping level (3840 on a linear 12-bit scale).
-double TransOpFilmStream::operator () (double x) const
+double TransOpFilmStream::do_convert (double x) const
{
constexpr double sc10 = 1024;
constexpr double bl12 = 64;
@@ -86,7 +90,12 @@ double TransOpFilmStream::operator () (double x) const
-/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+TransOpInterface::LinInfo TransOpFilmStream::do_get_info () const
+{
+ // R, G and B sensors don't have the same sensitivity, so white level
+ // has no real meaning here.
+ return { Type::OETF, Range::UNDEF, 1.0, 1.0, 0.0, 0.0 };
+}
diff --git a/src/fmtcl/TransOpFilmStream.h b/src/fmtcl/TransOpFilmStream.h
index 98d1eb9..e1c07ac 100644
--- a/src/fmtcl/TransOpFilmStream.h
+++ b/src/fmtcl/TransOpFilmStream.h
@@ -47,16 +47,16 @@ class TransOpFilmStream
explicit TransOpFilmStream (bool inv_flag);
virtual ~TransOpFilmStream () {}
- // TransOpInterface
- virtual double operator () (double x) const;
- virtual double get_max () const { return (1.0); }
-
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
protected:
+ // TransOpInterface
+ double do_convert (double x) const override;
+ LinInfo do_get_info () const override;
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpHlg.cpp b/src/fmtcl/TransOpHlg.cpp
index 670b3d6..f7b451f 100644
--- a/src/fmtcl/TransOpHlg.cpp
+++ b/src/fmtcl/TransOpHlg.cpp
@@ -49,7 +49,11 @@ TransOpHlg::TransOpHlg (bool inv_flag)
-double TransOpHlg::operator () (double x) const
+/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+
+
+
+double TransOpHlg::do_convert (double x) const
{
x = fstb::limit (x, 0.0, 1.0);
if (_inv_flag)
@@ -65,14 +69,15 @@ double TransOpHlg::operator () (double x) const
return x;
}
-double TransOpHlg::get_max () const
-{
- return (compute_inverse (1.0));
-}
+TransOpInterface::LinInfo TransOpHlg::do_get_info () const
+{
+ const double w_ref = compute_inverse (0.75);
-/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+ // Peak luminance is not fixed and depends on the actual hardware.
+ return { Type::OETF, Range::HDR, 1.0, w_ref, 0.0, 0.0 };
+}
diff --git a/src/fmtcl/TransOpHlg.h b/src/fmtcl/TransOpHlg.h
index bd2dc1f..911737b 100644
--- a/src/fmtcl/TransOpHlg.h
+++ b/src/fmtcl/TransOpHlg.h
@@ -47,16 +47,16 @@ class TransOpHlg
explicit TransOpHlg (bool inv_flag);
virtual ~TransOpHlg () {}
- // TransOpInterface
- virtual double operator () (double x) const;
- virtual double get_max () const;
-
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
protected:
+ // TransOpInterface
+ double do_convert (double x) const override;
+ LinInfo do_get_info () const override;
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpInterface.cpp b/src/fmtcl/TransOpInterface.cpp
new file mode 100644
index 0000000..f93e963
--- /dev/null
+++ b/src/fmtcl/TransOpInterface.cpp
@@ -0,0 +1,75 @@
+/*****************************************************************************
+
+ TransOpInterface.cpp
+ Author: Laurent de Soras, 2021
+
+--- Legal stuff ---
+
+This program is free software. It comes without any warranty, to
+the extent permitted by applicable law. You can redistribute it
+and/or modify it under the terms of the Do What The Fuck You Want
+To Public License, Version 2, as published by Sam Hocevar. See
+http://www.wtfpl.net/ for more details.
+
+*Tab=3***********************************************************************/
+
+
+
+#if defined (_MSC_VER)
+ #pragma warning (1 : 4130 4223 4705 4706)
+ #pragma warning (4 : 4355 4786 4800)
+#endif
+
+
+
+/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+
+#include "fmtcl/TransOpInterface.h"
+
+#include
+
+
+
+namespace fmtcl
+{
+
+
+
+/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+
+
+
+constexpr TransOpInterface::LinInfo TransOpInterface::_unbounded;
+
+
+
+double TransOpInterface::operator () (double x) const
+{
+ return do_convert (x);
+}
+
+
+
+TransOpInterface::LinInfo TransOpInterface::get_info () const
+{
+ const auto info = do_get_info ();
+
+ assert (info._vmax >= 1.0);
+ assert (info._wref > 0);
+ assert (info._scale_cdm2 >= 0);
+ assert (info._wpeak_cdm2 >= 0);
+
+ return info;
+}
+
+
+
+/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+
+
+
+} // namespace fmtcl
+
+
+
+/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpInterface.h b/src/fmtcl/TransOpInterface.h
index c925b1a..ee06051 100644
--- a/src/fmtcl/TransOpInterface.h
+++ b/src/fmtcl/TransOpInterface.h
@@ -41,12 +41,53 @@ class TransOpInterface
public:
+ enum class Type
+ {
+ UNDEF = 0, // Unknown, unspecified or not applicable (but still valid)
+ OETF,
+ EOTF
+ };
+
+ enum class Range
+ {
+ UNDEF = 0,
+ SDR,
+ HDR
+ };
+
+ // Information about the linear scale
+ class LinInfo
+ {
+ public:
+ Type _type = Type::UNDEF;
+ Range _range = Range::UNDEF;
+
+ // Maximum supported linear value, for 16-bit coding. Should be >= 1.0.
+ double _vmax = 1.0;
+
+ // Reference white level, linear scale. > 0. Set to 1.0 when unknown.
+ double _wref = 1.0;
+
+ // Luminance corresponding to linear 1.0, in cd/m^2.
+ // Not necessarily the peak white nor the reference white.
+ // Dedicated to EOTFs, but not mandatory. 0 = unknown/unspecified
+ double _scale_cdm2 = 0;
+
+ // Peak white, in cd/m^2.
+ // Dedicated to EOTFs, but not mandatory. 0 = unknown/unspecified
+ double _wpeak_cdm2 = 0;
+ };
+
+ // Return this if nothing is known (modifiers)
+ static constexpr LinInfo _unbounded { Type::UNDEF, Range::UNDEF, 1e9, 1, 0, 0 };
+
virtual ~TransOpInterface () {}
// It is the operator responsibility to clip the input and output
// (input domain or spec requirement).
- virtual double operator () (double x) const = 0;
- virtual double get_max () const { return (1e9); } // Linear
+ double operator () (double x) const;
+
+ LinInfo get_info () const;
@@ -54,6 +95,10 @@ class TransOpInterface
protected:
+ virtual double do_convert (double x) const = 0;
+ virtual LinInfo
+ do_get_info () const { return { }; }
+
}; // class TransOpInterface
diff --git a/src/fmtcl/TransOpLinPow.cpp b/src/fmtcl/TransOpLinPow.cpp
index b7ba45d..8011f36 100644
--- a/src/fmtcl/TransOpLinPow.cpp
+++ b/src/fmtcl/TransOpLinPow.cpp
@@ -73,7 +73,11 @@ TransOpLinPow::TransOpLinPow (bool inv_flag, double alpha, double beta, double p
-double TransOpLinPow::operator () (double x) const
+/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+
+
+
+double TransOpLinPow::do_convert (double x) const
{
double y = x;
@@ -138,7 +142,10 @@ double TransOpLinPow::operator () (double x) const
-/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+TransOpInterface::LinInfo TransOpLinPow::do_get_info () const
+{
+ return { Type::UNDEF, Range::UNDEF, 1.0, 1.0, _scale_cdm2, _wpeak_cdm2 };
+}
diff --git a/src/fmtcl/TransOpLinPow.h b/src/fmtcl/TransOpLinPow.h
index 18dfb52..98824d2 100644
--- a/src/fmtcl/TransOpLinPow.h
+++ b/src/fmtcl/TransOpLinPow.h
@@ -50,16 +50,16 @@ class TransOpLinPow
explicit TransOpLinPow (bool inv_flag, double alpha, double beta, double p1, double slope, double lb = 0, double ub = 1, double scneg = 1, double p2 = 1, double scale_cdm2 = 0, double wpeak_cdm2 = 0);
virtual ~TransOpLinPow () {}
- // TransOpInterface
- virtual double operator () (double x) const;
- virtual double get_max () const { return (_ub); }
-
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
protected:
+ // TransOpInterface
+ double do_convert (double x) const override;
+ LinInfo do_get_info () const override;
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpLogC.cpp b/src/fmtcl/TransOpLogC.cpp
index b6879a4..96f74dd 100644
--- a/src/fmtcl/TransOpLogC.cpp
+++ b/src/fmtcl/TransOpLogC.cpp
@@ -57,19 +57,6 @@ TransOpLogC::TransOpLogC (bool inv_flag, LType type, ExpIdx ei)
-// 1 is log peak white.
-double TransOpLogC::operator () (double x) const
-{
- return (_inv_flag) ? compute_inverse (x) : compute_direct (x);
-}
-
-double TransOpLogC::get_max () const
-{
- return compute_inverse (1.0);
-}
-
-
-
TransOpLogC::ExpIdx TransOpLogC::conv_logc_ei (int val_raw)
{
ExpIdx ei = ExpIdx_INVALID;
@@ -101,6 +88,24 @@ TransOpLogC::ExpIdx TransOpLogC::conv_logc_ei (int val_raw)
+// 1 is log peak white.
+double TransOpLogC::do_convert (double x) const
+{
+ return (_inv_flag) ? compute_inverse (x) : compute_direct (x);
+}
+
+
+
+TransOpInterface::LinInfo TransOpLogC::do_get_info () const
+{
+ const double grey18 = compute_inverse (400.0 / 1023.0);
+ const double white = grey18 * 100.0 / 18.0; // 100 or 90?
+
+ return { Type::OETF, Range::UNDEF, compute_inverse (1.0), white, 0.0, 0.0 };
+}
+
+
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpLogC.h b/src/fmtcl/TransOpLogC.h
index d8511b0..b8bc699 100644
--- a/src/fmtcl/TransOpLogC.h
+++ b/src/fmtcl/TransOpLogC.h
@@ -83,10 +83,6 @@ class TransOpLogC
explicit TransOpLogC (bool inv_flag, LType type, ExpIdx ei = ExpIdx_800);
virtual ~TransOpLogC () {}
- // TransOpInterface
- virtual double operator () (double x) const;
- virtual double get_max () const;
-
static ExpIdx conv_logc_ei (int val_raw);
@@ -95,6 +91,10 @@ class TransOpLogC
protected:
+ // TransOpInterface
+ double do_convert (double x) const override;
+ LinInfo do_get_info () const override;
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpLogTrunc.cpp b/src/fmtcl/TransOpLogTrunc.cpp
index cbe4371..7f4120b 100644
--- a/src/fmtcl/TransOpLogTrunc.cpp
+++ b/src/fmtcl/TransOpLogTrunc.cpp
@@ -51,7 +51,11 @@ TransOpLogTrunc::TransOpLogTrunc (bool inv_flag, double alpha, double beta)
-double TransOpLogTrunc::operator () (double x) const
+/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+
+
+
+double TransOpLogTrunc::do_convert (double x) const
{
x = fstb::limit (x, 0.0, 1.0);
double y = x;
@@ -77,10 +81,6 @@ double TransOpLogTrunc::operator () (double x) const
-/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
-
-
-
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpLogTrunc.h b/src/fmtcl/TransOpLogTrunc.h
index c7a7aaf..ec35428 100644
--- a/src/fmtcl/TransOpLogTrunc.h
+++ b/src/fmtcl/TransOpLogTrunc.h
@@ -47,16 +47,15 @@ class TransOpLogTrunc
explicit TransOpLogTrunc (bool inv_flag, double alpha, double beta);
virtual ~TransOpLogTrunc () {}
- // TransOpInterface
- virtual double operator () (double x) const;
- virtual double get_max () const { return (1.0); }
-
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
protected:
+ // TransOpInterface
+ double do_convert (double x) const override;
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpPow.cpp b/src/fmtcl/TransOpPow.cpp
index 61016c6..b1d12a2 100644
--- a/src/fmtcl/TransOpPow.cpp
+++ b/src/fmtcl/TransOpPow.cpp
@@ -58,7 +58,11 @@ TransOpPow::TransOpPow (bool inv_flag, double p_i, double alpha, double val_max,
-double TransOpPow::operator () (double x) const
+/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+
+
+
+double TransOpPow::do_convert (double x) const
{
x = std::max (x, 0.0);
double y = x;
@@ -79,7 +83,10 @@ double TransOpPow::operator () (double x) const
-/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+TransOpInterface::LinInfo TransOpPow::do_get_info () const
+{
+ return { Type::UNDEF, Range::SDR, _val_max, 1.0, _scale_cdm2, _wpeak_cdm2 };
+}
diff --git a/src/fmtcl/TransOpPow.h b/src/fmtcl/TransOpPow.h
index c36ccf8..5da4a1d 100644
--- a/src/fmtcl/TransOpPow.h
+++ b/src/fmtcl/TransOpPow.h
@@ -47,16 +47,16 @@ class TransOpPow
explicit TransOpPow (bool inv_flag, double p_i, double alpha = 1, double val_max = 1, double scale_cdm2 = 0, double wpeak_cdm2 = 0);
virtual ~TransOpPow () {}
- // TransOpInterface
- virtual double operator () (double x) const;
- virtual double get_max () const { return (_val_max); }
-
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
protected:
+ // TransOpInterface
+ double do_convert (double x) const override;
+ LinInfo do_get_info () const override;
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransOpSLog.cpp b/src/fmtcl/TransOpSLog.cpp
index a97bbdf..8ee54ad 100644
--- a/src/fmtcl/TransOpSLog.cpp
+++ b/src/fmtcl/TransOpSLog.cpp
@@ -51,22 +51,28 @@ TransOpSLog::TransOpSLog (bool inv_flag, bool slog2_flag)
-// 1 lin is reference white, peak white at 10 lin.
-double TransOpSLog::operator () (double x) const
-{
- return (_inv_flag) ? compute_inverse (x) : compute_direct (x);
-}
+/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
-double TransOpSLog::get_max () const
+double TransOpSLog::do_convert (double x) const
{
- return (_slog2_flag) ? 10.0 * _s2 : 10.0;
+ return (_inv_flag) ? compute_inverse (x) : compute_direct (x);
}
-/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+TransOpInterface::LinInfo TransOpSLog::do_get_info () const
+{
+ const int white = (_slog2_flag) ? 582 : 636;
+ return {
+ Type::UNDEF,
+ Range::UNDEF,
+ compute_inverse (double (1023 - 64) / double (940 - 64)),
+ compute_inverse (double (white - 64) / double (940 - 64)),
+ 0.0, 0.0
+ };
+}
diff --git a/src/fmtcl/TransOpSLog.h b/src/fmtcl/TransOpSLog.h
index b4a6314..d4c0f0d 100644
--- a/src/fmtcl/TransOpSLog.h
+++ b/src/fmtcl/TransOpSLog.h
@@ -47,16 +47,16 @@ class TransOpSLog
explicit TransOpSLog (bool inv_flag, bool slog2_flag);
virtual ~TransOpSLog () {}
- // TransOpInterface
- virtual double operator () (double x) const;
- virtual double get_max () const;
-
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
protected:
+ // TransOpInterface
+ double do_convert (double x) const override;
+ LinInfo do_get_info () const override;
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
@@ -73,6 +73,7 @@ class TransOpSLog
double compute_direct (double x) const;
double compute_inverse (double x) const;
+
const bool _inv_flag;
const bool _slog2_flag;
diff --git a/src/fmtcl/TransOpSLog3.cpp b/src/fmtcl/TransOpSLog3.cpp
index f0edf25..3dac3ca 100644
--- a/src/fmtcl/TransOpSLog3.cpp
+++ b/src/fmtcl/TransOpSLog3.cpp
@@ -50,7 +50,11 @@ TransOpSLog3::TransOpSLog3 (bool inv_flag)
-double TransOpSLog3::operator () (double x) const
+/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+
+
+
+double TransOpSLog3::do_convert (double x) const
{
x = std::max (x, 0.0);
@@ -59,7 +63,16 @@ double TransOpSLog3::operator () (double x) const
-/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
+TransOpInterface::LinInfo TransOpSLog3::do_get_info () const
+{
+ return {
+ Type::UNDEF,
+ Range::UNDEF,
+ log_to_lin (1.0),
+ log_to_lin (598.0 / 1023.0),
+ 0.0, 0.0
+ };
+}
diff --git a/src/fmtcl/TransOpSLog3.h b/src/fmtcl/TransOpSLog3.h
index 326e412..0188dbd 100644
--- a/src/fmtcl/TransOpSLog3.h
+++ b/src/fmtcl/TransOpSLog3.h
@@ -47,16 +47,16 @@ class TransOpSLog3
explicit TransOpSLog3 (bool inv_flag);
virtual ~TransOpSLog3 () {}
- // TransOpInterface
- virtual double operator () (double x) const;
- virtual double get_max () const { return (38.420934337202536904496058731147); }
-
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
protected:
+ // TransOpInterface
+ double do_convert (double x) const override;
+ LinInfo do_get_info () const override;
+
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransUtil.cpp b/src/fmtcl/TransUtil.cpp
index 3b40ff7..46808ee 100644
--- a/src/fmtcl/TransUtil.cpp
+++ b/src/fmtcl/TransUtil.cpp
@@ -55,6 +55,18 @@ namespace fmtcl
+std::string TransUtil::gen_degub_prop_name (int dbg)
+{
+ assert (dbg >= 0);
+
+ char txt_0 [127+1];
+ fstb::snprintf4all (txt_0, sizeof (txt_0), "FmtcTransferDbg%d", dbg);
+
+ return txt_0;
+}
+
+
+
// str should be already converted to lower case
TransCurve TransUtil::conv_string_to_curve (const std::string &str)
{
@@ -226,7 +238,7 @@ TransUtil::OpSPtr TransUtil::conv_curve_to_op (TransCurve c, bool inv_flag, Tran
}
break;
case TransCurve_LINEAR:
- ptr = OpSPtr (new TransOpBypass);
+ // Nothing
break;
case TransCurve_LOG100:
ptr = OpSPtr (new TransOpLogTrunc (inv_flag, 0.5, 0.01));
@@ -383,6 +395,32 @@ TransUtil::OpSPtr TransUtil::conv_curve_to_op (TransCurve c, bool inv_flag, Tran
+// System gamma taking surround luminance into account
+// lw: peak white luminance in cd/m^2
+// lamb: surround luminance in cd/m^2
+double TransUtil::compute_hlg_gamma (double lw, double lamb)
+{
+ assert (lw > 1e-6);
+ assert (lamb > 1e-6);
+
+ // BT.2390-9 p. 29
+ constexpr double gref = 1.2; // Gamma at reference luminance
+
+ constexpr double lref = 1000; // Display reference luminance
+ constexpr double kappa = 1.111;
+
+ constexpr double lsref = 5; // Surround reference luminance
+ constexpr double mu = 0.98;
+
+ const double dfact = pow (kappa, log2 (lw / lref ));
+ const double sfact = pow (mu , log2 (lamb / lsref));
+ const double gamma = gref * dfact * sfact;
+
+ return gamma;
+}
+
+
+
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
diff --git a/src/fmtcl/TransUtil.h b/src/fmtcl/TransUtil.h
index 6a0c492..63c46b7 100644
--- a/src/fmtcl/TransUtil.h
+++ b/src/fmtcl/TransUtil.h
@@ -46,9 +46,12 @@ class TransUtil
typedef std::shared_ptr OpSPtr;
+ static std::string
+ gen_degub_prop_name (int dbg);
static TransCurve
conv_string_to_curve (const std::string &str);
static OpSPtr conv_curve_to_op (TransCurve c, bool inv_flag, TransOpLogC::ExpIdx logc_ei);
+ static double compute_hlg_gamma (double lw, double lamb);
diff --git a/src/main-avs.cpp b/src/main-avs.cpp
index 24c5307..329103f 100644
--- a/src/main-avs.cpp
+++ b/src/main-avs.cpp
@@ -81,10 +81,12 @@ const char * __stdcall AvisynthPluginInit3 (::IScriptEnvironment *env_ptr, const
, &main_avs_create , nullptr
);
env_ptr->AddFunction (fmtcavs_TRANSFER,
- "c" "[transs]s" "[transd]s" "[cont]f" // 0
- "[gcor]f" "[bits]i" "[flt]b" "[fulls]b" // 4
- "[fulld]b" "[logceis]i" "[logceid]i" "[cpuopt]i" // 8
- "[blacklvl]f" "[planes]." // 12
+ "c" "[transs]s" "[transd]s" "[cont]f" // 0
+ "[gcor]f" "[bits]i" "[flt]b" "[fulls]b" // 4
+ "[fulld]b" "[logceis]i" "[logceid]i" "[cpuopt]i" // 8
+ "[blacklvl]f" "[sceneref]b" "[lb]f" "[lw]f" // 12
+ "[lws]f" "[lwd]f" "[ambient]f" "[match]i" // 16
+ "[gy]b" "[debug]i" // 20
, &main_avs_create , nullptr
);
diff --git a/src/main-vs.cpp b/src/main-vs.cpp
index 55dcde4..9c09e9e 100644
--- a/src/main-vs.cpp
+++ b/src/main-vs.cpp
@@ -583,7 +583,15 @@ VS_EXTERNAL_API (void) VapourSynthPluginInit (::VSConfigPlugin config_fnc, ::VSR
"logceid:int:opt;"
"cpuopt:int:opt;"
"blacklvl:float:opt;"
- "planes:float[]:opt;" // Masktools style
+ "sceneref:int:opt;"
+ "lb:float:opt;"
+ "lw:float:opt;"
+ "lws:float:opt;"
+ "lwd:float:opt;"
+ "ambient:float:opt;"
+ "match:int:opt;"
+ "gy:int:opt;"
+ "debug:int:opt;"
, &vsutl::Redirect ::create, 0, plugin_ptr
);