diff --git a/cmake/macros/TargetOpenEXR.cmake b/cmake/macros/TargetOpenEXR.cmake new file mode 100644 index 0000000000..8dbe6023b0 --- /dev/null +++ b/cmake/macros/TargetOpenEXR.cmake @@ -0,0 +1,20 @@ +# +# Copyright 2015 High Fidelity, Inc. +# Created by Olivier Prat on 2019/03/26 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_OPENEXR) + +if (NOT ANDROID) + # using VCPKG for OPENEXR + find_package(OpenEXR REQUIRED) + + include_directories(SYSTEM "${OpenEXR_INCLUDE_DIRS}") + target_link_libraries(${TARGET_NAME} ${OPENEXR_LIBRARIES}) + target_compile_definitions(${TARGET_NAME} PUBLIC OPENEXR_DLL) +endif() + +endmacro() + diff --git a/cmake/modules/FindOpenEXR.cmake b/cmake/modules/FindOpenEXR.cmake new file mode 100644 index 0000000000..a381c6db9a --- /dev/null +++ b/cmake/modules/FindOpenEXR.cmake @@ -0,0 +1,87 @@ +include(FindPackageHandleStandardArgs) + +find_path(OpenEXR_INCLUDE_DIRS OpenEXR/OpenEXRConfig.h) +find_path(OPENEXR_INCLUDE_PATHS NAMES ImfRgbaFile.h PATH_SUFFIXES OpenEXR) + +file(STRINGS "${OpenEXR_INCLUDE_DIRS}/OpenEXR/OpenEXRConfig.h" OPENEXR_CONFIG_H) + +string(REGEX REPLACE "^.*define OPENEXR_VERSION_MAJOR ([0-9]+).*$" "\\1" OpenEXR_VERSION_MAJOR "${OPENEXR_CONFIG_H}") +string(REGEX REPLACE "^.*define OPENEXR_VERSION_MINOR ([0-9]+).*$" "\\1" OpenEXR_VERSION_MINOR "${OPENEXR_CONFIG_H}") +set(OpenEXR_LIB_SUFFIX "${OpenEXR_VERSION_MAJOR}_${OpenEXR_VERSION_MINOR}") + +include(SelectLibraryConfigurations) + +if(NOT OpenEXR_BASE_LIBRARY) + find_library(OpenEXR_BASE_LIBRARY_RELEASE NAMES IlmImf-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_BASE_LIBRARY_DEBUG NAMES IlmImf-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_BASE) +endif() + +if(NOT OpenEXR_UTIL_LIBRARY) + find_library(OpenEXR_UTIL_LIBRARY_RELEASE NAMES IlmImfUtil-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_UTIL_LIBRARY_DEBUG NAMES IlmImfUtil-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_UTIL) +endif() + +if(NOT OpenEXR_HALF_LIBRARY) + find_library(OpenEXR_HALF_LIBRARY_RELEASE NAMES Half-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_HALF_LIBRARY_DEBUG NAMES Half-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_HALF) +endif() + +if(NOT OpenEXR_IEX_LIBRARY) + find_library(OpenEXR_IEX_LIBRARY_RELEASE NAMES Iex-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_IEX_LIBRARY_DEBUG NAMES Iex-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_IEX) +endif() + +if(NOT OpenEXR_MATH_LIBRARY) + find_library(OpenEXR_MATH_LIBRARY_RELEASE NAMES Imath-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_MATH_LIBRARY_DEBUG NAMES Imath-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_MATH) +endif() + +if(NOT OpenEXR_THREAD_LIBRARY) + find_library(OpenEXR_THREAD_LIBRARY_RELEASE NAMES IlmThread-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_THREAD_LIBRARY_DEBUG NAMES IlmThread-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_THREAD) +endif() + +if(NOT OpenEXR_IEXMATH_LIBRARY) + find_library(OpenEXR_IEXMATH_LIBRARY_RELEASE NAMES IexMath-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_IEXMATH_LIBRARY_DEBUG NAMES IexMath-${OpenEXR_LIB_SUFFIX}d) + select_library_configurations(OpenEXR_IEXMATH) +endif() + +set(OPENEXR_HALF_LIBRARY "${OpenEXR_HALF_LIBRARY}") +set(OPENEXR_IEX_LIBRARY "${OpenEXR_IEX_LIBRARY}") +set(OPENEXR_IMATH_LIBRARY "${OpenEXR_MATH_LIBRARY}") +set(OPENEXR_ILMIMF_LIBRARY "${OpenEXR_BASE_LIBRARY}") +set(OPENEXR_ILMIMFUTIL_LIBRARY "${OpenEXR_UTIL_LIBRARY}") +set(OPENEXR_ILMTHREAD_LIBRARY "${OpenEXR_THREAD_LIBRARY}") + +set(OpenEXR_LIBRARY "${OpenEXR_BASE_LIBRARY}") + +set(OpenEXR_LIBRARIES + ${OpenEXR_LIBRARY} + ${OpenEXR_MATH_LIBRARY} + ${OpenEXR_IEXMATH_LIBRARY} + ${OpenEXR_UTIL_LIBRARY} + ${OpenEXR_HALF_LIBRARY} + ${OpenEXR_IEX_LIBRARY} + ${OpenEXR_THREAD_LIBRARY} +) + +set(OPENEXR_LIBRARIES + ${OPENEXR_HALF_LIBRARY} + ${OPENEXR_IEX_LIBRARY} + ${OPENEXR_IMATH_LIBRARY} + ${OPENEXR_ILMIMF_LIBRARY} + ${OPENEXR_ILMTHREAD_LIBRARY} +) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(OpenEXR REQUIRED_VARS OpenEXR_LIBRARIES OpenEXR_INCLUDE_DIRS) + +if(OpenEXR_FOUND) + set(OPENEXR_FOUND 1) +endif() diff --git a/cmake/ports/hifi-deps/CONTROL b/cmake/ports/hifi-deps/CONTROL index e202d0c8f8..7fe104a235 100644 --- a/cmake/ports/hifi-deps/CONTROL +++ b/cmake/ports/hifi-deps/CONTROL @@ -1,4 +1,4 @@ Source: hifi-deps -Version: 0 +Version: 0.1 Description: Collected dependencies for High Fidelity applications -Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openssl (windows), tbb (!android&!osx), zlib +Build-Depends: bullet3, openexr (!android), draco, etc2comp, glm, nvtt, openssl (windows), tbb (!android&!osx), zlib diff --git a/libraries/image/CMakeLists.txt b/libraries/image/CMakeLists.txt index 4db39f2152..0c733ae789 100644 --- a/libraries/image/CMakeLists.txt +++ b/libraries/image/CMakeLists.txt @@ -3,6 +3,7 @@ setup_hifi_library() link_hifi_libraries(shared gpu) target_nvtt() target_etc2comp() +target_openexr() if (UNIX AND NOT APPLE) set(THREADS_PREFER_PTHREAD_FLAG ON) diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 2488b15fcd..58e8ee7bef 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -26,6 +26,7 @@ #include #include "TGAReader.h" +#include "OpenEXRReader.h" #include "ImageLogging.h" @@ -48,6 +49,7 @@ std::atomic RECTIFIED_TEXTURE_COUNT{ 0 }; // we use a ref here to work around static order initialization // possibly causing the element not to be constructed yet static const auto& HDR_FORMAT = gpu::Element::COLOR_R11G11B10; +const QImage::Format image::QIMAGE_HDRFORMAT = QImage::Format_RGB30; uint rectifyDimension(const uint& dimension) { if (dimension == 0) { @@ -86,7 +88,7 @@ QImage::Format hdrFormatForTarget(BackendTarget target) { if (target == BackendTarget::GLES32) { return QImage::Format_RGB32; } - return QImage::Format_RGB30; + return QIMAGE_HDRFORMAT; } TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) { @@ -186,7 +188,7 @@ static float denormalize(float value, const float minValue) { return value < minValue ? 0.0f : value; } -uint32 packR11G11B10F(const glm::vec3& color) { +static uint32 packR11G11B10F(const glm::vec3& color) { // Denormalize else unpacking gives high and incorrect values // See https://www.khronos.org/opengl/wiki/Small_Float_Formats for this min value static const auto minValue = 6.10e-5f; @@ -201,6 +203,21 @@ uint32 packR11G11B10F(const glm::vec3& color) { return glm::packF2x11_1x10(ucolor); } +static std::function getHDRPackingFunction(const gpu::Element& format) { + if (format == gpu::Element::COLOR_RGB9E5) { + return glm::packF3x9_E1x5; + } else if (format == gpu::Element::COLOR_R11G11B10) { + return packR11G11B10F; + } else { + qCWarning(imagelogging) << "Unknown handler format"; + Q_UNREACHABLE(); + } +} + +std::function getHDRPackingFunction() { + return getHDRPackingFunction(HDR_FORMAT); +} + QImage processRawImageData(QIODevice& content, const std::string& filename) { // Help the QImage loader by extracting the image file format from the url filename ext. // Some tga are not created properly without it. @@ -217,6 +234,11 @@ QImage processRawImageData(QIODevice& content, const std::string& filename) { return image; } content.reset(); + } else if (filenameExtension == "exr") { + QImage image = image::readOpenEXR(content, filename); + if (!image.isNull()) { + return image; + } } QImageReader imageReader(&content, filenameExtension.c_str()); @@ -397,14 +419,7 @@ struct OutputHandler : public nvtt::OutputHandler { struct PackedFloatOutputHandler : public OutputHandler { PackedFloatOutputHandler(gpu::Texture* texture, int face, gpu::Element format) : OutputHandler(texture, face) { - if (format == gpu::Element::COLOR_RGB9E5) { - _packFunc = glm::packF3x9_E1x5; - } else if (format == gpu::Element::COLOR_R11G11B10) { - _packFunc = packR11G11B10F; - } else { - qCWarning(imagelogging) << "Unknown handler format"; - Q_UNREACHABLE(); - } + _packFunc = getHDRPackingFunction(format); } virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index 40c31eeeff..66d8cfa15a 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -13,16 +13,20 @@ #define hifi_image_Image_h #include +#include #include #include "ColorChannel.h" class QByteArray; -class QImage; namespace image { +extern const QImage::Format QIMAGE_HDRFORMAT; + +std::function getHDRPackingFunction(); + namespace TextureUsage { enum Type { diff --git a/libraries/image/src/image/OpenEXRReader.cpp b/libraries/image/src/image/OpenEXRReader.cpp new file mode 100644 index 0000000000..565b1d585d --- /dev/null +++ b/libraries/image/src/image/OpenEXRReader.cpp @@ -0,0 +1,93 @@ +// +// OpenEXRReader.cpp +// image/src/image +// +// Created by Olivier Prat +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "OpenEXRReader.h" + +#include "Image.h" +#include "ImageLogging.h" + +#include +#include + +#include +#include +#include +#include + +class QIODeviceImfStream : public Imf::IStream { +public: + + QIODeviceImfStream(QIODevice& device, const std::string& filename) : + Imf::IStream(filename.c_str()), _device(device) { + } + + bool read(char c[/*n*/], int n) override { + if (_device.read(c, n) <= 0) { + qWarning(imagelogging) << "OpenEXR - in file " << fileName() << " : " << _device.errorString(); + return false; + } + return true; + } + + Imf::Int64 tellg() override { + return _device.pos(); + } + + void seekg(Imf::Int64 pos) override { + _device.seek(pos); + } + + void clear() override { + // Not much to do + } + +private: + + QIODevice& _device; +}; + +QImage image::readOpenEXR(QIODevice& content, const std::string& filename) { + QIODeviceImfStream device(content, filename); + + if (Imf::isOpenExrFile(device)) { + Imf::RgbaInputFile file(device); + Imath::Box2i viewport = file.dataWindow(); + Imf::Array2D pixels; + int width = viewport.max.x - viewport.min.x + 1; + int height = viewport.max.y - viewport.min.y + 1; + + pixels.resizeErase(height, width); + + file.setFrameBuffer(&pixels[0][0] - viewport.min.x - viewport.min.y * width, 1, width); + file.readPixels(viewport.min.y, viewport.max.y); + + QImage image{ width, height, QIMAGE_HDRFORMAT }; + auto packHDRPixel = getHDRPackingFunction(); + + for (int y = 0; y < height; y++) { + const auto srcScanline = pixels[height]; + gpu::uint32* dstScanline = (gpu::uint32*) image.scanLine(y); + + for (int x = 0; x < width; x++) { + const auto& srcPixel = srcScanline[x]; + auto& dstPixel = dstScanline[x]; + glm::vec3 floatPixel{ srcPixel.r, srcPixel.g, srcPixel.b }; + + dstPixel = packHDRPixel(floatPixel); + } + } + return image; + } else { + qWarning(imagelogging) << "OpenEXR - File " << filename.c_str() << " doesn't have the proper format"; + } + + return QImage(); +} diff --git a/libraries/image/src/image/OpenEXRReader.h b/libraries/image/src/image/OpenEXRReader.h new file mode 100644 index 0000000000..d7cf718977 --- /dev/null +++ b/libraries/image/src/image/OpenEXRReader.h @@ -0,0 +1,24 @@ +// +// OpenEXRReader.h +// image/src/image +// +// Created by Olivier Prat +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_image_OpenEXRReader_h +#define hifi_image_OpenEXRReader_h + +#include + +namespace image { + + // TODO Move this into a plugin that QImageReader can use + QImage readOpenEXR(QIODevice& contents, const std::string& filename); + +} + +#endif // hifi_image_OpenEXRReader_h