Skip to content

Commit 66cef4e

Browse files
committed
Partially fix GitHub issue #110.
To allow for reading and creating fully standard-compliant PGM/PPM files, the ITU-R BT.709 transfer function can now be explicitly chosen as input and/or output gamma by specifying `gamma bt709` for input images or the `File_Gamma=bt709` INI setting for output images, respectively. (Note however that at present PGM/PPM files still default to linear encoding, in spite of the standard.) Support for the very similar but slightly superior ITU-R BT.2020 transfer function has also been added along the way using corresponding syntax. In addition, both `bt709` and `bt2020` are now valid parameters for the `assumed_gamma` global setting and the `Display_Gamma` INI setting.
1 parent 598f29a commit 66cef4e

File tree

7 files changed

+124
-29
lines changed

7 files changed

+124
-29
lines changed

source/base/image/colourspace.cpp

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ boost::mutex GammaCurve::cacheMutex;
5858
// definitions of static GammaCurve-derivatives' member variables to satisfy the linker
5959
SimpleGammaCurvePtr NeutralGammaCurve::instance;
6060
SimpleGammaCurvePtr SRGBGammaCurve::instance;
61-
GammaCurvePtr ITURBT709GammaCurve::instance;
62-
GammaCurvePtr Rec1361GammaCurve::instance;
61+
SimpleGammaCurvePtr BT709GammaCurve::instance;
62+
SimpleGammaCurvePtr BT1361GammaCurve::instance;
63+
SimpleGammaCurvePtr BT2020GammaCurve::instance;
6364

6465
/*******************************************************************************/
6566

@@ -200,53 +201,96 @@ int SRGBGammaCurve::GetTypeId() const
200201

201202
/*******************************************************************************/
202203

203-
ITURBT709GammaCurve::ITURBT709GammaCurve() {}
204-
GammaCurvePtr ITURBT709GammaCurve::Get()
204+
BT709GammaCurve::BT709GammaCurve() {}
205+
SimpleGammaCurvePtr BT709GammaCurve::Get()
205206
{
206207
if (!instance)
207-
instance.reset(new ITURBT709GammaCurve());
208-
return GammaCurvePtr(instance);
208+
instance.reset(new BT709GammaCurve());
209+
return SimpleGammaCurvePtr(instance);
209210
}
210-
float ITURBT709GammaCurve::Encode(float x) const
211+
float BT709GammaCurve::Encode(float x) const
211212
{
212213
if (x < 0.018f) return x * 4.5f;
213214
else return 1.099f * pow(x, 0.45f) - 0.099f;
214215
}
215-
float ITURBT709GammaCurve::Decode(float x) const
216+
float BT709GammaCurve::Decode(float x) const
216217
{
218+
// NB: ITU-R BT.709 does not officially specify a decoding transfer function. The following is the actual inverse
219+
// of the implemented transfer function.
217220
if (x < 0.081f) return x / 4.5f;
218221
else return pow((x + 0.099f) / 1.099f, 1.0f/0.45f);
219222
}
220-
float ITURBT709GammaCurve::ApproximateDecodingGamma() const
223+
float BT709GammaCurve::ApproximateDecodingGamma() const
221224
{
222225
return 1.9f; // very rough approximation
223226
}
227+
int BT709GammaCurve::GetTypeId() const
228+
{
229+
return kPOVList_GammaType_BT709;
230+
}
224231

225232
/*******************************************************************************/
226233

227-
Rec1361GammaCurve::Rec1361GammaCurve() {}
228-
GammaCurvePtr Rec1361GammaCurve::Get()
234+
BT1361GammaCurve::BT1361GammaCurve() {}
235+
SimpleGammaCurvePtr BT1361GammaCurve::Get()
229236
{
230237
if (!instance)
231-
instance.reset(new Rec1361GammaCurve());
232-
return GammaCurvePtr(instance);
238+
instance.reset(new BT1361GammaCurve());
239+
return SimpleGammaCurvePtr(instance);
233240
}
234-
float Rec1361GammaCurve::Encode(float x) const
241+
float BT1361GammaCurve::Encode(float x) const
235242
{
236243
if (x < -0.0045f) return (1.099f * pow(-4*x, 0.45f) - 0.099f) / 4;
237244
else if (x < 0.018f) return x * 4.5f;
238245
else return 1.099f * pow(x,0.45f) - 0.099f;
239246
}
240-
float Rec1361GammaCurve::Decode(float x) const
247+
float BT1361GammaCurve::Decode(float x) const
241248
{
242249
if (x < -0.02025f) return pow((4*x + 0.099f) / 1.099f, 1.0f/0.45f) / -4;
243250
else if (x < 0.081f) return x / 4.5f;
244251
else return pow((x + 0.099f) / 1.099f, 1.0f/0.45f);
245252
}
246-
float Rec1361GammaCurve::ApproximateDecodingGamma() const
253+
float BT1361GammaCurve::ApproximateDecodingGamma() const
247254
{
248255
return 1.9f; // very rough approximation of the x>0 section
249256
}
257+
int BT1361GammaCurve::GetTypeId() const
258+
{
259+
return kPOVList_GammaType_BT1361;
260+
}
261+
262+
/*******************************************************************************/
263+
264+
BT2020GammaCurve::BT2020GammaCurve() {}
265+
SimpleGammaCurvePtr BT2020GammaCurve::Get()
266+
{
267+
if (!instance)
268+
instance.reset(new BT2020GammaCurve());
269+
return SimpleGammaCurvePtr(instance);
270+
}
271+
float BT2020GammaCurve::Encode(float x) const
272+
{
273+
// NB: We're using higher-precision coefficients than given in ITU-R BT.2020; note that this is perfectly in
274+
// accordance with the specification, as the numerical values given there are just approximations, and the
275+
// coefficients are instead defined as "the solutions to [a certain set of] simultaneous equations".
276+
if (x < 0.01805396851080780734f) return x * 4.5f;
277+
else return 1.09929682680944294035f * pow(x, 0.45f) - 0.09929682680944294035f;
278+
}
279+
float BT2020GammaCurve::Decode(float x) const
280+
{
281+
// NB: ITU-R BT.2020 does not officially specify a decoding transfer function. The following is the actual inverse
282+
// of the implemented transfer function.
283+
if (x < 0.08124285829863513301f) return x / 4.5f;
284+
else return pow((x + 0.09929682680944294035f) / 1.09929682680944294035f, 1.0f/0.45f);
285+
}
286+
float BT2020GammaCurve::ApproximateDecodingGamma() const
287+
{
288+
return 1.9f; // very rough approximation
289+
}
290+
int BT2020GammaCurve::GetTypeId() const
291+
{
292+
return kPOVList_GammaType_BT2020;
293+
}
250294

251295
/*******************************************************************************/
252296

@@ -394,6 +438,9 @@ SimpleGammaCurvePtr GetGammaCurve(GammaTypeId type, float param)
394438
case kPOVList_GammaType_Neutral: return NeutralGammaCurve::Get();
395439
case kPOVList_GammaType_PowerLaw: return PowerLawGammaCurve::GetByDecodingGamma(param);
396440
case kPOVList_GammaType_SRGB: return SRGBGammaCurve::Get();
441+
case kPOVList_GammaType_BT709: return BT709GammaCurve::Get();
442+
case kPOVList_GammaType_BT1361: return BT1361GammaCurve::Get();
443+
case kPOVList_GammaType_BT2020: return BT2020GammaCurve::Get();
397444
default: return PowerLawGammaCurve::GetByDecodingGamma(DEFAULT_FILE_GAMMA);
398445
}
399446
}

source/base/image/colourspace.h

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -463,35 +463,60 @@ class SRGBGammaCurve : public UniqueGammaCurve
463463

464464
/// Class representing the ITU-R BT.709 transfer function.
465465
///
466+
/// @remark The ITU-R BT.709 transfer function is identical to that defined in ITU-R BT.601.
467+
///
466468
/// @note This class does _not_ account for the "black digital count" and "white digital count" being defined
467469
/// as 16/255 and 235/255, respectively.
468470
///
469-
class ITURBT709GammaCurve : public GammaCurve // TODO we could make this a UniqueGammaCurve if we assign it a type ID
471+
class BT709GammaCurve : public UniqueGammaCurve
470472
{
471473
public:
472-
static GammaCurvePtr Get();
474+
static SimpleGammaCurvePtr Get();
473475
virtual float Encode(float x) const;
474476
virtual float Decode(float x) const;
475477
virtual float ApproximateDecodingGamma() const;
478+
virtual int GetTypeId() const;
476479
private:
477-
static GammaCurvePtr instance;
478-
ITURBT709GammaCurve();
480+
static SimpleGammaCurvePtr instance;
481+
BT709GammaCurve();
479482
};
480483

481-
/// Class representing the Rec1361 transfer function.
484+
/// Class representing the ITU-R BT.1361 transfer function.
482485
///
483486
/// This transfer function is a wide-gamut extension to that specified in ITU-R BT.709.
484487
///
485-
class Rec1361GammaCurve : public GammaCurve // TODO we could make this a UniqueGammaCurve if we assign it a type ID
488+
class BT1361GammaCurve : public UniqueGammaCurve
486489
{
487490
public:
488-
static GammaCurvePtr Get();
491+
static SimpleGammaCurvePtr Get();
489492
virtual float Encode(float x) const;
490493
virtual float Decode(float x) const;
491494
virtual float ApproximateDecodingGamma() const;
495+
virtual int GetTypeId() const;
492496
private:
493-
static GammaCurvePtr instance;
494-
Rec1361GammaCurve();
497+
static SimpleGammaCurvePtr instance;
498+
BT1361GammaCurve();
499+
};
500+
501+
/// Class representing the ITU-R BT.2020 transfer function.
502+
///
503+
/// @remark The ITU-R BT.2020 transfer function is essentially identical to that defined in ITU-R BT.601 and BT.709,
504+
/// albeit using more precise constants.
505+
///
506+
/// @note This class does _not_ account for the "black digital count" and "white digital count" being defined
507+
/// as 16/255 and 235/255, respectively.
508+
///
509+
class BT2020GammaCurve : public UniqueGammaCurve
510+
{
511+
public:
512+
static SimpleGammaCurvePtr Get();
513+
virtual float Encode(float x) const;
514+
virtual float Decode(float x) const;
515+
virtual float ApproximateDecodingGamma() const;
516+
virtual int GetTypeId() const;
517+
private:
518+
static SimpleGammaCurvePtr instance;
519+
BT2020GammaCurve();
495520
};
496521

497522
/// Class representing a classic constant-gamma (power-law) gamma encoding curve.
@@ -553,7 +578,10 @@ enum GammaTypeId
553578
kPOVList_GammaType_Unknown,
554579
kPOVList_GammaType_Neutral,
555580
kPOVList_GammaType_PowerLaw,
556-
kPOVList_GammaType_SRGB
581+
kPOVList_GammaType_SRGB,
582+
kPOVList_GammaType_BT709,
583+
kPOVList_GammaType_BT1361, ///< Currently not exposed to the user.
584+
kPOVList_GammaType_BT2020
557585
};
558586

559587
/// Generic transfer function factory.

source/frontend/processrenderoptions.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,11 +1086,13 @@ struct ProcessRenderOptions::Output_FileType_Table FileTypeTable[] =
10861086
struct ProcessRenderOptions::Parameter_Code_Table GammaTypeTable[] =
10871087
{
10881088

1089-
// code, internalId,
1090-
{ "SRGB", kPOVList_GammaType_SRGB },
1089+
// code, internalId,
1090+
{ "BT709", kPOVList_GammaType_BT709 },
1091+
{ "BT2020", kPOVList_GammaType_BT2020 },
1092+
{ "SRGB", kPOVList_GammaType_SRGB },
10911093

10921094
// end-of-list marker
1093-
{ NULL, 0 }
1095+
{ NULL, 0 }
10941096
};
10951097

10961098
/* Supported dither types */

source/frontend/renderfrontend.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,12 @@ void OutputOptions(POVMS_Object& cppmsg, TextStreamBuffer *tsb)
11471147
case kPOVList_GammaType_SRGB:
11481148
tsb->printf(" Graphic display......On (gamma: sRGB)\n");
11491149
break;
1150+
case kPOVList_GammaType_BT709:
1151+
tsb->printf(" Graphic display......On (gamma: ITU-R BT.709)\n");
1152+
break;
1153+
case kPOVList_GammaType_BT2020:
1154+
tsb->printf(" Graphic display......On (gamma: ITU-R BT.2020)\n");
1155+
break;
11501156
default:
11511157
throw POV_EXCEPTION_STRING("Unknown gamma mode in OutputOptions()");
11521158
}

source/parser/parser_materials.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,14 @@ SimpleGammaCurvePtr Parser::Parse_Gamma (void)
505505
gamma = SRGBGammaCurve::Get();
506506
EXIT
507507
END_CASE
508+
CASE (BT709_TOKEN)
509+
gamma = BT709GammaCurve::Get();
510+
EXIT
511+
END_CASE
512+
CASE (BT2020_TOKEN)
513+
gamma = BT2020GammaCurve::Get();
514+
EXIT
515+
END_CASE
508516
OTHERWISE
509517
{
510518
UNGET

source/parser/reservedwords.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ const RESERVED_WORD Reserved_Words[] = {
122122
{ BRICK_SIZE_TOKEN, "brick_size" },
123123
{ BRIGHTNESS_TOKEN, "brightness" },
124124
{ BRILLIANCE_TOKEN, "brilliance" },
125+
{ BT2020_TOKEN, "bt2020" },
126+
{ BT709_TOKEN, "bt709" },
125127
{ BUMP_MAP_TOKEN, "bump_map" },
126128
{ BUMP_SIZE_TOKEN, "bump_size" },
127129
{ BUMPS_TOKEN, "bumps" },

source/parser/reservedwords.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ enum TOKEN_IDS
258258
BRICK_SIZE_TOKEN,
259259
BRIGHTNESS_TOKEN,
260260
BRILLIANCE_TOKEN,
261+
BT2020_TOKEN,
262+
BT709_TOKEN,
261263
BUMP_MAP_TOKEN,
262264
BUMP_SIZE_TOKEN,
263265
BUMPS_TOKEN,

0 commit comments

Comments
 (0)