From 7c6991a7c9f8184954470588ac59bbf1c9040fe0 Mon Sep 17 00:00:00 2001 From: Anton Dukhovnikov Date: Fri, 24 Jul 2026 14:13:48 +1200 Subject: [PATCH 1/7] raw: fixes and improvements to thumbnail functionality Co-authored-by: Ken McGaugh Signed-off-by: Anton Dukhovnikov --- src/cmake/testing.cmake | 6 + src/doc/builtinplugins.rst | 9 + src/raw.imageio/rawinput.cpp | 159 ++++++++++++++---- .../ref/out-libraw0.20.0.txt | 105 ++++++++++++ testsuite/python-thumbnail-raw/ref/out.txt | 105 ++++++++++++ testsuite/python-thumbnail-raw/run.py | 7 + .../src/test_raw_thumbnail.py | 55 ++++++ testsuite/raw/ref/out-libraw-0.20.2-gh.txt | 14 ++ testsuite/raw/ref/out-libraw0.20.0-gh.txt | 14 ++ testsuite/raw/ref/out-libraw0.20.0.txt | 16 ++ testsuite/raw/ref/out-libraw0.21.0-gh.txt | 14 ++ testsuite/raw/ref/out-libraw0.21.0-mac.txt | 16 ++ testsuite/raw/ref/out.txt | 16 ++ 13 files changed, 499 insertions(+), 37 deletions(-) create mode 100644 testsuite/python-thumbnail-raw/ref/out-libraw0.20.0.txt create mode 100644 testsuite/python-thumbnail-raw/ref/out.txt create mode 100755 testsuite/python-thumbnail-raw/run.py create mode 100755 testsuite/python-thumbnail-raw/src/test_raw_thumbnail.py diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index e1be9b89f8..f82fce5044 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -302,6 +302,12 @@ macro (oiio_add_all_tests) IMAGEDIR oiio-images ENVIRONMENT "${_pybind_tests_pythonpath}" ) + oiio_add_tests ( + python-thumbnail-raw + FOUNDVAR LIBRAW_FOUND ENABLEVAR ENABLE_LIBRAW + IMAGEDIR oiio-images/raw + ENVIRONMENT "${_pybind_tests_pythonpath}" + ) else () set (nanobind_python_test_suffix "") endif () diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index 8f12260530..4bbb1beae3 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -2339,6 +2339,15 @@ options are supported: - A path to a file containing the list of bad pixels in libraw format: a plain text where each line describes a single bad pixel using three numbers separated by whitespace for the column, row and UNIX timestamp. + * - ``raw:thumbnail_index`` + - int + - An index of the thumbnail that gets returned from get_thumbnail(). The + default index (-1) leaves the choice to libraw, which normally would pick + the largest resolution. + * - ``raw:thumbnail_sort`` + - int + - If 1, the ``raw:thumbnail_index`` above picks a thumbnail from a list + sorted by size, so thumbnail_index = 0 always returns the smallest image. | .. _sec-bundledplugins-rla: diff --git a/src/raw.imageio/rawinput.cpp b/src/raw.imageio/rawinput.cpp index f2ffa9d2b7..968eec56b5 100644 --- a/src/raw.imageio/rawinput.cpp +++ b/src/raw.imageio/rawinput.cpp @@ -79,7 +79,10 @@ class RawInput final : public ImageInput { bool m_unpacked = false; std::unique_ptr m_processor; libraw_processed_image_t* m_image = nullptr; - bool m_do_scene_linear_scale = false; + libraw_processed_image_t* m_thumb = nullptr; + int m_thumb_index = -1; + + bool m_do_scene_linear_scale = false; float m_camera_to_scene_linear_scale = (1.0f / 0.45f); // see open_raw for details bool m_do_balance_clamped = false; @@ -1098,7 +1101,79 @@ RawInput::open_raw(bool unpack, bool process, const std::string& name, m_spec.attribute("Orientation", original_flip); } - // FIXME -- thumbnail possibly in m_processor->imgdata.thumbnail +#if LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0) + if (m_processor->imgdata.thumbnail.tlength > 0) { + auto width = m_processor->imgdata.thumbnail.twidth; + auto height = m_processor->imgdata.thumbnail.theight; + + // see note below + if (width == 0) + width = -1; + if (height == 0) + height = -1; + + m_spec.attribute("thumbnail_width", width); + m_spec.attribute("thumbnail_height", height); + } +#else + + auto thumb_count = m_processor->imgdata.thumbs_list.thumbcount; + if (thumb_count > 0) { + m_thumb_index = config.get_int_attribute("raw:thumbnail_index", -1); + + int width = 0; + int height = 0; + + if (m_thumb_index == -1) { + // Use the default from LibRaw, which will always be the largest one + width = m_processor->imgdata.thumbnail.twidth; + height = m_processor->imgdata.thumbnail.theight; + } else { + // clamp the thumbnail index + if (m_thumb_index < 0) + m_thumb_index = 0; + else if (m_thumb_index >= thumb_count) + m_thumb_index = thumb_count - 1; + + // sort the thumbnails by their size if requested + if (config.get_int_attribute("raw:thumbnail_sort", 0)) { + auto index_map = std::vector(thumb_count); + for (auto i = 0; i < thumb_count; ++i) + index_map[i] = i; + + std::sort(index_map.begin(), index_map.end(), + [&](size_t a, size_t b) { + return m_processor->imgdata.thumbs_list + .thumblist[a] + .tlength + < m_processor->imgdata.thumbs_list + .thumblist[b] + .tlength; + }); + m_thumb_index = index_map[m_thumb_index]; + } + + width = m_processor->imgdata.thumbs_list.thumblist[m_thumb_index] + .twidth; + height = m_processor->imgdata.thumbs_list.thumblist[m_thumb_index] + .theight; + } + + // LibRaw sometimes won't set the twidth/theight if the type is JPEG, + // however the OIIO::ImageBuf interface checks for non-zero + // "thumbnail_width" and "thumbnail_height" to determine if a thumbnail + // is present. In that case we set them to -1 which emables the ImageBuf + // interface but is clear that the dimensions have not been set. + if (width == 0) + width = -1; + if (height == 0) + height = -1; + + m_spec.attribute("thumbnail_width", width); + m_spec.attribute("thumbnail_height", height); + } +#endif + get_lensinfo(); get_shootinginfo(); @@ -1580,6 +1655,12 @@ RawInput::close() LibRaw::dcraw_clear_mem(m_image); m_image = nullptr; } + + if (m_thumb) { + LibRaw::dcraw_clear_mem(m_thumb); + m_thumb = nullptr; + } + m_processor.reset(); m_unpacked = false; m_process = true; @@ -1765,84 +1846,84 @@ bool RawInput::get_thumbnail(ImageBuf& thumb, int subimage) { if (m_processor == nullptr) { - _errorfmt(this, subimage, + _errorfmt(this, m_thumb_index, "ImageInput hasn't been initialised properly"); return false; } #if LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0) - if (subimage > 0) { - // Older versions of Libraw supported a single thumbnail per image. - // No error here. - return false; - } int errcode = m_processor->unpack_thumb(); if (errcode != 0) { if (errcode != LIBRAW_REQUEST_FOR_NONEXISTENT_IMAGE) - _errorfmt(this, subimage, "unpack_thumb error"); + _errorfmt(this, m_thumb_index, "unpack_thumb error"); return false; } #else - int errcode = m_processor->unpack_thumb_ex(subimage); + int errcode; + if (m_thumb_index == -1) + errcode = m_processor->unpack_thumb(); + else + errcode = m_processor->unpack_thumb_ex(m_thumb_index); + if (errcode != 0) { if (errcode != LIBRAW_REQUEST_FOR_NONEXISTENT_THUMBNAIL) - _errorfmt(this, subimage, "unpack_thumb_ex error"); + _errorfmt(this, m_thumb_index, "unpack_thumb_ex error"); return false; } #endif - libraw_processed_image_t* mem_thumb = m_processor->dcraw_make_mem_thumb( - &errcode); - if (mem_thumb == nullptr) { - _errorfmt(this, subimage, "dcraw_make_mem_thumb error"); - return false; + if (!m_thumb) { + m_thumb = m_processor->dcraw_make_mem_thumb(&errcode); + if (m_thumb == nullptr) { + _errorfmt(this, m_thumb_index, "dcraw_make_mem_thumb error"); + return false; + } } std::string image_type; - if (mem_thumb->type == LibRaw_image_formats::LIBRAW_IMAGE_JPEG) + if (m_thumb->type == LibRaw_image_formats::LIBRAW_IMAGE_JPEG) image_type = "jpeg"; - else if (mem_thumb->type == LibRaw_image_formats::LIBRAW_IMAGE_BITMAP) + else if (m_thumb->type == LibRaw_image_formats::LIBRAW_IMAGE_BITMAP) image_type = "bmp"; #if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 22, 0) - else if (mem_thumb->type == LibRaw_image_formats::LIBRAW_IMAGE_JPEGXL) + else if (m_thumb->type == LibRaw_image_formats::LIBRAW_IMAGE_JPEGXL) image_type = "jpegxl"; - else if (mem_thumb->type == LibRaw_image_formats::LIBRAW_IMAGE_H265) + else if (m_thumb->type == LibRaw_image_formats::LIBRAW_IMAGE_H265) image_type = "h265"; #endif if (image_type == "h265") { - _errorfmt(this, subimage, "h265 thumbnails are not supported yet"); + _errorfmt(this, m_thumb_index, "h265 thumbnails are not supported yet"); return false; } if (image_type.empty()) { - _errorfmt(this, subimage, "unknown image type {}", - static_cast(mem_thumb->type)); + _errorfmt(this, m_thumb_index, "unknown image type {}", + static_cast(m_thumb->type)); return false; } if (image_type == "bmp") { - size_t data_size = mem_thumb->width * mem_thumb->height - * mem_thumb->colors; - if (data_size != mem_thumb->data_size) + size_t data_size = m_thumb->width * m_thumb->height * m_thumb->colors; + if (data_size != m_thumb->data_size) return false; - ImageSpec image_spec(mem_thumb->width, mem_thumb->height, - mem_thumb->colors, TypeDesc::UCHAR); + ImageSpec image_spec(m_thumb->width, m_thumb->height, m_thumb->colors, + TypeDesc::UCHAR); thumb.reset(image_spec); - thumb.set_pixels(thumb.roi_full(), TypeDesc::UCHAR, mem_thumb->data); + thumb.set_pixels(thumb.roi_full(), TypeDesc::UCHAR, m_thumb->data); } else { auto image_input = OIIO::ImageInput::create(image_type, false); if (image_input == nullptr) { - _errorfmt(this, subimage, "OIIO::ImageInput::create(\{}\") error", - image_type); + _errorfmt(this, m_thumb_index, + "OIIO::ImageInput::create(\{}\") error", image_type); return false; } - Filesystem::IOMemReader proxy(mem_thumb->data, mem_thumb->data_size); + Filesystem::IOMemReader proxy(m_thumb->data, m_thumb->data_size); bool result = image_input->valid_file(&proxy); if (!result) { - _errorfmt(this, subimage, + _errorfmt(this, m_thumb_index, "the thumbnail is not a valid image of type \"{}\"", image_type); return false; @@ -1855,8 +1936,9 @@ RawInput::get_thumbnail(ImageBuf& thumb, int subimage) result = image_input->open("", image_spec, temp_spec); if (!result) { _errorfmt( - this, subimage, - "failed to initialise an ImageInput object with the thumbnail data"); + this, m_thumb_index, + "failed to initialise an ImageInput object with the thumbnail" + " data"); return false; } @@ -1866,11 +1948,14 @@ RawInput::get_thumbnail(ImageBuf& thumb, int subimage) thumb.localpixels()); if (!result) { _errorfmt( - this, subimage, - "failed to initialise an ImageInput object of type \"{}\" with the thumbnail data", + this, m_thumb_index, + "failed to initialise an ImageInput object of type \"{}\" with" + " the thumbnail data", image_type); + image_input->close(); return false; } + image_input->close(); } return true; diff --git a/testsuite/python-thumbnail-raw/ref/out-libraw0.20.0.txt b/testsuite/python-thumbnail-raw/ref/out-libraw0.20.0.txt new file mode 100644 index 0000000000..530c5de319 --- /dev/null +++ b/testsuite/python-thumbnail-raw/ref/out-libraw0.20.0.txt @@ -0,0 +1,105 @@ +RAW_CANON_EOS_7D.CR2 + Thumbnail index: -1 Sort: False + 5184 + 3456 + 3 + Thumbnail index: 0 Sort: False + 5184 + 3456 + 3 + Thumbnail index: 1 Sort: True + 5184 + 3456 + 3 +RAW_FUJI_F700.RAF + Thumbnail index: -1 Sort: False + 1280 + 960 + 3 + Thumbnail index: 0 Sort: False + 1280 + 960 + 3 + Thumbnail index: 1 Sort: True + 1280 + 960 + 3 +RAW_NIKON_D3X.NEF + Thumbnail index: -1 Sort: False + 6048 + 4032 + 3 + Thumbnail index: 0 Sort: False + 6048 + 4032 + 3 + Thumbnail index: 1 Sort: True + 6048 + 4032 + 3 +RAW_OLYMPUS_E3.ORF + Thumbnail index: -1 Sort: False + 1600 + 1200 + 3 + Thumbnail index: 0 Sort: False + 1600 + 1200 + 3 + Thumbnail index: 1 Sort: True + 1600 + 1200 + 3 +RAW_PANASONIC_DMC-GF1.RW2 + Thumbnail index: -1 Sort: False + 1920 + 1440 + 3 + Thumbnail index: 0 Sort: False + 1920 + 1440 + 3 + Thumbnail index: 1 Sort: True + 1920 + 1440 + 3 +RAW_PANASONIC_G1.RW2 + Thumbnail index: -1 Sort: False + 1920 + 1440 + 3 + Thumbnail index: 0 Sort: False + 1920 + 1440 + 3 + Thumbnail index: 1 Sort: True + 1920 + 1440 + 3 +RAW_PENTAX_K200D.PEF + Thumbnail index: -1 Sort: False + 3872 + 2592 + 3 + Thumbnail index: 0 Sort: False + 3872 + 2592 + 3 + Thumbnail index: 1 Sort: True + 3872 + 2592 + 3 +RAW_SONY_A300.ARW + Thumbnail index: -1 Sort: False + 1616 + 1080 + 3 + Thumbnail index: 0 Sort: False + 1616 + 1080 + 3 + Thumbnail index: 1 Sort: True + 1616 + 1080 + 3 +Done. diff --git a/testsuite/python-thumbnail-raw/ref/out.txt b/testsuite/python-thumbnail-raw/ref/out.txt new file mode 100644 index 0000000000..4b5252283f --- /dev/null +++ b/testsuite/python-thumbnail-raw/ref/out.txt @@ -0,0 +1,105 @@ +RAW_CANON_EOS_7D.CR2 + Thumbnail index: -1 Sort: False + 5184 + 3456 + 3 + Thumbnail index: 0 Sort: False + 5184 + 3456 + 3 + Thumbnail index: 1 Sort: True + 670 + 432 + 3 +RAW_FUJI_F700.RAF + Thumbnail index: -1 Sort: False + 1280 + 960 + 3 + Thumbnail index: 0 Sort: False + 160 + 120 + 3 + Thumbnail index: 1 Sort: True + 1280 + 960 + 3 +RAW_NIKON_D3X.NEF + Thumbnail index: -1 Sort: False + 6048 + 4032 + 3 + Thumbnail index: 0 Sort: False + 160 + 120 + 3 + Thumbnail index: 1 Sort: True + 570 + 375 + 3 +RAW_OLYMPUS_E3.ORF + Thumbnail index: -1 Sort: False + 1600 + 1200 + 3 + Thumbnail index: 0 Sort: False + 1600 + 1200 + 3 + Thumbnail index: 1 Sort: True + 1600 + 1200 + 3 +RAW_PANASONIC_DMC-GF1.RW2 + Thumbnail index: -1 Sort: False + 1920 + 1440 + 3 + Thumbnail index: 0 Sort: False + 1920 + 1440 + 3 + Thumbnail index: 1 Sort: True + 1920 + 1440 + 3 +RAW_PANASONIC_G1.RW2 + Thumbnail index: -1 Sort: False + 1920 + 1440 + 3 + Thumbnail index: 0 Sort: False + 1920 + 1440 + 3 + Thumbnail index: 1 Sort: True + 1920 + 1440 + 3 +RAW_PENTAX_K200D.PEF + Thumbnail index: -1 Sort: False + 3872 + 2592 + 3 + Thumbnail index: 0 Sort: False + 160 + 120 + 3 + Thumbnail index: 1 Sort: True + 3872 + 2592 + 3 +RAW_SONY_A300.ARW + Thumbnail index: -1 Sort: False + 1616 + 1080 + 3 + Thumbnail index: 0 Sort: False + 1616 + 1080 + 3 + Thumbnail index: 1 Sort: True + 1616 + 1080 + 3 +Done. diff --git a/testsuite/python-thumbnail-raw/run.py b/testsuite/python-thumbnail-raw/run.py new file mode 100755 index 0000000000..1c4b398c03 --- /dev/null +++ b/testsuite/python-thumbnail-raw/run.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + +command += pythonbin + " src/test_raw_thumbnail.py >> out.txt 2>&1" diff --git a/testsuite/python-thumbnail-raw/src/test_raw_thumbnail.py b/testsuite/python-thumbnail-raw/src/test_raw_thumbnail.py new file mode 100755 index 0000000000..c56ed9e54c --- /dev/null +++ b/testsuite/python-thumbnail-raw/src/test_raw_thumbnail.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + +from __future__ import annotations + +import OpenImageIO as oiio + +import os + +OIIO_TESTSUITE_IMAGEDIR = os.getenv('OIIO_TESTSUITE_IMAGEDIR') + +###################################################################### +# main test starts here + + +test_files = [ + 'RAW_CANON_EOS_7D.CR2', + 'RAW_FUJI_F700.RAF', + 'RAW_NIKON_D3X.NEF', + 'RAW_OLYMPUS_E3.ORF', + 'RAW_PANASONIC_DMC-GF1.RW2', + 'RAW_PANASONIC_G1.RW2', + 'RAW_PENTAX_K200D.PEF', + 'RAW_SONY_A300.ARW' +] + +def read_thumbnail(filename, index = -1, sort = False): + + hint = oiio.ImageSpec() + + if index != -1: + hint['raw:thumbnail_index'] = index + if sort: + hint['raw:thumbnail_sort'] = True + + input = oiio.ImageInput.open (OIIO_TESTSUITE_IMAGEDIR + '/' + file, hint) + thumbnail = input.get_thumbnail() + print(' Thumbnail index:', index, 'Sort:', sort) + print(' ', thumbnail.spec().width) + print(' ', thumbnail.spec().height) + print(' ', thumbnail.spec().nchannels) + +try: + for file in test_files: + print(file) + read_thumbnail(file, -1) + read_thumbnail(file, 0) + read_thumbnail(file, 1, True) + + print ("Done.") +except Exception as detail: + print ("Unknown exception:", detail) diff --git a/testsuite/raw/ref/out-libraw-0.20.2-gh.txt b/testsuite/raw/ref/out-libraw-0.20.2-gh.txt index bec9f0c940..fd4bf9983d 100644 --- a/testsuite/raw/ref/out-libraw-0.20.2-gh.txt +++ b/testsuite/raw/ref/out-libraw-0.20.2-gh.txt @@ -10,6 +10,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Firmware Version 1.0.7" + thumbnail_height: 3456 + thumbnail_width: 5184 Canon:AESetting: 0 (normal AE) Canon:AFAreaMode: 0 Canon:AFPoint: 0 @@ -96,6 +98,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Ver.1.00 " + thumbnail_height: 4032 + thumbnail_width: 6048 Exif:ApertureValue: 4.97085 (f/5.6) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -187,6 +191,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Digital Camera FinePix F700 Ver2.00" + thumbnail_height: 960 + thumbnail_width: 1280 Exif:ApertureValue: 3.69599 (f/3.6) Exif:BrightnessValue: 773/100 (7.73) Exif:CompressedBitsPerPixel: 30/10 (3) @@ -261,6 +267,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Ver.1.00 " + thumbnail_height: 4032 + thumbnail_width: 6048 Exif:ApertureValue: 4.97085 (f/5.6) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -355,6 +363,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Version 1.0 " + thumbnail_height: 1200 + thumbnail_width: 1600 Exif:ApertureValue: 2 (f/2.0) Exif:Contrast: 0 (normal) Exif:CustomRendered: 1 (yes) @@ -428,6 +438,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "K200D Ver 1.00 " + thumbnail_height: 2592 + thumbnail_width: 3872 Exif:ApertureValue: 6 (f/8.0) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -497,6 +509,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "DSLR-A300 v1.00" + thumbnail_height: 1080 + thumbnail_width: 1616 Exif:ApertureValue: 4.97085 (f/5.6) Exif:BrightnessValue: 137/100 (1.37) Exif:CompressedBitsPerPixel: 8/1 (8) diff --git a/testsuite/raw/ref/out-libraw0.20.0-gh.txt b/testsuite/raw/ref/out-libraw0.20.0-gh.txt index c6b813c1b7..885142ec1b 100644 --- a/testsuite/raw/ref/out-libraw0.20.0-gh.txt +++ b/testsuite/raw/ref/out-libraw0.20.0-gh.txt @@ -10,6 +10,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Firmware Version 1.0.7" + thumbnail_height: 3456 + thumbnail_width: 5184 Canon:AESetting: 0 (normal AE) Canon:AFAreaMode: 0 Canon:AFPoint: 0 @@ -96,6 +98,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Ver.1.00 " + thumbnail_height: 4032 + thumbnail_width: 6048 Exif:ApertureValue: 4.97085 (f/5.6) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -186,6 +190,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Digital Camera FinePix F700 Ver2.00" + thumbnail_height: 960 + thumbnail_width: 1280 Exif:ApertureValue: 3.69599 (f/3.6) Exif:BrightnessValue: 773/100 (7.73) Exif:CompressedBitsPerPixel: 30/10 (3) @@ -259,6 +265,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Ver.1.00 " + thumbnail_height: 4032 + thumbnail_width: 6048 Exif:ApertureValue: 4.97085 (f/5.6) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -352,6 +360,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Version 1.0 " + thumbnail_height: 1200 + thumbnail_width: 1600 Exif:ApertureValue: 2 (f/2.0) Exif:Contrast: 0 (normal) Exif:CustomRendered: 1 (yes) @@ -424,6 +434,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "K200D Ver 1.00 " + thumbnail_height: 2592 + thumbnail_width: 3872 Exif:ApertureValue: 6 (f/8.0) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -492,6 +504,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "DSLR-A300 v1.00" + thumbnail_height: 1080 + thumbnail_width: 1616 Exif:ApertureValue: 4.97085 (f/5.6) Exif:BrightnessValue: 137/100 (1.37) Exif:CompressedBitsPerPixel: 8/1 (8) diff --git a/testsuite/raw/ref/out-libraw0.20.0.txt b/testsuite/raw/ref/out-libraw0.20.0.txt index 8bceb81d29..eecc75b80d 100644 --- a/testsuite/raw/ref/out-libraw0.20.0.txt +++ b/testsuite/raw/ref/out-libraw0.20.0.txt @@ -10,6 +10,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Firmware Version 1.0.7" + thumbnail_height: 3456 + thumbnail_width: 5184 Canon:AESetting: 0 (normal AE) Canon:AFAreaMode: 0 Canon:AFPoint: 0 @@ -95,6 +97,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Ver.1.00 " + thumbnail_height: 4032 + thumbnail_width: 6048 Exif:ApertureValue: 4.97085 (f/5.6) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -185,6 +189,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Digital Camera FinePix F700 Ver2.00" + thumbnail_height: 960 + thumbnail_width: 1280 Exif:ApertureValue: 3.69599 (f/3.6) Exif:BrightnessValue: 773/100 (7.73) Exif:CompressedBitsPerPixel: 30/10 (3) @@ -258,6 +264,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Ver.1.00 " + thumbnail_height: 4032 + thumbnail_width: 6048 Exif:ApertureValue: 4.97085 (f/5.6) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -351,6 +359,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Version 1.0 " + thumbnail_height: 1200 + thumbnail_width: 1600 Exif:ApertureValue: 2 (f/2.0) Exif:Contrast: 0 (normal) Exif:CustomRendered: 1 (yes) @@ -422,6 +432,8 @@ Model: "DMC-G1" Orientation: 1 (normal) PixelAspectRatio: 1 + thumbnail_height: 1440 + thumbnail_width: 1920 Exif:ApertureValue: 5.3107 (f/6.3) Exif:DateTimeDigitized: "2008:12:10 15:06:33" Exif:DateTimeOriginal: "2008:12:10 15:06:33" @@ -463,6 +475,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "K200D Ver 1.00 " + thumbnail_height: 2592 + thumbnail_width: 3872 Exif:ApertureValue: 6 (f/8.0) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -531,6 +545,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "DSLR-A300 v1.00" + thumbnail_height: 1080 + thumbnail_width: 1616 Exif:ApertureValue: 4.97085 (f/5.6) Exif:BrightnessValue: 137/100 (1.37) Exif:CompressedBitsPerPixel: 8/1 (8) diff --git a/testsuite/raw/ref/out-libraw0.21.0-gh.txt b/testsuite/raw/ref/out-libraw0.21.0-gh.txt index e0afdb021a..e8ca649833 100644 --- a/testsuite/raw/ref/out-libraw0.21.0-gh.txt +++ b/testsuite/raw/ref/out-libraw0.21.0-gh.txt @@ -10,6 +10,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Firmware Version 1.0.7" + thumbnail_height: 3456 + thumbnail_width: 5184 Canon:AESetting: 0 (normal AE) Canon:AFPoint: 0 Canon:AverageBlackLevel: 2047 @@ -93,6 +95,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Ver.1.00 " + thumbnail_height: 4032 + thumbnail_width: 6048 Exif:ApertureValue: 4.97085 (f/5.6) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -181,6 +185,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Digital Camera FinePix F700 Ver2.00" + thumbnail_height: 960 + thumbnail_width: 1280 Exif:ApertureValue: 3.69599 (f/3.6) Exif:BrightnessValue: 773/100 (7.73) Exif:CompressedBitsPerPixel: 30/10 (3) @@ -253,6 +259,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Ver.1.00 " + thumbnail_height: 4032 + thumbnail_width: 6048 Exif:ApertureValue: 4.97085 (f/5.6) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -344,6 +352,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Version 1.0 " + thumbnail_height: 1200 + thumbnail_width: 1600 Exif:ApertureValue: 2 (f/2.0) Exif:Contrast: 0 (normal) Exif:CustomRendered: 1 (yes) @@ -418,6 +428,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "K200D Ver 1.00 " + thumbnail_height: 2592 + thumbnail_width: 3872 Exif:ApertureValue: 6 (f/8.0) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -487,6 +499,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "DSLR-A300 v1.00" + thumbnail_height: 1080 + thumbnail_width: 1616 Exif:ApertureValue: 4.97085 (f/5.6) Exif:BrightnessValue: 137/100 (1.37) Exif:CompressedBitsPerPixel: 8/1 (8) diff --git a/testsuite/raw/ref/out-libraw0.21.0-mac.txt b/testsuite/raw/ref/out-libraw0.21.0-mac.txt index 76ba8ad88b..b0be9ee7cb 100644 --- a/testsuite/raw/ref/out-libraw0.21.0-mac.txt +++ b/testsuite/raw/ref/out-libraw0.21.0-mac.txt @@ -10,6 +10,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Firmware Version 1.0.7" + thumbnail_height: 3456 + thumbnail_width: 5184 Canon:AESetting: 0 (normal AE) Canon:AFPoint: 0 Canon:AverageBlackLevel: 2047 @@ -93,6 +95,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Ver.1.00 " + thumbnail_height: 4032 + thumbnail_width: 6048 Exif:ApertureValue: 4.97085 (f/5.6) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -181,6 +185,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Digital Camera FinePix F700 Ver2.00" + thumbnail_height: 960 + thumbnail_width: 1280 Exif:ApertureValue: 3.69599 (f/3.6) Exif:BrightnessValue: 773/100 (7.73) Exif:CompressedBitsPerPixel: 30/10 (3) @@ -253,6 +259,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Ver.1.00 " + thumbnail_height: 4032 + thumbnail_width: 6048 Exif:ApertureValue: 4.97085 (f/5.6) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -344,6 +352,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Version 1.0 " + thumbnail_height: 1200 + thumbnail_width: 1600 Exif:ApertureValue: 2 (f/2.0) Exif:Contrast: 0 (normal) Exif:CustomRendered: 1 (yes) @@ -417,6 +427,8 @@ Model: "DMC-G1" Orientation: 1 (normal) PixelAspectRatio: 1 + thumbnail_height: 1440 + thumbnail_width: 1920 Exif:ApertureValue: 5.3107 (f/6.3) Exif:DateTimeDigitized: "2008:12:10 15:06:33" Exif:DateTimeOriginal: "2008:12:10 15:06:33" @@ -459,6 +471,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "K200D Ver 1.00 " + thumbnail_height: 2592 + thumbnail_width: 3872 Exif:ApertureValue: 6 (f/8.0) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -528,6 +542,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "DSLR-A300 v1.00" + thumbnail_height: 1080 + thumbnail_width: 1616 Exif:ApertureValue: 4.97085 (f/5.6) Exif:BrightnessValue: 137/100 (1.37) Exif:CompressedBitsPerPixel: 8/1 (8) diff --git a/testsuite/raw/ref/out.txt b/testsuite/raw/ref/out.txt index d29bc262d1..8288cee578 100644 --- a/testsuite/raw/ref/out.txt +++ b/testsuite/raw/ref/out.txt @@ -10,6 +10,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Firmware Version 1.0.7" + thumbnail_height: 3456 + thumbnail_width: 5184 Canon:AESetting: 0 (normal AE) Canon:AFAreaMode: 0 Canon:AFPoint: 0 @@ -95,6 +97,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Ver.1.00 " + thumbnail_height: 4032 + thumbnail_width: 6048 Exif:ApertureValue: 4.97085 (f/5.6) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -186,6 +190,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Digital Camera FinePix F700 Ver2.00" + thumbnail_height: 960 + thumbnail_width: 1280 Exif:ApertureValue: 3.69599 (f/3.6) Exif:BrightnessValue: 773/100 (7.73) Exif:CompressedBitsPerPixel: 30/10 (3) @@ -257,6 +263,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Ver.1.00 " + thumbnail_height: 4032 + thumbnail_width: 6048 Exif:ApertureValue: 4.97085 (f/5.6) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -351,6 +359,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "Version 1.0 " + thumbnail_height: 1200 + thumbnail_width: 1600 Exif:ApertureValue: 2 (f/2.0) Exif:Contrast: 0 (normal) Exif:CustomRendered: 1 (yes) @@ -422,6 +432,8 @@ Model: "DMC-G1" Orientation: 1 (normal) PixelAspectRatio: 1 + thumbnail_height: 1440 + thumbnail_width: 1920 Exif:ApertureValue: 5.3107 (f/6.3) Exif:DateTimeDigitized: "2008:12:10 15:06:33" Exif:DateTimeOriginal: "2008:12:10 15:06:33" @@ -459,6 +471,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "K200D Ver 1.00 " + thumbnail_height: 2592 + thumbnail_width: 3872 Exif:ApertureValue: 6 (f/8.0) Exif:Contrast: 0 (normal) Exif:CustomRendered: 0 (no) @@ -528,6 +542,8 @@ Orientation: 1 (normal) PixelAspectRatio: 1 Software: "DSLR-A300 v1.00" + thumbnail_height: 1080 + thumbnail_width: 1616 Exif:ApertureValue: 4.97085 (f/5.6) Exif:BrightnessValue: 137/100 (1.37) Exif:CompressedBitsPerPixel: 8/1 (8) From beb09cace352b57f4cf772292fecdc2a10c6dea8 Mon Sep 17 00:00:00 2001 From: Anton Dukhovnikov Date: Tue, 28 Jul 2026 14:54:37 +1200 Subject: [PATCH 2/7] address the code review issues Signed-off-by: Anton Dukhovnikov --- src/cmake/testing.cmake | 2 +- src/doc/builtinplugins.rst | 7 +++- src/raw.imageio/rawinput.cpp | 40 ++++++++++++++----- .../ref/out-libraw0.20.0.txt | 0 .../ref/out.txt | 0 .../run.py | 0 .../src/test_raw_thumbnail.py | 2 +- 7 files changed, 37 insertions(+), 14 deletions(-) rename testsuite/{python-thumbnail-raw => raw-thumbnail}/ref/out-libraw0.20.0.txt (100%) rename testsuite/{python-thumbnail-raw => raw-thumbnail}/ref/out.txt (100%) rename testsuite/{python-thumbnail-raw => raw-thumbnail}/run.py (100%) rename testsuite/{python-thumbnail-raw => raw-thumbnail}/src/test_raw_thumbnail.py (97%) diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index f82fce5044..cfb10c23ed 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -303,7 +303,7 @@ macro (oiio_add_all_tests) ENVIRONMENT "${_pybind_tests_pythonpath}" ) oiio_add_tests ( - python-thumbnail-raw + raw-thumbnail FOUNDVAR LIBRAW_FOUND ENABLEVAR ENABLE_LIBRAW IMAGEDIR oiio-images/raw ENVIRONMENT "${_pybind_tests_pythonpath}" diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index 4bbb1beae3..aca8793a57 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -2344,10 +2344,13 @@ options are supported: - An index of the thumbnail that gets returned from get_thumbnail(). The default index (-1) leaves the choice to libraw, which normally would pick the largest resolution. + (Default: -1) * - ``raw:thumbnail_sort`` - int - - If 1, the ``raw:thumbnail_index`` above picks a thumbnail from a list - sorted by size, so thumbnail_index = 0 always returns the smallest image. + - Controls the sort order of the list the thumbnail gets picked from. + -1 - sort small-to-large, 0 - don't sort, use the original file order, + 1 - sort large-to-small. + (Default: 0) | .. _sec-bundledplugins-rla: diff --git a/src/raw.imageio/rawinput.cpp b/src/raw.imageio/rawinput.cpp index 968eec56b5..677435da94 100644 --- a/src/raw.imageio/rawinput.cpp +++ b/src/raw.imageio/rawinput.cpp @@ -1136,20 +1136,40 @@ RawInput::open_raw(bool unpack, bool process, const std::string& name, m_thumb_index = thumb_count - 1; // sort the thumbnails by their size if requested - if (config.get_int_attribute("raw:thumbnail_sort", 0)) { + int sort_order = config.get_int_attribute("raw:thumbnail_sort", 0); + + if (sort_order < -1 || sort_order > 1) { + errorfmt("Invalid thumbnail sort order ({})", sort_order); + return false; + } + + if (sort_order != 0) { auto index_map = std::vector(thumb_count); for (auto i = 0; i < thumb_count; ++i) index_map[i] = i; - std::sort(index_map.begin(), index_map.end(), - [&](size_t a, size_t b) { - return m_processor->imgdata.thumbs_list - .thumblist[a] - .tlength - < m_processor->imgdata.thumbs_list - .thumblist[b] - .tlength; - }); + if (sort_order == -1) { + std::sort(index_map.begin(), index_map.end(), + [&](size_t a, size_t b) { + return m_processor->imgdata.thumbs_list + .thumblist[a] + .tlength + < m_processor->imgdata.thumbs_list + .thumblist[b] + .tlength; + }); + } else { + std::sort(index_map.begin(), index_map.end(), + [&](size_t a, size_t b) { + return m_processor->imgdata.thumbs_list + .thumblist[a] + .tlength + > m_processor->imgdata.thumbs_list + .thumblist[b] + .tlength; + }); + } + m_thumb_index = index_map[m_thumb_index]; } diff --git a/testsuite/python-thumbnail-raw/ref/out-libraw0.20.0.txt b/testsuite/raw-thumbnail/ref/out-libraw0.20.0.txt similarity index 100% rename from testsuite/python-thumbnail-raw/ref/out-libraw0.20.0.txt rename to testsuite/raw-thumbnail/ref/out-libraw0.20.0.txt diff --git a/testsuite/python-thumbnail-raw/ref/out.txt b/testsuite/raw-thumbnail/ref/out.txt similarity index 100% rename from testsuite/python-thumbnail-raw/ref/out.txt rename to testsuite/raw-thumbnail/ref/out.txt diff --git a/testsuite/python-thumbnail-raw/run.py b/testsuite/raw-thumbnail/run.py similarity index 100% rename from testsuite/python-thumbnail-raw/run.py rename to testsuite/raw-thumbnail/run.py diff --git a/testsuite/python-thumbnail-raw/src/test_raw_thumbnail.py b/testsuite/raw-thumbnail/src/test_raw_thumbnail.py similarity index 97% rename from testsuite/python-thumbnail-raw/src/test_raw_thumbnail.py rename to testsuite/raw-thumbnail/src/test_raw_thumbnail.py index c56ed9e54c..eedc14e3e3 100755 --- a/testsuite/python-thumbnail-raw/src/test_raw_thumbnail.py +++ b/testsuite/raw-thumbnail/src/test_raw_thumbnail.py @@ -34,7 +34,7 @@ def read_thumbnail(filename, index = -1, sort = False): if index != -1: hint['raw:thumbnail_index'] = index if sort: - hint['raw:thumbnail_sort'] = True + hint['raw:thumbnail_sort'] = -1 input = oiio.ImageInput.open (OIIO_TESTSUITE_IMAGEDIR + '/' + file, hint) thumbnail = input.get_thumbnail() From aceca747ff2481b428dd5083e5389eb79975e6cf Mon Sep 17 00:00:00 2001 From: Anton Dukhovnikov Date: Wed, 29 Jul 2026 10:05:09 +1200 Subject: [PATCH 3/7] change the unittests to use oiiotool Signed-off-by: Anton Dukhovnikov --- src/cmake/testing.cmake | 8 +- .../raw-thumbnail/ref/out-libraw0.20.0.txt | 177 +++++++----------- testsuite/raw-thumbnail/ref/out.txt | 177 +++++++----------- testsuite/raw-thumbnail/run.py | 40 +++- .../raw-thumbnail/src/test_raw_thumbnail.py | 55 ------ 5 files changed, 184 insertions(+), 273 deletions(-) delete mode 100755 testsuite/raw-thumbnail/src/test_raw_thumbnail.py diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index cfb10c23ed..559286e591 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -302,12 +302,6 @@ macro (oiio_add_all_tests) IMAGEDIR oiio-images ENVIRONMENT "${_pybind_tests_pythonpath}" ) - oiio_add_tests ( - raw-thumbnail - FOUNDVAR LIBRAW_FOUND ENABLEVAR ENABLE_LIBRAW - IMAGEDIR oiio-images/raw - ENVIRONMENT "${_pybind_tests_pythonpath}" - ) else () set (nanobind_python_test_suffix "") endif () @@ -461,7 +455,7 @@ macro (oiio_add_all_tests) IMAGEDIR oiio-images/psd) oiio_add_tests (ptex FOUNDVAR Ptex_FOUND ENABLEVAR ENABLE_PTEX) - oiio_add_tests (raw + oiio_add_tests (raw raw-thumbnail FOUNDVAR LIBRAW_FOUND ENABLEVAR ENABLE_LIBRAW IMAGEDIR oiio-images/raw) oiio_add_tests (rla diff --git a/testsuite/raw-thumbnail/ref/out-libraw0.20.0.txt b/testsuite/raw-thumbnail/ref/out-libraw0.20.0.txt index 530c5de319..86e4f792a9 100644 --- a/testsuite/raw-thumbnail/ref/out-libraw0.20.0.txt +++ b/testsuite/raw-thumbnail/ref/out-libraw0.20.0.txt @@ -1,105 +1,72 @@ -RAW_CANON_EOS_7D.CR2 - Thumbnail index: -1 Sort: False - 5184 - 3456 - 3 - Thumbnail index: 0 Sort: False - 5184 - 3456 - 3 - Thumbnail index: 1 Sort: True - 5184 - 3456 - 3 -RAW_FUJI_F700.RAF - Thumbnail index: -1 Sort: False - 1280 - 960 - 3 - Thumbnail index: 0 Sort: False - 1280 - 960 - 3 - Thumbnail index: 1 Sort: True - 1280 - 960 - 3 -RAW_NIKON_D3X.NEF - Thumbnail index: -1 Sort: False - 6048 - 4032 - 3 - Thumbnail index: 0 Sort: False - 6048 - 4032 - 3 - Thumbnail index: 1 Sort: True - 6048 - 4032 - 3 -RAW_OLYMPUS_E3.ORF - Thumbnail index: -1 Sort: False - 1600 - 1200 - 3 - Thumbnail index: 0 Sort: False - 1600 - 1200 - 3 - Thumbnail index: 1 Sort: True - 1600 - 1200 - 3 -RAW_PANASONIC_DMC-GF1.RW2 - Thumbnail index: -1 Sort: False - 1920 - 1440 - 3 - Thumbnail index: 0 Sort: False - 1920 - 1440 - 3 - Thumbnail index: 1 Sort: True - 1920 - 1440 - 3 -RAW_PANASONIC_G1.RW2 - Thumbnail index: -1 Sort: False - 1920 - 1440 - 3 - Thumbnail index: 0 Sort: False - 1920 - 1440 - 3 - Thumbnail index: 1 Sort: True - 1920 - 1440 - 3 -RAW_PENTAX_K200D.PEF - Thumbnail index: -1 Sort: False - 3872 - 2592 - 3 - Thumbnail index: 0 Sort: False - 3872 - 2592 - 3 - Thumbnail index: 1 Sort: True - 3872 - 2592 - 3 -RAW_SONY_A300.ARW - Thumbnail index: -1 Sort: False - 1616 - 1080 - 3 - Thumbnail index: 0 Sort: False - 1616 - 1080 - 3 - Thumbnail index: 1 Sort: True - 1616 - 1080 - 3 -Done. +RAW_CANON_EOS_7D.CR2 index -1 sort False +5184 x 3456, 3 channel, uint8 + channel list: R, G, B +RAW_CANON_EOS_7D.CR2 index 0 sort False +5184 x 3456, 3 channel, uint8 + channel list: R, G, B +RAW_CANON_EOS_7D.CR2 index 1 sort True +5184 x 3456, 3 channel, uint8 + channel list: R, G, B +RAW_FUJI_F700.RAF index -1 sort False +1280 x 960, 3 channel, uint8 + channel list: R, G, B +RAW_FUJI_F700.RAF index 0 sort False +1280 x 960, 3 channel, uint8 + channel list: R, G, B +RAW_FUJI_F700.RAF index 1 sort True +1280 x 960, 3 channel, uint8 + channel list: R, G, B +RAW_NIKON_D3X.NEF index -1 sort False +6048 x 4032, 3 channel, uint8 + channel list: R, G, B +RAW_NIKON_D3X.NEF index 0 sort False +6048 x 4032, 3 channel, uint8 + channel list: R, G, B +RAW_NIKON_D3X.NEF index 1 sort True +6048 x 4032, 3 channel, uint8 + channel list: R, G, B +RAW_OLYMPUS_E3.ORF index -1 sort False +1600 x 1200, 3 channel, uint8 + channel list: R, G, B +RAW_OLYMPUS_E3.ORF index 0 sort False +1600 x 1200, 3 channel, uint8 + channel list: R, G, B +RAW_OLYMPUS_E3.ORF index 1 sort True +1600 x 1200, 3 channel, uint8 + channel list: R, G, B +RAW_PANASONIC_DMC-GF1.RW2 index -1 sort False +1920 x 1440, 3 channel, uint8 + channel list: R, G, B +RAW_PANASONIC_DMC-GF1.RW2 index 0 sort False +1920 x 1440, 3 channel, uint8 + channel list: R, G, B +RAW_PANASONIC_DMC-GF1.RW2 index 1 sort True +1920 x 1440, 3 channel, uint8 + channel list: R, G, B +RAW_PANASONIC_G1.RW2 index -1 sort False +1920 x 1440, 3 channel, uint8 + channel list: R, G, B +RAW_PANASONIC_G1.RW2 index 0 sort False +1920 x 1440, 3 channel, uint8 + channel list: R, G, B +RAW_PANASONIC_G1.RW2 index 1 sort True +1920 x 1440, 3 channel, uint8 + channel list: R, G, B +RAW_PENTAX_K200D.PEF index -1 sort False +3872 x 2592, 3 channel, uint8 + channel list: R, G, B +RAW_PENTAX_K200D.PEF index 0 sort False +3872 x 2592, 3 channel, uint8 + channel list: R, G, B +RAW_PENTAX_K200D.PEF index 1 sort True +3872 x 2592, 3 channel, uint8 + channel list: R, G, B +RAW_SONY_A300.ARW index -1 sort False +1616 x 1080, 3 channel, uint8 + channel list: R, G, B +RAW_SONY_A300.ARW index 0 sort False +1616 x 1080, 3 channel, uint8 + channel list: R, G, B +RAW_SONY_A300.ARW index 1 sort True +1616 x 1080, 3 channel, uint8 + channel list: R, G, B diff --git a/testsuite/raw-thumbnail/ref/out.txt b/testsuite/raw-thumbnail/ref/out.txt index 4b5252283f..5e6ccb547f 100644 --- a/testsuite/raw-thumbnail/ref/out.txt +++ b/testsuite/raw-thumbnail/ref/out.txt @@ -1,105 +1,72 @@ -RAW_CANON_EOS_7D.CR2 - Thumbnail index: -1 Sort: False - 5184 - 3456 - 3 - Thumbnail index: 0 Sort: False - 5184 - 3456 - 3 - Thumbnail index: 1 Sort: True - 670 - 432 - 3 -RAW_FUJI_F700.RAF - Thumbnail index: -1 Sort: False - 1280 - 960 - 3 - Thumbnail index: 0 Sort: False - 160 - 120 - 3 - Thumbnail index: 1 Sort: True - 1280 - 960 - 3 -RAW_NIKON_D3X.NEF - Thumbnail index: -1 Sort: False - 6048 - 4032 - 3 - Thumbnail index: 0 Sort: False - 160 - 120 - 3 - Thumbnail index: 1 Sort: True - 570 - 375 - 3 -RAW_OLYMPUS_E3.ORF - Thumbnail index: -1 Sort: False - 1600 - 1200 - 3 - Thumbnail index: 0 Sort: False - 1600 - 1200 - 3 - Thumbnail index: 1 Sort: True - 1600 - 1200 - 3 -RAW_PANASONIC_DMC-GF1.RW2 - Thumbnail index: -1 Sort: False - 1920 - 1440 - 3 - Thumbnail index: 0 Sort: False - 1920 - 1440 - 3 - Thumbnail index: 1 Sort: True - 1920 - 1440 - 3 -RAW_PANASONIC_G1.RW2 - Thumbnail index: -1 Sort: False - 1920 - 1440 - 3 - Thumbnail index: 0 Sort: False - 1920 - 1440 - 3 - Thumbnail index: 1 Sort: True - 1920 - 1440 - 3 -RAW_PENTAX_K200D.PEF - Thumbnail index: -1 Sort: False - 3872 - 2592 - 3 - Thumbnail index: 0 Sort: False - 160 - 120 - 3 - Thumbnail index: 1 Sort: True - 3872 - 2592 - 3 -RAW_SONY_A300.ARW - Thumbnail index: -1 Sort: False - 1616 - 1080 - 3 - Thumbnail index: 0 Sort: False - 1616 - 1080 - 3 - Thumbnail index: 1 Sort: True - 1616 - 1080 - 3 -Done. +RAW_CANON_EOS_7D.CR2 index -1 sort False +5184 x 3456, 3 channel, uint8 + channel list: R, G, B +RAW_CANON_EOS_7D.CR2 index 0 sort False +5184 x 3456, 3 channel, uint8 + channel list: R, G, B +RAW_CANON_EOS_7D.CR2 index 1 sort True + 670 x 432, 3 channel, uint8 + channel list: R, G, B +RAW_FUJI_F700.RAF index -1 sort False +1280 x 960, 3 channel, uint8 + channel list: R, G, B +RAW_FUJI_F700.RAF index 0 sort False + 160 x 120, 3 channel, uint8 + channel list: R, G, B +RAW_FUJI_F700.RAF index 1 sort True +1280 x 960, 3 channel, uint8 + channel list: R, G, B +RAW_NIKON_D3X.NEF index -1 sort False +6048 x 4032, 3 channel, uint8 + channel list: R, G, B +RAW_NIKON_D3X.NEF index 0 sort False + 160 x 120, 3 channel, uint8 + channel list: R, G, B +RAW_NIKON_D3X.NEF index 1 sort True + 570 x 375, 3 channel, uint8 + channel list: R, G, B +RAW_OLYMPUS_E3.ORF index -1 sort False +1600 x 1200, 3 channel, uint8 + channel list: R, G, B +RAW_OLYMPUS_E3.ORF index 0 sort False +1600 x 1200, 3 channel, uint8 + channel list: R, G, B +RAW_OLYMPUS_E3.ORF index 1 sort True +1600 x 1200, 3 channel, uint8 + channel list: R, G, B +RAW_PANASONIC_DMC-GF1.RW2 index -1 sort False +1920 x 1440, 3 channel, uint8 + channel list: R, G, B +RAW_PANASONIC_DMC-GF1.RW2 index 0 sort False +1920 x 1440, 3 channel, uint8 + channel list: R, G, B +RAW_PANASONIC_DMC-GF1.RW2 index 1 sort True +1920 x 1440, 3 channel, uint8 + channel list: R, G, B +RAW_PANASONIC_G1.RW2 index -1 sort False +1920 x 1440, 3 channel, uint8 + channel list: R, G, B +RAW_PANASONIC_G1.RW2 index 0 sort False +1920 x 1440, 3 channel, uint8 + channel list: R, G, B +RAW_PANASONIC_G1.RW2 index 1 sort True +1920 x 1440, 3 channel, uint8 + channel list: R, G, B +RAW_PENTAX_K200D.PEF index -1 sort False +3872 x 2592, 3 channel, uint8 + channel list: R, G, B +RAW_PENTAX_K200D.PEF index 0 sort False + 160 x 120, 3 channel, uint8 + channel list: R, G, B +RAW_PENTAX_K200D.PEF index 1 sort True +3872 x 2592, 3 channel, uint8 + channel list: R, G, B +RAW_SONY_A300.ARW index -1 sort False +1616 x 1080, 3 channel, uint8 + channel list: R, G, B +RAW_SONY_A300.ARW index 0 sort False +1616 x 1080, 3 channel, uint8 + channel list: R, G, B +RAW_SONY_A300.ARW index 1 sort True +1616 x 1080, 3 channel, uint8 + channel list: R, G, B diff --git a/testsuite/raw-thumbnail/run.py b/testsuite/raw-thumbnail/run.py index 1c4b398c03..b8dcacbf00 100755 --- a/testsuite/raw-thumbnail/run.py +++ b/testsuite/raw-thumbnail/run.py @@ -4,4 +4,42 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO -command += pythonbin + " src/test_raw_thumbnail.py >> out.txt 2>&1" +import os +OIIO_TESTSUITE_IMAGEDIR = os.getenv('OIIO_TESTSUITE_IMAGEDIR') + +###################################################################### +# main test starts here + +redirect = " >> out.txt 2>&1 " + +test_files = [ + 'RAW_CANON_EOS_7D.CR2', + 'RAW_FUJI_F700.RAF', + 'RAW_NIKON_D3X.NEF', + 'RAW_OLYMPUS_E3.ORF', + 'RAW_PANASONIC_DMC-GF1.RW2', + 'RAW_PANASONIC_G1.RW2', + 'RAW_PENTAX_K200D.PEF', + 'RAW_SONY_A300.ARW' +] + +def read_thumbnail(filename, index = -1, sort = False): + + label = '{} index {} sort {}'.format(filename, index, sort) + params = '--echo "' + label + '" ' + if index != -1: + params += '-iconfig "raw:thumbnail_index" ' + str(index) + ' ' + if sort: + params += '-iconfig "raw:thumbnail_sort" -1 ' + + params += OIIO_TESTSUITE_IMAGEDIR + '/' + file + params += ' --thumbnail-get --eraseattrib ".*" --printinfo' + + return params + +for file in test_files: + command += oiiotool (read_thumbnail(file, -1)) + command += oiiotool (read_thumbnail(file, 0)) + command += oiiotool (read_thumbnail(file, 1, True)) + +outputs = [ "out.txt" ] diff --git a/testsuite/raw-thumbnail/src/test_raw_thumbnail.py b/testsuite/raw-thumbnail/src/test_raw_thumbnail.py deleted file mode 100755 index eedc14e3e3..0000000000 --- a/testsuite/raw-thumbnail/src/test_raw_thumbnail.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python - -# Copyright Contributors to the OpenImageIO project. -# SPDX-License-Identifier: Apache-2.0 -# https://github.com/AcademySoftwareFoundation/OpenImageIO - -from __future__ import annotations - -import OpenImageIO as oiio - -import os - -OIIO_TESTSUITE_IMAGEDIR = os.getenv('OIIO_TESTSUITE_IMAGEDIR') - -###################################################################### -# main test starts here - - -test_files = [ - 'RAW_CANON_EOS_7D.CR2', - 'RAW_FUJI_F700.RAF', - 'RAW_NIKON_D3X.NEF', - 'RAW_OLYMPUS_E3.ORF', - 'RAW_PANASONIC_DMC-GF1.RW2', - 'RAW_PANASONIC_G1.RW2', - 'RAW_PENTAX_K200D.PEF', - 'RAW_SONY_A300.ARW' -] - -def read_thumbnail(filename, index = -1, sort = False): - - hint = oiio.ImageSpec() - - if index != -1: - hint['raw:thumbnail_index'] = index - if sort: - hint['raw:thumbnail_sort'] = -1 - - input = oiio.ImageInput.open (OIIO_TESTSUITE_IMAGEDIR + '/' + file, hint) - thumbnail = input.get_thumbnail() - print(' Thumbnail index:', index, 'Sort:', sort) - print(' ', thumbnail.spec().width) - print(' ', thumbnail.spec().height) - print(' ', thumbnail.spec().nchannels) - -try: - for file in test_files: - print(file) - read_thumbnail(file, -1) - read_thumbnail(file, 0) - read_thumbnail(file, 1, True) - - print ("Done.") -except Exception as detail: - print ("Unknown exception:", detail) From 72127b3962f7fc38aa6847dea08e7fd74eb2525f Mon Sep 17 00:00:00 2001 From: Anton Dukhovnikov Date: Wed, 29 Jul 2026 10:17:36 +1200 Subject: [PATCH 4/7] error on negative thumbnail index Signed-off-by: Anton Dukhovnikov --- src/raw.imageio/rawinput.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/raw.imageio/rawinput.cpp b/src/raw.imageio/rawinput.cpp index 677435da94..3952cacc86 100644 --- a/src/raw.imageio/rawinput.cpp +++ b/src/raw.imageio/rawinput.cpp @@ -1130,8 +1130,10 @@ RawInput::open_raw(bool unpack, bool process, const std::string& name, height = m_processor->imgdata.thumbnail.theight; } else { // clamp the thumbnail index - if (m_thumb_index < 0) - m_thumb_index = 0; + if (m_thumb_index < 0) { + errorfmt("Invalid thumbnail index ({})", m_thumb_index); + return false; + } else if (m_thumb_index >= thumb_count) m_thumb_index = thumb_count - 1; From ce5458bb075cded491546eab9eb3fac5d9bfdc90 Mon Sep 17 00:00:00 2001 From: Anton Dukhovnikov Date: Wed, 29 Jul 2026 11:33:27 +1200 Subject: [PATCH 5/7] clang-format Signed-off-by: Anton Dukhovnikov --- src/raw.imageio/rawinput.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/raw.imageio/rawinput.cpp b/src/raw.imageio/rawinput.cpp index 3952cacc86..1b3f53e6b9 100644 --- a/src/raw.imageio/rawinput.cpp +++ b/src/raw.imageio/rawinput.cpp @@ -1133,9 +1133,9 @@ RawInput::open_raw(bool unpack, bool process, const std::string& name, if (m_thumb_index < 0) { errorfmt("Invalid thumbnail index ({})", m_thumb_index); return false; - } - else if (m_thumb_index >= thumb_count) + } else if (m_thumb_index >= thumb_count) { m_thumb_index = thumb_count - 1; + } // sort the thumbnails by their size if requested int sort_order = config.get_int_attribute("raw:thumbnail_sort", 0); From 2c57b97ff3d910a515abe09b2928000faa17378f Mon Sep 17 00:00:00 2001 From: Anton Dukhovnikov Date: Fri, 31 Jul 2026 11:36:31 +1200 Subject: [PATCH 6/7] Update src/raw.imageio/rawinput.cpp Co-authored-by: Jinnie Kim <71912315+jinhgkim@users.noreply.github.com> Signed-off-by: Anton Dukhovnikov --- src/raw.imageio/rawinput.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/raw.imageio/rawinput.cpp b/src/raw.imageio/rawinput.cpp index 1b3f53e6b9..0bda12e531 100644 --- a/src/raw.imageio/rawinput.cpp +++ b/src/raw.imageio/rawinput.cpp @@ -1124,18 +1124,17 @@ RawInput::open_raw(bool unpack, bool process, const std::string& name, int width = 0; int height = 0; + if (m_thumb_index < -1) { + errorfmt("Invalid thumbnail index ({})", m_thumb_index); + return false; + } if (m_thumb_index == -1) { // Use the default from LibRaw, which will always be the largest one width = m_processor->imgdata.thumbnail.twidth; height = m_processor->imgdata.thumbnail.theight; } else { - // clamp the thumbnail index - if (m_thumb_index < 0) { - errorfmt("Invalid thumbnail index ({})", m_thumb_index); - return false; - } else if (m_thumb_index >= thumb_count) { - m_thumb_index = thumb_count - 1; - } + m_thumb_index = std::min(m_thumb_index, thumb_count - 1); + } // sort the thumbnails by their size if requested int sort_order = config.get_int_attribute("raw:thumbnail_sort", 0); From e6bb6969f8d43df1404d1435bf2d1e761c842e33 Mon Sep 17 00:00:00 2001 From: Anton Dukhovnikov Date: Fri, 31 Jul 2026 11:50:53 +1200 Subject: [PATCH 7/7] fix build Signed-off-by: Anton Dukhovnikov --- src/raw.imageio/rawinput.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/raw.imageio/rawinput.cpp b/src/raw.imageio/rawinput.cpp index 0bda12e531..c9eef6fe13 100644 --- a/src/raw.imageio/rawinput.cpp +++ b/src/raw.imageio/rawinput.cpp @@ -1134,7 +1134,6 @@ RawInput::open_raw(bool unpack, bool process, const std::string& name, height = m_processor->imgdata.thumbnail.theight; } else { m_thumb_index = std::min(m_thumb_index, thumb_count - 1); - } // sort the thumbnails by their size if requested int sort_order = config.get_int_attribute("raw:thumbnail_sort", 0);