From ed36761bf019995b2ca66229f0f8aad83e432fa2 Mon Sep 17 00:00:00 2001 From: Anton Dukhovnikov Date: Thu, 23 Jul 2026 14:35:39 +1200 Subject: [PATCH 1/3] add thumbnail functionality to jpeg reader Co-authored-by: Ken McGaugh Signed-off-by: Anton Dukhovnikov --- src/cmake/testing.cmake | 1 + src/jpeg.imageio/jpeg_pvt.h | 11 +- src/jpeg.imageio/jpeginput.cpp | 144 +++++++++++++++++- testsuite/gpsread/ref/out.txt | 5 +- testsuite/jpeg-corrupt/ref/out-alt2.txt | 3 + testsuite/jpeg-corrupt/ref/out-alt3.txt | 3 + testsuite/jpeg-corrupt/ref/out-alt4.txt | 3 + testsuite/jpeg-corrupt/ref/out-alt5.txt | 3 + testsuite/jpeg-corrupt/ref/out.txt | 3 + .../python-imageinput/ref/out-python3-win.txt | 5 +- testsuite/python-imageinput/ref/out.txt | 5 +- testsuite/python-thumbnail-jpeg/ref/out.txt | 1 + testsuite/python-thumbnail-jpeg/run.py | 10 ++ .../src/test_jpeg_thumbnail.py | 28 ++++ 14 files changed, 218 insertions(+), 7 deletions(-) create mode 100644 testsuite/python-thumbnail-jpeg/ref/out.txt create mode 100755 testsuite/python-thumbnail-jpeg/run.py create mode 100755 testsuite/python-thumbnail-jpeg/src/test_jpeg_thumbnail.py diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index e1be9b89f8..5d4fc8c989 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -299,6 +299,7 @@ macro (oiio_add_all_tests) # These Python tests also need access to oiio-images oiio_add_tests ( python-imageinput python-imagebufalgo + python-thumbnail-jpeg IMAGEDIR oiio-images ENVIRONMENT "${_pybind_tests_pythonpath}" ) diff --git a/src/jpeg.imageio/jpeg_pvt.h b/src/jpeg.imageio/jpeg_pvt.h index faedb3c257..6f452346e8 100644 --- a/src/jpeg.imageio/jpeg_pvt.h +++ b/src/jpeg.imageio/jpeg_pvt.h @@ -64,7 +64,8 @@ class JpgInput final : public ImageInput { const char* format_name(void) const override { return "jpeg"; } int supports(string_view feature) const override { - return (feature == "exif" || feature == "iptc" || feature == "ioproxy"); + return (feature == "exif" || feature == "iptc" || feature == "ioproxy" + || feature == "thumbnail"); } bool valid_file(Filesystem::IOProxy* ioproxy) const override; @@ -77,6 +78,7 @@ class JpgInput final : public ImageInput { int z, void* data) override; bool read_native_scanlines(int subimage, int miplevel, int ybegin, int yend, span data) override; + bool get_thumbnail(ImageBuf& thumb, int subimage) override; bool close() override; const std::string& filename() const { return m_filename; } @@ -108,6 +110,10 @@ class JpgInput final : public ImageInput { uhdr_codec_private_t* m_uhdr_dec; #endif + // This will point to an embedded JPEG thumbnail if exists. + // This span is only valid until jpeg_destroy_decompress(&m_cinfo) is called. + cspan m_thumbnail_data; + void init() { m_raw = false; @@ -122,6 +128,7 @@ class JpgInput final : public ImageInput { #if defined(USE_UHDR) m_uhdr_dec = NULL; #endif + m_thumbnail_data = cspan(); } // Rummage through the JPEG "APP1" marker pointed to by buf, decoding @@ -139,6 +146,8 @@ class JpgInput final : public ImageInput { void close_file() { init(); } + void scan_for_thumbnail(cspan exif); + friend class JpgOutput; }; diff --git a/src/jpeg.imageio/jpeginput.cpp b/src/jpeg.imageio/jpeginput.cpp index cacd06d337..14ae537c54 100644 --- a/src/jpeg.imageio/jpeginput.cpp +++ b/src/jpeg.imageio/jpeginput.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -296,13 +297,14 @@ JpgInput::open(const std::string& name, ImageSpec& newspec) if (is_exif_marker(m)) { // The block starts with "Exif\0\0", so skip 6 bytes to get // to the start of the actual Exif data TIFF directory - bool ok = decode_exif(string_view((char*)m->data + 6, - m->data_length - 6), - m_spec); + auto exif = cspan((uint8_t*)m->data + 6, + m->data_length - 6); + bool ok = decode_exif(exif, m_spec); if (!ok && OIIO::get_int_attribute("imageinput:strict")) { errorfmt("Could not decode Exif"); return false; } + scan_for_thumbnail(exif); } else if (m->marker == (JPEG_APP0 + 1) && m->data_length >= 28 && !strncmp((const char*)m->data, "http://ns.adobe.com/xap/1.0/", 28)) { //NOSONAR @@ -791,4 +793,140 @@ JpgInput::jpeg_decode_iptc(string_view buf) return decode_iptc_iim(buf, m_spec); } +void +JpgInput::scan_for_thumbnail(cspan exif) +{ + try { + bool host_little = littleendian(); + bool file_little = (exif[0] == 0x49 && exif[1] == 0x49); + bool swab = (host_little != file_little); + + const uint8_t* soi_ptr = nullptr; + const uint8_t* eoi_ptr = nullptr; + + size_t i = 0; + while (i < exif.size() - 1) { + if (exif[i] == 0xFF && exif[i + 1] == 0xD8) { + soi_ptr = &exif[i]; + break; + } + i++; + } + + if (!soi_ptr) + return; + + // extract image properties along the way + uint16_t width = 0; + uint16_t height = 0; + int numchan = 0; + + while (i < exif.size() - 1) { + if (exif[i++] == 0xFF) { + const auto marker = exif[i]; + if (marker == 0xD9) { + eoi_ptr = &exif[i] + 1; // one past (exclusive) + break; + } else if (marker == 0xC0 || marker == 0xC2) { + uint16_t length = (exif.at(i + 2) << 8) + exif.at(i + 1); + height = (exif.at(i + 5) << 8) + exif.at(i + 4); + width = (exif.at(i + 7) << 8) + exif.at(i + 6); + numchan = exif[i + 8]; + + if (swab) { + swap_endian(&length); + swap_endian(&height); + swap_endian(&width); + } + + length -= 2; + + if (i + length < exif.size() - 1) { + i += length; + } + } else if ((marker >= 0xC0 && marker < 0xD0) + || (marker > 0xD9 && marker <= 0xFE)) { + // Skip ahead for markers that have an associated length + uint16_t length = (exif.at(i + 2) << 8) + exif.at(i + 1); + + if (swab) { + swap_endian(&length); + } + + length -= 2; + + if (i + length < exif.size() - 1) { + i += length; + } + } + } + } + + if (eoi_ptr) { + m_thumbnail_data = cspan(soi_ptr, eoi_ptr); + m_spec.attribute("thumbnail_width", width); + m_spec.attribute("thumbnail_height", height); + m_spec.attribute("thumbnail_nchannels", numchan); + } + + } catch (const std::exception& e) { + errorfmt("ERROR scanning for thumbnail: {}", e.what()); + } +} + +bool +JpgInput::get_thumbnail(ImageBuf& thumb, int subimage) +{ + if (!m_decomp_create) { + errorfmt("ImageInput hasn't been initialised properly"); + return false; + } + + if (m_thumbnail_data.empty()) { + errorfmt("No thumbnail found"); + return false; + } + + auto image_input = OIIO::ImageInput::create("jpeg", false); + if (image_input == nullptr) { + errorfmt("OIIO::ImageInput::create(\"jpeg\") error"); + return false; + } + + Filesystem::IOMemReader proxy(m_thumbnail_data.data(), + m_thumbnail_data.size()); + bool result = image_input->valid_file(&proxy); + if (!result) { + errorfmt("the thumbnail is not a valid image of type \"jpeg\""); + return false; + } + + ImageSpec temp_spec, image_spec; + Filesystem::IOProxy* pp = &proxy; + temp_spec.attribute("oiio:ioproxy", TypeDesc::PTR, &pp); + + result = image_input->open("", image_spec, temp_spec); + if (!result) { + errorfmt( + "failed to initialise an ImageInput object with the thumbnail data"); + return false; + } + + thumb.reset(image_spec); + + result = image_input->read_image(0, 0, 0, image_spec.nchannels, + image_spec.format, thumb.localpixels()); + + if (!result) { + errorfmt( + "failed to initialise an ImageInput object of type \"jpeg\" with the thumbnail data"); + image_input->close(); + return false; + } + + image_input->close(); + return true; +} + + OIIO_PLUGIN_NAMESPACE_END diff --git a/testsuite/gpsread/ref/out.txt b/testsuite/gpsread/ref/out.txt index 3e36ee7c64..d928106e43 100644 --- a/testsuite/gpsread/ref/out.txt +++ b/testsuite/gpsread/ref/out.txt @@ -1,12 +1,15 @@ Reading ../oiio-images/tahoe-gps.jpg ../oiio-images/tahoe-gps.jpg : 2048 x 1536, 3 channel, uint8 jpeg - SHA-1: C3B420EF8C20C4771F6F7BF89EA9D9D4002BA016 + SHA-1: 71EBEC73B8E8B3533780B15147E61D895C80E8B1 channel list: R, G, B Make: "HTC" Model: "T-Mobile G1" Orientation: 1 (normal) ResolutionUnit: "none" Software: "title;va" + thumbnail_height: 240 + thumbnail_nchannels: 3 + thumbnail_width: 320 XResolution: 72 YResolution: 72 Exif:ColorSpace: 1 diff --git a/testsuite/jpeg-corrupt/ref/out-alt2.txt b/testsuite/jpeg-corrupt/ref/out-alt2.txt index 4bb15103d5..52553d6424 100644 --- a/testsuite/jpeg-corrupt/ref/out-alt2.txt +++ b/testsuite/jpeg-corrupt/ref/out-alt2.txt @@ -4,6 +4,9 @@ src/corrupt-exif.jpg : 256 x 256, 3 channel, uint8 jpeg channel list: R, G, B Orientation: 1 (normal) ResolutionUnit: "in" + thumbnail_height: 160 + thumbnail_nchannels: 3 + thumbnail_width: 160 XResolution: 300 YResolution: 300 jpeg:subsampling: "4:2:0" diff --git a/testsuite/jpeg-corrupt/ref/out-alt3.txt b/testsuite/jpeg-corrupt/ref/out-alt3.txt index 459988f3cf..5a2a1b4819 100644 --- a/testsuite/jpeg-corrupt/ref/out-alt3.txt +++ b/testsuite/jpeg-corrupt/ref/out-alt3.txt @@ -4,6 +4,9 @@ src/corrupt-exif.jpg : 256 x 256, 3 channel, uint8 jpeg channel list: R, G, B Orientation: 1 (normal) ResolutionUnit: "in" + thumbnail_height: 160 + thumbnail_nchannels: 3 + thumbnail_width: 160 XResolution: 300 YResolution: 300 jpeg:subsampling: "4:2:0" diff --git a/testsuite/jpeg-corrupt/ref/out-alt4.txt b/testsuite/jpeg-corrupt/ref/out-alt4.txt index 22a7d09d32..932b64769a 100644 --- a/testsuite/jpeg-corrupt/ref/out-alt4.txt +++ b/testsuite/jpeg-corrupt/ref/out-alt4.txt @@ -4,6 +4,9 @@ src/corrupt-exif.jpg : 256 x 256, 3 channel, uint8 jpeg channel list: R, G, B Orientation: 1 (normal) ResolutionUnit: "in" + thumbnail_height: 160 + thumbnail_nchannels: 3 + thumbnail_width: 160 XResolution: 300 YResolution: 300 jpeg:subsampling: "4:2:0" diff --git a/testsuite/jpeg-corrupt/ref/out-alt5.txt b/testsuite/jpeg-corrupt/ref/out-alt5.txt index d8aa9d2c5f..8f2410da26 100644 --- a/testsuite/jpeg-corrupt/ref/out-alt5.txt +++ b/testsuite/jpeg-corrupt/ref/out-alt5.txt @@ -4,6 +4,9 @@ src/corrupt-exif.jpg : 256 x 256, 3 channel, uint8 jpeg channel list: R, G, B Orientation: 1 (normal) ResolutionUnit: "in" + thumbnail_height: 160 + thumbnail_nchannels: 3 + thumbnail_width: 160 XResolution: 300 YResolution: 300 jpeg:subsampling: "4:2:0" diff --git a/testsuite/jpeg-corrupt/ref/out.txt b/testsuite/jpeg-corrupt/ref/out.txt index 86e3266344..1c5d34f2e6 100644 --- a/testsuite/jpeg-corrupt/ref/out.txt +++ b/testsuite/jpeg-corrupt/ref/out.txt @@ -4,6 +4,9 @@ src/corrupt-exif.jpg : 256 x 256, 3 channel, uint8 jpeg channel list: R, G, B Orientation: 1 (normal) ResolutionUnit: "in" + thumbnail_height: 160 + thumbnail_nchannels: 3 + thumbnail_width: 160 XResolution: 300 YResolution: 300 jpeg:subsampling: "4:2:0" diff --git a/testsuite/python-imageinput/ref/out-python3-win.txt b/testsuite/python-imageinput/ref/out-python3-win.txt index c0b3429a5e..691369dd18 100644 --- a/testsuite/python-imageinput/ref/out-python3-win.txt +++ b/testsuite/python-imageinput/ref/out-python3-win.txt @@ -36,6 +36,9 @@ Opened "D:/a/OpenImageIO/OpenImageIO/build/testsuite/oiio-images/tahoe-gps.jpg" GPS:TimeStamp = (17.0, 56.0, 33.0) GPS:MapDatum = "WGS-84" GPS:DateStamp = "1915:08:08" + thumbnail_width = 320 + thumbnail_height = 240 + thumbnail_nchannels = 3 Opened "grid.tx" as a tiff resolution 1024x1024+0+0 @@ -230,7 +233,7 @@ Testing read_native_deep_image: Testing get_thumbnail: get_thumbnail returns ImageBuf: True - thumbnail initialized: False + thumbnail initialized: True Testing has_error/geterror: scanline on tiled None: True diff --git a/testsuite/python-imageinput/ref/out.txt b/testsuite/python-imageinput/ref/out.txt index 886a5e62f0..86f75969a6 100644 --- a/testsuite/python-imageinput/ref/out.txt +++ b/testsuite/python-imageinput/ref/out.txt @@ -36,6 +36,9 @@ Opened "../oiio-images/tahoe-gps.jpg" as a jpeg GPS:TimeStamp = (17.0, 56.0, 33.0) GPS:MapDatum = "WGS-84" GPS:DateStamp = "1915:08:08" + thumbnail_width = 320 + thumbnail_height = 240 + thumbnail_nchannels = 3 Opened "grid.tx" as a tiff resolution 1024x1024+0+0 @@ -230,7 +233,7 @@ Testing read_native_deep_image: Testing get_thumbnail: get_thumbnail returns ImageBuf: True - thumbnail initialized: False + thumbnail initialized: True Testing has_error/geterror: scanline on tiled None: True diff --git a/testsuite/python-thumbnail-jpeg/ref/out.txt b/testsuite/python-thumbnail-jpeg/ref/out.txt new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/testsuite/python-thumbnail-jpeg/ref/out.txt @@ -0,0 +1 @@ +Done. diff --git a/testsuite/python-thumbnail-jpeg/run.py b/testsuite/python-thumbnail-jpeg/run.py new file mode 100755 index 0000000000..ec41b055a9 --- /dev/null +++ b/testsuite/python-thumbnail-jpeg/run.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + + +import os + +command += pythonbin + " src/test_jpeg_thumbnail.py >> out.txt 2>&1" diff --git a/testsuite/python-thumbnail-jpeg/src/test_jpeg_thumbnail.py b/testsuite/python-thumbnail-jpeg/src/test_jpeg_thumbnail.py new file mode 100755 index 0000000000..2a1fa20ae6 --- /dev/null +++ b/testsuite/python-thumbnail-jpeg/src/test_jpeg_thumbnail.py @@ -0,0 +1,28 @@ +#!/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', + '../oiio-images') + +###################################################################### +# main test starts here + +try: + input = oiio.ImageInput.open (OIIO_TESTSUITE_IMAGEDIR + "/tahoe-gps.jpg") + thumbnail = input.get_thumbnail() + assert thumbnail is not None + assert thumbnail.spec().width == 320 + assert thumbnail.spec().height == 240 + assert thumbnail.spec().nchannels == 3 + print ("Done.") +except Exception as detail: + print ("Unknown exception:", detail) From 33db31998bfe997f8bdc629244ec5dc30c10f1da Mon Sep 17 00:00:00 2001 From: Anton Dukhovnikov Date: Wed, 29 Jul 2026 15:17:13 +1200 Subject: [PATCH 2/3] address the code review issues Signed-off-by: Anton Dukhovnikov --- src/cmake/testing.cmake | 2 +- src/jpeg.imageio/jpeginput.cpp | 133 +++++++++--------- testsuite/jpeg-thumbnail/ref/out.txt | 2 + testsuite/jpeg-thumbnail/run.py | 14 ++ testsuite/python-thumbnail-jpeg/ref/out.txt | 1 - testsuite/python-thumbnail-jpeg/run.py | 10 -- .../src/test_jpeg_thumbnail.py | 28 ---- 7 files changed, 80 insertions(+), 110 deletions(-) create mode 100644 testsuite/jpeg-thumbnail/ref/out.txt create mode 100755 testsuite/jpeg-thumbnail/run.py delete mode 100644 testsuite/python-thumbnail-jpeg/ref/out.txt delete mode 100755 testsuite/python-thumbnail-jpeg/run.py delete mode 100755 testsuite/python-thumbnail-jpeg/src/test_jpeg_thumbnail.py diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index 5d4fc8c989..1186ffcdfc 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -235,6 +235,7 @@ macro (oiio_add_all_tests) # Tests that require oiio-images: oiio_add_tests (gpsread + jpeg-thumbnail oiiotool-attribs texture-filtersize texture-filtersize-stochastic @@ -299,7 +300,6 @@ macro (oiio_add_all_tests) # These Python tests also need access to oiio-images oiio_add_tests ( python-imageinput python-imagebufalgo - python-thumbnail-jpeg IMAGEDIR oiio-images ENVIRONMENT "${_pybind_tests_pythonpath}" ) diff --git a/src/jpeg.imageio/jpeginput.cpp b/src/jpeg.imageio/jpeginput.cpp index 14ae537c54..4acf32aaf7 100644 --- a/src/jpeg.imageio/jpeginput.cpp +++ b/src/jpeg.imageio/jpeginput.cpp @@ -164,7 +164,7 @@ JpgInput::valid_file(Filesystem::IOProxy* ioproxy) const if (!ioproxy || ioproxy->mode() != Filesystem::IOProxy::Read) return false; - uint8_t magic[2] {}; + uint8_t magic[2] { }; const size_t numRead = ioproxy->pread(magic, sizeof(magic), 0); return numRead == sizeof(magic) && magic[0] == JPEG_MAGIC1 && magic[1] == JPEG_MAGIC2; @@ -796,81 +796,76 @@ JpgInput::jpeg_decode_iptc(string_view buf) void JpgInput::scan_for_thumbnail(cspan exif) { - try { - bool host_little = littleendian(); - bool file_little = (exif[0] == 0x49 && exif[1] == 0x49); - bool swab = (host_little != file_little); - - const uint8_t* soi_ptr = nullptr; - const uint8_t* eoi_ptr = nullptr; - - size_t i = 0; - while (i < exif.size() - 1) { - if (exif[i] == 0xFF && exif[i + 1] == 0xD8) { - soi_ptr = &exif[i]; - break; - } - i++; + bool host_little = littleendian(); + bool file_little = (exif[0] == 0x49 && exif[1] == 0x49); + bool swab = (host_little != file_little); + + const uint8_t* soi_ptr = nullptr; + const uint8_t* eoi_ptr = nullptr; + + size_t i = 0; + while (i < exif.size() - 1) { + if (exif[i] == 0xFF && exif[i + 1] == 0xD8) { + soi_ptr = &exif[i]; + break; } + i++; + } - if (!soi_ptr) - return; - - // extract image properties along the way - uint16_t width = 0; - uint16_t height = 0; - int numchan = 0; - - while (i < exif.size() - 1) { - if (exif[i++] == 0xFF) { - const auto marker = exif[i]; - if (marker == 0xD9) { - eoi_ptr = &exif[i] + 1; // one past (exclusive) - break; - } else if (marker == 0xC0 || marker == 0xC2) { - uint16_t length = (exif.at(i + 2) << 8) + exif.at(i + 1); - height = (exif.at(i + 5) << 8) + exif.at(i + 4); - width = (exif.at(i + 7) << 8) + exif.at(i + 6); - numchan = exif[i + 8]; - - if (swab) { - swap_endian(&length); - swap_endian(&height); - swap_endian(&width); - } + if (!soi_ptr) + return; - length -= 2; + // extract image properties along the way + uint16_t width = 0; + uint16_t height = 0; + int numchan = 0; - if (i + length < exif.size() - 1) { - i += length; - } - } else if ((marker >= 0xC0 && marker < 0xD0) - || (marker > 0xD9 && marker <= 0xFE)) { - // Skip ahead for markers that have an associated length - uint16_t length = (exif.at(i + 2) << 8) + exif.at(i + 1); + while (i < exif.size() - 1) { + if (exif[i++] == 0xFF) { + const auto marker = exif[i]; + if (marker == 0xD9) { + eoi_ptr = &exif[i] + 1; // one past (exclusive) + break; + } else if (marker == 0xC0 || marker == 0xC2) { + uint16_t length = (exif.at(i + 2) << 8) + exif.at(i + 1); + height = (exif.at(i + 5) << 8) + exif.at(i + 4); + width = (exif.at(i + 7) << 8) + exif.at(i + 6); + numchan = exif[i + 8]; + + if (swab) { + swap_endian(&length); + swap_endian(&height); + swap_endian(&width); + } - if (swab) { - swap_endian(&length); - } + length -= 2; + + if (i + length < exif.size() - 1) { + i += length; + } + } else if ((marker >= 0xC0 && marker < 0xD0) + || (marker > 0xD9 && marker <= 0xFE)) { + // Skip ahead for markers that have an associated length + uint16_t length = (exif.at(i + 2) << 8) + exif.at(i + 1); - length -= 2; + if (swab) { + swap_endian(&length); + } - if (i + length < exif.size() - 1) { - i += length; - } + length -= 2; + + if (i + length < exif.size() - 1) { + i += length; } } } + } - if (eoi_ptr) { - m_thumbnail_data = cspan(soi_ptr, eoi_ptr); - m_spec.attribute("thumbnail_width", width); - m_spec.attribute("thumbnail_height", height); - m_spec.attribute("thumbnail_nchannels", numchan); - } - - } catch (const std::exception& e) { - errorfmt("ERROR scanning for thumbnail: {}", e.what()); + if (eoi_ptr) { + m_thumbnail_data = cspan(soi_ptr, eoi_ptr); + m_spec.attribute("thumbnail_width", width); + m_spec.attribute("thumbnail_height", height); + m_spec.attribute("thumbnail_nchannels", numchan); } } @@ -878,7 +873,7 @@ bool JpgInput::get_thumbnail(ImageBuf& thumb, int subimage) { if (!m_decomp_create) { - errorfmt("ImageInput hasn't been initialised properly"); + errorfmt("ImageInput hasn't been initialized properly"); return false; } @@ -907,8 +902,7 @@ JpgInput::get_thumbnail(ImageBuf& thumb, int subimage) result = image_input->open("", image_spec, temp_spec); if (!result) { - errorfmt( - "failed to initialise an ImageInput object with the thumbnail data"); + errorfmt("{}", image_input->geterror()); return false; } @@ -918,8 +912,7 @@ JpgInput::get_thumbnail(ImageBuf& thumb, int subimage) image_spec.format, thumb.localpixels()); if (!result) { - errorfmt( - "failed to initialise an ImageInput object of type \"jpeg\" with the thumbnail data"); + errorfmt("{}", image_input->geterror()); image_input->close(); return false; } diff --git a/testsuite/jpeg-thumbnail/ref/out.txt b/testsuite/jpeg-thumbnail/ref/out.txt new file mode 100644 index 0000000000..dbba2d2969 --- /dev/null +++ b/testsuite/jpeg-thumbnail/ref/out.txt @@ -0,0 +1,2 @@ + 320 x 240, 3 channel, uint8 + channel list: R, G, B diff --git a/testsuite/jpeg-thumbnail/run.py b/testsuite/jpeg-thumbnail/run.py new file mode 100755 index 0000000000..3b335b895f --- /dev/null +++ b/testsuite/jpeg-thumbnail/run.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + + +import os +OIIO_TESTSUITE_IMAGEDIR = os.getenv('OIIO_TESTSUITE_IMAGEDIR') + +redirect = " >> out.txt 2>&1 " + +command += oiiotool ( OIIO_TESTSUITE_IMAGEDIR + '/tahoe-gps.jpg --thumbnail-get --eraseattrib ".*" --printinfo' ) +outputs = [ "out.txt" ] diff --git a/testsuite/python-thumbnail-jpeg/ref/out.txt b/testsuite/python-thumbnail-jpeg/ref/out.txt deleted file mode 100644 index 619c56180b..0000000000 --- a/testsuite/python-thumbnail-jpeg/ref/out.txt +++ /dev/null @@ -1 +0,0 @@ -Done. diff --git a/testsuite/python-thumbnail-jpeg/run.py b/testsuite/python-thumbnail-jpeg/run.py deleted file mode 100755 index ec41b055a9..0000000000 --- a/testsuite/python-thumbnail-jpeg/run.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python - -# Copyright Contributors to the OpenImageIO project. -# SPDX-License-Identifier: Apache-2.0 -# https://github.com/AcademySoftwareFoundation/OpenImageIO - - -import os - -command += pythonbin + " src/test_jpeg_thumbnail.py >> out.txt 2>&1" diff --git a/testsuite/python-thumbnail-jpeg/src/test_jpeg_thumbnail.py b/testsuite/python-thumbnail-jpeg/src/test_jpeg_thumbnail.py deleted file mode 100755 index 2a1fa20ae6..0000000000 --- a/testsuite/python-thumbnail-jpeg/src/test_jpeg_thumbnail.py +++ /dev/null @@ -1,28 +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', - '../oiio-images') - -###################################################################### -# main test starts here - -try: - input = oiio.ImageInput.open (OIIO_TESTSUITE_IMAGEDIR + "/tahoe-gps.jpg") - thumbnail = input.get_thumbnail() - assert thumbnail is not None - assert thumbnail.spec().width == 320 - assert thumbnail.spec().height == 240 - assert thumbnail.spec().nchannels == 3 - print ("Done.") -except Exception as detail: - print ("Unknown exception:", detail) From fc5b77342c91b44b73cb91092c4915fc9bac5b3d Mon Sep 17 00:00:00 2001 From: Anton Dukhovnikov Date: Wed, 29 Jul 2026 15:19:45 +1200 Subject: [PATCH 3/3] clang-format Signed-off-by: Anton Dukhovnikov --- src/jpeg.imageio/jpeginput.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jpeg.imageio/jpeginput.cpp b/src/jpeg.imageio/jpeginput.cpp index 4acf32aaf7..53400f4bad 100644 --- a/src/jpeg.imageio/jpeginput.cpp +++ b/src/jpeg.imageio/jpeginput.cpp @@ -164,7 +164,7 @@ JpgInput::valid_file(Filesystem::IOProxy* ioproxy) const if (!ioproxy || ioproxy->mode() != Filesystem::IOProxy::Read) return false; - uint8_t magic[2] { }; + uint8_t magic[2] {}; const size_t numRead = ioproxy->pread(magic, sizeof(magic), 0); return numRead == sizeof(magic) && magic[0] == JPEG_MAGIC1 && magic[1] == JPEG_MAGIC2;