Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/cmake/testing.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -455,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
Expand Down
12 changes: 12 additions & 0 deletions src/doc/builtinplugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2339,6 +2339,18 @@ 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.
(Default: -1)
* - ``raw:thumbnail_sort``
- int
- 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:
Expand Down
181 changes: 144 additions & 37 deletions src/raw.imageio/rawinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ class RawInput final : public ImageInput {
bool m_unpacked = false;
std::unique_ptr<LibRaw> 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;
Expand Down Expand Up @@ -1098,7 +1101,101 @@ 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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth considering:

Libraw 0.20.0 (our current minimum) dates from mid-2020, and 0.21.0 dates from the end of 2022.

It's not unreasonable for OIIO 3.2 (expected to release in September) to bump the minimum to 0.21 if it simplifies things on our end.

Opinions?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't object this. We have a check like that in 11 places, would be great to get rid of them.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends if you want this backported to 3.1. We can't backport a patch to a release branch if it requires changing the minimum version of a dependency.

But if you intended for this patch to be a 3.2+ feature, I'm happy to let a bump of the minimum LibRaw come along for the ride, if it would simplify this patch or other things you want to do with LibRaw.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say let's proceed with the current code. It doesn't add much duplication.
I'm happy to put a separate PR bumping the libraw version for 3.2 if you want.

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) {
errorfmt("Invalid thumbnail index ({})", m_thumb_index);
return false;
} else if (m_thumb_index >= thumb_count) {
m_thumb_index = thumb_count - 1;
Comment on lines +1132 to +1137

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: instead of clamping, could we reject invalid inputs (negative values) early? Right now -1 means "let libraw choose" but -2 silently becomes the first entry, which I don't think anyone would guess. I think clamping positive values is fine, since the intent is at least unambiguous there

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spot. That is my mistake and is a holdover from an earlier iteration. The positive clamp is intended to allow for selecting the largest thumbnail without having to know how many thumbnails are in the file, when used with sorting.

}
Comment on lines +1127 to +1138

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is optional, but we could simplify the branching like so:

Suggested change
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;
}
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 {
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);

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<size_t>(thumb_count);
for (auto i = 0; i < thumb_count; ++i)
index_map[i] = i;

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;
});
Comment on lines +1156 to +1162

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, would twidth * theight be a better sort key, or was tlength chosen because the dimensions aren't always populated in thumbs_list?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should presume thumbnails (reduced resolution versions of the main image) will all have the same aspect ratio. I think that you can sort by width, by height, or width*height and will still get the same ordering.

@kenmcgaugh kenmcgaugh Jul 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jinhgkim, yes the twidth and theight are not populated by libraw for some thumbnail types. Internally libraw uses tlength to find the largest thumbnail when the older interface is used, and is therefore always available. (I actually want the smallest thumbnail for my use-case).

} 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];
}

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();
Expand Down Expand Up @@ -1580,6 +1677,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;
Expand Down Expand Up @@ -1765,84 +1868,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<int>(mem_thumb->type));
_errorfmt(this, m_thumb_index, "unknown image type {}",
static_cast<int>(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;
Expand All @@ -1855,8 +1958,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;
}

Expand All @@ -1866,11 +1970,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;
Expand Down
72 changes: 72 additions & 0 deletions testsuite/raw-thumbnail/ref/out-libraw0.20.0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
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
Loading
Loading