diff --git a/CHANGES.md b/CHANGES.md index b6ec45fa44..7e94c67a45 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,9 @@ Release 2.1 (?? 2019) -- compared to 2.0 New minimum dependencies: Major new features and improvements: +* Support for HEIC/HEIF images. HEIC is the still-image sibling of HEVC + (a.k.a. H.265), and compresses to about half the size of JPEG but with + higher visual quality. (2.1.0) Public API changes: * Python: define `__version__` for the module. #2096 (2.1.0/2.0.4) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a581e3bc9..9320f2fe2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,7 @@ set (USE_EXTERNAL_PUGIXML OFF CACHE BOOL "Use an externally built shared library version of the pugixml library") set (PUGIXML_HOME "" CACHE STRING "Hint about where to find external PugiXML library") option (USE_DICOM "Use DICOM if DCMTK is found" ON) +option (USE_HEIF "Support HEIC/HEIF files, if libheif is found)" ON) set (TEX_BATCH_SIZE "" CACHE STRING "Force TextureSystem SIMD batch size (e.g. 16)") set (SOVERSION ${OIIO_VERSION_MAJOR}.${OIIO_VERSION_MINOR} CACHE STRING "Set the SO version in the SO name of the output library") @@ -370,6 +371,10 @@ oiio_add_tests (fits IMAGEDIR fits-images URL http://www.cv.nrao.edu/fits/data/tests/) +oiio_add_tests (heif + FOUNDVAR LIBHEIF_FOUND + URL https://github.com/nokiatech/heif/tree/gh-pages/content) + oiio_add_tests (webp FOUNDVAR WEBP_FOUND IMAGEDIR oiio-images/webp diff --git a/INSTALL.md b/INSTALL.md index 88bb7ff540..64c9091c72 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -56,6 +56,8 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * If you want support for converting to and from OpenCV data structures, or for capturing images from a camera: * OpenCV 2.x, 3.x, or 4.x + * If you want support for HEIF/HEIC images: + * libheic >= 1.3 (older versions may work, we haven't tested) Building OpenImageIO on Linux or OS X diff --git a/README.md b/README.md index 677a863463..9c6ebdc986 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,8 @@ OpenImageIO consists of: including TIFF, JPEG/JFIF, OpenEXR, PNG, HDR/RGBE, ICO, BMP, Targa, JPEG-2000, RMan Zfile, FITS, DDS, Softimage PIC, PNM, DPX, Cineon, IFF, Field3D, OpenVDB, Ptex, Photoshop PSD, Wavefront RLA, SGI, WebP, - GIF, DICOM, many "RAW" digital camera formats, and a variety of movie - formats (readable as individual frames). More are being developed + GIF, DICOM, HEIC/HEIF, many "RAW" digital camera formats, and a variety of + movie formats (readable as individual frames). More are being developed all the time. * Several command line image tools based on these classes, including diff --git a/site/spi/Makefile-bits-arnold b/site/spi/Makefile-bits-arnold index f4e8ddf9f8..fb8dae482e 100644 --- a/site/spi/Makefile-bits-arnold +++ b/site/spi/Makefile-bits-arnold @@ -131,7 +131,8 @@ ifeq (${SP_OS}, rhel7) -DHDF5_LIBRARIES=/usr/lib64/libhdf5.so \ -DNuke_ROOT=/net/apps/rhel7/foundry/nuke${NUKE_VERSION} \ -DLIBRAW_INCLUDEDIR_HINT=/usr/include/libraw-0.18.11 \ - -DLIBRAW_LIBDIR_HINT=/usr/lib64/libraw-0.18.11 + -DLIBRAW_LIBDIR_HINT=/usr/lib64/libraw-0.18.11 \ + -DLIBHEIF_PATH=/shots/spi/home/lib/arnold/rhel7/libheif-1.3.2 # Special sauce for Python 3.6 ifeq (${PYTHON_VERSION},3.6) diff --git a/site/spi/Makefile-bits-spcomp2 b/site/spi/Makefile-bits-spcomp2 index dd7414b6b7..f615bc15aa 100644 --- a/site/spi/Makefile-bits-spcomp2 +++ b/site/spi/Makefile-bits-spcomp2 @@ -146,7 +146,8 @@ ifeq (${SP_OS}, rhel7) -DHDF5_LIBRARIES=/usr/lib64/libhdf5.so \ -DNuke_ROOT=/net/apps/rhel7/foundry/nuke${NUKE_VERSION} \ -DLIBRAW_INCLUDEDIR_HINT=/usr/include/libraw-0.18.11 \ - -DLIBRAW_LIBDIR_HINT=/usr/lib64/libraw-0.18.11 + -DLIBRAW_LIBDIR_HINT=/usr/lib64/libraw-0.18.11 \ + -DLIBHEIF_PATH=/shots/spi/home/lib/arnold/rhel7/libheif-1.3.2 # Special sauce for Python 3.6 ifeq (${PYTHON_VERSION},3.6) diff --git a/src/build-scripts/install_homebrew_deps.bash b/src/build-scripts/install_homebrew_deps.bash index 9beef70aee..c8682b1717 100755 --- a/src/build-scripts/install_homebrew_deps.bash +++ b/src/build-scripts/install_homebrew_deps.bash @@ -47,6 +47,7 @@ brew install opencv brew install tbb brew install openvdb brew install pybind11 +brew install libheif if [ "$LINKSTATIC" == "1" ] ; then brew install little-cms2 tinyxml szip brew install homebrew/dupes/bzip2 diff --git a/src/cmake/externalpackages.cmake b/src/cmake/externalpackages.cmake index c07b2ddaa4..95c8f540e5 100644 --- a/src/cmake/externalpackages.cmake +++ b/src/cmake/externalpackages.cmake @@ -12,6 +12,7 @@ if (NOT VERBOSE) set (HDF5_FIND_QUIETLY true) set (IlmBase_FIND_QUIETLY true) set (JPEG_FIND_QUIETLY true) + set (LIBHEIF_FIND_QUIETLY true) set (LibRaw_FIND_QUIETLY true) set (Nuke_FIND_QUIETLY true) set (OpenColorIO_FIND_QUIETLY true) @@ -518,6 +519,15 @@ endif() ########################################################################### +########################################################################### +# HEIF +if (USE_HEIF) + find_package (Libheif 1.3) +endif() +# end HEIF setup +########################################################################### + + ########################################################################### # pybind11 diff --git a/src/cmake/modules/FindLibheif.cmake b/src/cmake/modules/FindLibheif.cmake new file mode 100644 index 0000000000..af35e7d623 --- /dev/null +++ b/src/cmake/modules/FindLibheif.cmake @@ -0,0 +1,56 @@ +# Module to find LIBHEIF +# +# This module will first look into the directories defined by the variables: +# - LIBHEIF_PATH, LIBHEIF_INCLUDE_PATH, LIBHEIF_LIBRARY_PATH +# +# This module defines the following variables: +# +# LIBHEIF_FOUND True if LIBHEIF was found. +# LIBHEIF_INCLUDES Where to find LIBHEIF headers +# LIBHEIF_LIBRARIES List of libraries to link against when using LIBHEIF +# LIBHEIF_VERSION Version of LIBHEIF (e.g., 3.6.2) + +include (FindPackageHandleStandardArgs) +include (FindPackageMessage) + +find_path (LIBHEIF_INCLUDE_DIR + libheif/heif_version.h + PATHS + ${LIBHEIF_INCLUDE_PATH} + ${LIBHEIF_PATH}/include/ + DOC "The directory where libheif headers reside") + +find_library (LIBHEIF_LIBRARY heif + PATHS ${LIBHEIF_PATH}/lib ${LIBHEIF_LIBRARY_PATH}) + +message (STATUS "LIBHEIF_INCLUDE_DIR = ${LIBHEIF_INCLUDE_DIR}") +if (LIBHEIF_INCLUDE_DIR) + file(STRINGS "${LIBHEIF_INCLUDE_DIR}/libheif/heif_version.h" TMP REGEX "^#define LIBHEIF_VERSION[ \t].*$") + string(REGEX MATCHALL "[0-9.]+" LIBHEIF_VERSION ${TMP}) +endif () + +if (LIBHEIF_INCLUDE_DIR AND LIBHEIF_LIBRARY) + set(LIBHEIF_FOUND TRUE) + set(LIBHEIF_INCLUDES "${LIBHEIF_INCLUDE_DIR}") + set(LIBHEIF_LIBRARIES "${LIBHEIF_LIBRARY}") + if (NOT LIBHEIF_FIND_QUIETLY) + message(STATUS "Found libheif ${LIBHEIF_VERSION} library ${LIBHEIF_LIBRARIES}") + message(STATUS "Found libheif includes ${LIBHEIF_INCLUDES}") + endif () +else() + set(LIBHEIF_FOUND FALSE) + message(STATUS "libheif not found. Specify LIBHEIF_PATH to locate it") +endif() + +include (FindPackageHandleStandardArgs) +find_package_handle_standard_args (LIBHEIF + REQUIRED_VARS LIBHEIF_INCLUDE_DIR + LIBHEIF_LIBRARIES + VERSION_VAR LIBHEIF_VERSION + ) + +mark_as_advanced ( + LIBHEIF_INCLUDE_DIR + LIBHEIF_LIBRARIES + LIBHEIF_VERSION + ) diff --git a/src/doc/builtinplugins.tex b/src/doc/builtinplugins.tex index 5ff7fa8dd9..87d6df8e19 100644 --- a/src/doc/builtinplugins.tex +++ b/src/doc/builtinplugins.tex @@ -403,6 +403,40 @@ \section{HDR/RGBE} RGBE header (if it's gamma corrected). \end{tabular} +\vspace{.25in} + + +\section{HEIF/HEIC} +\label{sec:bundledplugins:heif} +\index{HEIF} \endex{HEIC} + +HEIF is a container format for images compressed with the HEIC compression +standard (same compression as HEVC/H.265). It is used commonly for iPhone +camera pictures, but it is not Apple-specific and will probably become more +popualar on other platforms in coming years. HEIF files usually use the file +extension {\cf .HEIC}. + +HEIC compression is lossy, but is higher visual quality than JPEG while +taking only half the file size. Currently, OIIO's HEIF reader supports +reading files as RGB or RGBA, uint8 pixel values. Multi-image files are +currently supported for reading, but not yet writing. All pixel data is +uint8, though we hope to add support for HDR (more than 8 bits) in the +future. + +\subsubsection*{Configuration settings for HEIF output} + +When opening an HEIF \ImageOutput, the following special metadata tokens +control aspects of the writing itself: + +\vspace{.125in} +\noindent\begin{tabular}{p{1.5in}|p{0.5in}|p{3.25in}} +\ImageSpec Attribute & Type & HEIF header data or explanation \\ +\hline +\qkw{Compression} & string & If supplied, must be \qkw{heic}, but may + optionally have a quality value appended, like \qkw{heic:90}. + Quality can be 1-100, with 100 meaning lossless. The default is 75. \\[2ex] +\end{tabular} + \vspace{.25in} diff --git a/src/doc/oiiointro.tex b/src/doc/oiiointro.tex index f309dceac2..a1fb016779 100644 --- a/src/doc/oiiointro.tex +++ b/src/doc/oiiointro.tex @@ -294,6 +294,9 @@ \section{Acknowledgments} \item {\cf OpenVDB} \copyright\ 2012-2018 DreamWorks Animation LLC, Mozilla Public License 2.0. \item {\cf Thread Building Blocks} \copyright\ Intel. Apache 2.0 license. +\item {\cf libheif} \copyright 2017-2018 Struktur AG (LGPL). \\ + \url{https://github.com/strukturag/libheif} + \end{itemize} diff --git a/src/heif.imageio/CMakeLists.txt b/src/heif.imageio/CMakeLists.txt new file mode 100644 index 0000000000..f8e70c8217 --- /dev/null +++ b/src/heif.imageio/CMakeLists.txt @@ -0,0 +1,8 @@ +if (USE_HEIF AND LIBHEIF_FOUND) + add_oiio_plugin (heifinput.cpp heifoutput.cpp + INCLUDE_DIRS ${LIBHEIF_INCLUDES} + LINK_LIBRARIES ${LIBHEIF_LIBRARIES} + DEFINITIONS "-DUSE_HEIF=1") +else () + message (WARNING "heif plugin will not be built") +endif () diff --git a/src/heif.imageio/heifinput.cpp b/src/heif.imageio/heifinput.cpp new file mode 100644 index 0000000000..1adeabfaa1 --- /dev/null +++ b/src/heif.imageio/heifinput.cpp @@ -0,0 +1,286 @@ +/* + Copyright 2019 Larry Gritz and the other authors and contributors. + All Rights Reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the software's owners nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + (This is the Modified BSD License) +*/ + +#include +#include + +#include + + +// This plugin utilises libheif: +// https://github.com/strukturag/libheif +// +// General information about HEIF/HEIC: +// +// Sources of sample images: +// https://github.com/nokiatech/heif/tree/gh-pages/content + + +OIIO_PLUGIN_NAMESPACE_BEGIN + +class HeifInput final : public ImageInput { +public: + HeifInput() {} + virtual ~HeifInput() { close(); } + virtual const char* format_name(void) const override { return "heif"; } + virtual int supports(string_view feature) const override + { + return feature == "exif"; + } + // virtual bool valid_file(const std::string& filename) const override; + virtual bool open(const std::string& name, ImageSpec& newspec) override; + virtual bool open(const std::string& name, ImageSpec& newspec, + const ImageSpec& config) override; + virtual bool close() override; + virtual bool seek_subimage(int subimage, int miplevel) override; + virtual bool read_native_scanline(int subimage, int miplevel, int y, int z, + void* data) override; + +private: + std::string m_filename; + int m_subimage = -1; + int m_num_subimages = 0; + int m_has_alpha = false; + std::unique_ptr m_ctx; + heif_item_id m_primary_id; // id of primary image + std::vector m_item_ids; // ids of all other images + heif::ImageHandle m_ihandle; + heif::Image m_himage; +}; + + + +// Export version number and create function symbols +OIIO_PLUGIN_EXPORTS_BEGIN + +OIIO_EXPORT int heif_imageio_version = OIIO_PLUGIN_VERSION; + +OIIO_EXPORT const char* +heif_imageio_library_version() +{ + return "libheif " LIBHEIF_VERSION; +} + +OIIO_EXPORT ImageInput* +heif_input_imageio_create() +{ + return new HeifInput; +} + +OIIO_EXPORT const char* heif_input_extensions[] = { "heif", "heic", "heics", + nullptr }; + +OIIO_PLUGIN_EXPORTS_END + + +#if 0 +bool +HeifInput::valid_file(const std::string& filename) const +{ + const size_t magic_size = 12; + uint8_t magic[magic_size]; + FILE *file = Filesystem::fopen(filename, "rb"); + fread (magic, magic_size, 1, file); + fclose (file); + heif_filetype_result filetype_check = heif_check_filetype(magic,12); + return filetype_check != heif_filetype_no + && filetype_check != heif_filetype_yes_unsupported + // This is what the libheif example said to do, but I can't find + // the filetype constants declared anywhere. Are they obsolete? +} +#endif + + + +bool +HeifInput::open(const std::string& name, ImageSpec& newspec) +{ + // If user doesn't want to provide any config, just use an empty spec. + ImageSpec config; + return open(name, newspec, config); +} + + + +bool +HeifInput::open(const std::string& name, ImageSpec& newspec, + const ImageSpec& config) +{ + m_filename = name; + m_subimage = -1; + + m_ctx.reset(new heif::Context); + m_himage = heif::Image(); + m_ihandle = heif::ImageHandle(); + + try { + m_ctx->read_from_file(name); + // FIXME: should someday be read_from_reader to give full flexibility + + m_item_ids = m_ctx->get_list_of_top_level_image_IDs(); + m_primary_id = m_ctx->get_primary_image_ID(); + for (size_t i = 0; i < m_item_ids.size(); ++i) + if (m_item_ids[i] == m_primary_id) { + m_item_ids.erase(m_item_ids.begin() + i); + break; + } + // std::cout << " primary id: " << m_primary_id << "\n"; + // std::cout << " item ids: " << Strutil::join(m_item_ids, ", ") << "\n"; + m_num_subimages = 1 + int(m_item_ids.size()); + + } catch (const heif::Error& err) { + std::string e = err.get_message(); + errorf("%s", e.empty() ? "unknown exception" : e.c_str()); + return false; + } catch (const std::exception& err) { + std::string e = err.what(); + errorf("%s", e.empty() ? "unknown exception" : e.c_str()); + return false; + } + + bool ok = seek_subimage(0, 0); + newspec = spec(); + return ok; +} + + + +bool +HeifInput::close() +{ + m_himage = heif::Image(); + m_ihandle = heif::ImageHandle(); + m_ctx.reset(); + m_subimage = -1; + m_num_subimages = 0; + return true; +} + + + +bool +HeifInput::seek_subimage(int subimage, int miplevel) +{ + if (miplevel != 0) + return false; + + if (subimage == m_subimage) { + return true; // already there + } + + if (subimage >= m_num_subimages) { + return false; + } + + try { + auto id = (subimage == 0) ? m_primary_id : m_item_ids[subimage - 1]; + m_ihandle = m_ctx->get_image_handle(id); + m_has_alpha = m_ihandle.has_alpha_channel(); + auto chroma = m_has_alpha ? heif_chroma_interleaved_RGBA + : heif_chroma_interleaved_RGB; + m_himage = m_ihandle.decode_image(heif_colorspace_RGB, chroma); + + } catch (const heif::Error& err) { + std::string e = err.get_message(); + errorf("%s", e.empty() ? "unknown exception" : e.c_str()); + return false; + } catch (const std::exception& err) { + std::string e = err.what(); + errorf("%s", e.empty() ? "unknown exception" : e.c_str()); + return false; + } + + int bits = m_himage.get_bits_per_pixel(heif_channel_interleaved); + m_spec = ImageSpec(m_ihandle.get_width(), m_ihandle.get_height(), bits / 8, + TypeUInt8); + + m_spec.attribute("oiio:ColorSpace", "sRGB"); + + auto meta_ids = m_ihandle.get_list_of_metadata_block_IDs(); + // std::cout << "nmeta? " << meta_ids.size() << "\n"; + for (auto m : meta_ids) { + auto metacontents = m_ihandle.get_metadata(m); + if (Strutil::iequals(m_ihandle.get_metadata_type(m), "Exif") + && metacontents.size() >= 10) { + cspan s(&metacontents[10], metacontents.size() - 10); + decode_exif(s, m_spec); + } else if (0 // For now, skip this, I haven't seen anything useful + && Strutil::iequals(m_ihandle.get_metadata_type(m), "mime") + && Strutil::iequals(m_ihandle.get_metadata_content_type(m), + "application/rdf+xml")) { + decode_xmp(metacontents, m_spec); + } else { +#ifdef DEBUG + std::cout << "Don't know how to decode meta " << m + << " type=" << m_ihandle.get_metadata_type(m) + << " contenttype='" + << m_ihandle.get_metadata_content_type(m) << "'\n"; + std::cout << "---\n" + << string_view((const char*)&metacontents[0], + metacontents.size()) + << "\n---\n"; +#endif + } + } + + // Erase the orientation metadata becaue libheif appears to be doing + // the rotation-to-canonical-direction for us. + m_spec.erase_attribute("Orientation"); + + m_subimage = subimage; + return true; +} + + + +bool +HeifInput::read_native_scanline(int subimage, int miplevel, int y, int z, + void* data) +{ + lock_guard lock(m_mutex); + if (!seek_subimage(subimage, miplevel)) + return false; + if (y < 0 || y >= m_spec.height) // out of range scanline + return false; + + int ystride = 0; + const uint8_t* hdata = m_himage.get_plane(heif_channel_interleaved, + &ystride); + if (!hdata) { + errorf("Unknown read error"); + return false; + } + hdata += (y - m_spec.y) * ystride; + memcpy(data, hdata, m_spec.width * m_spec.pixel_bytes()); + return true; +} + + +OIIO_PLUGIN_NAMESPACE_END diff --git a/src/heif.imageio/heifoutput.cpp b/src/heif.imageio/heifoutput.cpp new file mode 100644 index 0000000000..55d4de275d --- /dev/null +++ b/src/heif.imageio/heifoutput.cpp @@ -0,0 +1,237 @@ +/* + Copyright 2019 Larry Gritz and the other authors and contributors. + All Rights Reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the software's owners nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + (This is the Modified BSD License) +*/ + + +#include +#include + +#include + + +OIIO_PLUGIN_NAMESPACE_BEGIN + +class HeifOutput final : public ImageOutput { +public: + HeifOutput() {} + virtual ~HeifOutput() { close(); } + virtual const char* format_name(void) const override { return "heif"; } + virtual int supports(string_view feature) const override + { + return feature == "alpha" || feature == "exif"; + } + virtual bool open(const std::string& name, const ImageSpec& spec, + OpenMode mode) override; + virtual bool write_scanline(int y, int z, TypeDesc format, const void* data, + stride_t xstride) override; + virtual bool write_tile(int x, int y, int z, TypeDesc format, + const void* data, stride_t xstride, + stride_t ystride, stride_t zstride) override; + virtual bool close() override; + +private: + std::string m_filename; + std::unique_ptr m_ctx; + heif::ImageHandle m_ihandle; + heif::Image m_himage; + heif::Encoder m_encoder { heif_compression_HEVC }; + std::vector scratch; + std::vector m_tilebuffer; +}; + + + +OIIO_PLUGIN_EXPORTS_BEGIN + +OIIO_EXPORT ImageOutput* +heif_output_imageio_create() +{ + return new HeifOutput; +} + +OIIO_EXPORT const char* heif_output_extensions[] = { "heif", "heic", "heics", + nullptr }; + +OIIO_PLUGIN_EXPORTS_END + + +bool +HeifOutput::open(const std::string& name, const ImageSpec& newspec, + OpenMode mode) +{ + if (mode != Create) { + errorf("%s does not support subimages or MIP levels", format_name()); + return false; + } + + m_filename = name; + // Save spec for later used + m_spec = newspec; + // heif always behaves like floating point + m_spec.set_format(TypeDesc::FLOAT); + + // Check for things heif can't support + if (m_spec.nchannels != 1 && m_spec.nchannels != 3 + && m_spec.nchannels != 4) { + errorf("heif can only support 1-, 3- or 4-channel images"); + return false; + } + if (m_spec.width < 1 || m_spec.height < 1) { + errorf("Image resolution must be at least 1x1, you asked for %d x %d", + m_spec.width, m_spec.height); + return false; + } + if (m_spec.depth < 1) + m_spec.depth = 1; + if (m_spec.depth > 1) { + errorf("%s does not support volume images (depth > 1)", format_name()); + return false; + } + + m_spec.set_format(TypeUInt8); // Only uint8 for now + + try { + m_ctx.reset(new heif::Context); + m_himage = heif::Image(); + static heif_chroma chromas[/*nchannels*/] + = { heif_chroma_undefined, heif_chroma_monochrome, + heif_chroma_undefined, heif_chroma_interleaved_RGB, + heif_chroma_interleaved_RGBA }; + m_himage.create(newspec.width, newspec.height, heif_colorspace_RGB, + chromas[m_spec.nchannels]); + m_himage.add_plane(heif_channel_interleaved, newspec.width, + newspec.height, 8 * m_spec.nchannels /*bit depth*/); + m_encoder = heif::Encoder(heif_compression_HEVC); + + } catch (const heif::Error& err) { + std::string e = err.get_message(); + errorf("%s", e.empty() ? "unknown exception" : e.c_str()); + return false; + } catch (const std::exception& err) { + std::string e = err.what(); + errorf("%s", e.empty() ? "unknown exception" : e.c_str()); + return false; + } + + // If user asked for tiles -- which this format doesn't support, emulate + // it by buffering the whole image. + if (m_spec.tile_width && m_spec.tile_height) + m_tilebuffer.resize(m_spec.image_bytes()); + + return true; +} + + + +bool +HeifOutput::write_scanline(int y, int z, TypeDesc format, const void* data, + stride_t xstride) +{ + data = to_native_scanline(format, data, xstride, scratch); + int hystride = 0; + uint8_t* hdata = m_himage.get_plane(heif_channel_interleaved, &hystride); + hdata += hystride * (y - m_spec.y); + memcpy(hdata, data, hystride); + return true; +} + + + +bool +HeifOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, + stride_t xstride, stride_t ystride, stride_t zstride) +{ + // Emulate tiles by buffering the whole image + return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, + zstride, &m_tilebuffer[0]); +} + + + +bool +HeifOutput::close() +{ + if (!m_ctx) { // already closed + return true; + } + + bool ok = true; + if (m_spec.tile_width) { + // We've been emulating tiles; now dump as scanlines. + ASSERT(m_tilebuffer.size()); + ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, + m_spec.format, &m_tilebuffer[0]); + std::vector().swap(m_tilebuffer); + } + + std::vector exifblob; + try { + auto compqual = m_spec.decode_compression_metadata("", 75); + if (compqual.first == "heic") { + if (compqual.second >= 100) + m_encoder.set_lossless(true); + else { + m_encoder.set_lossless(false); + m_encoder.set_lossy_quality(compqual.second); + } + } else if (compqual.first == "none") { + m_encoder.set_lossless(true); + } + encode_exif(m_spec, exifblob, endian::big); + m_ihandle = m_ctx->encode_image(m_himage, m_encoder); + std::vector head { 'E', 'x', 'i', 'f', 0, 0 }; + exifblob.insert(exifblob.begin(), head.begin(), head.end()); + try { + m_ctx->add_exif_metadata(m_ihandle, exifblob.data(), + exifblob.size()); + } catch (const heif::Error& err) { +#ifdef DEBUG + std::string e = err.get_message(); + Strutil::printf("%s", e.empty() ? "unknown exception" : e.c_str()); +#endif + } + m_ctx->set_primary_image(m_ihandle); + m_ctx->write_to_file(m_filename); + // FIXME: ^^^ should really use Writer for full generality + + } catch (const heif::Error& err) { + std::string e = err.get_message(); + errorf("%s", e.empty() ? "unknown exception" : e.c_str()); + return false; + } catch (const std::exception& err) { + std::string e = err.what(); + errorf("%s", e.empty() ? "unknown exception" : e.c_str()); + return false; + } + + m_ctx.reset(); + return ok; +} + +OIIO_PLUGIN_NAMESPACE_END diff --git a/src/include/OpenImageIO/tiffutils.h b/src/include/OpenImageIO/tiffutils.h index d348331a4b..df4b26e4a6 100644 --- a/src/include/OpenImageIO/tiffutils.h +++ b/src/include/OpenImageIO/tiffutils.h @@ -188,8 +188,12 @@ OIIO_API bool decode_exif (cspan exif, ImageSpec &spec); OIIO_API bool decode_exif (string_view exif, ImageSpec &spec); OIIO_API bool decode_exif (const void *exif, int length, ImageSpec &spec); // DEPRECATED (1.8) -/// Construct an Exif data block from the ImageSpec, appending the Exif -/// data as a big blob to the char vector. +/// Construct an Exif data block from the ImageSpec, appending the Exif +/// data as a big blob to the char vector. Endianness can be specified with +/// endianreq, defaulting to the native endianness of the running platform. +OIIO_API void encode_exif (const ImageSpec &spec, std::vector &blob, + OIIO::endian endianreq /* = endian::native*/); +// DEPRECATED(2.1) OIIO_API void encode_exif (const ImageSpec &spec, std::vector &blob); /// Helper: For the given OIIO metadata attribute name, look up the Exif tag @@ -221,7 +225,12 @@ OIIO_API void encode_iptc_iim (const ImageSpec &spec, std::vector &iptc); /// utility function to make it easy for multiple format plugins to /// support embedding XMP metadata without having to duplicate /// functionality within each plugin. -OIIO_API bool decode_xmp (const std::string &xml, ImageSpec &spec); +OIIO_API bool decode_xmp (cspan xml, ImageSpec &spec); +OIIO_API bool decode_xmp (string_view xml, ImageSpec &spec); +// DEPRECATED(2.1): +OIIO_API bool decode_xmp (const char* xml, ImageSpec &spec); +OIIO_API bool decode_xmp (const std::string& xml, ImageSpec &spec); + /// Find all the relavant metadata (IPTC, Exif, etc.) in spec and /// assemble it into an XMP XML string. This is a utility function to diff --git a/src/libOpenImageIO/exif.cpp b/src/libOpenImageIO/exif.cpp index 4417a934ad..c399d99765 100644 --- a/src/libOpenImageIO/exif.cpp +++ b/src/libOpenImageIO/exif.cpp @@ -799,7 +799,7 @@ read_exif_tag(ImageSpec& spec, const TIFFDirEntry* dirp, cspan buf, #if DEBUG_EXIF_READ std::cerr << "Read " << tagmap.mapname() << " "; - print_dir_entry(tagmap, dir, buf, offset_adjustment); + print_dir_entry(std::cerr, tagmap, dir, buf, offset_adjustment); #endif if (dir.tdir_tag == TIFFTAG_EXIFIFD || dir.tdir_tag == TIFFTAG_GPSIFD) { @@ -912,14 +912,6 @@ read_exif_tag(ImageSpec& spec, const TIFFDirEntry* dirp, cspan buf, -inline int -tagcompare(const TIFFDirEntry& a, const TIFFDirEntry& b) -{ - return (a.tdir_tag < b.tdir_tag); -} - - - /// Convert to the desired integer type and then append_tiff_dir_entry it. /// template @@ -927,7 +919,8 @@ static bool append_tiff_dir_entry_integer(const ParamValue& p, std::vector& dirs, std::vector& data, int tag, - TIFFDataType type, size_t offset_correction) + TIFFDataType type, size_t offset_correction, + OIIO::endian endianreq) { T i; switch (p.type().basetype) { @@ -937,7 +930,8 @@ append_tiff_dir_entry_integer(const ParamValue& p, case TypeDesc::INT16: i = (T) * (short*)p.data(); break; default: return false; } - append_tiff_dir_entry(dirs, data, tag, type, 1, &i, offset_correction); + append_tiff_dir_entry(dirs, data, tag, type, 1, &i, offset_correction, 0, + endianreq); return true; } @@ -950,7 +944,7 @@ append_tiff_dir_entry_integer(const ParamValue& p, static void encode_exif_entry(const ParamValue& p, int tag, std::vector& dirs, std::vector& data, const TagMap& tagmap, - size_t offset_correction) + size_t offset_correction, OIIO::endian endianreq) { if (tag < 0) return; @@ -964,7 +958,7 @@ encode_exif_entry(const ParamValue& p, int tag, std::vector& dirs, const char* s = *(const char**)p.data(); int len = strlen(s) + 1; append_tiff_dir_entry(dirs, data, tag, type, len, s, - offset_correction); + offset_correction, 0, endianreq); return; } break; @@ -976,7 +970,7 @@ encode_exif_entry(const ParamValue& p, int tag, std::vector& dirs, for (size_t i = 0; i < count; ++i) float_to_rational(f[i], rat[2 * i], rat[2 * i + 1]); append_tiff_dir_entry(dirs, data, tag, type, count, rat, - offset_correction); + offset_correction, 0, endianreq); return; } break; @@ -987,26 +981,24 @@ encode_exif_entry(const ParamValue& p, int tag, std::vector& dirs, for (size_t i = 0; i < count; ++i) float_to_rational(f[i], rat[2 * i], rat[2 * i + 1]); append_tiff_dir_entry(dirs, data, tag, type, count, rat, - offset_correction); + offset_correction, 0, endianreq); return; } break; case TIFF_SHORT: - if (append_tiff_dir_entry_integer(p, dirs, data, tag, - type, - offset_correction)) + if (append_tiff_dir_entry_integer( + p, dirs, data, tag, type, offset_correction, endianreq)) return; break; case TIFF_LONG: if (append_tiff_dir_entry_integer(p, dirs, data, tag, - type, - offset_correction)) + type, offset_correction, + endianreq)) return; break; case TIFF_BYTE: - if (append_tiff_dir_entry_integer(p, dirs, data, tag, - type, - offset_correction)) + if (append_tiff_dir_entry_integer( + p, dirs, data, tag, type, offset_correction, endianreq)) return; break; default: break; @@ -1045,31 +1037,58 @@ void pvt::append_tiff_dir_entry(std::vector& dirs, std::vector& data, int tag, TIFFDataType type, size_t count, const void* mydata, - size_t offset_correction, size_t offset_override) + size_t offset_correction, size_t offset_override, + OIIO::endian endianreq) { TIFFDirEntry dir; - dir.tdir_tag = tag; - dir.tdir_type = type; - dir.tdir_count = count; - size_t len = tiff_data_size(dir); + dir.tdir_tag = tag; + dir.tdir_type = type; + dir.tdir_count = count; + size_t len = tiff_data_size(dir); + char* ptr = nullptr; + bool data_in_offset = false; if (len <= 4) { dir.tdir_offset = 0; - if (mydata) - memcpy(&dir.tdir_offset, mydata, len); + data_in_offset = true; + if (mydata) { + ptr = (char*)&dir.tdir_offset; + memcpy(ptr, mydata, len); + } } else { if (mydata) { // Add to the data vector and use its offset + size_t oldsize = data.size(); dir.tdir_offset = data.size() - offset_correction; data.insert(data.end(), (char*)mydata, (char*)mydata + len); + ptr = &data[oldsize]; } else { // An offset override was given, use that, it means that data // ALREADY contains what we want. dir.tdir_offset = uint32_t(offset_override); } } + if (endianreq != endian::native) { + OIIO::swap_endian(&dir.tdir_tag); + OIIO::swap_endian(&dir.tdir_type); + OIIO::swap_endian(&dir.tdir_count); + if (!data_in_offset) + OIIO::swap_endian(&dir.tdir_offset); + if (ptr) { + if (type == TIFF_SHORT || type == TIFF_SSHORT) + OIIO::swap_endian((uint16_t*)ptr, count); + if (type == TIFF_LONG || type == TIFF_SLONG || type == TIFF_FLOAT + || type == TIFF_IFD) + OIIO::swap_endian((uint32_t*)ptr, count); + if (type == TIFF_LONG8 || type == TIFF_SLONG8 || type == TIFF_DOUBLE + || type == TIFF_IFD8) + OIIO::swap_endian((uint64_t*)ptr, count); + if (type == TIFF_RATIONAL || type == TIFF_SRATIONAL) + OIIO::swap_endian((uint32_t*)ptr, count * 2); + } + } // Don't double-add for (TIFFDirEntry& d : dirs) { - if (d.tdir_tag == tag) { + if (d.tdir_tag == dir.tdir_tag) { d = dir; return; } @@ -1176,8 +1195,10 @@ decode_exif(const void* exif, int length, ImageSpec& spec) template inline void -append(std::vector& blob, const T& v) +append(std::vector& blob, T v, endian endianreq = endian::native) { + if (endianreq != endian::native) + swap_endian(&v); blob.insert(blob.end(), (const char*)&v, (const char*)&v + sizeof(T)); } @@ -1191,10 +1212,20 @@ appendvec(std::vector& blob, const std::vector& v) +// DEPRECATED(2.1) +void +encode_exif(const ImageSpec& spec, std::vector& blob) +{ + encode_exif(spec, blob, endian::native); +} + + + // Construct an Exif data block from the ImageSpec, appending the Exif // data as a big blob to the char vector. void -encode_exif(const ImageSpec& spec, std::vector& blob) +encode_exif(const ImageSpec& spec, std::vector& blob, + OIIO::endian endianreq) { const TagMap& exif_tagmap(exif_tagmap_ref()); const TagMap& gps_tagmap(gps_tagmap_ref()); @@ -1243,9 +1274,9 @@ encode_exif(const ImageSpec& spec, std::vector& blob) // Put a TIFF header size_t tiffstart = blob.size(); // store initial size TIFFHeader head; - head.tiff_magic = littleendian() ? 0x4949 : 0x4d4d; + head.tiff_magic = (endianreq == endian::little) ? 0x4949 : 0x4d4d; head.tiff_version = 42; - // head.tiff_diroff -- fix below, once we know the sizes + // N.B. need to swap_endian head.tiff_diroff below, once we know the sizes append(blob, head); // Accumulate separate tag directories for TIFF, Exif, GPS, and Interop. @@ -1259,7 +1290,8 @@ encode_exif(const ImageSpec& spec, std::vector& blob) if (Strutil::starts_with(p.name(), "GPS:")) { int tag = gps_tagmap.tag(p.name()); if (tag >= 0) - encode_exif_entry(p, tag, gpsdirs, blob, gps_tagmap, tiffstart); + encode_exif_entry(p, tag, gpsdirs, blob, gps_tagmap, tiffstart, + endianreq); } else { // Not GPS int tag = exif_tagmap.tag(p.name()); @@ -1267,10 +1299,10 @@ encode_exif(const ImageSpec& spec, std::vector& blob) // This range of Exif tags go in the main TIFF directories, // not the Exif IFD. Whatever. encode_exif_entry(p, tag, tiffdirs, blob, exif_tagmap, - tiffstart); + tiffstart, endianreq); } else { encode_exif_entry(p, tag, exifdirs, blob, exif_tagmap, - tiffstart); + tiffstart, endianreq); } } } @@ -1293,12 +1325,14 @@ encode_exif(const ImageSpec& spec, std::vector& blob) if (exifdirs.size() || makerdirs.size()) { // Add some required Exif tags that wouldn't be in the spec append_tiff_dir_entry(exifdirs, blob, EXIF_EXIFVERSION, TIFF_UNDEFINED, - 4, "0230", tiffstart); + 4, "0230", tiffstart, 0, endianreq); append_tiff_dir_entry(exifdirs, blob, EXIF_FLASHPIXVERSION, - TIFF_UNDEFINED, 4, "0100", tiffstart); + TIFF_UNDEFINED, 4, "0100", tiffstart, 0, + endianreq); static char componentsconfig[] = { 1, 2, 3, 0 }; append_tiff_dir_entry(exifdirs, blob, EXIF_COMPONENTSCONFIGURATION, - TIFF_UNDEFINED, 4, componentsconfig, tiffstart); + TIFF_UNDEFINED, 4, componentsconfig, tiffstart, 0, + endianreq); } // If any GPS info was found, add a version tag to the GPS fields. @@ -1306,7 +1340,7 @@ encode_exif(const ImageSpec& spec, std::vector& blob) // Add some required Exif tags that wouldn't be in the spec static char ver[] = { 2, 2, 0, 0 }; append_tiff_dir_entry(gpsdirs, blob, GPSTAG_VERSIONID, TIFF_BYTE, 4, - &ver, tiffstart); + &ver, tiffstart, 0, endianreq); } // Compute offsets: @@ -1348,56 +1382,70 @@ encode_exif(const ImageSpec& spec, std::vector& blob) ASSERT(exifdirs.size()); // unsigned int size = (unsigned int) makerdirs_offset; append_tiff_dir_entry(exifdirs, blob, EXIF_MAKERNOTE, TIFF_BYTE, - makerdirs_size, nullptr, 0, makerdirs_offset); + makerdirs_size, nullptr, 0, makerdirs_offset, + endianreq); } // If any Exif info was found, add a Exif IFD tag to the TIFF dirs if (exifdirs.size()) { unsigned int size = (unsigned int)exifdirs_offset; append_tiff_dir_entry(tiffdirs, blob, TIFFTAG_EXIFIFD, TIFF_LONG, 1, - &size, tiffstart); + &size, tiffstart, 0, endianreq); } // If any GPS info was found, add a GPS IFD tag to the TIFF dirs if (gpsdirs.size()) { unsigned int size = (unsigned int)gpsdirs_offset; append_tiff_dir_entry(tiffdirs, blob, TIFFTAG_GPSIFD, TIFF_LONG, 1, - &size, tiffstart); + &size, tiffstart, 0, endianreq); } // All the tag dirs need to be sorted + // Create a lambda that tests for order, accounting for endianness + auto tagcompare = [=](const TIFFDirEntry& a, const TIFFDirEntry& b) { + auto atag = a.tdir_tag; + auto btag = b.tdir_tag; + if (endianreq != endian::native) { + swap_endian(&atag); + swap_endian(&btag); + } + return (atag < btag); + }; + std::sort(exifdirs.begin(), exifdirs.end(), tagcompare); std::sort(gpsdirs.begin(), gpsdirs.end(), tagcompare); std::sort(makerdirs.begin(), makerdirs.end(), tagcompare); // Now mash everything together size_t tiffdirstart = blob.size(); - append(blob, uint16_t(tiffdirs.size())); // ndirs for tiff - appendvec(blob, tiffdirs); // tiff dirs - append(blob, uint32_t(0)); // addr of next IFD (none) + append(blob, uint16_t(tiffdirs.size()), endianreq); // ndirs for tiff + appendvec(blob, tiffdirs); // tiff dirs + append(blob, uint32_t(0)); // addr of next IFD (none) if (exifdirs.size()) { ASSERT(blob.size() == exifdirs_offset + tiffstart); - append(blob, uint16_t(exifdirs.size())); // ndirs for exif - appendvec(blob, exifdirs); // exif dirs - append(blob, uint32_t(0)); // addr of next IFD (none) + append(blob, uint16_t(exifdirs.size()), endianreq); // ndirs for exif + appendvec(blob, exifdirs); // exif dirs + append(blob, uint32_t(0)); // addr of next IFD (none) } if (gpsdirs.size()) { ASSERT(blob.size() == gpsdirs_offset + tiffstart); - append(blob, uint16_t(gpsdirs.size())); // ndirs for gps - appendvec(blob, gpsdirs); // gps dirs - append(blob, uint32_t(0)); // addr of next IFD (none) + append(blob, uint16_t(gpsdirs.size()), endianreq); // ndirs for gps + appendvec(blob, gpsdirs); // gps dirs + append(blob, uint32_t(0)); // addr of next IFD (none) } if (makerdirs.size()) { ASSERT(blob.size() == makerdirs_offset + tiffstart); - append(blob, uint16_t(makerdirs.size())); // ndirs for canon - appendvec(blob, makerdirs); // canon dirs - append(blob, uint32_t(0)); // addr of next IFD (none) + append(blob, uint16_t(makerdirs.size()), endianreq); // ndirs for canon + appendvec(blob, makerdirs); // canon dirs + append(blob, uint32_t(0)); // addr of next IFD (none) } // Now go back and patch the header with the offset of the first TIFF // directory. - ((TIFFHeader*)(blob.data() + tiffstart))->tiff_diroff = tiffdirstart - - tiffstart; + uint32_t* diroff = &(((TIFFHeader*)(blob.data() + tiffstart))->tiff_diroff); + *diroff = tiffdirstart - tiffstart; + if (endianreq != endian::native) + swap_endian(diroff); #if DEBUG_EXIF_WRITE std::cerr << "resulting exif block is a total of " << blob.size() << "\n"; diff --git a/src/libOpenImageIO/exif.h b/src/libOpenImageIO/exif.h index 3fbc78c353..a6ae5e318b 100644 --- a/src/libOpenImageIO/exif.h +++ b/src/libOpenImageIO/exif.h @@ -134,7 +134,8 @@ void append_tiff_dir_entry (std::vector &dirs, std::vector &data, int tag, TIFFDataType type, size_t count, const void *mydata, size_t offset_correction, - size_t offset_override=0); + size_t offset_override = 0, + OIIO::endian endianreq = OIIO::endian::native); void decode_ifd (const unsigned char *ifd, cspan buf, ImageSpec &spec, const TagMap& tag_map, diff --git a/src/libOpenImageIO/imageioplugin.cpp b/src/libOpenImageIO/imageioplugin.cpp index 594971f377..9281fa630c 100644 --- a/src/libOpenImageIO/imageioplugin.cpp +++ b/src/libOpenImageIO/imageioplugin.cpp @@ -245,6 +245,7 @@ PLUGENTRY(ffmpeg); PLUGENTRY(field3d); PLUGENTRY(fits); PLUGENTRY(gif); +PLUGENTRY(heif); PLUGENTRY(hdr); PLUGENTRY(ico); PLUGENTRY(iff); @@ -311,6 +312,9 @@ catalog_builtin_plugins() DECLAREPLUG (fits); #ifdef USE_GIF DECLAREPLUG (gif); +#endif +#ifdef USE_HEIF + DECLAREPLUG (heif); #endif DECLAREPLUG (hdr); DECLAREPLUG (ico); diff --git a/src/libOpenImageIO/xmp.cpp b/src/libOpenImageIO/xmp.cpp index 78c614f4b7..16527dc288 100644 --- a/src/libOpenImageIO/xmp.cpp +++ b/src/libOpenImageIO/xmp.cpp @@ -400,8 +400,8 @@ add_attrib(ImageSpec& spec, const char* xmlname, const char* xmlvalue) // If not found, return false. If found, return true, store the // beginning and ending indices in startpos and endpos. static bool -extract_middle(const std::string& str, size_t pos, const char* startmarker, - const char* endmarker, size_t& startpos, size_t& endpos) +extract_middle(string_view str, size_t pos, string_view startmarker, + string_view endmarker, size_t& startpos, size_t& endpos) { startpos = str.find(startmarker, pos); if (startpos == std::string::npos) @@ -409,7 +409,7 @@ extract_middle(const std::string& str, size_t pos, const char* startmarker, endpos = str.find(endmarker, startpos); if (endpos == std::string::npos) return false; // end marker not found - endpos += strlen(endmarker); + endpos += endmarker.size(); return true; } @@ -475,8 +475,34 @@ decode_xmp_node(pugi::xml_node node, ImageSpec& spec, int level = 1, +// DEPRECATED(2.1) bool decode_xmp(const std::string& xml, ImageSpec& spec) +{ + return decode_xmp(string_view(xml), spec); +} + + + +// DEPRECATED(2.1) +bool +decode_xmp(const char* xml, ImageSpec& spec) +{ + return decode_xmp(string_view(xml), spec); +} + + + +bool +decode_xmp(cspan xml, ImageSpec& spec) +{ + return decode_xmp(string_view((const char*)xml.data(), xml.size()), spec); +} + + + +bool +decode_xmp(string_view xml, ImageSpec& spec) { #if DEBUG_XMP_READ std::cerr << "XMP dump:\n---\n" << xml << "\n---\n"; @@ -487,13 +513,13 @@ decode_xmp(const std::string& xml, ImageSpec& spec) extract_middle(xml, endpos, "", startpos, endpos);) { // Turn that middle section into an XML document - std::string rdf(xml, startpos, endpos - startpos); // scooch in + string_view rdf = xml.substr(startpos, endpos - startpos); // scooch in #if DEBUG_XMP_READ std::cerr << "RDF is:\n---\n" << rdf << "\n---\n"; #endif pugi::xml_document doc; pugi::xml_parse_result parse_result - = doc.load_buffer(&rdf[0], rdf.size(), + = doc.load_buffer(rdf.data(), rdf.size(), pugi::parse_default | pugi::parse_fragment); if (!parse_result) { #if DEBUG_XMP_READ diff --git a/testsuite/heif/ref/IMG_7702_small.heic b/testsuite/heif/ref/IMG_7702_small.heic new file mode 100644 index 0000000000..6ab2fe9422 Binary files /dev/null and b/testsuite/heif/ref/IMG_7702_small.heic differ diff --git a/testsuite/heif/ref/out.txt b/testsuite/heif/ref/out.txt new file mode 100644 index 0000000000..c9639d4c0d --- /dev/null +++ b/testsuite/heif/ref/out.txt @@ -0,0 +1,40 @@ +Reading ref/IMG_7702_small.heic +ref/IMG_7702_small.heic : 512 x 300, 3 channel, uint8 heif + SHA-1: C681C957FE12AEFBAED8F40DCC74E276C38D8165 + channel list: R, G, B + DateTime: "2019:01:21 16:10:54" + ExposureTime: 0.030303 + FNumber: 1.8 + Make: "Apple" + Model: "iPhone 7" + ResolutionUnit: 2 (inches) + Software: "12.1.2" + XResolution: 72 + YResolution: 72 + Exif:ApertureValue: 1.69599 (f/1.8) + Exif:BrightnessValue: 3.99501 + Exif:ColorSpace: 65535 + Exif:DateTimeDigitized: "2019:01:21 16:10:54" + Exif:DateTimeOriginal: "2019:01:21 16:10:54" + Exif:ExifVersion: "0221" + Exif:ExposureBiasValue: 0 + Exif:ExposureMode: 0 (auto) + Exif:ExposureProgram: 2 (normal program) + Exif:Flash: 24 (no flash, auto flash) + Exif:FlashPixVersion: "0100" + Exif:FocalLength: 3.99 (3.99 mm) + Exif:FocalLengthIn35mmFilm: 28 + Exif:LensMake: "Apple" + Exif:LensModel: "iPhone 7 back camera 3.99mm f/1.8" + Exif:LensSpecification: 3.99, 3.99, 1.8, 1.8 + Exif:MeteringMode: 5 (pattern) + Exif:PhotographicSensitivity: 20 + Exif:PixelXDimension: 4032 + Exif:PixelYDimension: 3024 + Exif:SceneCaptureType: 0 (standard) + Exif:SensingMethod: 2 (1-chip color area) + Exif:ShutterSpeedValue: 5.03599 (1/32 s) + Exif:SubsecTimeDigitized: "006" + Exif:SubsecTimeOriginal: "006" + Exif:WhiteBalance: 0 (auto) + oiio:ColorSpace: "sRGB" diff --git a/testsuite/heif/run.py b/testsuite/heif/run.py new file mode 100755 index 0000000000..28c81753f3 --- /dev/null +++ b/testsuite/heif/run.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +imagedir = "ref/" +files = [ "IMG_7702_small.heic" ] +for f in files: + command = command + info_command (os.path.join(imagedir, f))