Skip to content

Commit 6e5712b

Browse files
committed
feat: Refactor writing color space metadata to unify behavior
This adds new pvt::is_colorspace_srgb, pvt::get_colorspace_rec709_gamma, pvt::get_colorspace_icc_profile and pvt::get_colorspace_cicp functions to get colorspace metadata from an ImageSpec, to make logic between file formats consistent. This fixes an issue where tga and rla were incorrectly using g22_rec709 and g18_rec709 without _scene suffix, and not handling g24_rec709. There is existing consistency in that some file formats assume an empty oiio:ColorSpace to mean sRGB, and some don't. This inconsistency is preserved. Signed-off-by: Brecht Van Lommel <brecht@blender.org>
1 parent 200539d commit 6e5712b

16 files changed

Lines changed: 209 additions & 225 deletions

File tree

src/dpx.imageio/dpxoutput.cpp

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include <OpenImageIO/strutil.h>
1919
#include <OpenImageIO/typedesc.h>
2020

21+
#include "imageio_pvt.h"
22+
2123
OIIO_PLUGIN_NAMESPACE_BEGIN
2224

2325

@@ -436,18 +438,14 @@ DPXOutput::prep_subimage(int s, bool allocate)
436438
m_desc = get_image_descriptor();
437439

438440
// transfer function
439-
const ColorConfig& colorconfig = ColorConfig::default_colorconfig();
440-
std::string colorspace = spec_s.get_string_attribute("oiio:ColorSpace", "");
441-
if (colorconfig.equivalent(colorspace, "lin_rec709_scene"))
442-
m_transfer = dpx::kLinear;
443-
else if (colorconfig.equivalent(colorspace, "srgb_rec709_scene"))
441+
const float gamma = pvt::get_colorspace_rec709_gamma(spec_s);
442+
if (pvt::is_colorspace_srgb(spec_s, false))
444443
m_transfer = dpx::kITUR709;
445-
else if (colorconfig.equivalent(colorspace, "g22_rec709_scene")
446-
|| colorconfig.equivalent(colorspace, "g24_rec709_scene")
447-
|| colorconfig.equivalent(colorspace, "g18_rec709_scene")
448-
|| Strutil::istarts_with(colorspace, "Gamma"))
444+
else if (gamma == 1.0f)
445+
m_transfer = dpx::kLinear;
446+
else if (gamma != 0.0f)
449447
m_transfer = dpx::kUserDefined;
450-
else if (colorconfig.equivalent(colorspace, "KodakLog"))
448+
else if (spec_s.get_string_attribute("oiio:ColorSpace") == "KodakLog")
451449
m_transfer = dpx::kLogarithmic;
452450
else {
453451
std::string dpxtransfer = spec_s.get_string_attribute("dpx:Transfer",

src/heif.imageio/heifoutput.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include <OpenImageIO/platform.h>
1111
#include <OpenImageIO/tiffutils.h>
1212

13+
#include "imageio_pvt.h"
14+
1315
#include <libheif/heif_cxx.h>
1416

1517
#define MAKE_LIBHEIF_VERSION(a, b, c, d) \
@@ -250,12 +252,7 @@ HeifOutput::close()
250252
std::unique_ptr<heif_color_profile_nclx,
251253
void (*)(heif_color_profile_nclx*)>
252254
nclx(heif_nclx_color_profile_alloc(), heif_nclx_color_profile_free);
253-
const ColorConfig& colorconfig(ColorConfig::default_colorconfig());
254-
const ParamValue* p = m_spec.find_attribute("CICP",
255-
TypeDesc(TypeDesc::INT, 4));
256-
string_view colorspace = m_spec.get_string_attribute("oiio:ColorSpace");
257-
cspan<int> cicp = (p) ? p->as_cspan<int>()
258-
: colorconfig.get_cicp(colorspace);
255+
cspan<int> cicp = pvt::get_colorspace_cicp(m_spec);
259256
if (!cicp.empty()) {
260257
nclx->color_primaries = heif_color_primaries(cicp[0]);
261258
nclx->transfer_characteristics = heif_transfer_characteristics(

src/include/OpenImageIO/imageio.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4198,7 +4198,6 @@ OIIO_API void set_colorspace(ImageSpec& spec, string_view name);
41984198
/// @version 3.0
41994199
OIIO_API void set_colorspace_rec709_gamma(ImageSpec& spec, float gamma);
42004200

4201-
42024201
/// Are the two named color spaces equivalent, based on the default color
42034202
/// config in effect?
42044203
///

src/include/imageio_pvt.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,31 @@ parallel_convert_from_float(const float* src, void* dst, size_t nvals,
127127
OIIO_API bool
128128
check_texture_metadata_sanity(ImageSpec& spec);
129129

130+
/// Returns true if for the purpose of interop, the spec's metadata
131+
/// specifies a color space that should be encoded as sRGB.
132+
///
133+
/// If default_to_srgb is true, the colorspace will be assumed to
134+
/// be sRGB if no colorspace was specified in the spec.
135+
OIIO_API bool
136+
is_colorspace_srgb(const ImageSpec& spec, bool default_to_srgb = true);
137+
138+
/// If the spec's metadata specifies a color space with Rec709 primaries and
139+
/// gamma transfer function, return the gamma value. If not, return zero.
140+
OIIO_API float
141+
get_colorspace_rec709_gamma(const ImageSpec& spec);
142+
143+
// Returns ICC profile from the spec's metadata, either from an ICCProfile
144+
// attribute or from the colorspace if from_colorspace is true.
145+
// Returns an empty vector if not found.
146+
std::vector<uint8_t>
147+
get_colorspace_icc_profile(const ImageSpec& spec, bool from_colorspace = true);
148+
149+
// Returns ICC profile from the spec's metadata, either from a CICP attribute
150+
// or from the colorspace if from_colorspace is true.
151+
// Returns an empty span if not found.
152+
cspan<int>
153+
get_colorspace_cicp(const ImageSpec& spec, bool from_colorspace = true);
154+
130155
/// Get the timing report from log_time entries.
131156
OIIO_API std::string
132157
timing_report();

src/iv/imageviewer.cpp

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,11 @@
4444
#include <OpenImageIO/sysutil.h>
4545
#include <OpenImageIO/timer.h>
4646

47+
#include "imageio_pvt.h"
4748

4849
#include "ivutils.h"
4950

5051

51-
namespace {
52-
53-
inline bool
54-
IsSpecSrgb(const ImageSpec& spec)
55-
{
56-
return equivalent_colorspace(spec.get_string_attribute("oiio:ColorSpace"),
57-
"srgb_rec709_scene");
58-
}
59-
60-
} // namespace
61-
62-
6352
// clang-format off
6453
static const char *s_file_filters = ""
6554
"Image Files (*.bmp *.cin *.dcm *.dds *.dpx *.fits *.gif *.hdr *.ico *.iff "
@@ -1242,7 +1231,8 @@ ImageViewer::loadCurrentImage(int subimage, int miplevel)
12421231
//std::cerr << "Loading HALF-FLOAT as FLOAT\n";
12431232
read_format = TypeDesc::FLOAT;
12441233
}
1245-
if (IsSpecSrgb(image_spec) && !glwin->is_srgb_capable()) {
1234+
if (pvt::is_colorspace_srgb(image_spec)
1235+
&& !glwin->is_srgb_capable()) {
12461236
// If the image is in sRGB, but OpenGL can't load sRGB textures then
12471237
// we'll need to do the transformation on the CPU after loading the
12481238
// image. We (so far) can only do this with UINT8 images, so make
@@ -1257,7 +1247,8 @@ ImageViewer::loadCurrentImage(int subimage, int miplevel)
12571247
read_format = TypeDesc::UINT8;
12581248
allow_transforms = true;
12591249

1260-
if (IsSpecSrgb(image_spec) && !glwin->is_srgb_capable())
1250+
if (pvt::is_colorspace_srgb(image_spec)
1251+
&& !glwin->is_srgb_capable())
12611252
srgb_transform = true;
12621253
}
12631254

@@ -1443,7 +1434,7 @@ ImageViewer::exposureMinusOneTenthStop()
14431434
img->exposure(img->exposure() - 0.1);
14441435
if (!glwin->is_glsl_capable()) {
14451436
bool srgb_transform = (!glwin->is_srgb_capable()
1446-
&& IsSpecSrgb(img->spec()));
1437+
&& pvt::is_colorspace_srgb(img->spec()));
14471438
img->pixel_transform(srgb_transform, (int)current_color_mode(),
14481439
current_channel());
14491440
displayCurrentImage();
@@ -1462,7 +1453,7 @@ ImageViewer::exposureMinusOneHalfStop()
14621453
img->exposure(img->exposure() - 0.5);
14631454
if (!glwin->is_glsl_capable()) {
14641455
bool srgb_transform = (!glwin->is_srgb_capable()
1465-
&& IsSpecSrgb(img->spec()));
1456+
&& pvt::is_colorspace_srgb(img->spec()));
14661457
img->pixel_transform(srgb_transform, (int)current_color_mode(),
14671458
current_channel());
14681459
displayCurrentImage();
@@ -1481,7 +1472,7 @@ ImageViewer::exposurePlusOneTenthStop()
14811472
img->exposure(img->exposure() + 0.1);
14821473
if (!glwin->is_glsl_capable()) {
14831474
bool srgb_transform = (!glwin->is_srgb_capable()
1484-
&& IsSpecSrgb(img->spec()));
1475+
&& pvt::is_colorspace_srgb(img->spec()));
14851476
img->pixel_transform(srgb_transform, (int)current_color_mode(),
14861477
current_channel());
14871478
displayCurrentImage();
@@ -1500,7 +1491,7 @@ ImageViewer::exposurePlusOneHalfStop()
15001491
img->exposure(img->exposure() + 0.5);
15011492
if (!glwin->is_glsl_capable()) {
15021493
bool srgb_transform = (!glwin->is_srgb_capable()
1503-
&& IsSpecSrgb(img->spec()));
1494+
&& pvt::is_colorspace_srgb(img->spec()));
15041495
img->pixel_transform(srgb_transform, (int)current_color_mode(),
15051496
current_channel());
15061497
displayCurrentImage();
@@ -1520,7 +1511,7 @@ ImageViewer::gammaMinus()
15201511
img->gamma(img->gamma() - 0.05);
15211512
if (!glwin->is_glsl_capable()) {
15221513
bool srgb_transform = (!glwin->is_srgb_capable()
1523-
&& IsSpecSrgb(img->spec()));
1514+
&& pvt::is_colorspace_srgb(img->spec()));
15241515
img->pixel_transform(srgb_transform, (int)current_color_mode(),
15251516
current_channel());
15261517
displayCurrentImage();
@@ -1539,7 +1530,7 @@ ImageViewer::gammaPlus()
15391530
img->gamma(img->gamma() + 0.05);
15401531
if (!glwin->is_glsl_capable()) {
15411532
bool srgb_transform = (!glwin->is_srgb_capable()
1542-
&& IsSpecSrgb(img->spec()));
1533+
&& pvt::is_colorspace_srgb(img->spec()));
15431534
img->pixel_transform(srgb_transform, (int)current_color_mode(),
15441535
current_channel());
15451536
displayCurrentImage();
@@ -1572,7 +1563,7 @@ ImageViewer::viewChannel(int c, COLOR_MODE colormode)
15721563
IvImage* img = cur();
15731564
if (img) {
15741565
bool srgb_transform = (!glwin->is_srgb_capable()
1575-
&& IsSpecSrgb(img->spec()));
1566+
&& pvt::is_colorspace_srgb(img->spec()));
15761567
img->pixel_transform(srgb_transform, (int)colormode, c);
15771568
}
15781569
} else {

src/iv/ivgl.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
# include <QPen>
1919
#endif
2020

21+
#include "imageio_pvt.h"
2122
#include "ivutils.h"
2223
#include <OpenImageIO/strutil.h>
2324
#include <OpenImageIO/timer.h>
@@ -2198,9 +2199,7 @@ IvGL::typespec_to_opengl(const ImageSpec& spec, int nchannels, GLenum& gltype,
21982199
break;
21992200
}
22002201

2201-
bool issrgb
2202-
= equivalent_colorspace(spec.get_string_attribute("oiio:ColorSpace"),
2203-
"srgb_rec709_scene");
2202+
bool issrgb = pvt::is_colorspace_srgb(spec);
22042203

22052204
glinternalformat = nchannels;
22062205
if (nchannels == 1) {

src/jpeg.imageio/jpegoutput.cpp

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include <OpenImageIO/imageio.h>
1313
#include <OpenImageIO/tiffutils.h>
1414

15+
#include "imageio_pvt.h"
16+
1517
#include "jpeg_pvt.h"
1618

1719
OIIO_PLUGIN_NAMESPACE_BEGIN
@@ -268,8 +270,7 @@ JpgOutput::open(const std::string& name, const ImageSpec& newspec,
268270
}
269271
}
270272

271-
if (equivalent_colorspace(m_spec.get_string_attribute("oiio:ColorSpace"),
272-
"srgb_rec709_scene"))
273+
if (pvt::is_colorspace_srgb(m_spec, false))
273274
m_spec.attribute("Exif:ColorSpace", 1);
274275

275276
// Write EXIF info
@@ -320,37 +321,32 @@ JpgOutput::open(const std::string& name, const ImageSpec& newspec,
320321
m_spec.set_format(TypeDesc::UINT8); // JPG is only 8 bit
321322

322323
// Write ICC profile, if we have anything
323-
if (auto icc_profile_parameter = m_spec.find_attribute(ICC_PROFILE_ATTR)) {
324-
cspan<unsigned char> icc_profile((unsigned char*)
325-
icc_profile_parameter->data(),
326-
icc_profile_parameter->type().size());
327-
if (icc_profile.size() && icc_profile.data()) {
328-
/* Calculate the number of markers we'll need, rounding up of course */
329-
size_t num_markers = icc_profile.size() / MAX_DATA_BYTES_IN_MARKER;
330-
if (num_markers * MAX_DATA_BYTES_IN_MARKER
331-
!= std::size(icc_profile))
332-
num_markers++;
333-
int curr_marker = 1; /* per spec, count starts at 1*/
334-
std::vector<JOCTET> profile(MAX_DATA_BYTES_IN_MARKER
335-
+ ICC_HEADER_SIZE);
336-
size_t icc_profile_length = icc_profile.size();
337-
while (icc_profile_length > 0) {
338-
// length of profile to put in this marker
339-
size_t length = std::min(icc_profile_length,
340-
size_t(MAX_DATA_BYTES_IN_MARKER));
341-
icc_profile_length -= length;
342-
// Write the JPEG marker header (APP2 code and marker length)
343-
strcpy((char*)profile.data(), "ICC_PROFILE"); // NOSONAR
344-
profile[11] = 0;
345-
profile[12] = curr_marker;
346-
profile[13] = (JOCTET)num_markers;
347-
OIIO_ASSERT(profile.size() >= ICC_HEADER_SIZE + length);
348-
spancpy(make_span(profile), ICC_HEADER_SIZE, icc_profile,
349-
length * (curr_marker - 1), length);
350-
jpeg_write_marker(&m_cinfo, JPEG_APP0 + 2, profile.data(),
351-
ICC_HEADER_SIZE + length);
352-
curr_marker++;
353-
}
324+
std::vector<uint8_t> icc_profile = pvt::get_colorspace_icc_profile(m_spec);
325+
if (icc_profile.size()) {
326+
/* Calculate the number of markers we'll need, rounding up of course */
327+
size_t num_markers = icc_profile.size() / MAX_DATA_BYTES_IN_MARKER;
328+
if (num_markers * MAX_DATA_BYTES_IN_MARKER != std::size(icc_profile))
329+
num_markers++;
330+
int curr_marker = 1; /* per spec, count starts at 1*/
331+
std::vector<JOCTET> profile(MAX_DATA_BYTES_IN_MARKER + ICC_HEADER_SIZE);
332+
size_t icc_profile_length = icc_profile.size();
333+
while (icc_profile_length > 0) {
334+
// length of profile to put in this marker
335+
size_t length = std::min(icc_profile_length,
336+
size_t(MAX_DATA_BYTES_IN_MARKER));
337+
icc_profile_length -= length;
338+
// Write the JPEG marker header (APP2 code and marker length)
339+
strcpy((char*)profile.data(), "ICC_PROFILE"); // NOSONAR
340+
profile[11] = 0;
341+
profile[12] = curr_marker;
342+
profile[13] = (JOCTET)num_markers;
343+
OIIO_ASSERT(profile.size() >= ICC_HEADER_SIZE + length);
344+
spancpy(make_span(profile), ICC_HEADER_SIZE,
345+
cspan<uint8_t>(icc_profile), length * (curr_marker - 1),
346+
length);
347+
jpeg_write_marker(&m_cinfo, JPEG_APP0 + 2, profile.data(),
348+
ICC_HEADER_SIZE + length);
349+
curr_marker++;
354350
}
355351
}
356352

src/jpeg2000.imageio/jpeg2000output.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -481,10 +481,10 @@ Jpeg2000Output::create_jpeg2000_image()
481481
// someboody comes along that desperately needs JPEG2000 and ICC
482482
// profiles, maybe they will be motivated enough to track down the
483483
// problem.
484-
const ParamValue *icc = m_spec.find_attribute ("ICCProfile");
485-
if (icc && icc->type().basetype == TypeDesc::UINT8 && icc->type().arraylen > 0) {
486-
m_image->icc_profile_len = icc->type().arraylen;
487-
m_image->icc_profile_buf = (unsigned char *) icc->data();
484+
std::vector<uint8_t> icc_profile = get_colorspace_icc_profile(m_spec);
485+
if (icc_profile.size()) {
486+
m_image->icc_profile_len = icc_profile.size();
487+
m_image->icc_profile_buf = (unsigned char *) icc_profile.data();
488488
}
489489
#endif
490490

src/jpegxl.imageio/jxloutput.cpp

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// https://github.com/AcademySoftwareFoundation/OpenImageIO
44

55
#include <cassert>
6+
#include <cstdint>
67
#include <cstdio>
78
#include <vector>
89

@@ -11,6 +12,8 @@
1112
#include <OpenImageIO/imageio.h>
1213
#include <OpenImageIO/tiffutils.h>
1314

15+
#include "imageio_pvt.h"
16+
1417
#include <jxl/decode.h>
1518
#include <jxl/encode.h>
1619
#include <jxl/encode_cxx.h>
@@ -539,18 +542,12 @@ JxlOutput::save_image(const void* data)
539542
}
540543

541544
// Write the ICC profile, if available
542-
const ParamValue* icc_profile_parameter = m_spec.find_attribute(
543-
"ICCProfile");
544-
if (icc_profile_parameter != nullptr) {
545-
unsigned char* icc_profile
546-
= (unsigned char*)icc_profile_parameter->data();
547-
uint32_t length = icc_profile_parameter->type().size();
548-
if (icc_profile && length) {
549-
if (JXL_ENC_SUCCESS
550-
!= JxlEncoderSetICCProfile(m_encoder.get(), icc_profile,
551-
length)) {
552-
errorfmt("JxlEncoderSetICCProfile failed\n");
553-
}
545+
std::vector<uint8_t> icc_profile = pvt::get_colorspace_icc_profile(m_spec);
546+
if (icc_profile.size()) {
547+
if (JXL_ENC_SUCCESS
548+
!= JxlEncoderSetICCProfile(m_encoder.get(), icc_profile.data(),
549+
icc_profile.size())) {
550+
errorfmt("JxlEncoderSetICCProfile failed\n");
554551
}
555552
}
556553

0 commit comments

Comments
 (0)