Skip to content

Commit e8f42d0

Browse files
committed
Color transform unpremult logic changes.
After some deep thinking and discussion with people who know more color than me, I have become convinced that you *almost always* want to make sure to un-premultiply colors (i.e. divide by alpha) before doing color space transformations with OCIO, and then re-multiply by alpha (to get back to "associated alpha") immediately after the color transformation. To support this, * For the ImageBufAlgo color transformation functions, change the default value of the `unpremult` parameter from `false` to `true`. * `oiiotool --autocc` will do the unpremult/premult steps surrounding any automatic color transformations that it does upon input and output. * `oiiotool --colorconvert` and the various `--ocio` commands now take an optional modifier `:unpremult=1` which will cause the conversion to be internally bracked by a unpremult/premult step (when you do this, you no longer need an explicit --unpremult and --premult on the command line). * oiiotool now issues warnings for cases where it has a good reason to think you might be double-unpremulting (for example, if you do both --unpremult and follow it by a --colorconvert:unpremult=1). Later, we'll stage a second part of this where we change the default value for unpremult= to be 1, so that the modifier is not needed (at which point you REALLY don't want the explicit --unpremult/--premult, or it will be wrong). On the whole, I think this makes the situation less error prone for casual users. Most oiiotool command lines should just use --autocc and then not need to worry about any explicit use of `--colorconvert` or `--unpremult/--premult`. For the rare case when more control is needed, the individual commands and modifiers can be used. Note that for oiiotool, `--autocc` is not on by default. If you want images auto-converted to linear space and ouputs auto-converted from linear to the desired output space, you need to use `--autocc`.
1 parent 41bc41e commit e8f42d0

File tree

8 files changed

+123
-35
lines changed

8 files changed

+123
-35
lines changed

CHANGES.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ Public API changes:
4343
only be used to pass into certain IBA functions). The color space names
4444
"rgb" and "default" are now understood to be synonyms for the default
4545
"linear" color space. #1788 (1.9.0)
46+
* `ImageBufAlgo::colorconvert` and various `ocio` transformations have
47+
changed the default value of their `unpremult` parameter from `false` to
48+
`true`, reflecting the fact that we believe this is almost always the more
49+
correct choice. (1.9.2)
4650
* Remove long-deprecated API calls:
4751
* ImageBuf::get_pixels/get_pixel_channels varieties deprecated since 1.6.
4852
* ImageBuf::set_deep_value_uint, deprecated since 1.7.
@@ -68,6 +72,14 @@ Fixes, minor enhancements, and performance improvements:
6872
look good converted to greyscale, and usable by people with color
6973
blindness. #1820 (1.9.2)
7074
* oiiotool no longer enables autotile by default. #1856 (1.9.2)
75+
* `--colorconvert`, `--tocolorspace`, and all of the `--ocio` commands
76+
now by default will bracket the transformation unpremult/premult
77+
steps, i.e., divide the RGB values by alpha (if present and nonzero)
78+
before the color operation, then immediately re-multiply by alpha.
79+
This can be disabled using a new optional modifier `:unpremult=0`.
80+
(1.9.2)
81+
* `--autocc` will also cause unpremult/premult to bracket any color
82+
transformations it does automatically for read and write. (1.9.2)
7183
* ImageBufAlgo:
7284
* `color_map()` new maps "inferno", "magma", "plasma", "viridis".
7385
#1820 (1.9.2)

src/doc/oiiotool.tex

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,7 @@ \subsection*{Reading images}
10191019
\apiend
10201020

10211021
\apiitem{\ce --autocc}
1022-
Turns on automatic color conversion: Every input image file will be
1022+
Turns on automatic color space conversion: Every input image file will be
10231023
immediately converted to a scene-referred linear color space, and every file
10241024
written will be first transformed to an appropriate output color space based
10251025
on the filename or type. Additionally, if the name of an output file
@@ -2903,6 +2903,11 @@ \section{\oiiotool commands for color management}
29032903
& {\cf value=}\emph{str} & Adds a key/value pair to the ``context'' that
29042904
OpenColorIO will used when applying the look. Multiple key/value pairs
29052905
may be specified by making each one a comma-separated list. \\
2906+
& {\cf unpremult=}\emph{val} & If the numeric \emph{val} is nonzero, the
2907+
pixel values will be ``un-premultipled'' (divided by alpha) prior to
2908+
the actual color conversion, and then re-multipled by alpha afterwards.
2909+
The default is 0, meaning the color transformation not will be
2910+
automatically bracketed by divide-by-alpha / mult-by-alpha operations. \\
29062911
& {\cf strict=}\emph{val} & When nonzero (the default), an
29072912
inability to perform the color transform will be a hard error. If
29082913
strict is 0, inability to find the transformation will just print a
@@ -2936,6 +2941,11 @@ \section{\oiiotool commands for color management}
29362941
& {\cf value=}\emph{str} & Adds a key/value pair to the ``context'' that
29372942
OpenColorIO will used when applying the look. Multiple key/value pairs
29382943
may be specified by making each one a comma-separated list. \\
2944+
& {\cf unpremult=}\emph{val} & If the numeric \emph{val} is nonzero, the
2945+
pixel values will be ``un-premultipled'' (divided by alpha) prior to
2946+
the actual color conversion, and then re-multipled by alpha afterwards.
2947+
The default is 0, meaning the color transformation not will be
2948+
automatically bracketed by divide-by-alpha / mult-by-alpha operations. \\
29392949
\end{tabular}
29402950

29412951
This command is only meaningful if OIIO was compiled with OCIO support
@@ -2966,6 +2976,11 @@ \section{\oiiotool commands for color management}
29662976
& {\cf value=}\emph{str} & Adds a key/value pair to the ``context'' that
29672977
OpenColorIO will used when applying the display transform. Multiple key/value pairs
29682978
may be specified by making each one a comma-separated list. \\
2979+
& {\cf unpremult=}\emph{val} & If the numeric \emph{val} is nonzero, the
2980+
pixel values will be ``un-premultipled'' (divided by alpha) prior to
2981+
the actual color conversion, and then re-multipled by alpha afterwards.
2982+
The default is 0, meaning the color transformation not will be
2983+
automatically bracketed by divide-by-alpha / mult-by-alpha operations. \\
29692984
\end{tabular}
29702985

29712986
This command is only meaningful if OIIO was compiled with OCIO support
@@ -2989,6 +3004,11 @@ \section{\oiiotool commands for color management}
29893004
\begin{tabular}{p{10pt} p{1in} p{3.75in}}
29903005
& {\cf inverse=}\emph{val} & If \emph{val} is nonzero, inverts the
29913006
color transformation. \\
3007+
& {\cf unpremult=}\emph{val} & If the numeric \emph{val} is nonzero, the
3008+
pixel values will be ``un-premultipled'' (divided by alpha) prior to
3009+
the actual color conversion, and then re-multipled by alpha afterwards.
3010+
The default is 0, meaning the color transformation not will be
3011+
automatically bracketed by divide-by-alpha / mult-by-alpha operations. \\
29923012
\end{tabular}
29933013

29943014
This command is only meaningful if OIIO was compiled with OCIO support

src/doc/openimageio.tex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
\bigskip \\
9595
}
9696
\date{{\large
97-
Date: 5 Dec 2017
97+
Date: 3 Feb 2018
9898
\\ (with corrections, 4 Feb 2018)
9999
}}
100100

src/include/OpenImageIO/imagebufalgo.h

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -970,15 +970,17 @@ bool OIIO_API rangeexpand (ImageBuf &dst, const ImageBuf &src,
970970
/// size as specified by roi. If roi is not defined it will be all
971971
/// of dst, if dst is defined, or all of src, if dst is not yet defined.
972972
///
973-
/// If unpremult is true, unpremultiply before color conversion, then
974-
/// premultiply after the color conversion. You may want to use this
975-
/// flag if your image contains an alpha channel.
973+
/// If unpremult is true, divide the RGB channels by alpha (if it exists and
974+
/// is nonzero) before color conversion, then re-multiply by alpha after the
975+
/// after the color conversion. Passing unpremult=false skips this step,
976+
/// which may be desirable if you know that the image is "unassociated alpha"
977+
/// (a.k.a. "not pre-multiplied colors").
976978
///
977979
/// Return true on success, false on error (with an appropriate error
978980
/// message set in dst).
979981
bool OIIO_API colorconvert (ImageBuf &dst, const ImageBuf &src,
980982
string_view from, string_view to,
981-
bool unpremult=false,
983+
bool unpremult=true,
982984
string_view context_key="",
983985
string_view context_value="",
984986
ColorConfig *colorconfig=NULL,
@@ -991,9 +993,11 @@ bool OIIO_API colorconvert (ImageBuf &dst, const ImageBuf &src,
991993
/// size as specified by roi. If roi is not defined it will be all
992994
/// of dst, if dst is defined, or all of src, if dst is not yet defined.
993995
///
994-
/// If unpremult is true, unpremultiply before color conversion, then
995-
/// premultiply after the color conversion. You may want to use this
996-
/// flag if your image contains an alpha channel.
996+
/// If unpremult is true, divide the RGB channels by alpha (if it exists and
997+
/// is nonzero) before color conversion, then re-multiply by alpha after the
998+
/// after the color conversion. Passing unpremult=false skips this step,
999+
/// which may be desirable if you know that the image is "unassociated alpha"
1000+
/// (a.k.a. "not pre-multiplied colors").
9971001
///
9981002
/// Return true on success, false on error (with an appropriate error
9991003
/// message set in dst).
@@ -1029,7 +1033,7 @@ bool OIIO_API colorconvert (float *color, int nchannels,
10291033
/// message set in dst).
10301034
bool OIIO_API ociolook (ImageBuf &dst, const ImageBuf &src,
10311035
string_view looks, string_view from, string_view to,
1032-
bool unpremult=false, bool inverse=false,
1036+
bool unpremult=true, bool inverse=false,
10331037
string_view key="", string_view value="",
10341038
ColorConfig *colorconfig=NULL,
10351039
ROI roi=ROI::All(), int nthreads=0);
@@ -1038,23 +1042,25 @@ bool OIIO_API ociolook (ImageBuf &dst, const ImageBuf &src,
10381042
/// "display" transform. If from or looks are NULL, it will not
10391043
/// override the look or source color space (subtly different than
10401044
/// passing "", the empty string, which means to use no look or source
1041-
/// space).
1045+
/// space). If inverse is true, it will reverse the color transformation.
10421046
///
10431047
/// If dst is not yet initialized, it will be allocated to the same
10441048
/// size as specified by roi. If roi is not defined it will be all
10451049
/// of dst, if dst is defined, or all of src, if dst is not yet defined.
10461050
/// In-place operations (dst == src) are supported.
10471051
///
1048-
/// If unpremult is true, unpremultiply before color conversion, then
1049-
/// premultiply after the color conversion. You may want to use this
1050-
/// flag if your image contains an alpha channel.
1052+
/// If unpremult is true, divide the RGB channels by alpha (if it exists and
1053+
/// is nonzero) before color conversion, then re-multiply by alpha after the
1054+
/// after the color conversion. Passing unpremult=false skips this step,
1055+
/// which may be desirable if you know that the image is "unassociated alpha"
1056+
/// (a.k.a. "not pre-multiplied colors").
10511057
///
10521058
/// Return true on success, false on error (with an appropriate error
10531059
/// message set in dst).
10541060
bool OIIO_API ociodisplay (ImageBuf &dst, const ImageBuf &src,
10551061
string_view display, string_view view,
10561062
string_view from="", string_view looks="",
1057-
bool unpremult=false,
1063+
bool unpremult=true,
10581064
string_view key="", string_view value="",
10591065
ColorConfig *colorconfig=NULL,
10601066
ROI roi=ROI::All(), int nthreads=0);
@@ -1067,15 +1073,17 @@ bool OIIO_API ociodisplay (ImageBuf &dst, const ImageBuf &src,
10671073
/// size as specified by roi. If roi is not defined it will be all
10681074
/// of dst, if dst is defined, or all of src, if dst is not yet defined.
10691075
///
1070-
/// If unpremult is true, unpremultiply before color conversion, then
1071-
/// premultiply after the color conversion. You may want to use this
1072-
/// flag if your image contains an alpha channel.
1076+
/// If unpremult is true, divide the RGB channels by alpha (if it exists and
1077+
/// is nonzero) before color conversion, then re-multiply by alpha after the
1078+
/// after the color conversion. Passing unpremult=false skips this step,
1079+
/// which may be desirable if you know that the image is "unassociated alpha"
1080+
/// (a.k.a. "not pre-multiplied colors").
10731081
///
10741082
/// Return true on success, false on error (with an appropriate error
10751083
/// message set in dst).
10761084
bool OIIO_API ociofiletransform (ImageBuf &dst, const ImageBuf &src,
10771085
string_view name,
1078-
bool unpremult=false, bool inverse=false,
1086+
bool unpremult=true, bool inverse=false,
10791087
ColorConfig *colorconfig=NULL,
10801088
ROI roi=ROI::All(), int nthreads=0);
10811089

src/libOpenImageIO/imagebufalgo_pixelmath.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,11 @@ ImageBufAlgo::unpremult (ImageBuf &dst, const ImageBuf &src,
562562
{
563563
if (! IBAprep (roi, &dst, &src, IBAprep_CLAMP_MUTUAL_NCHANNELS))
564564
return false;
565-
if (src.spec().alpha_channel < 0) {
565+
if (src.spec().alpha_channel < 0
566+
// Wise? || src.spec().get_int_attribute("oiio:UnassociatedAlpha") != 0
567+
) {
568+
// If there is no alpha channel, just *copy* instead of dividing
569+
// by alpha.
566570
if (&dst != &src)
567571
return paste (dst, src.spec().x, src.spec().y, src.spec().z,
568572
roi.chbegin, src, roi, nthreads);
@@ -571,6 +575,8 @@ ImageBufAlgo::unpremult (ImageBuf &dst, const ImageBuf &src,
571575
bool ok;
572576
OIIO_DISPATCH_COMMON_TYPES2 (ok, "unpremult", unpremult_, dst.spec().format,
573577
src.spec().format, dst, src, roi, nthreads);
578+
// Mark the output as having unassociated alpha
579+
dst.specmod().attribute ("oiio:UnassociatedAlpha", 1);
574580
return ok;
575581
}
576582

@@ -624,6 +630,8 @@ ImageBufAlgo::premult (ImageBuf &dst, const ImageBuf &src,
624630
bool ok;
625631
OIIO_DISPATCH_COMMON_TYPES2 (ok, "premult", premult_, dst.spec().format,
626632
src.spec().format, dst, src, roi, nthreads);
633+
// Clear the output of any prior marking of associated alpha
634+
dst.specmod().erase_attribute ("oiio:UnassociatedAlpha");
627635
return ok;
628636
}
629637

src/oiiotool/oiiotool.cpp

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1761,6 +1761,7 @@ class OpColorConvert : public OiiotoolOp {
17611761
}
17621762
virtual void option_defaults () {
17631763
options["strict"] = "1";
1764+
options["unpremult"] = "0";
17641765
}
17651766
virtual bool setup () {
17661767
if (fromspace == tospace) {
@@ -1777,8 +1778,12 @@ class OpColorConvert : public OiiotoolOp {
17771778
string_view contextkey = options["key"];
17781779
string_view contextvalue = options["value"];
17791780
bool strict = Strutil::from_string<int>(options["strict"]);
1781+
bool unpremult = Strutil::from_string<int>(options["unpremult"]);
1782+
if (unpremult && img[1]->spec().get_int_attribute("oiio:UnassociatedAlpha") && img[1]->spec().alpha_channel >= 0) {
1783+
ot.warning (opname(), "Image appears to already be unassociated alpha (un-premultiplied color), beware double unpremult. Don't use --unpremult and also --colorconvert:unpremult=1.");
1784+
}
17801785
bool ok = ImageBufAlgo::colorconvert (*img[0], *img[1],
1781-
fromspace, tospace, false,
1786+
fromspace, tospace, unpremult,
17821787
contextkey, contextvalue,
17831788
&ot.colorconfig);
17841789
if (!ok && !strict) {
@@ -1822,6 +1827,7 @@ class OpOcioLook : public OiiotoolOp {
18221827
virtual void option_defaults () {
18231828
options["from"] = "current";
18241829
options["to"] = "current";
1830+
options["unpremult"] = "0";
18251831
}
18261832
virtual int impl (ImageBuf **img) {
18271833
string_view lookname = args[1];
@@ -1830,12 +1836,13 @@ class OpOcioLook : public OiiotoolOp {
18301836
string_view contextkey = options["key"];
18311837
string_view contextvalue = options["value"];
18321838
bool inverse = Strutil::from_string<int> (options["inverse"]);
1839+
bool unpremult = Strutil::from_string<int>(options["unpremult"]);
18331840
if (fromspace == "current" || fromspace == "")
18341841
fromspace = img[1]->spec().get_string_attribute ("oiio:Colorspace", "Linear");
18351842
if (tospace == "current" || tospace == "")
18361843
tospace = img[1]->spec().get_string_attribute ("oiio:Colorspace", "Linear");
18371844
return ImageBufAlgo::ociolook (*img[0], *img[1], lookname,
1838-
fromspace, tospace, false, inverse,
1845+
fromspace, tospace, unpremult, inverse,
18391846
contextkey, contextvalue,
18401847
&ot.colorconfig);
18411848
}
@@ -1851,6 +1858,7 @@ class OpOcioDisplay : public OiiotoolOp {
18511858
: OiiotoolOp (ot, opname, argc, argv, 1) { }
18521859
virtual void option_defaults () {
18531860
options["from"] = "current";
1861+
options["unpremult"] = "0";
18541862
}
18551863
virtual int impl (ImageBuf **img) {
18561864
string_view displayname = args[1];
@@ -1859,12 +1867,13 @@ class OpOcioDisplay : public OiiotoolOp {
18591867
string_view contextkey = options["key"];
18601868
string_view contextvalue = options["value"];
18611869
bool override_looks = options.find("looks") != options.end();
1870+
bool unpremult = Strutil::from_string<int>(options["unpremult"]);
18621871
if (fromspace == "current" || fromspace == "")
18631872
fromspace = img[1]->spec().get_string_attribute ("oiio:Colorspace", "Linear");
18641873
return ImageBufAlgo::ociodisplay (*img[0], *img[1], displayname,
18651874
viewname, fromspace,
18661875
override_looks ? options["looks"] : std::string(""),
1867-
false, contextkey, contextvalue, &ot.colorconfig);
1876+
unpremult, contextkey, contextvalue, &ot.colorconfig);
18681877
}
18691878
};
18701879

@@ -1876,12 +1885,15 @@ class OpOcioFileTransform : public OiiotoolOp {
18761885
public:
18771886
OpOcioFileTransform (Oiiotool &ot, string_view opname, int argc, const char *argv[])
18781887
: OiiotoolOp (ot, opname, argc, argv, 1) { }
1879-
virtual void option_defaults () { }
1888+
virtual void option_defaults () {
1889+
options["unpremult"] = "0";
1890+
}
18801891
virtual int impl (ImageBuf **img) {
18811892
string_view name = args[1];
18821893
bool inverse = Strutil::from_string<int> (options["inverse"]);
1894+
bool unpremult = Strutil::from_string<int>(options["unpremult"]);
18831895
return ImageBufAlgo::ociofiletransform (*img[0], *img[1], name,
1884-
false, inverse, &ot.colorconfig);
1896+
inverse, unpremult, &ot.colorconfig);
18851897
}
18861898
};
18871899

@@ -2504,8 +2516,33 @@ BINARY_IMAGE_COLOR_OP (absdiffc, ImageBufAlgo::absdiff, 0);
25042516
BINARY_IMAGE_COLOR_OP (powc, ImageBufAlgo::pow, 1.0f);
25052517

25062518
UNARY_IMAGE_OP (abs, ImageBufAlgo::abs);
2507-
UNARY_IMAGE_OP (unpremult, ImageBufAlgo::unpremult);
2508-
UNARY_IMAGE_OP (premult, ImageBufAlgo::premult);
2519+
2520+
2521+
2522+
class OpPremult : public OiiotoolOp {
2523+
public:
2524+
OpPremult (Oiiotool &ot, string_view opname, int argc, const char *argv[])
2525+
: OiiotoolOp (ot, opname, argc, argv, 1) {}
2526+
virtual int impl (ImageBuf **img) {
2527+
return ImageBufAlgo::premult (*img[0], *img[1]);
2528+
}
2529+
};
2530+
OP_CUSTOMCLASS (premult, OpPremult, 1);
2531+
2532+
2533+
2534+
class OpUnpremult : public OiiotoolOp {
2535+
public:
2536+
OpUnpremult (Oiiotool &ot, string_view opname, int argc, const char *argv[])
2537+
: OiiotoolOp (ot, opname, argc, argv, 1) {}
2538+
virtual int impl (ImageBuf **img) {
2539+
if (img[1]->spec().get_int_attribute("oiio:UnassociatedAlpha") && img[1]->spec().alpha_channel >= 0) {
2540+
ot.warning (opname(), "Image appears to already be unassociated alpha (un-premultiplied color), beware double unpremult.");
2541+
}
2542+
return ImageBufAlgo::unpremult (*img[0], *img[1]);
2543+
}
2544+
};
2545+
OP_CUSTOMCLASS (unpremult, OpUnpremult, 1);
25092546

25102547

25112548

@@ -5289,13 +5326,13 @@ getargs (int argc, char *argv[])
52895326
"--tocolorspace %@ %s", action_tocolorspace, NULL,
52905327
"Convert the current image's pixels to a named color space",
52915328
"--colorconvert %@ %s %s", action_colorconvert, NULL, NULL,
5292-
"Convert pixels from 'src' to 'dst' color space (options: key=, value=)",
5329+
"Convert pixels from 'src' to 'dst' color space (options: key=, value=, unpremult=)",
52935330
"--ociolook %@ %s", action_ociolook, NULL,
5294-
"Apply the named OCIO look (options: from=, to=, inverse=, key=, value=)",
5331+
"Apply the named OCIO look (options: from=, to=, inverse=, key=, value=, unpremult=)",
52955332
"--ociodisplay %@ %s %s", action_ociodisplay, NULL, NULL,
5296-
"Apply the named OCIO display and view (options: from=, looks=, key=, value=)",
5333+
"Apply the named OCIO display and view (options: from=, looks=, key=, value=, unpremult=)",
52975334
"--ociofiletransform %@ %s", action_ociofiletransform, NULL,
5298-
"Apply the named OCIO filetransform (options: inverse=)",
5335+
"Apply the named OCIO filetransform (options: inverse=, unpremult=)",
52995336
"--unpremult %@", action_unpremult, NULL,
53005337
"Divide all color channels of the current image by the alpha to \"un-premultiply\"",
53015338
"--premult %@", action_premult, NULL,

testsuite/oiiotool-spi/ref/out.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
Reading iffout_vd8.1001.iff
2-
iffout_vd8.1001.iff : 1998 x 1080, 4 channel, uint8 iff
1+
Reading iff_vd8.1001.iff
2+
iff_vd8.1001.iff : 1998 x 1080, 4 channel, uint8 iff
33
SHA-1: 8DCC7F60EEA962A70ACE8E443BFA73D899123332
44
channel list: R, G, B, A
55
tile size: 64 x 64
@@ -16,3 +16,5 @@ Comparing "ep0400-v2_bg1_v101_1kalxog_vd8.1001.jpg" and "../../../../../spi-oiio
1616
PASS
1717
Comparing "os0225_110_lightingfix_v002.0101.png" and "../../../../../spi-oiio-tests/ref/os0225_110_lightingfix_v002.0101.png"
1818
PASS
19+
Comparing "iff_vd8.1001.iff" and "../../../../../spi-oiio-tests/ref/iff_vd8.1001.iff"
20+
PASS

0 commit comments

Comments
 (0)