From 82122b795a69f043f7e74c41f534698e0276291c Mon Sep 17 00:00:00 2001 From: jinniekim Date: Tue, 9 Jun 2026 00:15:40 -0700 Subject: [PATCH 01/11] feat(oiiotool): Add `--get-thumbnail` Assisted-by: Claude Code / opus-4.8 Signed-off-by: Jinnie Kim --- src/cmake/testing.cmake | 1 + src/doc/oiiotool.rst | 31 +++++++++++ src/oiiotool/oiiotool.cpp | 51 ++++++++++++++++++ testsuite/oiiotool-thumbnail/ref/out.txt | 28 ++++++++++ testsuite/oiiotool-thumbnail/ref/thumb.tif | Bin 0 -> 11757 bytes testsuite/oiiotool-thumbnail/run.py | 40 ++++++++++++++ .../oiiotool-thumbnail/src/with-thumbnail.psd | Bin 0 -> 29926 bytes 7 files changed, 151 insertions(+) create mode 100644 testsuite/oiiotool-thumbnail/ref/out.txt create mode 100644 testsuite/oiiotool-thumbnail/ref/thumb.tif create mode 100644 testsuite/oiiotool-thumbnail/run.py create mode 100644 testsuite/oiiotool-thumbnail/src/with-thumbnail.psd diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index 15605b35c2..6e9c0d2712 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -184,6 +184,7 @@ macro (oiio_add_all_tests) oiiotool-readerror oiiotool-subimage oiiotool-text + oiiotool-thumbnail oiiotool-xform diff flip diff --git a/src/doc/oiiotool.rst b/src/doc/oiiotool.rst index ce6fd570c2..33e34e4af0 100644 --- a/src/doc/oiiotool.rst +++ b/src/doc/oiiotool.rst @@ -2339,6 +2339,37 @@ current top image. Additionally, this command can be used to remove one subimage (leaving the others) by using the optional modifier `--subimage:delete=1`. +.. option:: --get-thumbnail + + Replace the top image on the stack with its embedded thumbnail. + A thumbnail is a property of the whole file, not of an individual subimage. + Because this replaces the top image, use ``--dup`` beforehand if you also + want to keep the original. + + Optional appended modifiers include: + + `:fail=` *int* (default: 1) + If 1, it is an error if the image has no embedded thumbnail. + If 0, an empty (0x0) image is pushed in its place instead, so a batch + script can continue; guard any subsequent output (see the example + below), since writing the empty image is itself an error. + + `:index=` *int* (default: 0) + Selects which embedded thumbnail to retrieve, for formats that can + store more than one (such as some camera raw formats). Currently only + the primary thumbnail (`index=0`, the default) is available; a nonzero + value is an error until multiple-thumbnail support is added (see issue + #4888). + + Examples:: + + # Save the thumbnail + oiiotool input.exr --dup --get-thumbnail -o thumb.jpg + + # Batch-safe: substitute an empty image for missing thumbnails, and + # guard the output so only real thumbnails are written + oiiotool input.exr --get-thumbnail:fail=0 --if "{TOP.width}" -o thumb.jpg --endif + .. option:: --sisplit Remove the top image from the stack, split it into its constituent diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index 1b2d8006eb..c53b337309 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -3214,6 +3214,54 @@ action_subimage_append_all(Oiiotool& ot, cspan argv) +// --get-thumbnail +static void +action_get_thumbnail(Oiiotool& ot, cspan argv) +{ + if (ot.postpone_callback(1, action_get_thumbnail, argv)) + return; + string_view command = ot.express(argv[0]); + OTScopedTimer timer(ot, command); + + // Parse options from the command token, e.g. + // --get-thumbnail:fail=0:index=0 + auto options = ot.extract_options(command); + + bool fail_if_missing = options.get_int("fail", 1); + int index = options.get_int("index", 0); + + // The current ImageInput/ImageBuf API exposes only a single (primary) + // thumbnail per subimage. Formats that embed multiple thumbnails at + // different resolutions (such as some camera raw formats) cannot yet be + // distinguished, so for now only index 0 is valid. The `:index=` modifier + // reserves the syntax for when the API gains multi-thumbnail support. + // See https://github.com/AcademySoftwareFoundation/OpenImageIO/issues/4888 + if (index != 0) { + ot.errorfmt(command, + "Thumbnail index {} is not available; only the primary " + "thumbnail (index 0) is currently supported", + index); + return; + } + + ImageRecRef A = ot.pop(); + ot.read(A); + + auto thumb = (*A)(0, 0).get_thumbnail(); + if (!thumb || !thumb->initialized()) { + if (fail_if_missing) { + ot.errorfmt(command, "Image \"{}\" has no thumbnail", A->name()); + ot.push(A); + return; + } + ot.push(new ImageRec(ImageBufRef(new ImageBuf()), false)); + return; + } + ot.push(new ImageRec(ImageBufRef(new ImageBuf(*thumb)), false)); +} + + + // --colorcount static void action_colorcount(Oiiotool& ot, cspan argv) @@ -7435,6 +7483,9 @@ Oiiotool::getargs(int argc, char* argv[]) ap.arg("--flatten") .help("Flatten deep image to non-deep") .OTACTION(action_flatten); + ap.arg("--get-thumbnail") + .help("Extract an embedded thumbnail (options: fail=, index=)") + .OTACTION(action_get_thumbnail); ap.separator("Image stack manipulation:"); ap.arg("--label %s") diff --git a/testsuite/oiiotool-thumbnail/ref/out.txt b/testsuite/oiiotool-thumbnail/ref/out.txt new file mode 100644 index 0000000000..904a4bdd67 --- /dev/null +++ b/testsuite/oiiotool-thumbnail/ref/out.txt @@ -0,0 +1,28 @@ +thumb.tif : 160 x 120, 3 channel, uint8 tiff +thumb0.tif : 160 x 120, 3 channel, uint8 tiff +thumb_f0.tif : 160 x 120, 3 channel, uint8 tiff +thumb_foo.tif : 160 x 120, 3 channel, uint8 tiff +thumb_dup.tif : 160 x 120, 3 channel, uint8 tiff +full.tif : 200 x 150, 3 channel, uint8 tiff +oiiotool ERROR: --get-thumbnail : Image "../common/tahoe-small.tif" has no thumbnail +Full command line was: +> oiiotool ../common/tahoe-small.tif --get-thumbnail +fail=0 path completed +oiiotool ERROR: -o : tiff image resolution must be at least 1x1, you asked for 0x0 +Full command line was: +> oiiotool ../common/tahoe-small.tif --get-thumbnail:fail=0 -o empty_thumb.tif +no thumbnail, skipped -o +oiiotool ERROR: --get-thumbnail:index=1 : Thumbnail index 1 is not available; only the primary thumbnail (index 0) is currently supported +Full command line was: +> oiiotool ../common/tahoe-small.tif --get-thumbnail:index=1 +oiiotool ERROR: --get-thumbnail:index=-1 : Thumbnail index -1 is not available; only the primary thumbnail (index 0) is currently supported +Full command line was: +> oiiotool ../common/tahoe-small.tif --get-thumbnail:index=-1 +oiiotool ERROR: --get-thumbnail:index=1 : Thumbnail index 1 is not available; only the primary thumbnail (index 0) is currently supported +Full command line was: +> oiiotool src/with-thumbnail.psd --get-thumbnail:index=1 +oiiotool ERROR: --get-thumbnail:index=1:fail=0 : Thumbnail index 1 is not available; only the primary thumbnail (index 0) is currently supported +Full command line was: +> oiiotool ../common/tahoe-small.tif --get-thumbnail:index=1:fail=0 +Comparing "thumb.tif" and "ref/thumb.tif" +PASS diff --git a/testsuite/oiiotool-thumbnail/ref/thumb.tif b/testsuite/oiiotool-thumbnail/ref/thumb.tif new file mode 100644 index 0000000000000000000000000000000000000000..2aad8fcb4ce981026b683ccca9bda42355698b36 GIT binary patch literal 11757 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 literal 0 HcmV?d00001 From 0f1baf9f50e2582d0f36ad6e025eed20e78c0ab2 Mon Sep 17 00:00:00 2001 From: Jinnie Kim Date: Tue, 9 Jun 2026 23:13:48 -0700 Subject: [PATCH 02/11] feat(oiiotool): Add `-i:get_thumbnail` modifier Assisted-by: Claude Code / opus-4.8 Signed-off-by: Jinnie Kim --- src/doc/oiiotool.rst | 18 +++++++++++++++--- src/oiiotool/oiiotool.cpp | 24 ++++++++++++++++++++++-- testsuite/oiiotool-thumbnail/ref/out.txt | 13 +++++++++++++ testsuite/oiiotool-thumbnail/run.py | 20 ++++++++++++++++++++ 4 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/doc/oiiotool.rst b/src/doc/oiiotool.rst index 33e34e4af0..51e48b16b6 100644 --- a/src/doc/oiiotool.rst +++ b/src/doc/oiiotool.rst @@ -1232,6 +1232,18 @@ Reading images to following the input with a `--ch` command, except that by integrating into the `-i`, it potentially can avoid the I/O of the unneeded channels. + `:get_thumbnail=` *int* + If nonzero, read the file's embedded thumbnail instead of its main + image (equivalent to a following `--get-thumbnail`). The `:fail=` and + `:index=` modifiers are forwarded (e.g. `-i:get_thumbnail=1:fail=0`), + and any auto-orientation or color conversion applies to the thumbnail. + Since a thumbnail is display-referred (typically sRGB), `:autocc=` will + linearize it like any other input. + + Examples:: + + # Extract just the thumbnail from a large image + oiiotool -i:get_thumbnail=1 input.psd -o thumb.jpg .. option:: --iconfig @@ -2342,7 +2354,7 @@ current top image. .. option:: --get-thumbnail Replace the top image on the stack with its embedded thumbnail. - A thumbnail is a property of the whole file, not of an individual subimage. + The thumbnail associated with the first subimage (subimage 0) is used. Because this replaces the top image, use ``--dup`` beforehand if you also want to keep the original. @@ -2364,11 +2376,11 @@ current top image. Examples:: # Save the thumbnail - oiiotool input.exr --dup --get-thumbnail -o thumb.jpg + oiiotool input.psd --dup --get-thumbnail -o thumb.jpg # Batch-safe: substitute an empty image for missing thumbnails, and # guard the output so only real thumbnails are written - oiiotool input.exr --get-thumbnail:fail=0 --if "{TOP.width}" -o thumb.jpg --endif + oiiotool input.psd --get-thumbnail:fail=0 --if "{TOP.width}" -o thumb.jpg --endif .. option:: --sisplit diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index c53b337309..8030d83d15 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -3245,7 +3245,11 @@ action_get_thumbnail(Oiiotool& ot, cspan argv) } ImageRecRef A = ot.pop(); - ot.read(A); + // Reading the spec loads the thumbnail, so avoid a full pixel read. + if (!ot.read_nativespec(A)) { + ot.push(A); + return; + } auto thumb = (*A)(0, 0).get_thumbnail(); if (!thumb || !thumb->initialized()) { @@ -5543,6 +5547,7 @@ input_file(Oiiotool& ot, cspan argv) ot.printinfo_format); TypeDesc input_dataformat(fileoptions.get_string("type")); std::string channel_set = fileoptions["ch"]; + bool get_thumbnail = fileoptions.get_int("get_thumbnail", 0); for (int i = 0; i < std::ssize(argv); i++) { // FIXME: this loop is pointless, since there is ever only one arg @@ -5685,6 +5690,20 @@ input_file(Oiiotool& ot, cspan argv) // the input timer. timer.stop(); + if (get_thumbnail && !substitute) { + // Swap in the embedded thumbnail via the --get-thumbnail logic. + // Done before autoorient/autocc so they apply to the thumbnail. + std::string thumbcmd = "--get-thumbnail"; + if (fileoptions.contains("fail")) + thumbcmd += Strutil::fmt::format(":fail={}", + fileoptions.get_int("fail")); + if (fileoptions.contains("index")) + thumbcmd += Strutil::fmt::format(":index={}", + fileoptions.get_int("index")); + const char* argv[] = { thumbcmd.c_str() }; + action_get_thumbnail(ot, argv); + } + if (ot.autoorient) { void action_reorient(Oiiotool & ot, cspan argv); const char* argv[] = { "--reorient" }; @@ -7029,7 +7048,8 @@ Oiiotool::getargs(int argc, char* argv[]) ap.separator("Commands that read images:"); ap.arg("-i %s:FILENAME") - .help("Input file (options: autocc=, ch=, info=, infoformat=, native=, now=, type=, unpremult=)") + .help("Input file (options: autocc=, ch=, get_thumbnail=, info=, " + "infoformat=, native=, now=, type=, unpremult=)") .OTACTION(input_file); ap.arg("--iconfig %s:NAME %s:VALUE") .help("Sets input config attribute (options: type=...)") diff --git a/testsuite/oiiotool-thumbnail/ref/out.txt b/testsuite/oiiotool-thumbnail/ref/out.txt index 904a4bdd67..6c2149ee70 100644 --- a/testsuite/oiiotool-thumbnail/ref/out.txt +++ b/testsuite/oiiotool-thumbnail/ref/out.txt @@ -24,5 +24,18 @@ Full command line was: oiiotool ERROR: --get-thumbnail:index=1:fail=0 : Thumbnail index 1 is not available; only the primary thumbnail (index 0) is currently supported Full command line was: > oiiotool ../common/tahoe-small.tif --get-thumbnail:index=1:fail=0 +thumb_i.tif : 160 x 120, 3 channel, uint8 tiff +Computing diff of "thumb_i.tif" vs "thumb.tif" +PASS +autoorient 160x120 +autocc 160x120 +oiiotool ERROR: --get-thumbnail : Image "../common/tahoe-small.tif" has no thumbnail +Full command line was: +> oiiotool -i:get_thumbnail=1 ../common/tahoe-small.tif +fail=0 path completed +oiiotool ERROR: --get-thumbnail:index=1 : Thumbnail index 1 is not available; only the primary thumbnail (index 0) is currently supported +Full command line was: +> oiiotool -i:get_thumbnail=1:index=1 src/with-thumbnail.psd +get_thumbnail=0 200x150 Comparing "thumb.tif" and "ref/thumb.tif" PASS diff --git a/testsuite/oiiotool-thumbnail/run.py b/testsuite/oiiotool-thumbnail/run.py index 9f2863e7c9..1d01f865de 100644 --- a/testsuite/oiiotool-thumbnail/run.py +++ b/testsuite/oiiotool-thumbnail/run.py @@ -37,4 +37,24 @@ command += oiiotool (psd + " --get-thumbnail:index=1", failureok=True) command += oiiotool (no_thumb + " --get-thumbnail:index=1:fail=0", failureok=True) +# Test that the -i:get_thumbnail=1 read modifier produces the same thumbnail +# as the equivalent --get-thumbnail command above +command += oiiotool ("-i:get_thumbnail=1 " + psd + " -o thumb_i.tif") +command += info_command ("thumb_i.tif", verbose=False, hash=False) +command += oiiotool ("--diff thumb_i.tif thumb.tif") + +# Test that autoorient/autocc act on the thumbnail not the main image +command += oiiotool ("--autoorient -i:get_thumbnail=1 " + psd + + " --echo \"autoorient {TOP.width}x{TOP.height}\"") +command += oiiotool ("--autocc -i:get_thumbnail=1 " + psd + + " --echo \"autocc {TOP.width}x{TOP.height}\"") + +# Test that -i:get_thumbnail=1 mirrors `--get-thumbnail` +command += oiiotool ("-i:get_thumbnail=1 " + no_thumb, failureok=True) +command += oiiotool ("-i:get_thumbnail=1:fail=0 " + no_thumb + " --echo \"fail=0 path completed\"") +command += oiiotool ("-i:get_thumbnail=1:index=1 " + psd, failureok=True) + +# Test that -i:get_thumbnail=0 returns the main image +command += oiiotool ("-i:get_thumbnail=0 " + psd + " --echo \"get_thumbnail=0 {TOP.width}x{TOP.height}\"") + outputs = [ "thumb.tif", "out.txt" ] From 37f013f85cc056e08948d8bdc80bc5301e5957c7 Mon Sep 17 00:00:00 2001 From: Jinnie Kim Date: Sun, 14 Jun 2026 00:48:57 -0700 Subject: [PATCH 03/11] Rename folder Signed-off-by: Jinnie Kim --- .../ref/out.txt | 0 .../ref/thumb.tif | Bin .../run.py | 0 .../src/with-thumbnail.psd | Bin 4 files changed, 0 insertions(+), 0 deletions(-) rename testsuite/{oiiotool-thumbnail => oiiotool-get-thumbnail}/ref/out.txt (100%) rename testsuite/{oiiotool-thumbnail => oiiotool-get-thumbnail}/ref/thumb.tif (100%) rename testsuite/{oiiotool-thumbnail => oiiotool-get-thumbnail}/run.py (100%) rename testsuite/{oiiotool-thumbnail => oiiotool-get-thumbnail}/src/with-thumbnail.psd (100%) diff --git a/testsuite/oiiotool-thumbnail/ref/out.txt b/testsuite/oiiotool-get-thumbnail/ref/out.txt similarity index 100% rename from testsuite/oiiotool-thumbnail/ref/out.txt rename to testsuite/oiiotool-get-thumbnail/ref/out.txt diff --git a/testsuite/oiiotool-thumbnail/ref/thumb.tif b/testsuite/oiiotool-get-thumbnail/ref/thumb.tif similarity index 100% rename from testsuite/oiiotool-thumbnail/ref/thumb.tif rename to testsuite/oiiotool-get-thumbnail/ref/thumb.tif diff --git a/testsuite/oiiotool-thumbnail/run.py b/testsuite/oiiotool-get-thumbnail/run.py similarity index 100% rename from testsuite/oiiotool-thumbnail/run.py rename to testsuite/oiiotool-get-thumbnail/run.py diff --git a/testsuite/oiiotool-thumbnail/src/with-thumbnail.psd b/testsuite/oiiotool-get-thumbnail/src/with-thumbnail.psd similarity index 100% rename from testsuite/oiiotool-thumbnail/src/with-thumbnail.psd rename to testsuite/oiiotool-get-thumbnail/src/with-thumbnail.psd From 1121b3c31e84b9213ab860942cfab9777bd6125b Mon Sep 17 00:00:00 2001 From: Jinnie Kim Date: Sun, 14 Jun 2026 19:52:35 -0700 Subject: [PATCH 04/11] oiiotool-get-thumbnail: Tighten the unittests Assisted-by: Claude Code / opus-4.8 Signed-off-by: Jinnie Kim --- testsuite/oiiotool-get-thumbnail/ref/out.txt | 35 +++--------- testsuite/oiiotool-get-thumbnail/run.py | 58 ++++++++------------ 2 files changed, 31 insertions(+), 62 deletions(-) diff --git a/testsuite/oiiotool-get-thumbnail/ref/out.txt b/testsuite/oiiotool-get-thumbnail/ref/out.txt index 6c2149ee70..2be87f841a 100644 --- a/testsuite/oiiotool-get-thumbnail/ref/out.txt +++ b/testsuite/oiiotool-get-thumbnail/ref/out.txt @@ -1,38 +1,17 @@ -thumb.tif : 160 x 120, 3 channel, uint8 tiff -thumb0.tif : 160 x 120, 3 channel, uint8 tiff -thumb_f0.tif : 160 x 120, 3 channel, uint8 tiff -thumb_foo.tif : 160 x 120, 3 channel, uint8 tiff -thumb_dup.tif : 160 x 120, 3 channel, uint8 tiff -full.tif : 200 x 150, 3 channel, uint8 tiff +modifiers 160x120 +thumbnail 160x120 +full 200x150 oiiotool ERROR: --get-thumbnail : Image "../common/tahoe-small.tif" has no thumbnail Full command line was: > oiiotool ../common/tahoe-small.tif --get-thumbnail -fail=0 path completed -oiiotool ERROR: -o : tiff image resolution must be at least 1x1, you asked for 0x0 -Full command line was: -> oiiotool ../common/tahoe-small.tif --get-thumbnail:fail=0 -o empty_thumb.tif -no thumbnail, skipped -o -oiiotool ERROR: --get-thumbnail:index=1 : Thumbnail index 1 is not available; only the primary thumbnail (index 0) is currently supported -Full command line was: -> oiiotool ../common/tahoe-small.tif --get-thumbnail:index=1 -oiiotool ERROR: --get-thumbnail:index=-1 : Thumbnail index -1 is not available; only the primary thumbnail (index 0) is currently supported -Full command line was: -> oiiotool ../common/tahoe-small.tif --get-thumbnail:index=-1 -oiiotool ERROR: --get-thumbnail:index=1 : Thumbnail index 1 is not available; only the primary thumbnail (index 0) is currently supported -Full command line was: -> oiiotool src/with-thumbnail.psd --get-thumbnail:index=1 +no thumbnail, skipped output oiiotool ERROR: --get-thumbnail:index=1:fail=0 : Thumbnail index 1 is not available; only the primary thumbnail (index 0) is currently supported Full command line was: -> oiiotool ../common/tahoe-small.tif --get-thumbnail:index=1:fail=0 -thumb_i.tif : 160 x 120, 3 channel, uint8 tiff +> oiiotool src/with-thumbnail.psd --get-thumbnail:index=1:fail=0 Computing diff of "thumb_i.tif" vs "thumb.tif" PASS -autoorient 160x120 -autocc 160x120 -oiiotool ERROR: --get-thumbnail : Image "../common/tahoe-small.tif" has no thumbnail -Full command line was: -> oiiotool -i:get_thumbnail=1 ../common/tahoe-small.tif -fail=0 path completed +autoorient/autocc 160x120 +input fail=0 returned empty oiiotool ERROR: --get-thumbnail:index=1 : Thumbnail index 1 is not available; only the primary thumbnail (index 0) is currently supported Full command line was: > oiiotool -i:get_thumbnail=1:index=1 src/with-thumbnail.psd diff --git a/testsuite/oiiotool-get-thumbnail/run.py b/testsuite/oiiotool-get-thumbnail/run.py index 1d01f865de..69d0441a9b 100644 --- a/testsuite/oiiotool-get-thumbnail/run.py +++ b/testsuite/oiiotool-get-thumbnail/run.py @@ -10,51 +10,41 @@ psd = "src/with-thumbnail.psd" no_thumb = "../common/tahoe-small.tif" -# Test extracting a present thumbnail +# Test extracting a present thumbnail. command += oiiotool (psd + " --get-thumbnail -o thumb.tif") -command += info_command ("thumb.tif", verbose=False, hash=False) -command += oiiotool (psd + " --get-thumbnail:index=0 -o thumb0.tif") -command += info_command ("thumb0.tif", verbose=False, hash=False) -command += oiiotool (psd + " --get-thumbnail:fail=0 -o thumb_f0.tif") -command += info_command ("thumb_f0.tif", verbose=False, hash=False) -command += oiiotool (psd + " --get-thumbnail:foo=1 -o thumb_foo.tif") -command += info_command ("thumb_foo.tif", verbose=False, hash=False) - -# Test stack integrity -command += oiiotool (psd + " --dup --get-thumbnail -o thumb_dup.tif --pop -o full.tif") -command += info_command ("thumb_dup.tif", verbose=False, hash=False) -command += info_command ("full.tif", verbose=False, hash=False) - -# Test missing thumbnail + +# Test valid modifiers and stack integrity. +command += oiiotool (psd + " --get-thumbnail:index=0:fail=0" + + " --echo \"modifiers {TOP.width}x{TOP.height}\"") +command += oiiotool (psd + " --dup --get-thumbnail" + + " --echo \"thumbnail {TOP.width}x{TOP.height}\"" + + " --pop --echo \"full {TOP.width}x{TOP.height}\"") + +# Test missing-thumbnail behavior. command += oiiotool (no_thumb + " --get-thumbnail", failureok=True) -command += oiiotool (no_thumb + " --get-thumbnail:fail=0 --echo \"fail=0 path completed\"") -command += oiiotool (no_thumb + " --get-thumbnail:fail=0 -o empty_thumb.tif", failureok=True) -command += oiiotool (no_thumb + " --get-thumbnail:fail=0 --if \"{TOP.width}\" -o guarded.tif --else --echo \"no thumbnail, skipped -o\" --endif") +command += oiiotool (no_thumb + " --get-thumbnail:fail=0" + + " --if \"{TOP.width}\" --echo unexpected" + + " --else --echo \"no thumbnail, skipped output\" --endif") -# Test index modifier -command += oiiotool (no_thumb + " --get-thumbnail:index=1", failureok=True) -command += oiiotool (no_thumb + " --get-thumbnail:index=-1", failureok=True) -command += oiiotool (psd + " --get-thumbnail:index=1", failureok=True) -command += oiiotool (no_thumb + " --get-thumbnail:index=1:fail=0", failureok=True) +# Test that fail=0 does not suppress an invalid index. +command += oiiotool (psd + " --get-thumbnail:index=1:fail=0", failureok=True) # Test that the -i:get_thumbnail=1 read modifier produces the same thumbnail -# as the equivalent --get-thumbnail command above +# as the equivalent --get-thumbnail command above. command += oiiotool ("-i:get_thumbnail=1 " + psd + " -o thumb_i.tif") -command += info_command ("thumb_i.tif", verbose=False, hash=False) command += oiiotool ("--diff thumb_i.tif thumb.tif") -# Test that autoorient/autocc act on the thumbnail not the main image -command += oiiotool ("--autoorient -i:get_thumbnail=1 " + psd - + " --echo \"autoorient {TOP.width}x{TOP.height}\"") -command += oiiotool ("--autocc -i:get_thumbnail=1 " + psd - + " --echo \"autocc {TOP.width}x{TOP.height}\"") +# Test that autoorient/autocc act on the thumbnail, not the main image. +command += oiiotool ("--autoorient --autocc -i:get_thumbnail=1 " + psd + + " --echo \"autoorient/autocc {TOP.width}x{TOP.height}\"") -# Test that -i:get_thumbnail=1 mirrors `--get-thumbnail` -command += oiiotool ("-i:get_thumbnail=1 " + no_thumb, failureok=True) -command += oiiotool ("-i:get_thumbnail=1:fail=0 " + no_thumb + " --echo \"fail=0 path completed\"") +# Test that -i:get_thumbnail forwards modifiers. +command += oiiotool ("-i:get_thumbnail=1:fail=0 " + no_thumb + + " --if \"{TOP.width}\" --echo unexpected" + + " --else --echo \"input fail=0 returned empty\" --endif") command += oiiotool ("-i:get_thumbnail=1:index=1 " + psd, failureok=True) -# Test that -i:get_thumbnail=0 returns the main image +# Test that -i:get_thumbnail=0 returns the main image. command += oiiotool ("-i:get_thumbnail=0 " + psd + " --echo \"get_thumbnail=0 {TOP.width}x{TOP.height}\"") outputs = [ "thumb.tif", "out.txt" ] From 3dd5e8c18decce9c6ecbccb4b9769eb417b14cd1 Mon Sep 17 00:00:00 2001 From: Jinnie Kim Date: Sun, 14 Jun 2026 19:55:13 -0700 Subject: [PATCH 05/11] fix(targa): Correct embedded thumbnail writing Encode the embedded thumbnail the same way as the main image: bottom-up scanlines, deassociated alpha, and BGR(A) channel order. The original implementation rather dumped the raw in-memory buffer, which left the thumbnail flipped, R/B-swapped, and premultiplied. Also clamp an oversized thumbnail to 255 rather than 256: each postage-stamp dimension is a single byte, so 256 wrapped to 0 and the thumbnail was silently dropped on read. Assisted-by: Claude Code / opus-4.8 Signed-off-by: Jinnie Kim --- src/targa.imageio/targaoutput.cpp | 32 ++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/targa.imageio/targaoutput.cpp b/src/targa.imageio/targaoutput.cpp index ef75e2c0bb..71b4107f48 100644 --- a/src/targa.imageio/targaoutput.cpp +++ b/src/targa.imageio/targaoutput.cpp @@ -261,9 +261,25 @@ TGAOutput::write_tga20_data_fields() OIIO_DASSERT(tw && th && tc == m_spec.nchannels); ofs_thumb = (uint32_t)iotell(); // dump thumbnail size - if (!write(tw) || !write(th) - || !write(m_thumb.localpixels(), m_thumb.spec().image_bytes())) + if (!write(tw) || !write(th)) return false; + // Encode the thumbnail in TGA pixel order: write scanlines + // bottom-up, deassociate alpha, and convert RGB(A) to BGR(A). + // Similar to `TGAOutput::write_scanline`. + std::vector buf(m_thumb.spec().scanline_bytes()); + for (int y = th - 1; y >= 0; --y) { + if (!m_thumb.get_pixels(ROI(0, tw, y, y + 1, 0, 1, 0, tc), + make_span(buf))) + return false; + if (m_convert_alpha) + deassociateAlpha(buf.data(), tw, tc, m_spec.alpha_channel, + m_gamma); + if (tc >= 3) + for (int x = 0; x < tw; ++x) + std::swap(buf[x * tc], buf[x * tc + 2]); + if (!write(buf.data(), tc, tw)) + return false; + } } // prepare the footer @@ -671,15 +687,17 @@ TGAOutput::set_thumbnail(const ImageBuf& thumb) // Zero size thumbnail or channels don't match return false; } - // TARGA has a limitation of 256 res for thumbnail dimensions, and - // must be UINT8. + // TARGA thumbnails must be UINT8, and each dimension is stored in a single + // byte, so the maximum size is 255 (256 would truncate to 0 and the reader + // would treat the thumbnail as absent). if (thumb.spec().width >= 256 || thumb.spec().height >= 256) { - ROI roi(0, 256, 0, 256, 0, 1, 0, thumb.nchannels()); + // Resize to fit within 255 while preserving aspect ratio. + ROI roi(0, 255, 0, 255, 0, 1, 0, thumb.nchannels()); float ratio = float(thumb.spec().width) / float(thumb.spec().height); if (ratio >= 1.0f) { - roi.yend = (int)roundf(256.0f / ratio); + roi.yend = (int)roundf(255.0f / ratio); } else { - roi.xend = (int)roundf(256.0f * ratio); + roi.xend = (int)roundf(255.0f * ratio); } m_thumb = ImageBufAlgo::resize(thumb, ImageBufAlgo::KWArgs(), roi, this->threads()); From 4bfe52948c10f9c083e00fb396a11e73081c68ba Mon Sep 17 00:00:00 2001 From: Jinnie Kim Date: Sun, 14 Jun 2026 19:56:48 -0700 Subject: [PATCH 06/11] fix(targa): Fix reading embedded thumbnails TGA stores unassociated alpha, but OIIO keeps it associated in memory. `readimg()` converts on read; `get_thumbnail()` did not, so RGBA thumbnails came back too bright. Apply the same `associateAlpha()` conversion to the thumbnail. get_thumbnail() is moved below associateAlpha() so it can call it without a forward declaration. Assisted-by: Claude Code / opus-4.8 Signed-off-by: Jinnie Kim --- src/targa.imageio/targainput.cpp | 160 +++++++++++++++++-------------- 1 file changed, 89 insertions(+), 71 deletions(-) diff --git a/src/targa.imageio/targainput.cpp b/src/targa.imageio/targainput.cpp index d770ab9593..8211f63c98 100644 --- a/src/targa.imageio/targainput.cpp +++ b/src/targa.imageio/targainput.cpp @@ -529,77 +529,6 @@ TGAInput::read_tga2_header() -bool -TGAInput::get_thumbnail(ImageBuf& thumb, int subimage) -{ - if (m_ofs_thumb <= 0) - return false; // no thumbnail info - - lock_guard lock(*this); - bool result = false; - int64_t save_offset = iotell(); - - if (!ioseek(m_ofs_thumb)) - return false; - - // Read the thumbnail dimensions -- sometimes it's 0x0 to indicate no - // thumbnail. - unsigned char res[2]; - if (!ioread(&res, 2, 1)) - return false; - if (res[0] > 0 && res[1] > 0) { - // Most of this code is a dupe of readimg(); according to the spec, - // the thumbnail is in the same format as the main image but - // uncompressed. - ImageSpec thumbspec(res[0], res[1], m_spec.nchannels, TypeUInt8); - thumbspec.set_colorspace("srgb_rec709_scene"); - thumb.reset(thumbspec); - int bytespp = (m_tga.bpp == 15) ? 2 : (m_tga.bpp / 8); - int palbytespp = (m_tga.cmap_size == 15) ? 2 : (m_tga.cmap_size / 8); - int alphabits = m_tga.attr & 0x0F; - if (alphabits == 0 && m_tga.bpp == 32) - alphabits = 8; - // read palette, if there is any - std::unique_ptr palette; - size_t palette_alloc_size = 0; - if (is_palette()) { - if (!ioseek(m_ofs_palette)) { - return false; - } - palette_alloc_size = palbytespp * m_tga.cmap_length; - palette.reset(new unsigned char[palette_alloc_size]); - if (!ioread(palette.get(), palbytespp, m_tga.cmap_length)) - return false; - if (!ioseek(m_ofs_thumb + 2)) { - return false; - } - } - // load pixel data - unsigned char pixel[4]; - unsigned char in[4]; - for (int64_t y = thumbspec.height - 1; y >= 0; y--) { - char* img = (char*)thumb.pixeladdr(0, y); - for (int64_t x = 0; x < thumbspec.width; - x++, img += m_spec.nchannels) { - if (!ioread(in, bytespp, 1)) - return false; - if (!decode_pixel(in, pixel, palette.get(), bytespp, palbytespp, - palette_alloc_size)) - return false; - memcpy(img, pixel, m_spec.nchannels); - } - } - result = true; - } - - if (!ioseek(save_offset)) { - return false; - } - return result; -} - - - inline bool TGAInput::decode_pixel(unsigned char* in, unsigned char* out, unsigned char* palette, int bytespp, int palbytespp, @@ -734,6 +663,95 @@ associateAlpha(T* data, int64_t size, int channels, int alpha_channel, +bool +TGAInput::get_thumbnail(ImageBuf& thumb, int subimage) +{ + if (m_ofs_thumb <= 0) + return false; // no thumbnail info + + lock_guard lock(*this); + bool result = false; + int64_t save_offset = iotell(); + + if (!ioseek(m_ofs_thumb)) + return false; + + // Read the thumbnail dimensions -- sometimes it's 0x0 to indicate no + // thumbnail. + unsigned char res[2]; + if (!ioread(&res, 2, 1)) + return false; + if (res[0] > 0 && res[1] > 0) { + // Most of this code is a dupe of readimg(); according to the spec, + // the thumbnail is in the same format as the main image but + // uncompressed. + ImageSpec thumbspec(res[0], res[1], m_spec.nchannels, TypeUInt8); + thumbspec.set_colorspace("srgb_rec709_scene"); + thumb.reset(thumbspec); + int bytespp = (m_tga.bpp == 15) ? 2 : (m_tga.bpp / 8); + int palbytespp = (m_tga.cmap_size == 15) ? 2 : (m_tga.cmap_size / 8); + int alphabits = m_tga.attr & 0x0F; + if (alphabits == 0 && m_tga.bpp == 32) + alphabits = 8; + // read palette, if there is any + std::unique_ptr palette; + size_t palette_alloc_size = 0; + if (is_palette()) { + if (!ioseek(m_ofs_palette)) { + return false; + } + palette_alloc_size = palbytespp * m_tga.cmap_length; + palette.reset(new unsigned char[palette_alloc_size]); + if (!ioread(palette.get(), palbytespp, m_tga.cmap_length)) + return false; + if (!ioseek(m_ofs_thumb + 2)) { + return false; + } + } + // load pixel data + unsigned char pixel[4]; + unsigned char in[4]; + for (int64_t y = thumbspec.height - 1; y >= 0; y--) { + char* img = (char*)thumb.pixeladdr(0, y); + for (int64_t x = 0; x < thumbspec.width; + x++, img += m_spec.nchannels) { + if (!ioread(in, bytespp, 1)) + return false; + if (!decode_pixel(in, pixel, palette.get(), bytespp, palbytespp, + palette_alloc_size)) + return false; + memcpy(img, pixel, m_spec.nchannels); + } + } + // Convert to associated alpha, matching readimg() and OIIO's in-memory + // convention; TGA stores unassociated (unpremultiplied) alpha. + if (m_spec.alpha_channel != -1 && !m_keep_unassociated_alpha + && m_alpha_type != TGA_ALPHA_PREMULTIPLIED) { + bool alpha0_everywhere = (m_tga_version == 1); + int64_t size = thumbspec.image_pixels(); + unsigned char* tpx = (unsigned char*)thumb.localpixels(); + for (int64_t i = 0; i < size; ++i) + if (tpx[i * thumbspec.nchannels + m_spec.alpha_channel]) { + alpha0_everywhere = false; + break; + } + if (!alpha0_everywhere) { + float gamma = m_spec.get_float_attribute("oiio:Gamma", 1.0f); + associateAlpha(tpx, size, thumbspec.nchannels, + m_spec.alpha_channel, gamma); + } + } + result = true; + } + + if (!ioseek(save_offset)) { + return false; + } + return result; +} + + + bool TGAInput::readimg() { From 4a34dedb0bdfcf0aa5f89763bcad3ab25fda4dee Mon Sep 17 00:00:00 2001 From: Jinnie Kim Date: Sun, 14 Jun 2026 19:57:42 -0700 Subject: [PATCH 07/11] feat(oiiotool): Add `--set-thumbnail` Assisted-by: Claude Code / opus-4.8 Signed-off-by: Jinnie Kim --- src/cmake/testing.cmake | 4 +- src/doc/oiiotool.rst | 13 ++ src/libOpenImageIO/imagebuf.cpp | 4 + src/libOpenImageIO/imagebuf_test.cpp | 158 +++++++++++++++++++ src/oiiotool/oiiotool.cpp | 57 ++++++- testsuite/oiiotool-set-thumbnail/ref/out.txt | 33 ++++ testsuite/oiiotool-set-thumbnail/run.py | 34 ++++ 7 files changed, 298 insertions(+), 5 deletions(-) create mode 100644 testsuite/oiiotool-set-thumbnail/ref/out.txt create mode 100644 testsuite/oiiotool-set-thumbnail/run.py diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index 6e9c0d2712..492408e2d9 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -184,7 +184,7 @@ macro (oiio_add_all_tests) oiiotool-readerror oiiotool-subimage oiiotool-text - oiiotool-thumbnail + oiiotool-get-thumbnail oiiotool-xform diff flip @@ -475,6 +475,8 @@ macro (oiio_add_all_tests) ENABLEVAR ENABLE_TARGA IMAGEDIR oiio-images) endif() + oiio_add_tests (oiiotool-set-thumbnail + ENABLEVAR ENABLE_TARGA) if (WIN32) if (OIIO_BUILD_TOOLS) # Add test for long path handling if support is enabled at the system level. diff --git a/src/doc/oiiotool.rst b/src/doc/oiiotool.rst index 51e48b16b6..43304668fa 100644 --- a/src/doc/oiiotool.rst +++ b/src/doc/oiiotool.rst @@ -2382,6 +2382,19 @@ current top image. # guard the output so only real thumbnails are written oiiotool input.psd --get-thumbnail:fail=0 --if "{TOP.width}" -o thumb.jpg --endif +.. option:: --set-thumbnail + + Remove the top image from the stack and attach it as the thumbnail of the + image now on top (stored on the first subimage). The thumbnail may be + prepared beforehand with the usual image operations. It is written out only + if the output format supports embedded thumbnails, and may be resized or + otherwise adjusted to satisfy that format's restrictions. + + Examples:: + + # Attach a 128x128 box-filtered copy of the image as its thumbnail + oiiotool input.exr --dup --resize:filter=box 128x128 --set-thumbnail -o out_with_thumb.tga + .. option:: --sisplit Remove the top image from the stack, split it into its constituent diff --git a/src/libOpenImageIO/imagebuf.cpp b/src/libOpenImageIO/imagebuf.cpp index 46b23b5dce..8cc68faf97 100644 --- a/src/libOpenImageIO/imagebuf.cpp +++ b/src/libOpenImageIO/imagebuf.cpp @@ -1983,6 +1983,10 @@ ImageBufImpl::set_thumbnail(const ImageBuf& thumb, DoLock do_lock) clear_thumbnail(DoLock(false) /* we already hold the lock */); if (thumb.initialized()) { m_thumbnail.reset(new ImageBuf(thumb)); + m_spec.attribute("thumbnail_width", thumb.spec().width); + m_spec.attribute("thumbnail_height", thumb.spec().height); + m_spec.attribute("thumbnail_nchannels", thumb.spec().nchannels); + m_has_thumbnail = true; } } diff --git a/src/libOpenImageIO/imagebuf_test.cpp b/src/libOpenImageIO/imagebuf_test.cpp index 695e524426..e54d09fec3 100644 --- a/src/libOpenImageIO/imagebuf_test.cpp +++ b/src/libOpenImageIO/imagebuf_test.cpp @@ -441,6 +441,161 @@ time_get_pixels() +void +test_thumbnail() +{ + std::cout << "\nTesting set_thumbnail, get_thumbnail, clear_thumbnail:\n"; + ImageBuf A(ImageSpec(64, 48, 3, TypeUInt8)); + ImageBufAlgo::zero(A); + OIIO_CHECK_ASSERT(!A.has_thumbnail()); + + // Non-square asymmetric vertical gradient. The top/bottom colors are + // deliberately not R/B mirror images, so a flip, an R/B swap, and both + // together each alter the image. + auto gradient = [](int w, int h, int nchans) { + ImageBuf buf(ImageSpec(w, h, nchans, TypeUInt8)); + static const float top[4] = { 0.2f, 0.3f, 0.8f, 1.0f }; + static const float bottom[4] = { 0.7f, 0.9f, 0.4f, 1.0f }; + ImageBufAlgo::fill(buf, cspan(top), cspan(bottom)); + return buf; + }; + ImageBuf thumb = gradient(16, 12, 3); + + A.set_thumbnail(thumb); + OIIO_CHECK_ASSERT(A.has_thumbnail()); + auto t = A.get_thumbnail(); + OIIO_CHECK_ASSERT(t && t->initialized()); + OIIO_CHECK_EQUAL(t->spec().width, 16); + OIIO_CHECK_EQUAL(t->spec().height, 12); + OIIO_CHECK_EQUAL(A.spec().get_int_attribute("thumbnail_width"), 16); + OIIO_CHECK_EQUAL(A.spec().get_int_attribute("thumbnail_height"), 12); + OIIO_CHECK_EQUAL(A.spec().get_int_attribute("thumbnail_nchannels"), 3); + OIIO_CHECK_EQUAL(ImageBufAlgo::compare(*t, thumb, 0.0f, 0.0f).nfail, 0); + + // Test that `set_thumbnail` stores a deep copy. Mutating the source image + // afterward must not affect the stored thumbnail. + ImageBufAlgo::zero(thumb); + t = A.get_thumbnail(); + OIIO_CHECK_EQUAL( + ImageBufAlgo::compare(*t, gradient(16, 12, 3), 0.0f, 0.0f).nfail, 0); + + // Replace A's thumbnail with a new image. + A.set_thumbnail(gradient(8, 6, 3)); + t = A.get_thumbnail(); + OIIO_CHECK_EQUAL(t->spec().width, 8); + OIIO_CHECK_EQUAL(t->spec().height, 6); + OIIO_CHECK_EQUAL(A.spec().get_int_attribute("thumbnail_width"), 8); + OIIO_CHECK_EQUAL(A.spec().get_int_attribute("thumbnail_height"), 6); + + // Test that setting an uninitialized thumbnail clears it. + A.set_thumbnail(ImageBuf()); + OIIO_CHECK_ASSERT(!A.has_thumbnail()); + OIIO_CHECK_EQUAL(A.spec().get_int_attribute("thumbnail_width"), 0); + + // Test that `clear_thumbnail` removes the thumbnail and its metadata. + A.set_thumbnail(gradient(16, 12, 3)); + OIIO_CHECK_ASSERT(A.has_thumbnail()); + A.clear_thumbnail(); + OIIO_CHECK_ASSERT(!A.has_thumbnail()); + OIIO_CHECK_EQUAL(A.spec().get_int_attribute("thumbnail_width"), 0); +} + + + +void +test_thumbnail_tga() +{ + std::cout << "\nTesting thumbnail round trip through a TGA file:\n"; + ImageBuf A(ImageSpec(64, 48, 3, TypeUInt8)); + ImageBufAlgo::zero(A); + + // Non-square asymmetric vertical gradient. The top/bottom colors are + // deliberately not R/B mirror images, so a flip, an R/B swap, and both + // together each alter the image. + auto gradient = [](int w, int h, int nchans) { + ImageBuf buf(ImageSpec(w, h, nchans, TypeUInt8)); + static const float top[4] = { 0.2f, 0.3f, 0.8f, 1.0f }; + static const float bottom[4] = { 0.7f, 0.9f, 0.4f, 1.0f }; + ImageBufAlgo::fill(buf, cspan(top), cspan(bottom)); + return buf; + }; + + // Test that the thumbnail content survives a write/read round trip exactly. + A.set_thumbnail(gradient(16, 12, 3)); + OIIO_CHECK_ASSERT(A.write("imagebuf_test_thumb1.tga")); + { + ImageBuf in("imagebuf_test_thumb1.tga"); + OIIO_CHECK_ASSERT(in.has_thumbnail()); + auto rt = in.get_thumbnail(); + OIIO_CHECK_ASSERT(rt && rt->initialized()); + OIIO_CHECK_EQUAL(rt->spec().width, 16); + OIIO_CHECK_EQUAL(rt->spec().height, 12); + OIIO_CHECK_EQUAL(rt->spec().nchannels, 3); + OIIO_CHECK_EQUAL( + ImageBufAlgo::compare(*rt, gradient(16, 12, 3), 0.0f, 0.0f).nfail, + 0); + } + + // Test an oversized thumbnail is resized to fit the + // format's 255 pixel dimension limit, preserving aspect ratio. + A.set_thumbnail(gradient(512, 384, 3)); + OIIO_CHECK_ASSERT(A.write("imagebuf_test_thumb2.tga")); + { + ImageBuf in("imagebuf_test_thumb2.tga"); + OIIO_CHECK_ASSERT(in.has_thumbnail()); + auto rt = in.get_thumbnail(); + OIIO_CHECK_ASSERT(rt && rt->initialized()); + OIIO_CHECK_EQUAL(rt->spec().width, 255); + OIIO_CHECK_EQUAL(rt->spec().height, 191); + } + + A.set_thumbnail(gradient(384, 512, 3)); + OIIO_CHECK_ASSERT(A.write("imagebuf_test_thumb3.tga")); + { + ImageBuf in("imagebuf_test_thumb3.tga"); + OIIO_CHECK_ASSERT(in.has_thumbnail()); + auto rt = in.get_thumbnail(); + OIIO_CHECK_ASSERT(rt && rt->initialized()); + OIIO_CHECK_EQUAL(rt->spec().width, 191); + OIIO_CHECK_EQUAL(rt->spec().height, 255); + } + + // Test a thumbnail whose channel count doesn't match the image can't be + // stored in a TGA file; the image is written without one. + A.set_thumbnail(gradient(16, 12, 4)); + OIIO_CHECK_ASSERT(A.write("imagebuf_test_thumb4.tga")); + { + ImageBuf in("imagebuf_test_thumb4.tga"); + OIIO_CHECK_ASSERT(!in.has_thumbnail()); + } + + // Test conversion between associated and unassociated alpha. + ImageBuf rgba_image(ImageSpec(64, 48, 4, TypeUInt8)); + ImageBufAlgo::zero(rgba_image); + ImageBuf rgba_thumb(ImageSpec(16, 12, 4, TypeUInt8)); + const float premult_rgba[4] = { 0.4f, 0.3f, 0.2f, 0.5f }; + ImageBufAlgo::fill(rgba_thumb, cspan(premult_rgba)); + rgba_image.set_thumbnail(rgba_thumb); + OIIO_CHECK_ASSERT(rgba_image.write("imagebuf_test_thumb5.tga")); + { + ImageBuf in("imagebuf_test_thumb5.tga"); + OIIO_CHECK_ASSERT(in.has_thumbnail()); + auto rt = in.get_thumbnail(); + OIIO_CHECK_ASSERT(rt && rt->initialized()); + OIIO_CHECK_EQUAL( + ImageBufAlgo::compare(*rt, rgba_thumb, 0.005f, 0.005f).nfail, + 0); + } + + for (const char* f : + { "imagebuf_test_thumb1.tga", "imagebuf_test_thumb2.tga", + "imagebuf_test_thumb3.tga", "imagebuf_test_thumb4.tga", + "imagebuf_test_thumb5.tga" }) + Filesystem::remove(f); +} + + + void test_read_channel_subset() { @@ -842,6 +997,9 @@ main(int argc, char* argv[]) test_set_get_pixels(); time_get_pixels(); + test_thumbnail(); + test_thumbnail_tga(); + test_write_over(); test_uncaught_error(); diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index 8030d83d15..7aed9c8ba5 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -887,6 +887,7 @@ adjust_output_options(string_view filename, ImageSpec& spec, const ImageSpec* nativespec, const Oiiotool& ot, int subimage_index, int nsubimages, bool format_supports_tiles, + bool format_supports_thumbnail, const ParamValueList& fileoptions, bool was_direct_read = false) { @@ -1059,6 +1060,16 @@ adjust_output_options(string_view filename, ImageSpec& spec, spec.erase_attribute("oiio:SHA-1"); spec.erase_attribute("oiio:ConstantColor"); spec.erase_attribute("oiio:AverageColor"); + + // If the output format can't embed a thumbnail, don't let the thumbnail + // bookkeeping attributes leak into the file as metadata describing a + // thumbnail that isn't actually there. + if (!format_supports_thumbnail) { + spec.erase_attribute("thumbnail_width"); + spec.erase_attribute("thumbnail_height"); + spec.erase_attribute("thumbnail_nchannels"); + spec.erase_attribute("thumbnail_image"); + } } @@ -3266,6 +3277,39 @@ action_get_thumbnail(Oiiotool& ot, cspan argv) +// --set-thumbnail +static void +action_set_thumbnail(Oiiotool& ot, cspan argv) +{ + if (ot.postpone_callback(2, action_set_thumbnail, argv)) + return; + string_view command = ot.express(argv[0]); + OTScopedTimer timer(ot, command); + + // Top image is the thumbnail + ImageRecRef T = ot.pop(); + ImageRecRef A = ot.pop(); + if (!ot.read(T) || !ot.read(A)) { + ot.push(A); + ot.push(T); + return; + } + + const ImageBuf& thumb((*T)(0, 0)); + if (!thumb.initialized()) { + ot.errorfmt(command, "Thumbnail image \"{}\" is empty", T->name()); + ot.push(A); + ot.push(T); + return; + } + + (*A)(0, 0).set_thumbnail(thumb); + A->update_spec_from_imagebuf(0, 0); + ot.push(A); +} + + + // --colorcount static void action_colorcount(Oiiotool& ot, cspan argv) @@ -6005,6 +6049,7 @@ output_file(Oiiotool& ot, cspan argv) bool supports_negativeorigin = out->supports("negativeorigin"); bool supports_tiles = out->supports("tiles") || ot.output_force_tiles; bool procedural = out->supports("procedural"); + bool supports_thumbnail = out->supports("thumbnail"); if (!ot.read()) { return; } @@ -6184,7 +6229,7 @@ output_file(Oiiotool& ot, cspan argv) if (do_tex || do_latlong || do_bumpslopes) { ImageSpec configspec; adjust_output_options(filename, configspec, nullptr, ot, 0, 1, - supports_tiles, fileoptions); + supports_tiles, supports_thumbnail, fileoptions); prep_texture_config(ot, configspec, fileoptions); ImageBufAlgo::MakeTextureMode mode = ImageBufAlgo::MakeTxTexture; if (do_shad) @@ -6214,8 +6259,8 @@ output_file(Oiiotool& ot, cspan argv) for (int s = 0, send = ir->subimages(); s < send; ++s) { ImageSpec spec = *ir->spec(s, 0); adjust_output_options(filename, spec, ir->nativespec(s), ot, s, - send, supports_tiles, fileoptions, - (*ir)[s].was_direct_read()); + send, supports_tiles, supports_thumbnail, + fileoptions, (*ir)[s].was_direct_read()); // If it's not tiled and MIP-mapped, remove any "textureformat" if (!spec.tile_pixels() || ir->miplevels(s) <= 1) spec.erase_attribute("textureformat"); @@ -6256,7 +6301,8 @@ output_file(Oiiotool& ot, cspan argv) for (int m = 0, mend = ir->miplevels(s); m < mend && ok; ++m) { ImageSpec spec = *ir->spec(s, m); adjust_output_options(filename, spec, ir->nativespec(s, m), ot, - s, send, supports_tiles, fileoptions, + s, send, supports_tiles, + supports_thumbnail, fileoptions, (*ir)[s].was_direct_read()); if (s > 0 || m > 0) { // already opened first subimage/level if (!out->open(tmpfilename, spec, mode)) { @@ -7506,6 +7552,9 @@ Oiiotool::getargs(int argc, char* argv[]) ap.arg("--get-thumbnail") .help("Extract an embedded thumbnail (options: fail=, index=)") .OTACTION(action_get_thumbnail); + ap.arg("--set-thumbnail") + .help("Attach the top image as the thumbnail of the image below it") + .OTACTION(action_set_thumbnail); ap.separator("Image stack manipulation:"); ap.arg("--label %s") diff --git a/testsuite/oiiotool-set-thumbnail/ref/out.txt b/testsuite/oiiotool-set-thumbnail/ref/out.txt new file mode 100644 index 0000000000..ba4c175ad2 --- /dev/null +++ b/testsuite/oiiotool-set-thumbnail/ref/out.txt @@ -0,0 +1,33 @@ +after set-thumbnail: 512x384 +Reading out.tga +out.tga : 512 x 384, 3 channel, uint8 targa + SHA-1: 55B55C41EB65BF7B741671DBFFA29CA970D4877C + channel list: R, G, B + compression: "rle" + thumbnail_height: 38 + thumbnail_nchannels: 3 + thumbnail_width: 50 + oiio:BitsPerSample: 8 + targa:version: 2 +Computing diff of "thumb.tif" vs "resize" +PASS +Reading no_thumb.tif +no_thumb.tif : 512 x 384, 3 channel, uint8 tiff + channel list: R, G, B + compression: "lzw" + Orientation: 1 (normal) + PixelAspectRatio: 1 + planarconfig: "contig" + ResolutionUnit: "in" + XResolution: 72 + YResolution: 72 + oiio:BitsPerSample: 8 + tiff:Compression: 5 + tiff:PhotometricInterpretation: 2 + tiff:PlanarConfiguration: 1 + tiff:RowsPerStrip: 32 +oiiotool ERROR: --set-thumbnail : Thumbnail image "" is empty +Full command line was: +> oiiotool ../common/tahoe-small.tif --dup --get-thumbnail:fail=0 --set-thumbnail +oiiotool WARNING: --set-thumbnail : pending command never executed +oiiotool WARNING : oiiotool produced no output. Did you forget -o? diff --git a/testsuite/oiiotool-set-thumbnail/run.py b/testsuite/oiiotool-set-thumbnail/run.py new file mode 100644 index 0000000000..02efe5c9ca --- /dev/null +++ b/testsuite/oiiotool-set-thumbnail/run.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + +# Capture error output +redirect = " >> out.txt 2>&1 " + +src = "../common/tahoe-small.tif" + +# Test that a thumbnail embeds into TGA (currently the only format that can), +# is reported in the file's metadata, and leaves the main image on the stack. +command += oiiotool (src + " --dup --resize:filter=box 50x38 --set-thumbnail" + + " --echo \"after set-thumbnail: {TOP.width}x{TOP.height}\"" + + " -o out.tga") +command += info_command ("out.tga", safematch=True) + +# Test that an embed/extract round trip matches a directly resized image. +command += oiiotool ("out.tga --get-thumbnail -o thumb.tif") +command += oiiotool ("--warn 0.005 --fail 0.005 thumb.tif " + src + + " --resize:filter=box 50x38 --diff") + +# Test that thumbnail metadata is omitted from formats without thumbnail +# support. +command += oiiotool (src + " --dup --resize:filter=box 50x38 --set-thumbnail -o no_thumb.tif") +command += info_command ("no_thumb.tif", safematch=True, hash=False) + +# Test error cases: an empty thumbnail image, and too few images on the stack. +command += oiiotool (src + " --dup --get-thumbnail:fail=0 --set-thumbnail", + failureok=True) +command += oiiotool (src + " --set-thumbnail", failureok=True) + +outputs = [ "out.txt" ] From 8281f49184458c3df022dd6151503fee30fd749a Mon Sep 17 00:00:00 2001 From: Jinnie Kim Date: Sun, 14 Jun 2026 20:49:15 -0700 Subject: [PATCH 08/11] clang format Signed-off-by: Jinnie Kim --- src/libOpenImageIO/imagebuf_test.cpp | 3 +-- src/oiiotool/oiiotool.cpp | 4 ++-- src/targa.imageio/targainput.cpp | 2 +- src/targa.imageio/targaoutput.cpp | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/libOpenImageIO/imagebuf_test.cpp b/src/libOpenImageIO/imagebuf_test.cpp index e54d09fec3..64bc63646d 100644 --- a/src/libOpenImageIO/imagebuf_test.cpp +++ b/src/libOpenImageIO/imagebuf_test.cpp @@ -583,8 +583,7 @@ test_thumbnail_tga() auto rt = in.get_thumbnail(); OIIO_CHECK_ASSERT(rt && rt->initialized()); OIIO_CHECK_EQUAL( - ImageBufAlgo::compare(*rt, rgba_thumb, 0.005f, 0.005f).nfail, - 0); + ImageBufAlgo::compare(*rt, rgba_thumb, 0.005f, 0.005f).nfail, 0); } for (const char* f : diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index 7aed9c8ba5..940cae9314 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -6047,8 +6047,8 @@ output_file(Oiiotool& ot, cspan argv) } bool supports_displaywindow = out->supports("displaywindow"); bool supports_negativeorigin = out->supports("negativeorigin"); - bool supports_tiles = out->supports("tiles") || ot.output_force_tiles; - bool procedural = out->supports("procedural"); + bool supports_tiles = out->supports("tiles") || ot.output_force_tiles; + bool procedural = out->supports("procedural"); bool supports_thumbnail = out->supports("thumbnail"); if (!ot.read()) { return; diff --git a/src/targa.imageio/targainput.cpp b/src/targa.imageio/targainput.cpp index 8211f63c98..c6e6ccb824 100644 --- a/src/targa.imageio/targainput.cpp +++ b/src/targa.imageio/targainput.cpp @@ -728,7 +728,7 @@ TGAInput::get_thumbnail(ImageBuf& thumb, int subimage) if (m_spec.alpha_channel != -1 && !m_keep_unassociated_alpha && m_alpha_type != TGA_ALPHA_PREMULTIPLIED) { bool alpha0_everywhere = (m_tga_version == 1); - int64_t size = thumbspec.image_pixels(); + int64_t size = thumbspec.image_pixels(); unsigned char* tpx = (unsigned char*)thumb.localpixels(); for (int64_t i = 0; i < size; ++i) if (tpx[i * thumbspec.nchannels + m_spec.alpha_channel]) { diff --git a/src/targa.imageio/targaoutput.cpp b/src/targa.imageio/targaoutput.cpp index 71b4107f48..a1af3a8921 100644 --- a/src/targa.imageio/targaoutput.cpp +++ b/src/targa.imageio/targaoutput.cpp @@ -688,7 +688,7 @@ TGAOutput::set_thumbnail(const ImageBuf& thumb) return false; } // TARGA thumbnails must be UINT8, and each dimension is stored in a single - // byte, so the maximum size is 255 (256 would truncate to 0 and the reader + // byte, so the maximum size is 255 (256 would truncate to 0 and the reader // would treat the thumbnail as absent). if (thumb.spec().width >= 256 || thumb.spec().height >= 256) { // Resize to fit within 255 while preserving aspect ratio. From 470541cef0ef9bfbd3ed2352500bf93892bd9c1e Mon Sep 17 00:00:00 2001 From: Jinnie Kim Date: Sun, 26 Jul 2026 12:21:39 -0700 Subject: [PATCH 09/11] refactor(oiiotool): Rename `--get/set-thumbnail` to `--thumbnail-get/set` Signed-off-by: Jinnie Kim --- src/cmake/testing.cmake | 4 +-- src/doc/oiiotool.rst | 12 ++++---- src/oiiotool/oiiotool.cpp | 28 +++++++++--------- .../ref/out.txt | 10 +++---- .../ref/thumb.tif | Bin .../run.py | 14 ++++----- .../src/with-thumbnail.psd | Bin .../ref/out.txt | 8 ++--- .../run.py | 12 ++++---- 9 files changed, 44 insertions(+), 44 deletions(-) rename testsuite/{oiiotool-get-thumbnail => oiiotool-thumbnail-get}/ref/out.txt (64%) rename testsuite/{oiiotool-get-thumbnail => oiiotool-thumbnail-get}/ref/thumb.tif (100%) rename testsuite/{oiiotool-get-thumbnail => oiiotool-thumbnail-get}/run.py (81%) rename testsuite/{oiiotool-get-thumbnail => oiiotool-thumbnail-get}/src/with-thumbnail.psd (100%) rename testsuite/{oiiotool-set-thumbnail => oiiotool-thumbnail-set}/ref/out.txt (78%) rename testsuite/{oiiotool-set-thumbnail => oiiotool-thumbnail-set}/run.py (70%) diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index 492408e2d9..e1be9b89f8 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -184,7 +184,7 @@ macro (oiio_add_all_tests) oiiotool-readerror oiiotool-subimage oiiotool-text - oiiotool-get-thumbnail + oiiotool-thumbnail-get oiiotool-xform diff flip @@ -475,7 +475,7 @@ macro (oiio_add_all_tests) ENABLEVAR ENABLE_TARGA IMAGEDIR oiio-images) endif() - oiio_add_tests (oiiotool-set-thumbnail + oiio_add_tests (oiiotool-thumbnail-set ENABLEVAR ENABLE_TARGA) if (WIN32) if (OIIO_BUILD_TOOLS) diff --git a/src/doc/oiiotool.rst b/src/doc/oiiotool.rst index 43304668fa..933b5e3a0b 100644 --- a/src/doc/oiiotool.rst +++ b/src/doc/oiiotool.rst @@ -1234,7 +1234,7 @@ Reading images channels. `:get_thumbnail=` *int* If nonzero, read the file's embedded thumbnail instead of its main - image (equivalent to a following `--get-thumbnail`). The `:fail=` and + image (equivalent to a following `--thumbnail-get`). The `:fail=` and `:index=` modifiers are forwarded (e.g. `-i:get_thumbnail=1:fail=0`), and any auto-orientation or color conversion applies to the thumbnail. Since a thumbnail is display-referred (typically sRGB), `:autocc=` will @@ -2351,7 +2351,7 @@ current top image. Additionally, this command can be used to remove one subimage (leaving the others) by using the optional modifier `--subimage:delete=1`. -.. option:: --get-thumbnail +.. option:: --thumbnail-get Replace the top image on the stack with its embedded thumbnail. The thumbnail associated with the first subimage (subimage 0) is used. @@ -2376,13 +2376,13 @@ current top image. Examples:: # Save the thumbnail - oiiotool input.psd --dup --get-thumbnail -o thumb.jpg + oiiotool input.psd --dup --thumbnail-get -o thumb.jpg # Batch-safe: substitute an empty image for missing thumbnails, and # guard the output so only real thumbnails are written - oiiotool input.psd --get-thumbnail:fail=0 --if "{TOP.width}" -o thumb.jpg --endif + oiiotool input.psd --thumbnail-get:fail=0 --if "{TOP.width}" -o thumb.jpg --endif -.. option:: --set-thumbnail +.. option:: --thumbnail-set Remove the top image from the stack and attach it as the thumbnail of the image now on top (stored on the first subimage). The thumbnail may be @@ -2393,7 +2393,7 @@ current top image. Examples:: # Attach a 128x128 box-filtered copy of the image as its thumbnail - oiiotool input.exr --dup --resize:filter=box 128x128 --set-thumbnail -o out_with_thumb.tga + oiiotool input.exr --dup --resize:filter=box 128x128 --thumbnail-set -o out_with_thumb.tga .. option:: --sisplit diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index 940cae9314..6c2c6855cd 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -3225,17 +3225,17 @@ action_subimage_append_all(Oiiotool& ot, cspan argv) -// --get-thumbnail +// --thumbnail-get static void -action_get_thumbnail(Oiiotool& ot, cspan argv) +action_thumbnail_get(Oiiotool& ot, cspan argv) { - if (ot.postpone_callback(1, action_get_thumbnail, argv)) + if (ot.postpone_callback(1, action_thumbnail_get, argv)) return; string_view command = ot.express(argv[0]); OTScopedTimer timer(ot, command); // Parse options from the command token, e.g. - // --get-thumbnail:fail=0:index=0 + // --thumbnail-get:fail=0:index=0 auto options = ot.extract_options(command); bool fail_if_missing = options.get_int("fail", 1); @@ -3277,11 +3277,11 @@ action_get_thumbnail(Oiiotool& ot, cspan argv) -// --set-thumbnail +// --thumbnail-set static void -action_set_thumbnail(Oiiotool& ot, cspan argv) +action_thumbnail_set(Oiiotool& ot, cspan argv) { - if (ot.postpone_callback(2, action_set_thumbnail, argv)) + if (ot.postpone_callback(2, action_thumbnail_set, argv)) return; string_view command = ot.express(argv[0]); OTScopedTimer timer(ot, command); @@ -5735,9 +5735,9 @@ input_file(Oiiotool& ot, cspan argv) timer.stop(); if (get_thumbnail && !substitute) { - // Swap in the embedded thumbnail via the --get-thumbnail logic. + // Swap in the embedded thumbnail via the --thumbnail-get logic. // Done before autoorient/autocc so they apply to the thumbnail. - std::string thumbcmd = "--get-thumbnail"; + std::string thumbcmd = "--thumbnail-get"; if (fileoptions.contains("fail")) thumbcmd += Strutil::fmt::format(":fail={}", fileoptions.get_int("fail")); @@ -5745,7 +5745,7 @@ input_file(Oiiotool& ot, cspan argv) thumbcmd += Strutil::fmt::format(":index={}", fileoptions.get_int("index")); const char* argv[] = { thumbcmd.c_str() }; - action_get_thumbnail(ot, argv); + action_thumbnail_get(ot, argv); } if (ot.autoorient) { @@ -7549,12 +7549,12 @@ Oiiotool::getargs(int argc, char* argv[]) ap.arg("--flatten") .help("Flatten deep image to non-deep") .OTACTION(action_flatten); - ap.arg("--get-thumbnail") + ap.arg("--thumbnail-get") .help("Extract an embedded thumbnail (options: fail=, index=)") - .OTACTION(action_get_thumbnail); - ap.arg("--set-thumbnail") + .OTACTION(action_thumbnail_get); + ap.arg("--thumbnail-set") .help("Attach the top image as the thumbnail of the image below it") - .OTACTION(action_set_thumbnail); + .OTACTION(action_thumbnail_set); ap.separator("Image stack manipulation:"); ap.arg("--label %s") diff --git a/testsuite/oiiotool-get-thumbnail/ref/out.txt b/testsuite/oiiotool-thumbnail-get/ref/out.txt similarity index 64% rename from testsuite/oiiotool-get-thumbnail/ref/out.txt rename to testsuite/oiiotool-thumbnail-get/ref/out.txt index 2be87f841a..93f099045d 100644 --- a/testsuite/oiiotool-get-thumbnail/ref/out.txt +++ b/testsuite/oiiotool-thumbnail-get/ref/out.txt @@ -1,18 +1,18 @@ modifiers 160x120 thumbnail 160x120 full 200x150 -oiiotool ERROR: --get-thumbnail : Image "../common/tahoe-small.tif" has no thumbnail +oiiotool ERROR: --thumbnail-get : Image "../common/tahoe-small.tif" has no thumbnail Full command line was: -> oiiotool ../common/tahoe-small.tif --get-thumbnail +> oiiotool ../common/tahoe-small.tif --thumbnail-get no thumbnail, skipped output -oiiotool ERROR: --get-thumbnail:index=1:fail=0 : Thumbnail index 1 is not available; only the primary thumbnail (index 0) is currently supported +oiiotool ERROR: --thumbnail-get:index=1:fail=0 : Thumbnail index 1 is not available; only the primary thumbnail (index 0) is currently supported Full command line was: -> oiiotool src/with-thumbnail.psd --get-thumbnail:index=1:fail=0 +> oiiotool src/with-thumbnail.psd --thumbnail-get:index=1:fail=0 Computing diff of "thumb_i.tif" vs "thumb.tif" PASS autoorient/autocc 160x120 input fail=0 returned empty -oiiotool ERROR: --get-thumbnail:index=1 : Thumbnail index 1 is not available; only the primary thumbnail (index 0) is currently supported +oiiotool ERROR: --thumbnail-get:index=1 : Thumbnail index 1 is not available; only the primary thumbnail (index 0) is currently supported Full command line was: > oiiotool -i:get_thumbnail=1:index=1 src/with-thumbnail.psd get_thumbnail=0 200x150 diff --git a/testsuite/oiiotool-get-thumbnail/ref/thumb.tif b/testsuite/oiiotool-thumbnail-get/ref/thumb.tif similarity index 100% rename from testsuite/oiiotool-get-thumbnail/ref/thumb.tif rename to testsuite/oiiotool-thumbnail-get/ref/thumb.tif diff --git a/testsuite/oiiotool-get-thumbnail/run.py b/testsuite/oiiotool-thumbnail-get/run.py similarity index 81% rename from testsuite/oiiotool-get-thumbnail/run.py rename to testsuite/oiiotool-thumbnail-get/run.py index 69d0441a9b..6b5d976b64 100644 --- a/testsuite/oiiotool-get-thumbnail/run.py +++ b/testsuite/oiiotool-thumbnail-get/run.py @@ -11,26 +11,26 @@ no_thumb = "../common/tahoe-small.tif" # Test extracting a present thumbnail. -command += oiiotool (psd + " --get-thumbnail -o thumb.tif") +command += oiiotool (psd + " --thumbnail-get -o thumb.tif") # Test valid modifiers and stack integrity. -command += oiiotool (psd + " --get-thumbnail:index=0:fail=0" +command += oiiotool (psd + " --thumbnail-get:index=0:fail=0" + " --echo \"modifiers {TOP.width}x{TOP.height}\"") -command += oiiotool (psd + " --dup --get-thumbnail" +command += oiiotool (psd + " --dup --thumbnail-get" + " --echo \"thumbnail {TOP.width}x{TOP.height}\"" + " --pop --echo \"full {TOP.width}x{TOP.height}\"") # Test missing-thumbnail behavior. -command += oiiotool (no_thumb + " --get-thumbnail", failureok=True) -command += oiiotool (no_thumb + " --get-thumbnail:fail=0" +command += oiiotool (no_thumb + " --thumbnail-get", failureok=True) +command += oiiotool (no_thumb + " --thumbnail-get:fail=0" + " --if \"{TOP.width}\" --echo unexpected" + " --else --echo \"no thumbnail, skipped output\" --endif") # Test that fail=0 does not suppress an invalid index. -command += oiiotool (psd + " --get-thumbnail:index=1:fail=0", failureok=True) +command += oiiotool (psd + " --thumbnail-get:index=1:fail=0", failureok=True) # Test that the -i:get_thumbnail=1 read modifier produces the same thumbnail -# as the equivalent --get-thumbnail command above. +# as the equivalent --thumbnail-get command above. command += oiiotool ("-i:get_thumbnail=1 " + psd + " -o thumb_i.tif") command += oiiotool ("--diff thumb_i.tif thumb.tif") diff --git a/testsuite/oiiotool-get-thumbnail/src/with-thumbnail.psd b/testsuite/oiiotool-thumbnail-get/src/with-thumbnail.psd similarity index 100% rename from testsuite/oiiotool-get-thumbnail/src/with-thumbnail.psd rename to testsuite/oiiotool-thumbnail-get/src/with-thumbnail.psd diff --git a/testsuite/oiiotool-set-thumbnail/ref/out.txt b/testsuite/oiiotool-thumbnail-set/ref/out.txt similarity index 78% rename from testsuite/oiiotool-set-thumbnail/ref/out.txt rename to testsuite/oiiotool-thumbnail-set/ref/out.txt index ba4c175ad2..67af6b31f3 100644 --- a/testsuite/oiiotool-set-thumbnail/ref/out.txt +++ b/testsuite/oiiotool-thumbnail-set/ref/out.txt @@ -1,4 +1,4 @@ -after set-thumbnail: 512x384 +after thumbnail-set: 512x384 Reading out.tga out.tga : 512 x 384, 3 channel, uint8 targa SHA-1: 55B55C41EB65BF7B741671DBFFA29CA970D4877C @@ -26,8 +26,8 @@ no_thumb.tif : 512 x 384, 3 channel, uint8 tiff tiff:PhotometricInterpretation: 2 tiff:PlanarConfiguration: 1 tiff:RowsPerStrip: 32 -oiiotool ERROR: --set-thumbnail : Thumbnail image "" is empty +oiiotool ERROR: --thumbnail-set : Thumbnail image "" is empty Full command line was: -> oiiotool ../common/tahoe-small.tif --dup --get-thumbnail:fail=0 --set-thumbnail -oiiotool WARNING: --set-thumbnail : pending command never executed +> oiiotool ../common/tahoe-small.tif --dup --thumbnail-get:fail=0 --thumbnail-set +oiiotool WARNING: --thumbnail-set : pending command never executed oiiotool WARNING : oiiotool produced no output. Did you forget -o? diff --git a/testsuite/oiiotool-set-thumbnail/run.py b/testsuite/oiiotool-thumbnail-set/run.py similarity index 70% rename from testsuite/oiiotool-set-thumbnail/run.py rename to testsuite/oiiotool-thumbnail-set/run.py index 02efe5c9ca..93742950da 100644 --- a/testsuite/oiiotool-set-thumbnail/run.py +++ b/testsuite/oiiotool-thumbnail-set/run.py @@ -11,24 +11,24 @@ # Test that a thumbnail embeds into TGA (currently the only format that can), # is reported in the file's metadata, and leaves the main image on the stack. -command += oiiotool (src + " --dup --resize:filter=box 50x38 --set-thumbnail" - + " --echo \"after set-thumbnail: {TOP.width}x{TOP.height}\"" +command += oiiotool (src + " --dup --resize:filter=box 50x38 --thumbnail-set" + + " --echo \"after thumbnail-set: {TOP.width}x{TOP.height}\"" + " -o out.tga") command += info_command ("out.tga", safematch=True) # Test that an embed/extract round trip matches a directly resized image. -command += oiiotool ("out.tga --get-thumbnail -o thumb.tif") +command += oiiotool ("out.tga --thumbnail-get -o thumb.tif") command += oiiotool ("--warn 0.005 --fail 0.005 thumb.tif " + src + " --resize:filter=box 50x38 --diff") # Test that thumbnail metadata is omitted from formats without thumbnail # support. -command += oiiotool (src + " --dup --resize:filter=box 50x38 --set-thumbnail -o no_thumb.tif") +command += oiiotool (src + " --dup --resize:filter=box 50x38 --thumbnail-set -o no_thumb.tif") command += info_command ("no_thumb.tif", safematch=True, hash=False) # Test error cases: an empty thumbnail image, and too few images on the stack. -command += oiiotool (src + " --dup --get-thumbnail:fail=0 --set-thumbnail", +command += oiiotool (src + " --dup --thumbnail-get:fail=0 --thumbnail-set", failureok=True) -command += oiiotool (src + " --set-thumbnail", failureok=True) +command += oiiotool (src + " --thumbnail-set", failureok=True) outputs = [ "out.txt" ] From c0563674dd466415e47738ae643ebf706623b302 Mon Sep 17 00:00:00 2001 From: Jinnie Kim Date: Sun, 26 Jul 2026 23:08:41 -0700 Subject: [PATCH 10/11] feat(oiiotool): Generate a thumbnail for `--thumbnail-get:fail=0` Assisted-by: Claude Code / opus-4.8 Signed-off-by: Jinnie Kim --- src/doc/oiiotool.rst | 12 ++++---- src/oiiotool/oiiotool.cpp | 29 +++++++++++++++++- .../oiiotool-thumbnail-get/ref/generated.tif | Bin 0 -> 100379 bytes testsuite/oiiotool-thumbnail-get/ref/out.txt | 9 ++++-- testsuite/oiiotool-thumbnail-get/run.py | 26 +++++++++++++--- testsuite/oiiotool-thumbnail-set/ref/out.txt | 3 -- testsuite/oiiotool-thumbnail-set/run.py | 6 ++-- 7 files changed, 64 insertions(+), 21 deletions(-) create mode 100644 testsuite/oiiotool-thumbnail-get/ref/generated.tif diff --git a/src/doc/oiiotool.rst b/src/doc/oiiotool.rst index 933b5e3a0b..9ea590db6e 100644 --- a/src/doc/oiiotool.rst +++ b/src/doc/oiiotool.rst @@ -2362,9 +2362,10 @@ current top image. `:fail=` *int* (default: 1) If 1, it is an error if the image has no embedded thumbnail. - If 0, an empty (0x0) image is pushed in its place instead, so a batch - script can continue; guard any subsequent output (see the example - below), since writing the empty image is itself an error. + If 0, one is generated by downsizing the image to fit within a 256x256 + box, preserving aspect ratio and never enlarging, so a 512x384 image + yields a 256x192 thumbnail. Generating reads the image's pixels; + retrieving an embedded thumbnail does not. `:index=` *int* (default: 0) Selects which embedded thumbnail to retrieve, for formats that can @@ -2378,9 +2379,8 @@ current top image. # Save the thumbnail oiiotool input.psd --dup --thumbnail-get -o thumb.jpg - # Batch-safe: substitute an empty image for missing thumbnails, and - # guard the output so only real thumbnails are written - oiiotool input.psd --thumbnail-get:fail=0 --if "{TOP.width}" -o thumb.jpg --endif + # Fall back to a generated thumbnail if the file has none + oiiotool input.psd --thumbnail-get:fail=0 -o thumb.jpg .. option:: --thumbnail-set diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index 6c2c6855cd..8f9a307b67 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -3269,7 +3269,34 @@ action_thumbnail_get(Oiiotool& ot, cspan argv) ot.push(A); return; } - ot.push(new ImageRec(ImageBufRef(new ImageBuf()), false)); + + // synthesize a thumbnail from the image itself + if (!ot.read(A)) { + ot.push(A); + return; + } + + const ImageBuf& Aib((*A)(0, 0)); + const ImageSpec& Aspec(Aib.spec()); + + // Fit inside a box, preserving aspect ratio and never enlarging + const int boxsize = 256; + int longest = std::max(1, + std::max(Aspec.full_width, Aspec.full_height)); + float scale = std::min(1.0f, float(boxsize) / float(longest)); + int tw = std::max(1, int(roundf(Aspec.full_width * scale))); + int th = std::max(1, int(roundf(Aspec.full_height * scale))); + ROI roi(0, tw, 0, th, 0, 1, 0, Aspec.nchannels); + + ImageBufRef generated(new ImageBuf( + ImageBufAlgo::resize(Aib, ImageBufAlgo::KWArgs(), roi))); + + if (generated->has_error()) { + ot.errorfmt(command, "{}", generated->geterror()); + ot.push(A); + return; + } + ot.push(new ImageRec(generated, false)); return; } ot.push(new ImageRec(ImageBufRef(new ImageBuf(*thumb)), false)); diff --git a/testsuite/oiiotool-thumbnail-get/ref/generated.tif b/testsuite/oiiotool-thumbnail-get/ref/generated.tif new file mode 100644 index 0000000000000000000000000000000000000000..5a0bbf5bbcf3acfb616acb6851b0db4581b80644 GIT binary patch literal 100379 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

||U=8%BU!2mH9(84MI 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 oiiotool ../common/tahoe-small.tif --thumbnail-get -no thumbnail, skipped output +generated 256x192 +portrait 192x256 +small source 40x30 +display window 256x256 oiiotool ERROR: --thumbnail-get:index=1:fail=0 : Thumbnail index 1 is not available; only the primary thumbnail (index 0) is currently supported Full command line was: > oiiotool src/with-thumbnail.psd --thumbnail-get:index=1:fail=0 Computing diff of "thumb_i.tif" vs "thumb.tif" PASS autoorient/autocc 160x120 -input fail=0 returned empty +input fail=0 generated 256x192 oiiotool ERROR: --thumbnail-get:index=1 : Thumbnail index 1 is not available; only the primary thumbnail (index 0) is currently supported Full command line was: > oiiotool -i:get_thumbnail=1:index=1 src/with-thumbnail.psd get_thumbnail=0 200x150 Comparing "thumb.tif" and "ref/thumb.tif" PASS +Comparing "generated.tif" and "ref/generated.tif" +PASS diff --git a/testsuite/oiiotool-thumbnail-get/run.py b/testsuite/oiiotool-thumbnail-get/run.py index 6b5d976b64..d11a59220e 100644 --- a/testsuite/oiiotool-thumbnail-get/run.py +++ b/testsuite/oiiotool-thumbnail-get/run.py @@ -22,9 +22,26 @@ # Test missing-thumbnail behavior. command += oiiotool (no_thumb + " --thumbnail-get", failureok=True) + +# With fail=0, a missing thumbnail is generated from the image itself. command += oiiotool (no_thumb + " --thumbnail-get:fail=0" - + " --if \"{TOP.width}\" --echo unexpected" - + " --else --echo \"no thumbnail, skipped output\" --endif") + + " --echo \"generated {TOP.width}x{TOP.height}\"" + + " -o generated.tif") + +# The aspect ratio is preserved regardless of orientation. +command += oiiotool (no_thumb + " --transpose -o portrait.tif") +command += oiiotool ("portrait.tif --thumbnail-get:fail=0" + + " --echo \"portrait {TOP.width}x{TOP.height}\"") + +# A source already smaller than the box is never enlarged. +command += oiiotool ("--pattern constant:color=0.25,0.5,0.75 40x30 3 -o small.tif") +command += oiiotool ("small.tif --thumbnail-get:fail=0" + + " --echo \"small source {TOP.width}x{TOP.height}\"") + +# Sizing follows the display window, not the data window. +command += oiiotool (no_thumb + " --fullsize 800x800 -o bigwindow.exr") +command += oiiotool ("bigwindow.exr --thumbnail-get:fail=0" + + " --echo \"display window {TOP.width}x{TOP.height}\"") # Test that fail=0 does not suppress an invalid index. command += oiiotool (psd + " --thumbnail-get:index=1:fail=0", failureok=True) @@ -40,11 +57,10 @@ # Test that -i:get_thumbnail forwards modifiers. command += oiiotool ("-i:get_thumbnail=1:fail=0 " + no_thumb - + " --if \"{TOP.width}\" --echo unexpected" - + " --else --echo \"input fail=0 returned empty\" --endif") + + " --echo \"input fail=0 generated {TOP.width}x{TOP.height}\"") command += oiiotool ("-i:get_thumbnail=1:index=1 " + psd, failureok=True) # Test that -i:get_thumbnail=0 returns the main image. command += oiiotool ("-i:get_thumbnail=0 " + psd + " --echo \"get_thumbnail=0 {TOP.width}x{TOP.height}\"") -outputs = [ "thumb.tif", "out.txt" ] +outputs = [ "thumb.tif", "generated.tif", "out.txt" ] diff --git a/testsuite/oiiotool-thumbnail-set/ref/out.txt b/testsuite/oiiotool-thumbnail-set/ref/out.txt index 67af6b31f3..214735aec3 100644 --- a/testsuite/oiiotool-thumbnail-set/ref/out.txt +++ b/testsuite/oiiotool-thumbnail-set/ref/out.txt @@ -26,8 +26,5 @@ no_thumb.tif : 512 x 384, 3 channel, uint8 tiff tiff:PhotometricInterpretation: 2 tiff:PlanarConfiguration: 1 tiff:RowsPerStrip: 32 -oiiotool ERROR: --thumbnail-set : Thumbnail image "" is empty -Full command line was: -> oiiotool ../common/tahoe-small.tif --dup --thumbnail-get:fail=0 --thumbnail-set oiiotool WARNING: --thumbnail-set : pending command never executed oiiotool WARNING : oiiotool produced no output. Did you forget -o? diff --git a/testsuite/oiiotool-thumbnail-set/run.py b/testsuite/oiiotool-thumbnail-set/run.py index 93742950da..47fc566927 100644 --- a/testsuite/oiiotool-thumbnail-set/run.py +++ b/testsuite/oiiotool-thumbnail-set/run.py @@ -26,9 +26,7 @@ command += oiiotool (src + " --dup --resize:filter=box 50x38 --thumbnail-set -o no_thumb.tif") command += info_command ("no_thumb.tif", safematch=True, hash=False) -# Test error cases: an empty thumbnail image, and too few images on the stack. -command += oiiotool (src + " --dup --thumbnail-get:fail=0 --thumbnail-set", - failureok=True) -command += oiiotool (src + " --thumbnail-set", failureok=True) +# Needs two images; with one it never runs, only warns. +command += oiiotool (src + " --thumbnail-set") outputs = [ "out.txt" ] From dfd54e6e310d1feaf0588019f694d3e8176ef00c Mon Sep 17 00:00:00 2001 From: Jinnie Kim Date: Sun, 26 Jul 2026 23:48:04 -0700 Subject: [PATCH 11/11] test(targa): Update thumbnail refs for associated alpha get_thumbnail() now associates alpha to match readimg(), so the old references no longer match. Only CTC32 and UTC32 change and the rest without alpha remain unaffected. Signed-off-by: Jinnie Kim --- testsuite/targa-thumbnail/ref/CTC32.TGA.tif | Bin 724 -> 1367 bytes testsuite/targa-thumbnail/ref/UTC32.TGA.tif | Bin 724 -> 1367 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/testsuite/targa-thumbnail/ref/CTC32.TGA.tif b/testsuite/targa-thumbnail/ref/CTC32.TGA.tif index 1c4d741744b56232b77b42b71afc5f6cdceab054..0834ad01870c428ef41af30e9d5dbb2fc4edc8e2 100644 GIT binary patch literal 1367 zcmeHGzi$&U81*F;>QY1z&;<$0Y=}Gi++5OFxfYZN(IJ3ZBv>yO)1Jcp(4Em8S%8Uu zf*}JVBO8_YA!Y^!#Kg!3jI4Z4+$ck(h!F{&p8Wjz`Lq4`-R|xc^tp!68HB7lvJgVo z0M-Jp0bhaGCqUiASAm~092TQ>YX#~48{n*o>s;pz*Xu7Bj)89&{TJXD7xdh!p>q)X z3Ghu^=iOV->;u>Nz5|YG7J333K{suyX!c!#k;XGvJONndeCuvF9);~8+1bXON>p(cOBLWM zewY-Z@<}jE2T}mnVM_2+qDDTst|##Q{vF&+bBR62vqQ!>b{)5|#W=r;IdeH>E$Vu< zyCqmtG(2n?fz9&a1F_e;VYY&iPY#vJ1f^wJI_0L5<_FYmx7(C)%DD{+yLgRFo!IXAB1EBjpn~=GXN1!=&@>k%|^R?-p_)c3N+J9I7bFTga D4v8Bn literal 724 zcmebD)MB{B$iTqQz`)4N0Aw&Y05KyHn+eEf0g4?0VrHl~2awGMWitWUf{ZK-Ao>yz z3qi#}W{E=CApK%cwgQkX4pn~&$d*A;Z^X#NAOaM71jM#bagez|NNR$CY>;`kfEWZg zpfD522H|f&91pad5ekhAj0~*|%&d$o6$~w`3@oioO&BWXygl#8)!@Lx;`sOf;>VU1 z5o}G2n)&an%tFKK-&x6R+5Y+Y-vx2s|JKKUziey&KV|;>`pxI(zy0}B{r8_gvupq5 zoIh`W^ZE1pZ$772*Zn(bXa7HKzWwdbpZ&l6{AvFC&&_}N|Je1|?r)%*ML^#!fW#jt x+(AHqV2myR1}&i&-2hZWN{p@ms>c?i2Y}*)Vsr;kO=deUI2E3NdV`$U1ppYMov8o- diff --git a/testsuite/targa-thumbnail/ref/UTC32.TGA.tif b/testsuite/targa-thumbnail/ref/UTC32.TGA.tif index 1c4d741744b56232b77b42b71afc5f6cdceab054..0834ad01870c428ef41af30e9d5dbb2fc4edc8e2 100644 GIT binary patch literal 1367 zcmeHGzi$&U81*F;>QY1z&;<$0Y=}Gi++5OFxfYZN(IJ3ZBv>yO)1Jcp(4Em8S%8Uu zf*}JVBO8_YA!Y^!#Kg!3jI4Z4+$ck(h!F{&p8Wjz`Lq4`-R|xc^tp!68HB7lvJgVo z0M-Jp0bhaGCqUiASAm~092TQ>YX#~48{n*o>s;pz*Xu7Bj)89&{TJXD7xdh!p>q)X z3Ghu^=iOV->;u>Nz5|YG7J333K{suyX!c!#k;XGvJONndeCuvF9);~8+1bXON>p(cOBLWM zewY-Z@<}jE2T}mnVM_2+qDDTst|##Q{vF&+bBR62vqQ!>b{)5|#W=r;IdeH>E$Vu< zyCqmtG(2n?fz9&a1F_e;VYY&iPY#vJ1f^wJI_0L5<_FYmx7(C)%DD{+yLgRFo!IXAB1EBjpn~=GXN1!=&@>k%|^R?-p_)c3N+J9I7bFTga D4v8Bn literal 724 zcmebD)MB{B$iTqQz`)4N0Aw&Y05KyHn+eEf0g4?0VrHl~2awGMWitWUf{ZK-Ao>yz z3qi#}W{E=CApK%cwgQkX4pn~&$d*A;Z^X#NAOaM71jM#bagez|NNR$CY>;`kfEWZg zpfD522H|f&91pad5ekhAj0~*|%&d$o6$~w`3@oioO&BWXygl#8)!@Lx;`sOf;>VU1 z5o}G2n)&an%tFKK-&x6R+5Y+Y-vx2s|JKKUziey&KV|;>`pxI(zy0}B{r8_gvupq5 zoIh`W^ZE1pZ$772*Zn(bXa7HKzWwdbpZ&l6{AvFC&&_}N|Je1|?r)%*ML^#!fW#jt x+(AHqV2myR1}&i&-2hZWN{p@ms>c?i2Y}*)Vsr;kO=deUI2E3NdV`$U1ppYMov8o-