-
Notifications
You must be signed in to change notification settings - Fork 687
feat(jpeg): add thumbnail functionality to jpeg reader #5333
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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> | ||
|
|
@@ -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 | ||
|
|
@@ -791,4 +793,141 @@ JpgInput::jpeg_decode_iptc(string_view buf) | |
| return decode_iptc_iim(buf, m_spec); | ||
| } | ||
|
|
||
| void | ||
| JpgInput::scan_for_thumbnail(cspan<uint8_t> 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<uint8_t>(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()); | ||
| } | ||
|
Comment on lines
+872
to
+874
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the try/catch here? Is there anything called inside that will actually throw an exception? |
||
| } | ||
|
|
||
| bool | ||
| JpgInput::get_thumbnail(ImageBuf& thumb, int subimage) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 initialised properly"); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I truly hate to pick on this, but in this code base we try to uniformly use the American spelling conventions: "initialized" |
||
| 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) { | ||
| std::string errmsg = OIIO::geterror(); | ||
| errorfmt("{}", errmsg.size() ? errmsg.c_str() : "unknown error"); | ||
| return false; | ||
| } | ||
|
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( | ||
| "failed to initialise an ImageInput object with the thumbnail data"); | ||
| return false; | ||
| } | ||
|
Comment on lines
+910
to
+914
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
|
||
| 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; | ||
| } | ||
|
Comment on lines
+921
to
+926
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here, too. Either just return false with no error, or do |
||
|
|
||
| image_input->close(); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Done. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code performs a basic scan for the subimage tag in the exif, it doesn't check if the found bit pattern is not a part of any other exif data. We may want to move this functionality to exif.cpp, but that may affect any other readers relying on exif.