diff --git a/src/doc/oiiotool.rst b/src/doc/oiiotool.rst index d06a21f6b8..ab1289fc72 100644 --- a/src/doc/oiiotool.rst +++ b/src/doc/oiiotool.rst @@ -2385,6 +2385,14 @@ current top image. name, nor value), then this will just leave the images as-is, without any unnecessary expense or pointless copying of images in memory. +.. option:: --nchannels + + Replaces the top image with a new image whose channels are the the first *n* + channels of the input image. If *n* is less than the number of + channels in the input image, the extra channels will simply be ignored. + If *n* is greater than the number of channels in the input image, the + additional channels will be filled with 0 values. + .. option:: --chappend Replaces the top two (or more) images on the stack with a single new diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index c86f3449f5..5c756d5fd1 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -2836,6 +2836,38 @@ action_channels(Oiiotool& ot, cspan argv) +// --nchannels +static void +action_nchannels(Oiiotool& ot, cspan argv) +{ + if (ot.postpone_callback(1, action_nchannels, argv)) + return; + + // Get the requested number of channels + int n = Strutil::stoi(ot.express(argv[1])); + if (n < 1) { + ot.errorfmt(argv[0], "nchannels must be at least 1 (got {})", n); + return; + } + + // We build the indices into a vector, then join them into a string. + std::vector indices; + for (int i = 0; i < n; ++i) + indices.push_back(i); + + // Convert to ustring to ensure the underlying char* is interned. + // This guarantees the pointer remains valid even if action_channels + // defers execution (postpones) beyond the scope of this function. + ustring channel_list(Strutil::join(indices, ",")); + + // Hand off to action_channels with our generated index list. + // this ensures we are always doing the same thing as --ch + const char* fake_argv[] = { argv[0], channel_list.c_str() }; + action_channels(ot, fake_argv); +} + + + // --chappend static void action_chappend(Oiiotool& ot, cspan argv) @@ -7277,6 +7309,9 @@ Oiiotool::getargs(int argc, char* argv[]) ap.arg("--ch %s:CHANLIST") .help("Select or shuffle channels (e.g., \"R,G,B\", \"B,G,R\", \"2,3,4\")") .OTACTION(action_channels); + ap.arg("--nchannels %d:N") + .help("Force the current image to have N channels (padding with 0.0 as needed)") + .OTACTION(action_nchannels); ap.arg("--chappend") .help("Append the channels of the last two images") .OTACTION(action_chappend); diff --git a/testsuite/oiiotool/ref/out.txt b/testsuite/oiiotool/ref/out.txt index 679adc453e..9b01433faa 100644 --- a/testsuite/oiiotool/ref/out.txt +++ b/testsuite/oiiotool/ref/out.txt @@ -62,6 +62,10 @@ oiiotool ERROR: -o : Non-existent output directory: folder1/folder2 Full command line was: > oiiotool --create 2x2 1 -o folder1/folder2/out.tif folder1/folder2/out.tif : 2 x 2, 1 channel, float tiff +WARNING: --ch: Unknown channel name "R", filling with 0 (actual channels: "Y") +WARNING: --ch: Unknown channel name "R", filling with 0 (actual channels: "Y") + channel list: Y + channel list: R Comparing "filled.tif" and "ref/filled.tif" PASS Comparing "autotrim.tif" and "ref/autotrim.tif" @@ -204,3 +208,11 @@ Comparing "box_over_missing2.tif" and "ref/box_over_missing2.tif" PASS Comparing "box_over_missing3.tif" and "ref/box_over_missing3.tif" PASS +Comparing "single_channel_black.tif" and "ref/single_channel_black.tif" +PASS +Comparing "single_channel_good.tif" and "ref/single_channel_good.tif" +PASS +Comparing "single_channel_good_y.exr" and "ref/single_channel_good_y.exr" +PASS +Comparing "single_channel_good_r.exr" and "ref/single_channel_good_r.exr" +PASS diff --git a/testsuite/oiiotool/ref/single_channel_black.tif b/testsuite/oiiotool/ref/single_channel_black.tif new file mode 100644 index 0000000000..27a3dc04d3 Binary files /dev/null and b/testsuite/oiiotool/ref/single_channel_black.tif differ diff --git a/testsuite/oiiotool/ref/single_channel_good.tif b/testsuite/oiiotool/ref/single_channel_good.tif new file mode 100644 index 0000000000..d4755527cd Binary files /dev/null and b/testsuite/oiiotool/ref/single_channel_good.tif differ diff --git a/testsuite/oiiotool/ref/single_channel_good_r.exr b/testsuite/oiiotool/ref/single_channel_good_r.exr new file mode 100644 index 0000000000..95a21fbd39 Binary files /dev/null and b/testsuite/oiiotool/ref/single_channel_good_r.exr differ diff --git a/testsuite/oiiotool/ref/single_channel_good_y.exr b/testsuite/oiiotool/ref/single_channel_good_y.exr new file mode 100644 index 0000000000..7dd8f3e3c5 Binary files /dev/null and b/testsuite/oiiotool/ref/single_channel_good_y.exr differ diff --git a/testsuite/oiiotool/run.py b/testsuite/oiiotool/run.py index 6d9b711a70..f182ae19d2 100755 --- a/testsuite/oiiotool/run.py +++ b/testsuite/oiiotool/run.py @@ -266,6 +266,24 @@ command += oiiotool (f"--create-dir --create 2x2 1 -o {root_folder}/folder2/out.tif") command += oiiotool (f"--info {root_folder}/folder2/out.tif") + +# in a tif the single channel doesn't really have a name so oiio gives it the name Y +# so if you naively do --ch R you'll get a black image (with a warning which we capture in out.txt) +command += oiiotool ("src/single_channel_tif.tif --ch R -o single_channel_black.tif") +# using nchannels 1 will always do the right thing +command += oiiotool ("src/single_channel_tif.tif --nchannels 1 -o single_channel_good.tif") +# exrs have explicit channel names, but you don't need to know the name if you are using nchannels +# so you can easily process a mix of frames without thinking about the name of the channels +# this can also be accomplished with --ch 0, but --nchannels 1 is a bit more intuitive +# and matches maketx +command += oiiotool ("src/single_channel_y.exr --nchannels 1 -o single_channel_good_y.exr") +command += oiiotool ("src/single_channel_r.exr --nchannels 1 -o single_channel_good_r.exr") + +# the info -v will print out the channel names and make sure they came out good in the output +command += oiiotool ("--info -v single_channel_good_y.exr | grep 'channel list'") +command += oiiotool ("--info -v single_channel_good_r.exr | grep 'channel list'") + + # To add more tests, just append more lines like the above and also add # the new 'feature.tif' (or whatever you call it) to the outputs list, # below. @@ -312,6 +330,10 @@ "const5-rgb.tif", "box_over_missing2.tif", "box_over_missing3.tif", + "single_channel_black.tif", + "single_channel_good.tif", + "single_channel_good_y.exr", + "single_channel_good_r.exr", "out.txt" ] #print "Running this command:\n" + command + "\n" diff --git a/testsuite/oiiotool/src/single_channel_r.exr b/testsuite/oiiotool/src/single_channel_r.exr new file mode 100644 index 0000000000..b4401ebf5b Binary files /dev/null and b/testsuite/oiiotool/src/single_channel_r.exr differ diff --git a/testsuite/oiiotool/src/single_channel_tif.tif b/testsuite/oiiotool/src/single_channel_tif.tif new file mode 100644 index 0000000000..1d753c2c6a Binary files /dev/null and b/testsuite/oiiotool/src/single_channel_tif.tif differ diff --git a/testsuite/oiiotool/src/single_channel_y.exr b/testsuite/oiiotool/src/single_channel_y.exr new file mode 100644 index 0000000000..c300c1af62 Binary files /dev/null and b/testsuite/oiiotool/src/single_channel_y.exr differ