Skip to content
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

feat(jpeg): Support reading Ultra HDR images #4484

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ jobs:
setenvs: export USE_ICC=1 USE_OPENVDB=0
OIIO_EXTRA_CPP_ARGS="-fp-model=precise"
FREETYPE_VERSION=VER-2-13-0
DISABLE_libuhdr=1
# For icc, use fp-model precise to eliminate needless LSB errors
# that make test results differ from other platforms.
- desc: icx/C++17 py3.9 exr3.1 ocio2.2 qt5.15
Expand Down
45 changes: 45 additions & 0 deletions src/cmake/build_libuhdr.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright Contributors to the OpenImageIO project.
# SPDX-License-Identifier: Apache-2.0
# https://github.com/AcademySoftwareFoundation/OpenImageIO

######################################################################
# libuhdr by hand!
######################################################################

set_cache (libuhdr_BUILD_VERSION 1.2.0 "libuhdr version for local builds")
set (libuhdr_GIT_REPOSITORY "https://github.com/google/libultrahdr")
set (libuhdr_GIT_TAG "v${libuhdr_BUILD_VERSION}")

set_cache (libuhdr_BUILD_SHARED_LIBS OFF
DOC "Should execute a local libuhdr build, if necessary, build shared libraries" ADVANCED)

build_dependency_with_cmake(libuhdr
VERSION ${libuhdr_BUILD_VERSION}
GIT_REPOSITORY ${libuhdr_GIT_REPOSITORY}
GIT_TAG ${libuhdr_GIT_TAG}
CMAKE_ARGS
-D BUILD_SHARED_LIBS=${libuhdr_BUILD_SHARED_LIBS}
-D CMAKE_INSTALL_LIBDIR=lib
-D CMAKE_POSITION_INDEPENDENT_CODE=ON
-D UHDR_BUILD_EXAMPLES=FALSE
-D UHDR_ENABLE_LOGS=TRUE
)

if (WIN32)
file (GLOB _lib_files "${libuhdr_LOCAL_BUILD_DIR}/Release/*.lib")
file (COPY ${_lib_files} DESTINATION ${libuhdr_LOCAL_INSTALL_DIR}/lib)
unset (_lib_files)
file (GLOB _header_files "${libuhdr_LOCAL_SOURCE_DIR}/ultrahdr_api.h")
file (COPY ${_header_files} DESTINATION ${libuhdr_LOCAL_INSTALL_DIR}/include)
unset (_header_files)
endif ()

set (libuhdr_ROOT ${libuhdr_LOCAL_INSTALL_DIR})

find_package(libuhdr REQUIRED)

set (libuhdr_VERSION ${libuhdr_BUILD_VERSION})

if (libuhdr_BUILD_SHARED_LIBS)
install_local_dependency_libs (uhdr uhdr)
endif ()
4 changes: 4 additions & 0 deletions src/cmake/externalpackages.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ checked_find_package (fmt REQUIRED
get_target_property(FMT_INCLUDE_DIR fmt::fmt-header-only INTERFACE_INCLUDE_DIRECTORIES)


# Ultra HDR
checked_find_package (libuhdr)


###########################################################################

list (SORT CFP_ALL_BUILD_DEPS_FOUND COMPARE STRING CASE INSENSITIVE)
Expand Down
30 changes: 30 additions & 0 deletions src/cmake/modules/Findlibuhdr.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Module to find libuhdr
#
# Copyright Contributors to the OpenImageIO project.
# SPDX-License-Identifier: Apache-2.0
# https://github.com/AcademySoftwareFoundation/OpenImageIO
#
# This module defines the following variables:
#
# libuhdr_FOUND True if libuhdr was found.
# LIBUHDR_INCLUDE_DIR Where to find libuhdr headers
# LIBUHDR_LIBRARY Library for uhdr

include (FindPackageHandleStandardArgs)

find_path(LIBUHDR_INCLUDE_DIR
NAMES
ultrahdr_api.h
PATH_SUFFIXES
include
)

find_library(LIBUHDR_LIBRARY uhdr
PATH_SUFFIXES
lib
)

find_package_handle_standard_args (libuhdr
REQUIRED_VARS LIBUHDR_INCLUDE_DIR
LIBUHDR_LIBRARY
)
12 changes: 12 additions & 0 deletions src/doc/builtinplugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,18 @@ via the `ImageInput::set_ioproxy()` method and the special
mode and do not support tiled image input or output.


**Ultra HDR**

JPEG input also suports Ultra HDR images.
Ultra HDR is an image format that encodes a high dynamic range image
in a JPEG image file by including a gain map in addition to the
primary image.
See https://developer.android.com/media/platform/hdr-image-format for
a complete reference on the Ultra HDR image format.
In the specific case of reading an Ultra HDR image, JPEG input will also
support alpha channels and high dynamic range imagery (`half` pixels).



|

Expand Down
7 changes: 7 additions & 0 deletions src/jpeg.imageio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@
# SPDX-License-Identifier: Apache-2.0
# https://github.com/AcademySoftwareFoundation/OpenImageIO

if (libuhdr_FOUND)
set (UHDR_DEFS USE_UHDR)
endif ()

add_oiio_plugin (jpeginput.cpp jpegoutput.cpp
INCLUDE_DIRS ${LIBUHDR_INCLUDE_DIR}
LINK_LIBRARIES
$<TARGET_NAME_IF_EXISTS:libjpeg-turbo::jpeg>
$<TARGET_NAME_IF_EXISTS:JPEG::JPEG>
${LIBUHDR_LIBRARY}
DEFINITIONS ${UHDR_DEFS}
)
14 changes: 14 additions & 0 deletions src/jpeg.imageio/jpeg_pvt.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ extern "C" {
# define OIIO_JPEG_LIB_VERSION JPEG_LIB_VERSION
#endif

#if defined(USE_UHDR)
# include <ultrahdr_api.h>
#endif


OIIO_PLUGIN_NAMESPACE_BEGIN

Expand Down Expand Up @@ -95,6 +99,10 @@ class JpgInput final : public ImageInput {
jvirt_barray_ptr* m_coeffs;
std::vector<unsigned char> m_cmyk_buf; // For CMYK translation
std::unique_ptr<ImageSpec> m_config; // Saved copy of configuration spec
bool m_is_uhdr; // Is interpreted as Ultra HDR image
#if defined(USE_UHDR)
uhdr_codec_private_t* m_uhdr_dec;
#endif

void init()
{
Expand All @@ -106,6 +114,10 @@ class JpgInput final : public ImageInput {
m_jerr.jpginput = this;
ioproxy_clear();
m_config.reset();
m_is_uhdr = false;
#if defined(USE_UHDR)
m_uhdr_dec = NULL;
#endif
}

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

bool read_icc_profile(j_decompress_ptr cinfo, ImageSpec& spec);

bool read_uhdr(Filesystem::IOProxy* ioproxy);

void close_file() { init(); }

friend class JpgOutput;
Expand Down
115 changes: 115 additions & 0 deletions src/jpeg.imageio/jpeginput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,14 @@ JpgInput::open(const std::string& name, ImageSpec& newspec)

read_icc_profile(&m_cinfo, m_spec); /// try to read icc profile

// Try to interpret as Ultra HDR image.
// The libultrahdr API requires to load the whole file content in memory
// therefore we first check for the presence of the "hdrgm:Version" metadata
// to avoid this costly process when not necessary.
// https://developer.android.com/media/platform/hdr-image-format#signal_of_the_format
if (m_spec.find_attribute("hdrgm:Version"))
m_is_uhdr = read_uhdr(m_io);

newspec = m_spec;
return true;
}
Expand Down Expand Up @@ -406,6 +414,86 @@ JpgInput::read_icc_profile(j_decompress_ptr cinfo, ImageSpec& spec)



bool
JpgInput::read_uhdr(Filesystem::IOProxy* ioproxy)
{
#if defined(USE_UHDR)
// Read entire file content into buffer.
const size_t buffer_size = ioproxy->size();
std::vector<unsigned char> buffer(buffer_size);
ioproxy->pread(buffer.data(), buffer_size, 0);

// Check if this is an actual Ultra HDR image.
const bool detect_uhdr = is_uhdr_image(buffer.data(), buffer.size());
if (!detect_uhdr)
return false;

// Create Ultra HDR decoder.
// Do not forget to release it once we don't need it,
// i.e if this function returns false
// or when we call close().
m_uhdr_dec = uhdr_create_decoder();

// Prepare decoder input.
// Note: we currently do not override any of the
// default settings.
uhdr_compressed_image_t uhdr_compressed;
uhdr_compressed.data = buffer.data();
uhdr_compressed.data_sz = buffer.size();
uhdr_compressed.capacity = buffer.size();
uhdr_dec_set_image(m_uhdr_dec, &uhdr_compressed);

// Decode Ultra HDR image
// and check for decoding errors.
uhdr_error_info_t err_info = uhdr_decode(m_uhdr_dec);

if (err_info.error_code != UHDR_CODEC_OK) {
errorfmt("Ultra HDR decoding failed with error code {}",
int(err_info.error_code));
if (err_info.has_detail != 0)
errorfmt("Additional error details: {}", err_info.detail);
uhdr_release_decoder(m_uhdr_dec);
return false;
}

// Update spec with decoded image properties.
// Note: we currently only support a subset of all possible
// Ultra HDR image formats.
uhdr_raw_image_t* uhdr_raw = uhdr_get_decoded_image(m_uhdr_dec);

int nchannels;
TypeDesc desc;
switch (uhdr_raw->fmt) {
case UHDR_IMG_FMT_32bppRGBA8888:
nchannels = 4;
desc = TypeDesc::UINT8;
break;
case UHDR_IMG_FMT_64bppRGBAHalfFloat:
nchannels = 4;
desc = TypeDesc::HALF;
break;
case UHDR_IMG_FMT_24bppRGB888:
nchannels = 3;
desc = TypeDesc::UINT8;
break;
default:
errorfmt("Unsupported Ultra HDR image format: {}", int(uhdr_raw->fmt));
uhdr_release_decoder(m_uhdr_dec);
return false;
}

ImageSpec newspec = ImageSpec(uhdr_raw->w, uhdr_raw->h, nchannels, desc);
newspec.extra_attribs = m_spec.extra_attribs;
m_spec = newspec;

return true;
#else
return false;
#endif
}



static void
cmyk_to_rgb(int n, const unsigned char* cmyk, size_t cmyk_stride,
unsigned char* rgb, size_t rgb_stride)
Expand Down Expand Up @@ -453,6 +541,28 @@ JpgInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/,
OIIO_DASSERT(m_next_scanline == 0 && current_subimage() == subimage);
}

#if defined(USE_UHDR)
if (m_is_uhdr) {
uhdr_raw_image_t* uhdr_raw = uhdr_get_decoded_image(m_uhdr_dec);

unsigned int nbytes;
switch (uhdr_raw->fmt) {
case UHDR_IMG_FMT_32bppRGBA8888: nbytes = 4; break;
case UHDR_IMG_FMT_64bppRGBAHalfFloat: nbytes = 8; break;
case UHDR_IMG_FMT_24bppRGB888: nbytes = 3; break;
default: return false;
}

const size_t row_size = uhdr_raw->stride[UHDR_PLANE_PACKED] * nbytes;
unsigned char* top_left = static_cast<unsigned char*>(
uhdr_raw->planes[UHDR_PLANE_PACKED]);
unsigned char* row_data_start = top_left + row_size * y;
memcpy(data, row_data_start, row_size);

return true;
}
#endif

// Set up our custom error handler
if (setjmp(m_jerr.setjmp_buffer)) {
// Jump to here if there's a libjpeg internal error
Expand Down Expand Up @@ -494,6 +604,11 @@ JpgInput::close()
if (m_decomp_create)
jpeg_destroy_decompress(&m_cinfo);
m_decomp_create = false;
#if defined(USE_UHDR)
if (m_is_uhdr)
uhdr_release_decoder(m_uhdr_dec);
m_is_uhdr = false;
#endif
close_file();
}
init(); // Reset to initial state
Expand Down
Loading