Skip to content

Commit fe9d572

Browse files
authored
oiiotool: allow all filtered ops to take highlightcomp= modifier (#3239)
When doing operations with filters that have negative lobes, you can sometimes get visible ringing artifacts in very high contrast regions of HDR images (you tend not to see these artifacts in images that have range [0,1] because the ringing is very low amplitude unless the input is very large). You can do "highlight compensation" to reduce the artifacts when they occur by doing --rangecompress, which does a log transform on the input, then the filtered op, then --rangeexpand to transform back to a linear space. But that makes for kind of verbose and clumsy command lines, and makes it easy to err by doing the transforms in the wrong sequence or omitting one. The -otex command takes a highlightcomp=1 modifier, which does this bracketing automatically, foolproof, with a compact notation. This patch adds support for optional highlightcomp=1 modifier for all of the oiiotool commands that let you specify a filter= option: --rotate, --warp, --resize, --fit, and --pixelaspect.
1 parent 399e1fa commit fe9d572

File tree

2 files changed

+114
-22
lines changed

2 files changed

+114
-22
lines changed

src/doc/oiiotool.rst

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2910,10 +2910,16 @@ current top image.
29102910

29112911
Optional appended modifiers include:
29122912

2913-
`filter=` *name*
2913+
`:filter=` *name*
29142914
Filter name. The default is `blackman-harris` when increasing
29152915
resolution, `lanczos3` when decreasing resolution.
29162916

2917+
`:highlightcomp=` *val*
2918+
If nonzero, does highlight compensation by surrounding the filtered
2919+
operation with the equivalent of `--rangecompress` and
2920+
`--rangeexpand`, which can reduce visible ringing artifacts when a
2921+
filter with negative lobes is used on a very high-contrast HDR image.
2922+
29172923
`:subimages=` *indices-or-names*
29182924
Include/exclude subimages (see :ref:`sec-oiiotool-subimage-modifier`).
29192925

@@ -2940,6 +2946,11 @@ current top image.
29402946

29412947
- `filter=` *name* : Filter name. The default is `blackman-harris` when
29422948
increasing resolution, `lanczos3` when decreasing resolution.
2949+
- `highlightcomp=` *val* : If nonzero, does highlight compensation by
2950+
surrounding the filtered operation with the equivalent of
2951+
`--rangecompress` and `--rangeexpand`, which can reduce visible ringing
2952+
artifacts when a filter with negative lobes is used on a very
2953+
high-contrast HDR image.
29432954
- `fillmode=` *mode* : determines which of several methods will be used
29442955
to determine how the image will fill the new frame, if its aspect
29452956
ratio does not precisely match the original source aspect ratio:
@@ -3013,7 +3024,13 @@ current top image.
30133024

30143025
Optional appended modifiers include:
30153026

3016-
- `filter=` *name* : Filter name. The default is `lanczos3`.
3027+
- `:filter=` *name* : Filter name. The default is `lanczos3`.
3028+
3029+
- `:highlightcomp=` *val* : If nonzero, does highlight compensation by
3030+
surrounding the filtered operation with the equivalent of
3031+
`--rangecompress` and `--rangeexpand`, which can reduce visible
3032+
ringing artifacts when a filter with negative lobes is used on a very
3033+
high-contrast HDR image.
30173034

30183035
Examples::
30193036

@@ -3030,13 +3047,19 @@ current top image.
30303047

30313048
Optional appended modifiers include:
30323049

3033-
`center=` *x,y*
3050+
`:center=` *x,y*
30343051
Alternate center of rotation.
30353052

3036-
`filter=` *name*
3053+
`:filter=` *name*
30373054
Filter name. The default is `lanczos3`.
30383055

3039-
`recompute_roi=` *val*
3056+
`:highlightcomp=` *val*
3057+
If nonzero, does highlight compensation by surrounding the filtered
3058+
operation with the equivalent of `--rangecompress` and
3059+
`--rangeexpand`, which can reduce visible ringing artifacts when a
3060+
filter with negative lobes is used on a very high-contrast HDR image.
3061+
3062+
`:recompute_roi=` *val*
30403063
If nonzero, recompute the pixel data window to exactly hold the
30413064
transformed image (default=0).
30423065

@@ -3063,10 +3086,16 @@ current top image.
30633086

30643087
Optional appended modifiers include:
30653088

3066-
`filter=` *name*
3089+
`:filter=` *name*
30673090
Filter name. The default is `lanczos3`.
30683091

3069-
`recompute_roi=` *val*
3092+
`:highlightcomp=` *val*
3093+
If nonzero, does highlight compensation by surrounding the filtered
3094+
operation with the equivalent of `--rangecompress` and
3095+
`--rangeexpand`, which can reduce visible ringing artifacts when a
3096+
filter with negative lobes is used on a very high-contrast HDR image.
3097+
3098+
`:recompute_roi=` *val*
30703099
If nonzero, recompute the pixel data window to exactly hold the
30713100
transformed image (default=0).
30723101

src/oiiotool/oiiotool.cpp

Lines changed: 78 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3208,6 +3208,7 @@ action_reorient(int argc, const char* argv[])
32083208
OIIOTOOL_OP(rotate, 1, [](OiiotoolOp& op, span<ImageBuf*> img) {
32093209
float angle = Strutil::from_string<float>(op.args(1));
32103210
std::string filtername = op.options()["filter"];
3211+
bool highlightcomp = op.options().get_int("highlightcomp");
32113212
bool recompute_roi = op.options().get_int("recompute_roi");
32123213
std::string cent = op.options()["center"];
32133214
string_view center(cent);
@@ -3222,25 +3223,55 @@ OIIOTOOL_OP(rotate, 1, [](OiiotoolOp& op, span<ImageBuf*> img) {
32223223
cx = 0.5f * (src_roi_full.xbegin + src_roi_full.xend);
32233224
cy = 0.5f * (src_roi_full.ybegin + src_roi_full.yend);
32243225
}
3225-
return ImageBufAlgo::rotate(*img[0], *img[1], angle * float(M_PI / 180.0),
3226-
cx, cy, filtername, 0.0f, recompute_roi);
3226+
bool ok = true;
3227+
ImageBuf tmpimg;
3228+
ImageBuf* src = img[1];
3229+
if (highlightcomp) {
3230+
// If the caller requested highlight compensation for an HDR image to
3231+
// prevent ringing artifacts, we make a temporary image with the
3232+
// reduced-contrast data.
3233+
ok &= ImageBufAlgo::rangecompress(tmpimg, *src);
3234+
src = &tmpimg;
3235+
}
3236+
ok &= ImageBufAlgo::rotate(*img[0], *src, angle * float(M_PI / 180.0), cx,
3237+
cy, filtername, 0.0f, recompute_roi);
3238+
if (highlightcomp && ok) {
3239+
// re-expand the range in place
3240+
ok &= ImageBufAlgo::rangeexpand(*img[0], *img[0]);
3241+
}
3242+
return ok;
32273243
});
32283244

32293245

32303246

32313247
// --warp
32323248
OIIOTOOL_OP(warp, 1, [](OiiotoolOp& op, span<ImageBuf*> img) {
32333249
std::string filtername = op.options()["filter"];
3250+
bool highlightcomp = op.options().get_int("highlightcomp");
32343251
bool recompute_roi = op.options().get_int("recompute_roi");
32353252
std::vector<float> M(9);
32363253
if (Strutil::extract_from_list_string(M, op.args(1)) != 9) {
32373254
ot.error(op.opname(),
32383255
"expected 9 comma-separatd floats to form a 3x3 matrix");
32393256
return false;
32403257
}
3241-
return ImageBufAlgo::warp(*img[0], *img[1], *(Imath::M33f*)&M[0],
3242-
filtername, 0.0f, recompute_roi,
3243-
ImageBuf::WrapDefault);
3258+
bool ok = true;
3259+
ImageBuf tmpimg;
3260+
ImageBuf* src = img[1];
3261+
if (highlightcomp) {
3262+
// If the caller requested highlight compensation for an HDR image to
3263+
// prevent ringing artifacts, we make a temporary image with the
3264+
// reduced-contrast data.
3265+
ok &= ImageBufAlgo::rangecompress(tmpimg, *src);
3266+
src = &tmpimg;
3267+
}
3268+
ok &= ImageBufAlgo::warp(*img[0], *src, *(Imath::M33f*)&M[0], filtername,
3269+
0.0f, recompute_roi, ImageBuf::WrapDefault);
3270+
if (highlightcomp && ok) {
3271+
// re-expand the range in place
3272+
ok &= ImageBufAlgo::rangeexpand(*img[0], *img[0]);
3273+
}
3274+
return ok;
32443275
});
32453276

32463277

@@ -3802,6 +3833,7 @@ class OpResize final : public OiiotoolOp {
38023833
virtual bool impl(span<ImageBuf*> img) override
38033834
{
38043835
std::string filtername = options()["filter"];
3836+
bool highlightcomp = options().get_int("highlightcomp");
38053837
if (ot.debug) {
38063838
const ImageSpec& newspec(img[0]->spec());
38073839
const ImageSpec& Aspec(img[1]->spec());
@@ -3811,8 +3843,23 @@ class OpResize final : public OiiotoolOp {
38113843
<< (filtername.size() ? filtername.c_str() : "default")
38123844
<< " filter\n";
38133845
}
3814-
return ImageBufAlgo::resize(*img[0], *img[1], filtername, 0.0f,
3815-
img[0]->roi());
3846+
bool ok = true;
3847+
ImageBuf tmpimg;
3848+
ImageBuf* src = img[1];
3849+
if (highlightcomp) {
3850+
// If the caller requested highlight compensation for an HDR image
3851+
// to prevent ringing artifacts, we make a temporary image with
3852+
// the reduced-contrast data.
3853+
ok &= ImageBufAlgo::rangecompress(tmpimg, *src);
3854+
src = &tmpimg;
3855+
}
3856+
ok &= ImageBufAlgo::resize(*img[0], *src, filtername, 0.0f,
3857+
img[0]->roi());
3858+
if (highlightcomp && ok) {
3859+
// re-expand the range in place
3860+
ok &= ImageBufAlgo::rangeexpand(*img[0], *img[0]);
3861+
}
3862+
return ok;
38163863
}
38173864
};
38183865

@@ -3851,18 +3898,31 @@ action_fit(cspan<const char*> argv)
38513898
std::string filtername = options["filter"];
38523899
std::string fillmode = options["fillmode"];
38533900
bool exact = options.get_int("exact");
3901+
bool highlightcomp = options.get_int("highlightcomp");
38543902

38553903
int subimages = allsubimages ? A->subimages() : 1;
38563904
ImageRecRef R(new ImageRec(A->name(), subimages));
38573905
for (int s = 0; s < subimages; ++s) {
38583906
ImageSpec newspec = (*A)(s, 0).spec();
3907+
ImageBuf tmpimg;
3908+
ImageBuf* src = &((*A)(s, 0));
3909+
if (highlightcomp) {
3910+
// If the caller requested highlight compensation for an HDR image
3911+
// to prevent ringing artifacts, we make a temporary image with
3912+
// the reduced-contrast data.
3913+
ImageBufAlgo::rangecompress(tmpimg, *src);
3914+
src = &tmpimg;
3915+
}
38593916
newspec.width = newspec.full_width = fit_full_width;
38603917
newspec.height = newspec.full_height = fit_full_height;
38613918
newspec.x = newspec.full_x = fit_full_x;
38623919
newspec.y = newspec.full_y = fit_full_y;
38633920
(*R)(s, 0).reset(newspec);
3864-
ImageBufAlgo::fit((*R)(s, 0), (*A)(s, 0), filtername, 0.0f, fillmode,
3865-
exact);
3921+
ImageBufAlgo::fit((*R)(s, 0), *src, filtername, 0.0f, fillmode, exact);
3922+
if (highlightcomp) {
3923+
// re-expand the range in place
3924+
ImageBufAlgo::rangeexpand((*R)(s, 0), (*R)(s, 0));
3925+
}
38663926
R->update_spec_from_imagebuf(s, 0);
38673927
}
38683928
ot.pop();
@@ -3938,6 +3998,7 @@ action_pixelaspect(int argc, const char* argv[])
39383998

39393999
auto options = ot.extract_options(command);
39404000
std::string filtername = options["filter"];
4001+
bool highlightcomp = options.get_int("highlightcomp");
39414002

39424003
if (ot.debug) {
39434004
std::cout << " Scaling "
@@ -3955,6 +4016,8 @@ action_pixelaspect(int argc, const char* argv[])
39554016
std::string command = "resize";
39564017
if (filtername.size())
39574018
command += Strutil::sprintf(":filter=%s", filtername);
4019+
if (highlightcomp)
4020+
command += ":highlightcomp=1";
39584021
const char* newargv[2] = { command.c_str(), resize.c_str() };
39594022
action_resize(2, newargv);
39604023
A = ot.top();
@@ -4816,7 +4879,7 @@ prep_texture_config(ImageSpec& configspec, ParamValueList& fileoptions)
48164879
configspec.attribute(
48174880
"maketx:highlightcomp",
48184881
fileoptions.get_int("highlightcomp",
4819-
fileoptions.get_int("hilightcomp",
4882+
fileoptions.get_int("highlightcomp",
48204883
fileoptions.get_int("hicomp"))));
48214884
configspec.attribute("maketx:sharpen", fileoptions.get_float("sharpen"));
48224885
if (fileoptions.contains("filter") || fileoptions.contains("filtername"))
@@ -6042,19 +6105,19 @@ getargs(int argc, char* argv[])
60426105
.help("Resample (640x480, 50%) (options: interp=0)")
60436106
.action(action_resample);
60446107
ap.arg("--resize %s:GEOM")
6045-
.help("Resize (640x480, 50%) (options: filter=%s)")
6108+
.help("Resize (640x480, 50%) (options: filter=%s, highlightcomp=%d)")
60466109
.action(action_resize);
60476110
ap.arg("--fit %s:GEOM")
6048-
.help("Resize to fit within a window size (options: filter=%s, pad=%d, exact=%d)")
6111+
.help("Resize to fit within a window size (options: filter=%s, pad=%d, fillmode=%s, exact=%d, highlightcomp=%d)")
60496112
.action(action_fit);
60506113
ap.arg("--pixelaspect %g:ASPECT")
6051-
.help("Scale up the image's width or height to match the given pixel aspect ratio (options: filter=%s)")
6114+
.help("Scale up the image's width or height to match the given pixel aspect ratio (options: filter=%s, highlightcomp=%d)")
60526115
.action(action_pixelaspect);
60536116
ap.arg("--rotate %g:DEGREES")
6054-
.help("Rotate pixels (degrees clockwise) around the center of the display window (options: filter=%s, center=%f,%f, recompute_roi=%d")
6117+
.help("Rotate pixels (degrees clockwise) around the center of the display window (options: filter=%s, center=%f,%f, recompute_roi=%d, highlightcomp=%d")
60556118
.action(action_rotate);
60566119
ap.arg("--warp %s:MATRIX")
6057-
.help("Warp pixels (argument is a 3x3 matrix, separated by commas) (options: filter=%s, recompute_roi=%d)")
6120+
.help("Warp pixels (argument is a 3x3 matrix, separated by commas) (options: filter=%s, recompute_roi=%d, highlightcomp=%d)")
60586121
.action(action_warp);
60596122
ap.arg("--convolve")
60606123
.help("Convolve with a kernel")

0 commit comments

Comments
 (0)