Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/cmake/testing.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion src/jpeg.imageio/jpeg_pvt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<std::byte> data) override;
bool get_thumbnail(ImageBuf& thumb, int subimage) override;
bool close() override;

const std::string& filename() const { return m_filename; }
Expand Down Expand Up @@ -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<uint8_t> m_thumbnail_data;

void init()
{
m_raw = false;
Expand All @@ -122,6 +128,7 @@ class JpgInput final : public ImageInput {
#if defined(USE_UHDR)
m_uhdr_dec = NULL;
#endif
m_thumbnail_data = cspan<uint8_t>();
}

// Rummage through the JPEG "APP1" marker pointed to by buf, decoding
Expand All @@ -139,6 +146,8 @@ class JpgInput final : public ImageInput {

void close_file() { init(); }

void scan_for_thumbnail(cspan<uint8_t> exif);

friend class JpgOutput;
};

Expand Down
137 changes: 134 additions & 3 deletions src/jpeg.imageio/jpeginput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <OpenImageIO/filesystem.h>
#include <OpenImageIO/fmath.h>
#include <OpenImageIO/imagebuf.h>
#include <OpenImageIO/imageio.h>
#include <OpenImageIO/strutil.h>
#include <OpenImageIO/tiffutils.h>
Expand Down Expand Up @@ -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>((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
Expand Down Expand Up @@ -791,4 +793,133 @@ JpgInput::jpeg_decode_iptc(string_view buf)
return decode_iptc_iim(buf, m_spec);
}

void
JpgInput::scan_for_thumbnail(cspan<uint8_t> exif)
{
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<uint8_t>(soi_ptr, eoi_ptr);
m_spec.attribute("thumbnail_width", width);
m_spec.attribute("thumbnail_height", height);
m_spec.attribute("thumbnail_nchannels", numchan);
}
}

bool
JpgInput::get_thumbnail(ImageBuf& thumb, int subimage)

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.

The get_thumbnail() methods in the TGA input and ImageBuf contain lock guards. Does this method need to be thread safe?

{
if (!m_decomp_create) {
errorfmt("ImageInput hasn't been initialized 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;
}
Comment thread
antond-weta marked this conversation as resolved.

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("{}", image_input->geterror());
return false;
}
Comment on lines +904 to +907

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.

I think that if get_thumbnail, it's sufficient to just return false. But if you really want to return an error message, what you should be returning is image_input->geterror().

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.

done


thumb.reset(image_spec);

result = image_input->read_image(0, 0, 0, image_spec.nchannels,
image_spec.format, thumb.localpixels());

if (!result) {
errorfmt("{}", image_input->geterror());
image_input->close();
return false;
}
Comment on lines +914 to +918

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.

Here, too. Either just return false with no error, or do

errorfmt("{}", image_input->geterror());

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.

done


image_input->close();

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.

Without this I was getting address sanitiser errors in the image_input's destructor. I couldn't understand the error, and don't really think that it is related to the change.

return true;
}


OIIO_PLUGIN_NAMESPACE_END
5 changes: 4 additions & 1 deletion testsuite/gpsread/ref/out.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 3 additions & 0 deletions testsuite/jpeg-corrupt/ref/out-alt2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions testsuite/jpeg-corrupt/ref/out-alt3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions testsuite/jpeg-corrupt/ref/out-alt4.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions testsuite/jpeg-corrupt/ref/out-alt5.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions testsuite/jpeg-corrupt/ref/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions testsuite/jpeg-thumbnail/ref/out.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
320 x 240, 3 channel, uint8
channel list: R, G, B
14 changes: 14 additions & 0 deletions testsuite/jpeg-thumbnail/run.py
Original file line number Diff line number Diff line change
@@ -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" ]
5 changes: 4 additions & 1 deletion testsuite/python-imageinput/ref/out-python3-win.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion testsuite/python-imageinput/ref/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading