From 21d546135123693e6523317b02ae38f9a0d3010f Mon Sep 17 00:00:00 2001 From: Anton Dukhovnikov Date: Mon, 5 Aug 2024 20:11:43 +1200 Subject: [PATCH] Implement OIIO-based version of the tool Signed-off-by: Anton Dukhovnikov --- .github/workflows/ci.yml | 31 +- CMakeLists.txt | 4 +- build_scripts/install_deps.bash | 1 + build_scripts/install_deps_linux.bash | 8 +- build_scripts/install_deps_mac.bash | 2 +- build_scripts/install_deps_windows.bash | 1 + configure.cmake | 1 + include/rawtoaces/define.h | 18 +- include/rawtoaces/metadata.h | 29 + include/rawtoaces/rawtoaces_util.h | 167 +++++ include/rawtoaces/rta.h | 15 +- src/rawtoaces2/CMakeLists.txt | 21 + src/rawtoaces2/main.cpp | 101 +++ src/rawtoaces_idt/rta.cpp | 98 +-- src/rawtoaces_util/acesrender.cpp | 45 +- src/rawtoaces_util2/CMakeLists.txt | 37 + src/rawtoaces_util2/rawtoaces_util.cpp | 955 ++++++++++++++++++++++++ unittest/testDNGIdt.cpp | 119 +-- 18 files changed, 1512 insertions(+), 141 deletions(-) create mode 100644 include/rawtoaces/metadata.h create mode 100644 include/rawtoaces/rawtoaces_util.h create mode 100644 src/rawtoaces2/CMakeLists.txt create mode 100644 src/rawtoaces2/main.cpp create mode 100644 src/rawtoaces_util2/CMakeLists.txt create mode 100644 src/rawtoaces_util2/rawtoaces_util.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 801e584..fda874e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,7 @@ jobs: CC: ${{matrix.cc_compiler}} CMAKE_CXX_STANDARD: ${{matrix.cxx_std}} OPENEXR_VERSION: ${{matrix.openexr_ver}} + CMAKE_CXX_FLAGS: "-lstdc++fs" steps: - uses: actions/checkout@v4 @@ -83,40 +84,46 @@ jobs: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + title: [ubuntu, windows, macos] build_type: [Release] c_compiler: [gcc, clang, cl] include: - - os: windows-latest + - title: windows + os: windows-latest c_compiler: cl cpp_compiler: cl install_deps: install_deps_windows toolchain_file: "C:/vcpkg/scripts/buildsystems/vcpkg.cmake" build_shared_libs: OFF - - os: ubuntu-latest + - title: ubuntu + os: ubuntu-24.04 c_compiler: gcc cpp_compiler: g++ install_deps: install_deps_linux - - os: ubuntu-latest - c_compiler: clang + - title: ubuntu + os: ubuntu-24.04 cpp_compiler: clang++ install_deps: install_deps_linux - - os: macos-latest + - title: macos + os: macos-latest c_compiler: clang cpp_compiler: clang++ install_deps: install_deps_mac exclude: - - os: windows-latest + - title: windows c_compiler: gcc - - os: windows-latest + - title: windows c_compiler: clang - - os: ubuntu-latest + - title: ubuntu c_compiler: cl - - os: macos-latest + - title: macos c_compiler: cl - - os: macos-latest + - title: macos c_compiler: gcc + env: + CMAKE_CXX_FLAGS: "-lstdc++fs" + steps: - uses: actions/checkout@v4 @@ -140,7 +147,7 @@ jobs: cmake -B ${{ steps.strings.outputs.build-output-dir }} -S ${{ github.workspace }} - -DCXX_STANDARD=C++14 + -DCXX_STANDARD=C++17 -DCMAKE_TOOLCHAIN_FILE="${{ matrix.toolchain_file }}" -DENABLE_SHARED="${{ matrix.build_shared_libs }}" diff --git a/CMakeLists.txt b/CMakeLists.txt index c1405f7..0d6f761 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,9 +12,9 @@ set(RAWTOACESLIB "rawtoaces_util") set( CMAKE_MACOSX_RPATH 1 ) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") -# set(warnings "/W4 /WX /EHsc") add_compile_options ( /W0 ) add_compile_definitions( NOMINMAX ) + add_compile_options ( $<$:/utf-8> ) endif() if (NOT CONFIGURED_ONCE) @@ -76,6 +76,8 @@ add_subdirectory("src/${RAWTOACESIDTLIB}") add_subdirectory("src/${RAWTOACESLIB}") add_subdirectory("src/rawtoaces") +add_subdirectory("src/rawtoaces_util2") +add_subdirectory("src/rawtoaces2") # Create a RAWTOACESBuildTreeSettings.cmake file for the use from the build tree file(RELATIVE_PATH CONF_REL_INCLUDE_DIR "${INSTALL_CMAKE_DIR}" "${INSTALL_INCLUDE_DIR}") diff --git a/build_scripts/install_deps.bash b/build_scripts/install_deps.bash index b56f40a..d951995 100755 --- a/build_scripts/install_deps.bash +++ b/build_scripts/install_deps.bash @@ -8,6 +8,7 @@ time sudo apt-get update time sudo apt-get -q -f install -y \ libunwind-dev libilmbase-dev libopenexr-dev \ + libopenimageio-dev \ libboost-dev libboost-thread-dev libboost-filesystem-dev \ libboost-test-dev \ libraw-dev libceres-dev diff --git a/build_scripts/install_deps_linux.bash b/build_scripts/install_deps_linux.bash index e29f9c4..a72b1d8 100755 --- a/build_scripts/install_deps_linux.bash +++ b/build_scripts/install_deps_linux.bash @@ -5,7 +5,11 @@ set -ex time sudo apt-get update time sudo apt-get -q -f install -y \ - libunwind-dev libimath-dev \ + libunwind-dev \ + libimath-dev libopenexr-dev \ libboost-dev libboost-filesystem-dev \ libboost-test-dev \ - libraw-dev libceres-dev + libraw-dev libceres-dev \ + libopencv-dev \ + openimageio-tools \ + libopenimageio-dev diff --git a/build_scripts/install_deps_mac.bash b/build_scripts/install_deps_mac.bash index 223fd73..15d4f44 100755 --- a/build_scripts/install_deps_mac.bash +++ b/build_scripts/install_deps_mac.bash @@ -2,4 +2,4 @@ set -ex -brew install ceres-solver imath openexr libraw boost +brew install ceres-solver imath openexr libraw boost openimageio diff --git a/build_scripts/install_deps_windows.bash b/build_scripts/install_deps_windows.bash index b0dd8a1..9743b62 100755 --- a/build_scripts/install_deps_windows.bash +++ b/build_scripts/install_deps_windows.bash @@ -6,6 +6,7 @@ vcpkg install \ libraw:x64-windows \ ceres:x64-windows \ imath:x64-windows \ + openimageio:x64-windows \ boost-system:x64-windows \ boost-foreach:x64-windows \ boost-filesystem:x64-windows \ diff --git a/configure.cmake b/configure.cmake index 6e831fe..51b9cec 100644 --- a/configure.cmake +++ b/configure.cmake @@ -3,6 +3,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules/") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_INSTALL_PREFIX}/share/CMake") +find_package ( OpenImageIO CONFIG REQUIRED ) find_package ( AcesContainer CONFIG REQUIRED ) find_package ( Eigen3 CONFIG REQUIRED ) find_package ( Imath CONFIG REQUIRED ) diff --git a/include/rawtoaces/define.h b/include/rawtoaces/define.h index 3b3a28a..77794c1 100644 --- a/include/rawtoaces/define.h +++ b/include/rawtoaces/define.h @@ -266,6 +266,20 @@ static const double XYZ_acesrgb_4[4][4] = { { 0.0, 0.0, 0.0, 1.0 } }; +static const float XYZ_acesrgb_float_4[4][4] = { + { 1.0498110175, 0.0000000000, -0.0000974845, 0.0 }, + { -0.4959030231, 1.3733130458, 0.0982400361, 0.0 }, + { 0.0000000000, 0.0000000000, 0.9912520182, 0.0 }, + { 0.0, 0.0, 0.0, 1.0 } +}; + +static const float XYZ_acesrgb_transposed_4[4][4] = { + { 1.0498110175, -0.4959030231, 0.0000000000, 0.0 }, + { 0.0000000000, 1.3733130458, 0.0000000000, 0.0 }, + { -0.0000974845, 0.0982400361, 0.9912520182, 0.0 }, + { 0.0000000000, 0.0000000000, 0.0000000000, 1.0 } +}; + static const double acesrgb_XYZ_3[3][3] = { { 0.952552395938186, 0.0, 9.36786316604686e-05 }, { 0.343966449765075, 0.728166096613485, -0.0721325463785608 }, @@ -390,8 +404,8 @@ inline bool isCTLetterDigit( const char c ) // to represent color temperature(s) (e.g., D60, 3200K) inline bool isValidCT( string str ) { - int i = 0; - int length = str.length(); + int i = 0; + size_t length = str.length(); if ( length == 0 ) return false; diff --git a/include/rawtoaces/metadata.h b/include/rawtoaces/metadata.h new file mode 100644 index 0000000..3abd574 --- /dev/null +++ b/include/rawtoaces/metadata.h @@ -0,0 +1,29 @@ +// Copyright Contributors to the rawtoaces project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/rawtoaces + +#ifndef RTA_METADATA_H_ +#define RTA_METADATA_H_ + +namespace rta +{ + +struct Metadata +{ + // Colorimetry + std::vector cameraCalibration1; + std::vector cameraCalibration2; + std::vector xyz2rgbMatrix1; + std::vector xyz2rgbMatrix2; + double calibrationIlluminant1; + double calibrationIlluminant2; + + std::vector analogBalance; + std::vector neutralRGB; + + double baselineExposure; +}; + +} // namespace rta + +#endif // RTA_METADATA_H_ diff --git a/include/rawtoaces/rawtoaces_util.h b/include/rawtoaces/rawtoaces_util.h new file mode 100644 index 0000000..618644f --- /dev/null +++ b/include/rawtoaces/rawtoaces_util.h @@ -0,0 +1,167 @@ +// Copyright Contributors to the rawtoaces project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/rawtoaces + +#ifndef _RAWTOACES_UTIL_H_ +#define _RAWTOACES_UTIL_H_ + +#include +#include +#include + +namespace rta +{ + +/// An image converter converts an image read from a camera raw image file +/// into an ACESContainer compatible image. +class ImageConverter +{ +public: + ImageConverter(); + + /// The white balancing method to use for conversion can be specified + /// + enum class WBMethod + { + /// Use the metadata provided in the image file. This mode is mostly + /// usable with DNG files, as the information needed for conversion + /// is mandatory in the DNG format. + Metadata, + /// White balance to a specified illuminant. See the `illuminant` + /// property for more information on the supported illuminants. This + /// mode can only be used if spectral sensitivities are available for + /// the camera. + Illuminant, + /// Calculate white balance by averaging over a specified region of + /// the image. See `wbBox`. In this mode if an empty box if provided, + /// white balancing is done by averaging over the whole image. + Box, + /// Use custom white balancing multipliers. This mode is useful if + /// the white balancing coefficients are calculated by an external + /// tool. + Custom + } wbMethod = WBMethod::Metadata; + + enum class MatrixMethod + { + /// Use the camera spectral sensitivity curves to solve for the colour + /// conversion matrix. In this mode the illuminant is either provided + /// directly in `illuminant` if `wbMethod` == + /// `WBMethod::Illuminant`, or the best illuminant is derived from the + /// white balancing multipliers. + Spectral, + /// Use the metadata provided in the image file. This mode is mostly + /// usable with DNG files, as the information needed for conversion + /// is mandatory in the DNG format. + Metadata, + /// Use the Adobe colour matrix for the camera supplied in LibRaw. + Adobe, + /// Specify a custom matrix in `colourMatrix`. This mode is useful if + /// the matrix is calculated by an external tool. + Custom + } matrixMethod = MatrixMethod::Spectral; + + /// An illuminant to use for white balancing and/or colour matrix + /// calculation. Only used when `wbMethod` == + /// `WBMethod::Illuminant` or `matrixMethod` == `MatrixMethod::Spectral`. + /// An illuminant can be provided as a black body correlated colour + /// temperature, like `3200K`; or a D-series illuminant, like `D56`; + /// or any other illuminant, in such case it must be present in the data + /// folder. + std::string illuminant; + + float headroom = 6.0; + int wbBox[4] = { 0 }; + float customWB[4] = { 1.0, 1.0, 1.0, 1.0 }; + float customMatrix[3][3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; + + bool no_auto_bright = false; + float adjust_maximum_threshold = 0.75; + int black_level = -1; + int saturation_level = -1; + bool half_size = false; + int highlight_mode = 0; + int flip = 0; + int cropbox[4] = { 0, 0, 0, 0 }; + + int verbosity; + + /// Returns a class which will be used for parsing the command line + /// parameters. Can be used to add additional parameters before calling + /// `parse()`. The additional parameters will be parsed by ignored by this + /// class. + /// @result A non-const reference to an + /// initialised OIIO::ArgParse class. + OIIO::ArgParse &argParse(); + + /// Returns an image buffer, containing the image in the current state + /// after the previous step of processing. The image can be modified before + /// executing the next step if needed. + /// @result A non-const reference to the image buffer. + OIIO::ImageBuf &imageBuffer(); + + /// Parse the command line parameters. This method can be used to + /// configure the converter instead of modifying each conversion parameter + /// individually. Additional command line parameters can be added by + /// modifying the structure returned by `argParse()`. + /// @param argc + /// number of parameters + /// @param argv + /// list of parameters + /// @result + /// `true` if parsed successfully + bool parse( int argc, const char *argv[] ); + + /// Configures the converter using the requested white balance and colour + /// matrix method, and the metadata of the file provided in `input_file`. + /// @param input_filename + /// A file name of the raw image file to read the metadata from. + /// @result + /// `true` if configured successfully. + bool configure( const std::string &input_filename ); + + /// Loads an image to convert. Note that the image file name in + /// `input_filename` can be differnt from the one used in `configure()`. + /// This is useful for configuring the converter using one image, and + /// applying the conversion to a different one, or multiple images. + /// @param input_filename + /// A file name of the raw image file to read the pixels from. + /// @result + /// `true` if loaded successfully. + bool load( const std::string &input_filename ); + + /// Converts the image from raw camera colour space to ACES. + /// @result + /// `true` if converted successfully. + bool process(); + + /// Saves the image into ACES Container. + /// @result + /// `true` if saved successfully. + bool save( const std::string &output_filename ); + +private: + void initArgParse(); + void prepareIDT_DNG(); + void prepareIDT_nonDNG(); + void prepareIDT_spectral( bool calc_white_balance, bool calc_matrix ); + void applyMatrix( const std::vector> &matrix ); + + bool _is_DNG; + std::string _configFilename; + std::string _cameraMake; + std::string _cameraModel; + + OIIO::ArgParse _argParse; + OIIO::ImageSpec _inputHint; + OIIO::ImageSpec _inputFull; + OIIO::ImageBuf _imageBuffer; + + std::vector _WB_mults; + std::vector> _IDT_matrix; + std::vector> _CAT_matrix; +}; + +} // namespace rta + +#endif // _RAWTOACES_UTIL_H_ diff --git a/include/rawtoaces/rta.h b/include/rawtoaces/rta.h index bb867dc..25b4237 100644 --- a/include/rawtoaces/rta.h +++ b/include/rawtoaces/rta.h @@ -60,6 +60,9 @@ #include #include +#include "mathOps.h" +#include "metadata.h" + using namespace std; namespace rta @@ -226,7 +229,7 @@ class DNGIdt { public: DNGIdt(); - DNGIdt( libraw_rawdata_t R ); + DNGIdt( const Metadata &metadata ); virtual ~DNGIdt(); double ccttoMired( const double cct ) const; @@ -247,16 +250,10 @@ class DNGIdt void getCameraXYZMtxAndWhitePoint(); private: - vector _cameraCalibration1DNG; - vector _cameraCalibration2DNG; vector _cameraToXYZMtx; - vector _xyz2rgbMatrix1DNG; - vector _xyz2rgbMatrix2DNG; - vector _analogBalanceDNG; - vector _neutralRGBDNG; vector _cameraXYZWhitePoint; - vector _calibrateIllum; - double _baseExpo; + + Metadata _metadata; }; struct Objfun diff --git a/src/rawtoaces2/CMakeLists.txt b/src/rawtoaces2/CMakeLists.txt new file mode 100644 index 0000000..4f6c092 --- /dev/null +++ b/src/rawtoaces2/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.5) +include_directories( "${CMAKE_CURRENT_SOURCE_DIR}" ) + +add_executable( rawtoaces2 + main.cpp +) + +set_property(TARGET rawtoaces2 PROPERTY CXX_STANDARD 17) + +target_include_directories( rawtoaces2 + PUBLIC + ${OpenImageIO_INCLUDE_DIR} +) + +target_link_libraries ( rawtoaces2 + PUBLIC +# OpenImageIO::OpenImageIO + rawtoaces_util2 +) + +install( TARGETS rawtoaces2 DESTINATION bin ) diff --git a/src/rawtoaces2/main.cpp b/src/rawtoaces2/main.cpp new file mode 100644 index 0000000..318a9ef --- /dev/null +++ b/src/rawtoaces2/main.cpp @@ -0,0 +1,101 @@ +// Copyright Contributors to the rawtoaces project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/rawtoaces + +#include + +#include + +using namespace rta; + +int main( int argc, const char *argv[] ) +{ + ImageConverter converter; + + if ( !converter.parse( argc, argv ) ) + { + return 1; + } + + auto &argParse = converter.argParse(); + auto files = argParse["filename"].as_vec(); + std::vector files_to_convert; + + for ( auto filename: files ) + { + if ( !std::filesystem::exists( filename ) ) + { + std::cerr << "File or directory not found: " << filename + << std::endl; + return 1; + } + + auto canonical_filename = std::filesystem::canonical( filename ); + + if ( std::filesystem::is_directory( filename ) ) + { + auto it = std::filesystem::directory_iterator( filename ); + + for ( auto filename2: it ) + { + if ( std::filesystem::is_regular_file( filename ) || + std::filesystem::is_symlink( filename ) ) + { + files_to_convert.push_back( filename2.path().string() ); + } + } + } + else if ( + std::filesystem::is_regular_file( filename ) || + std::filesystem::is_symlink( filename ) ) + { + files_to_convert.push_back( filename ); + } + else + { + std::cerr << "Not a file or directory: " << filename << std::endl; + return 1; + } + } + + for ( auto const &input_filename: files_to_convert ) + { + std::string output_filename = input_filename; + size_t pos = input_filename.rfind( '.' ); + if ( pos != std::string::npos ) + { + output_filename = input_filename.substr( 0, pos ); + } + output_filename += "_oiio.exr"; + + if ( !converter.configure( input_filename ) ) + { + std::cerr << "Failed to configure the reader for the file: " + << input_filename << std::endl; + return 1; + } + + if ( !converter.load( input_filename ) ) + { + std::cerr << "Failed to read for the file: " << input_filename + << std::endl; + return 1; + } + + if ( !converter.process() ) + { + std::cerr << "Failed to convert the file: " << input_filename + << std::endl; + return 1; + } + + if ( !converter.save( output_filename ) ) + { + std::cerr << "Failed to save the file: " << output_filename + << std::endl; + return 1; + } + } + + return 0; +} diff --git a/src/rawtoaces_idt/rta.cpp b/src/rawtoaces_idt/rta.cpp index 73b2973..6582f3f 100644 --- a/src/rawtoaces_idt/rta.cpp +++ b/src/rawtoaces_idt/rta.cpp @@ -592,7 +592,7 @@ int Spst::getWLIncrement() int Spst::loadSpst( const string &path, const char *maker, const char *model ) { - assert( path.length() > 0 && maker != nullptr && model != nullptr ); + assert( path.length() > 0 ); vector rgbsen; vector max( 3, dmin ); @@ -603,12 +603,12 @@ int Spst::loadSpst( const string &path, const char *maker, const char *model ) read_json( path, pt ); string cmaker = pt.get( "header.manufacturer" ); - if ( cmp_str( maker, cmaker.c_str() ) ) + if ( maker != nullptr && cmp_str( maker, cmaker.c_str() ) ) return 0; setBrand( cmaker.c_str() ); string cmodel = pt.get( "header.model" ); - if ( cmp_str( model, cmodel.c_str() ) ) + if ( model != nullptr && cmp_str( model, cmodel.c_str() ) ) return 0; setModel( cmodel.c_str() ); @@ -1543,67 +1543,21 @@ const vector Idt::getWB() const DNGIdt::DNGIdt() { - _cameraCalibration1DNG = vector( 9, 1.0 ); - _cameraCalibration2DNG = vector( 9, 1.0 ); - _cameraToXYZMtx = vector( 9, 1.0 ); - _xyz2rgbMatrix1DNG = vector( 9, 1.0 ); - _xyz2rgbMatrix2DNG = vector( 9, 1.0 ); - _analogBalanceDNG = vector( 3, 1.0 ); - _neutralRGBDNG = vector( 3, 1.0 ); - _cameraXYZWhitePoint = vector( 3, 1.0 ); - _calibrateIllum = vector( 2, 1.0 ); - _baseExpo = 1.0; -} - -DNGIdt::DNGIdt( libraw_rawdata_t R ) -{ - _cameraCalibration1DNG = vector( 9, 1.0 ); - _cameraCalibration2DNG = vector( 9, 1.0 ); - _cameraToXYZMtx = vector( 9, 1.0 ); - _xyz2rgbMatrix1DNG = vector( 9, 1.0 ); - _xyz2rgbMatrix2DNG = vector( 9, 1.0 ); - _analogBalanceDNG = vector( 3, 1.0 ); - _neutralRGBDNG = vector( 3, 1.0 ); - _cameraXYZWhitePoint = vector( 3, 1.0 ); - _calibrateIllum = vector( 2, 1.0 ); - -#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION( 0, 20, 0 ) - _baseExpo = static_cast( R.color.dng_levels.baseline_exposure ); -#else - _baseExpo = static_cast( R.color.baseline_exposure ); -#endif - _calibrateIllum[0] = static_cast( R.color.dng_color[0].illuminant ); - _calibrateIllum[1] = static_cast( R.color.dng_color[1].illuminant ); - - FORI( 3 ) - { - _neutralRGBDNG[i] = 1.0 / static_cast( R.color.cam_mul[i] ); - } + _cameraToXYZMtx = vector( 9, 1.0 ); + _cameraXYZWhitePoint = vector( 3, 1.0 ); +} - FORIJ( 3, 3 ) - { - _xyz2rgbMatrix1DNG[i * 3 + j] = - static_cast( ( R.color.dng_color[0].colormatrix )[i][j] ); - _xyz2rgbMatrix2DNG[i * 3 + j] = - static_cast( ( R.color.dng_color[1].colormatrix )[i][j] ); - _cameraCalibration1DNG[i * 3 + j] = - static_cast( ( R.color.dng_color[0].calibration )[i][j] ); - _cameraCalibration2DNG[i * 3 + j] = - static_cast( ( R.color.dng_color[1].calibration )[i][j] ); - } +DNGIdt::DNGIdt( const Metadata &metadata ) // libraw_rawdata_t R ) +{ + _metadata = metadata; } DNGIdt::~DNGIdt() { - clearVM( _cameraCalibration1DNG ); - clearVM( _cameraCalibration2DNG ); + _metadata = Metadata(); + clearVM( _cameraToXYZMtx ); - clearVM( _xyz2rgbMatrix1DNG ); - clearVM( _xyz2rgbMatrix2DNG ); - clearVM( _analogBalanceDNG ); - clearVM( _neutralRGBDNG ); clearVM( _cameraXYZWhitePoint ); - clearVM( _calibrateIllum ); } double DNGIdt::ccttoMired( const double cct ) const @@ -1689,9 +1643,9 @@ vector DNGIdt::XYZtoCameraWeightedMatrix( double weight = std::max( 0.0, std::min( 1.0, ( mir1 - mir0 ) / ( mir1 - mir2 ) ) ); vector result = - subVectors( _xyz2rgbMatrix2DNG, _xyz2rgbMatrix1DNG ); + subVectors( _metadata.xyz2rgbMatrix2, _metadata.xyz2rgbMatrix1 ); scaleVector( result, weight ); - result = addVectors( result, _xyz2rgbMatrix1DNG ); + result = addVectors( result, _metadata.xyz2rgbMatrix1 ); return result; } @@ -1699,23 +1653,24 @@ vector DNGIdt::XYZtoCameraWeightedMatrix( vector DNGIdt::findXYZtoCameraMtx( const vector &neutralRGB ) const { + assert( _metadata.xyz2rgbMatrix2.size() > 0 ); - if ( _calibrateIllum.size() == 0 ) + if ( _metadata.xyz2rgbMatrix2.size() == 0 ) { - fprintf( stderr, " No calibration illuminants were found. \n " ); - return _xyz2rgbMatrix1DNG; + fprintf( stderr, " Only one calibration matrix found. \n " ); + return _metadata.xyz2rgbMatrix1; } if ( neutralRGB.size() == 0 ) { fprintf( stderr, " no neutral RGB values were found. \n " ); - return _xyz2rgbMatrix1DNG; + return _metadata.xyz2rgbMatrix1; } double cct1 = lightSourceToColorTemp( - static_cast( _calibrateIllum[0] ) ); + static_cast( _metadata.calibrationIlluminant1 ) ); double cct2 = lightSourceToColorTemp( - static_cast( _calibrateIllum[1] ) ); + static_cast( _metadata.calibrationIlluminant2 ) ); double mir1 = ccttoMired( cct1 ); double mir2 = ccttoMired( cct2 ); @@ -1737,7 +1692,7 @@ DNGIdt::findXYZtoCameraMtx( const vector &neutralRGB ) const lerror = mir - ccttoMired( XYZToColorTemperature( mulVector( invertV( XYZtoCameraWeightedMatrix( mir, mir1, mir2 ) ), - _neutralRGBDNG ) ) ); + _metadata.neutralRGB ) ) ); if ( std::fabs( lerror - 0.0 ) <= 1e-09 ) { @@ -1837,19 +1792,20 @@ vector DNGIdt::matrixRGBtoXYZ( const double chromaticities[][2] ) const void DNGIdt::getCameraXYZMtxAndWhitePoint() { - _cameraToXYZMtx = invertV( findXYZtoCameraMtx( _neutralRGBDNG ) ); + _cameraToXYZMtx = invertV( findXYZtoCameraMtx( _metadata.neutralRGB ) ); assert( std::fabs( sumVector( _cameraToXYZMtx ) - 0.0 ) > 1e-09 ); - scaleVector( _cameraToXYZMtx, std::pow( 2.0, _baseExpo ) ); + scaleVector( _cameraToXYZMtx, std::pow( 2.0, _metadata.baselineExposure ) ); - if ( _neutralRGBDNG.size() > 0 ) + if ( _metadata.neutralRGB.size() > 0 ) { - _cameraXYZWhitePoint = mulVector( _cameraToXYZMtx, _neutralRGBDNG ); + _cameraXYZWhitePoint = + mulVector( _cameraToXYZMtx, _metadata.neutralRGB ); } else { _cameraXYZWhitePoint = colorTemperatureToXYZ( - lightSourceToColorTemp( _calibrateIllum[0] ) ); + lightSourceToColorTemp( _metadata.calibrationIlluminant1 ) ); } scaleVector( _cameraXYZWhitePoint, 1.0 / _cameraXYZWhitePoint[1] ); diff --git a/src/rawtoaces_util/acesrender.cpp b/src/rawtoaces_util/acesrender.cpp index 069eb10..c7a2a0e 100644 --- a/src/rawtoaces_util/acesrender.cpp +++ b/src/rawtoaces_util/acesrender.cpp @@ -1736,9 +1736,52 @@ float *AcesRender::renderDNG() #define P _rawProcessor->imgdata.idata +#ifdef R +# undef R +#endif + +#define R _rawProcessor->imgdata.rawdata + assert( _image && P.dng_version ); - DNGIdt *dng = new DNGIdt( _rawProcessor->imgdata.rawdata ); + Metadata metadata; + metadata.neutralRGB.resize( 3 ); + metadata.xyz2rgbMatrix1.resize( 9 ); + metadata.xyz2rgbMatrix2.resize( 9 ); + metadata.cameraCalibration1.resize( 9 ); + metadata.cameraCalibration2.resize( 9 ); + +#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION( 0, 20, 0 ) + metadata.baselineExposure = + static_cast( R.color.dng_levels.baseline_exposure ); +#else + metadata.baselineExposure = + static_cast( R.color.baseline_exposure ); +#endif + metadata.calibrationIlluminant1 = + static_cast( R.color.dng_color[0].illuminant ); + metadata.calibrationIlluminant2 = + static_cast( R.color.dng_color[1].illuminant ); + + FORI( 3 ) + { + metadata.neutralRGB[i] = + 1.0 / static_cast( R.color.cam_mul[i] ); + } + + FORIJ( 3, 3 ) + { + metadata.xyz2rgbMatrix1[i * 3 + j] = + static_cast( ( R.color.dng_color[0].colormatrix )[i][j] ); + metadata.xyz2rgbMatrix2[i * 3 + j] = + static_cast( ( R.color.dng_color[1].colormatrix )[i][j] ); + metadata.cameraCalibration1[i * 3 + j] = + static_cast( ( R.color.dng_color[0].calibration )[i][j] ); + metadata.cameraCalibration2[i * 3 + j] = + static_cast( ( R.color.dng_color[1].calibration )[i][j] ); + } + + DNGIdt *dng = new DNGIdt( metadata ); _catm = dng->getDNGCATMatrix(); _idtm = dng->getDNGIDTMatrix(); diff --git a/src/rawtoaces_util2/CMakeLists.txt b/src/rawtoaces_util2/CMakeLists.txt new file mode 100644 index 0000000..1265985 --- /dev/null +++ b/src/rawtoaces_util2/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.5) +include_directories( "${CMAKE_CURRENT_SOURCE_DIR}" ) + +add_library ( rawtoaces_util2 ${DO_SHARED} + rawtoaces_util.cpp + ${PROJECT_SOURCE_DIR}/include/rawtoaces/rawtoaces_util.h +) + +set_property(TARGET rawtoaces_util2 PROPERTY CXX_STANDARD 17) + +if ( OIIO_FOUND ) + target_include_directories ( rawtoaces_util2 PRIVATE ${OIIO_INCLUDE_DIRS} ) + target_link_directories ( rawtoaces_util2 PUBLIC ${OIIO_LIBRARY_DIRS} ) + target_link_libraries ( rawtoaces_util2 + PUBLIC + ${OIIO_LIBRARIES} + ${OIIO_LDFLAGS_OTHER} + ) +endif() + +target_link_libraries ( rawtoaces_util2 + PUBLIC + ${RAWTOACESIDTLIB} + OpenImageIO::OpenImageIO +) + +set_target_properties( rawtoaces_util2 PROPERTIES + SOVERSION ${RAWTOACES_MAJOR_VERSION}.${RAWTOACES_MINOR_VERSION}.${RAWTOACES_PATCH_VERSION} + VERSION ${RAWTOACES_VERSION} ) + +install( + FILES + ${PROJECT_SOURCE_DIR}/include/rawtoaces/rawtoaces_util.h + DESTINATION include/rawtoaces +) + +install( TARGETS rawtoaces_util2 DESTINATION lib ) diff --git a/src/rawtoaces_util2/rawtoaces_util.cpp b/src/rawtoaces_util2/rawtoaces_util.cpp new file mode 100644 index 0000000..742232a --- /dev/null +++ b/src/rawtoaces_util2/rawtoaces_util.cpp @@ -0,0 +1,955 @@ +// Copyright Contributors to the rawtoaces project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/rawtoaces + +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace rta +{ + +std::string findFile( const std::string &filename ) +{ + auto paths = pathsFinder(); + + for ( auto &path: paths.paths ) + { + std::string full_path = path + "/" + filename; + if ( std::filesystem::exists( full_path ) ) + return full_path; + } + return ""; +} + +std::vector collectDataFiles( const std::string &type ) +{ + std::vector result; + + auto paths = pathsFinder(); + + for ( auto &path: paths.paths ) + { + auto it = std::filesystem::directory_iterator( path + "/" + type ); + + for ( auto filename2: it ) + { + auto p = filename2.path(); + if ( filename2.path().extension() == ".json" ) + { + result.push_back( filename2.path().string() ); + } + } + } + return result; +} + +ImageConverter::ImageConverter() +{ + initArgParse(); +} + +const char *HelpString = + "Rawtoaces converts raw image files from a digital camera to " + "the Academy Colour Encoding System (ACES) compliant images.\n" + "The process consists of two parts:\n" + "- the colour values get converted from the camera native colour " + "space to the ACES AP0 (see \"SMPTE ST 2065-1\"), and \n" + "- the image file gets converted from the camera native raw " + "file format to the ACES Image Container file format " + "(see \"SMPTE ST 2065-4\").\n" + "\n" + "Rawtoaces supports the following white-balancing modes:\n" + "- \"metadata\" uses the white-balancing coefficients from the raw " + "image file, provided by the camera.\n" + "- \"illuminant\" performs white balancing to the illuminant, " + "provided in the \"--illuminant\" parameter. The list of the " + "supported illuminants can be seen using the " + "\"--list-illuminants\" parameter. This mode requires spectral " + "sensitivity data for the camera model the image comes from. " + "The list of cameras such data is available for, can be " + "seen using the \"--list-cameras\" parameter.\n" + "- \"box\" performs white-balancing to make the given region of " + "the image appear neutral gray. The box position (origin and size) " + "can be specified using the \"--wb-box\" parameter. In case no such " + "parameter provided, the whole image is used for white-balancing.\n" + "- \"custom\" uses the custom white balancing coefficients " + "provided using the -\"custom-wb\" parameter.\n" + "\n" + "Rawtoaces supports the following methods of color matrix " + "computation:\n" + "- \"spectral\" uses the camera sensor's spectral sensitivity data " + "to compute the optimal matrix. This mode requires spectral " + "sensitivity data for the camera model the image comes from. " + "The list of cameras such data is available for, can be " + "seen using the \"--list-cameras\" parameter.\n" + "- \"metadata\" uses the matrix (matrices) contained in the raw " + "image file metadata. This mode works best with the images using " + "the DNG format, as the DNG standard mandates the presense of " + "such matrices.\n" + "- \"Adobe\" uses the Adobe coefficients provided by LibRaw. \n" + "- \"custom\" uses a user-provided color conversion matrix. " + "A matrix can be specified using the \"--custom-mat\" parameter.\n" + "\n" + "The paths rawtoaces uses to search for the spectral sensitivity " + "data can be specified in the AMPAS_DATA_PATH environment " + "variable.\n"; + +const char *UsageString = + "\n" + " rawtoaces --wb-method METHOD --mat-method METHOD [PARAMS] " + "path/to/dir/or/file ...\n" + "Examples: \n" + " rawtoaces --wb-method metadata --mat-method metadata raw_file.cr3\n" + " rawtoaces --wb-method illuminant --illuminant 3200K --mat-method " + "spectral raw_file.cr3\n"; + +void ImageConverter::initArgParse() +{ + _argParse.intro( HelpString ).usage( UsageString ); + _argParse.print_defaults( true ); + _argParse.add_help( true ); + _argParse.add_version( "VERSION NUMBER" ); + + _argParse.arg( "filename" ).action( OIIO::ArgParse::append() ).hidden(); + + _argParse.arg( "--wb-method" ) + .help( + "White balance method. Supported options: metadata, illuminant, " + "box, custom." ) + .metavar( "STR" ) + .defaultval( "metadata" ) + .action( OIIO::ArgParse::store() ); + + _argParse.arg( "--mat-method" ) + .help( + "IDT matrix calculation method. Supported options: spectral, " + "metadata, Adobe, custom." ) + .metavar( "STR" ) + .defaultval( "spectral" ) + .action( OIIO::ArgParse::store() ); + + _argParse.arg( "--illuminant" ) + .help( "Illuminant for white balancing. (default = D55)" ) + .metavar( "STR" ) + .action( OIIO::ArgParse::store() ); + + _argParse.arg( "--wb-box" ) + .help( + "Box to use for white balancing. (default = (0,0,0,0) - full " + "image)" ) + .nargs( 4 ) + .metavar( "X Y W H" ) + .action( OIIO::ArgParse::store() ); + + _argParse.arg( "--custom-wb" ) + .help( "Custom white balance multipliers." ) + .nargs( 4 ) + .metavar( "R G B G" ) + .action( OIIO::ArgParse::store() ); + + _argParse.arg( "--custom-mat" ) + .help( "Custom camera RGB to XYZ matrix." ) + .nargs( 9 ) + .metavar( "Rr Rg Rb Gr Gg Gb Br Bg Bb" ) + .action( OIIO::ArgParse::store() ); + + _argParse.arg( "--headroom" ) + .help( "Highlight headroom factor." ) + .metavar( "VAL" ) + .defaultval( 6.0f ) + .action( OIIO::ArgParse::store() ); + + _argParse.separator( "Raw conversion options:" ); + + _argParse.arg( "--no-auto-bright" ) + .help( "Disable automatic exposure adjustment." ) + .action( OIIO::ArgParse::store_true() ); + + _argParse.arg( "--adjust-maximum-threshold" ) + .help( + "Automatically lower the linearity threshold provided in the " + "metadata by this scaling factor." ) + .metavar( "VAL" ) + .defaultval( 0.75f ) + .action( OIIO::ArgParse::store() ); + + _argParse.arg( "--black-level" ) + .help( "If >= 0, override the black level." ) + .metavar( "VAL" ) + .defaultval( -1 ) + .action( OIIO::ArgParse::store() ); + + _argParse.arg( "--saturation-level" ) + .help( + "If not 0, override the level which appears to be saturated " + "after normalisation." ) + .metavar( "VAL" ) + .defaultval( 0 ) + .action( OIIO::ArgParse::store() ); + + _argParse.arg( "--chromatic-aberration" ) + .help( + "Red and blue scale factors for chromatic aberration correction. " + "The value of 1 means no correction." ) + .metavar( "R B" ) + .nargs( 2 ) + .defaultval( 1.0f ) + .action( OIIO::ArgParse::store() ); + + _argParse.arg( "--half-size" ) + .help( "If present, decode image at half size resolution." ) + .action( OIIO::ArgParse::store_true() ); + + _argParse.arg( "--highlight-mode" ) + .help( "0 = clip, 1 = unclip, 2 = blend, 3..9 = rebuild." ) + .metavar( "VAL" ) + .defaultval( 0 ) + .action( OIIO::ArgParse::store() ); + + _argParse.arg( "--cropbox" ) + .help( + "Apply custom crop. If not present, the default crop is applied, " + "which should match the crop of the in-camera JPEG." ) + .nargs( 4 ) + .metavar( "X Y W H" ) + .action( OIIO::ArgParse::store() ); + + _argParse.arg( "--flip" ) + .help( + "If not 0, override the orientation specified in the metadata. " + "1..8 correspond to EXIF orientation codes " + "(3 = 180 deg, 6 = 90 deg CCW, 8 = 90 deg CW.)" ) + .metavar( "VAL" ) + .defaultval( 0 ) + .action( OIIO::ArgParse::store() ); + + _argParse.arg( "--denoise_threshold" ) + .help( "Wavelet denoising threshold" ) + .metavar( "VAL" ) + .defaultval( 0 ) + .action( OIIO::ArgParse::store() ); + + _argParse.separator( "Benchmarking and debugging:" ); + + _argParse.arg( "--list-cameras" ) + .help( "Shows the list of cameras supported in spectral mode." ) + .action( OIIO::ArgParse::store_true() ); + + _argParse.arg( "--list-illuminants" ) + .help( "Shows the list of illuminants supported in spectral mode." ) + .action( OIIO::ArgParse::store_true() ); + + _argParse.arg( "--verbose" ) + .help( "Verbosity level. 0 = off, 1 = info, 2 = debug." ) + .defaultval( 0 ) + .action( OIIO::ArgParse::store() ); +} + +OIIO::ArgParse &ImageConverter::argParse() +{ + return _argParse; +} + +OIIO::ImageBuf &ImageConverter::imageBuffer() +{ + return _imageBuffer; +} + +template +bool check_param( + const std::string &mode_name, + const std::string &mode_value, + const std::string ¶m_name, + const std::vector ¶m_value, + size_t correct_size, + const std::string &default_value_message, + bool is_correct_mode, + F1 on_success, + F2 on_failure ) +{ + if ( is_correct_mode ) + { + if ( param_value.size() == correct_size ) + { + on_success(); + return true; + } + else + { + if ( ( param_value.size() == 0 ) || + ( ( param_value.size() == 1 ) && ( param_value[0] == 0 ) ) ) + { + std::cerr << "Warning: " << mode_name << " was set to \"" + << mode_value << "\", but no \"--" << param_name + << "\" parameter provided. " << default_value_message + << " will be used." << std::endl; + + on_failure(); + return false; + } + + if ( param_value.size() != correct_size ) + { + std::cerr << "Warning: The parameter \"" << param_name + << "\" must have " << correct_size << " values. " + << default_value_message << " will be used." + << std::endl; + + on_failure(); + return false; + } + } + } + else + { + if ( ( param_value.size() > 1 ) || + ( ( param_value.size() == 1 ) && ( param_value[0] != 0 ) ) ) + { + std::cerr << "Warning: the \"--" << param_name + << "\" parameter provided, but the " << mode_name + << " is different from \"" << mode_value << "\". " + << default_value_message << std::endl; + + on_failure(); + return false; + } + else + { + return true; + } + } +} + +bool ImageConverter::parse( int argc, const char *argv[] ) +{ + if ( _argParse.parse_args( argc, argv ) ) + { + // report error? + return false; + } + + if ( _argParse["list-cameras"].get() ) + { + std::cout + << "Spectral sensitivity data are available for the following cameras:" + << std::endl; + + Idt idt; + auto paths = collectDataFiles( "camera" ); + std::vector cameras; + for ( auto path: paths ) + { + Spst spst; + if ( spst.loadSpst( path, nullptr, nullptr ) ) + { + cameras.push_back( + std::string( spst.getBrand() ) + " " + spst.getModel() ); + } + } + + std::sort( cameras.begin(), cameras.end() ); + + for ( auto s: cameras ) + std::cout << "- " << s << std::endl; + std::cout << std::endl; + exit( 0 ); + } + + if ( _argParse["list-illuminants"].get() ) + { + std::cout << "The following illuminants are supported:" << std::endl; + std::cout << "- The standard illuminant series D (e.g., D60, D6025)" + << std::endl; + std::cout << "- Black-body radiation (e.g., 3200K)" << std::endl; + + Idt idt; + auto paths = collectDataFiles( "illuminant" ); + std::vector illuminants; + for ( auto path: paths ) + { + Illum illum; + if ( illum.readSPD( path, "na" ) ) + { + illuminants.push_back( illum.getIllumType() ); + } + } + + std::sort( illuminants.begin(), illuminants.end() ); + + for ( auto s: illuminants ) + std::cout << "- " << s << std::endl; + std::cout << std::endl; + exit( 0 ); + } + + verbosity = _argParse["verbose"].get(); + + std::string wb_method = _argParse["wb-method"].get(); + + if ( wb_method == "metadata" ) + { + wbMethod = WBMethod::Metadata; + } + else if ( wb_method == "illuminant" ) + { + wbMethod = WBMethod::Illuminant; + } + else if ( wb_method == "box" ) + { + wbMethod = WBMethod::Box; + } + else if ( wb_method == "custom" ) + { + wbMethod = WBMethod::Custom; + } + else + { + std::cerr << std::endl + << "Unsupported white balancing method: \"" << wb_method + << "\"." << std::endl; + + return false; + } + + std::string mat_method = _argParse["mat-method"].get(); + + if ( mat_method == "spectral" ) + { + matrixMethod = MatrixMethod::Spectral; + } + else if ( mat_method == "metadata" ) + { + matrixMethod = MatrixMethod::Metadata; + } + else if ( mat_method == "Adobe" ) + { + matrixMethod = MatrixMethod::Adobe; + } + else if ( mat_method == "custom" ) + { + matrixMethod = MatrixMethod::Custom; + } + else + { + std::cerr << std::endl + << "Unsupported matrix method: \"" << mat_method << "\"." + << std::endl; + + return false; + } + + headroom = _argParse["headroom"].get(); + + std::string illum = _argParse["illuminant"].get(); + + if ( wbMethod == WBMethod::Illuminant ) + { + if ( illum.size() == 0 ) + { + std::cerr << "Warning: the white balancing method was set to " + << "\"illuminant\", but no \"--illuminant\" parameter " + << "provided. " << illuminant << " will be used." + << std::endl; + } + } + else + { + if ( illum.size() != 0 ) + { + std::cerr << "Warning: the \"--illuminant\" parameter provided " + << "but the white balancing mode different from " + << "\"illuminant\" " + << "requested. The custom illuminant will be ignored." + << std::endl; + } + } + + auto box = _argParse["wb-box"].as_vec(); + check_param( + "white balancing mode", + "box", + "wb-box", + box, + 4, + "The box will be ignored.", + wbMethod == WBMethod::Box, + [&]() { + for ( int i = 0; i < 4; i++ ) + wbBox[i] = box[i]; + }, + [&]() { + for ( int i = 0; i < 4; i++ ) + wbBox[i] = 0; + } ); + + auto custom_wb = _argParse["custom-wb"].as_vec(); + check_param( + "white balancing mode", + "custom", + "custom-wb", + custom_wb, + 4, + "The scalers will be ignored. The default values of (1, 1, 1)", + wbMethod == WBMethod::Custom, + [&]() { + for ( int i = 0; i < 4; i++ ) + customWB[i] = custom_wb[i]; + }, + [&]() { + for ( int i = 0; i < 4; i++ ) + customWB[i] = 1.0; + } ); + + auto custom_mat = _argParse["custom-mat"].as_vec(); + check_param( + "matrix mode", + "custom", + "custom-mat", + custom_mat, + 9, + "Identity matrix", + matrixMethod == MatrixMethod::Custom, + [&]() { + for ( int i = 0; i < 3; i++ ) + for ( int j = 0; j < 3; j++ ) + customMatrix[i][j] = custom_mat[i * 3 + j]; + }, + [&]() { + for ( int i = 0; i < 3; i++ ) + for ( int j = 0; j < 3; j++ ) + customMatrix[i][j] = i == j ? 1.0 : 0.0; + } ); + + auto crop = _argParse["cropbox"].as_vec(); + if ( crop.size() == 4 ) + { + for ( size_t i = 0; i < 4; i++ ) + cropbox[i] = crop[i]; + } + + no_auto_bright = _argParse["no-auto-bright"].get(); + adjust_maximum_threshold = _argParse["adjust-maximum-threshold"].get(); + black_level = _argParse["black-level"].get(); + saturation_level = _argParse["saturation-level"].get(); + half_size = _argParse["half-size"].get(); + highlight_mode = _argParse["highlight-mode"].get(); + flip = _argParse["flip"].get(); + + return true; +} + +bool ImageConverter::configure( const std::string &input_filename ) +{ + _configFilename = input_filename; + + _inputHint = OIIO::ImageSpec(); + + _inputHint["raw:ColorSpace"] = "XYZ"; + _inputHint["raw:use_camera_wb"] = 0; + _inputHint["raw:use_auto_wb"] = 0; + + _inputHint["raw:auto_bright"] = no_auto_bright ? 0 : 1; + _inputHint["raw:adjust_maximum_thr"] = adjust_maximum_threshold; + _inputHint["raw:user_black"] = black_level; + _inputHint["raw:user_sat"] = saturation_level; + _inputHint["raw:half_size"] = (int)half_size; + _inputHint["raw:flip"] = flip; + _inputHint["raw:HighlightMode"] = highlight_mode; + + if ( cropbox[2] != 0 && cropbox[3] != 0 ) + { + _inputHint.attribute( + "raw:cropbox", OIIO::TypeDesc( OIIO::TypeDesc::INT, 4 ), cropbox ); + } + + auto imageInput = OIIO::ImageInput::create( "raw", false, &_inputHint ); + imageInput->open( input_filename, _inputFull, _inputHint ); + + _is_DNG = _inputFull.extra_attribs.find( "raw:dng:version" )->get_int() > 0; + + switch ( wbMethod ) + { + case WBMethod::Metadata: { + float user_mul[4]; + + for ( int i = 0; i < 4; i++ ) + { + user_mul[i] = _inputFull.find_attribute( "raw:cam_mul" ) + ->get_float_indexed( i ) / + 256.0; + } + + _inputHint.attribute( + "raw:user_mul", + OIIO::TypeDesc( OIIO::TypeDesc::FLOAT, 4 ), + user_mul ); + break; + } + + case WBMethod::Illuminant: { + std::string lower_illuminant( illuminant ); + std::transform( + lower_illuminant.begin(), + lower_illuminant.end(), + lower_illuminant.begin(), + []( unsigned char c ) { return std::tolower( c ); } ); + + if ( !isValidCT( lower_illuminant ) ) + { + std::cerr << "Unrecognised illuminant \'" << illuminant << "\'" + << std::endl; + return false; + } + + break; + } + case WBMethod::Box: + + if ( wbBox[2] == 0 || wbBox[3] == 0 ) + { + // Empty box, use whole image. + _inputHint["raw:use_auto_wb"] = 1; + } + else + { + int32_t box[4]; + for ( int i = 0; i < 4; i++ ) + { + box[i] = wbBox[i]; + } + _inputHint.attribute( + "raw:greybox", + OIIO::TypeDesc( OIIO::TypeDesc::INT, 4 ), + box ); + } + break; + + case WBMethod::Custom: + _inputHint.attribute( + "raw:user_mul", + OIIO::TypeDesc( OIIO::TypeDesc::FLOAT, 4 ), + customWB ); + break; + + default: break; + } + + switch ( matrixMethod ) + { + case MatrixMethod::Spectral: + _inputHint["raw:ColorSpace"] = "raw"; + _inputHint["raw:use_camera_matrix"] = 0; + break; + case MatrixMethod::Metadata: + _inputHint["raw:use_camera_matrix"] = _is_DNG ? 1 : 3; + break; + case MatrixMethod::Adobe: + _inputHint["raw:use_camera_matrix"] = 1; + break; + case MatrixMethod::Custom: + _inputHint["raw:use_camera_matrix"] = 0; + _inputHint["raw:ColorSpace"] = "raw"; + + _IDT_matrix.resize( 3 ); + for ( int i = 0; i < 3; i++ ) + { + _IDT_matrix[i].resize( 3 ); + for ( int j = 0; j < 3; j++ ) + { + _IDT_matrix[i][j] = customMatrix[i][j]; + } + } + + break; + default: break; + } + + bool spectral_white_balance = wbMethod == WBMethod::Illuminant; + bool spectral_matrix = matrixMethod == MatrixMethod::Spectral; + + if ( spectral_white_balance || spectral_matrix ) + { + float pre_mul[4]; + + for ( int i = 0; i < 4; i++ ) + { + pre_mul[i] = _inputFull.find_attribute( "raw:pre_mul" ) + ->get_float_indexed( i ); + } + + _cameraMake = _inputFull["Make"]; + _cameraModel = _inputFull["Model"]; + + prepareIDT_spectral( spectral_white_balance, spectral_matrix ); + + if ( spectral_white_balance ) + { + float user_mul[4]; + + for ( int i = 0; i < _WB_mults.size(); i++ ) + { + user_mul[i] = _WB_mults[i]; + } + if ( _WB_mults.size() == 3 ) + user_mul[3] = _WB_mults[1]; + + _inputHint.attribute( + "raw:user_mul", + OIIO::TypeDesc( OIIO::TypeDesc::FLOAT, 4 ), + user_mul ); + } + } + + if ( matrixMethod == MatrixMethod::Metadata || + matrixMethod == MatrixMethod::Adobe ) + { + if ( _is_DNG ) + { + _inputHint["raw:use_camera_matrix"] = 1; + _inputHint["raw:use_camera_wb"] = 1; + + prepareIDT_DNG(); + } + else + { + prepareIDT_nonDNG(); + } + } + + return true; +} + +bool ImageConverter::load( const std::string &input_filename ) +{ + if ( _configFilename != input_filename ) + { + auto imageInput = OIIO::ImageInput::create( "raw", false, &_inputHint ); + imageInput->open( input_filename, _inputFull, _inputHint ); + } + + _imageBuffer = + OIIO::ImageBuf( input_filename, 0, 0, nullptr, &_inputHint, nullptr ); + bool result = _imageBuffer.init_spec( input_filename, 0, 0 ); + if ( result ) + { + auto &spec = _imageBuffer.spec(); + auto channels = spec.nchannels; + result = + _imageBuffer.read( 0, 0, 0, channels, true, OIIO::TypeDesc::FLOAT ); + } + return result; +} + +void ImageConverter::applyMatrix( + const std::vector> &matrix ) +{ + float M[4][4]; + + size_t n = matrix.size(); + + if ( n ) + { + size_t m = matrix[0].size(); + + for ( size_t i = 0; i < n; i++ ) + { + for ( size_t j = 0; j < m; j++ ) + { + M[j][i] = matrix[i][j]; + } + + for ( size_t j = m; j < 4; j++ ) + M[j][i] = 0; + } + + for ( size_t i = n; i < 4; i++ ) + { + for ( size_t j = 0; j < m; j++ ) + M[j][i] = 0; + for ( size_t j = m; j < 4; j++ ) + M[j][i] = 1; + } + } + + _imageBuffer = OIIO::ImageBufAlgo::colormatrixtransform( _imageBuffer, M ); +} + +bool ImageConverter::process() +{ + if ( _IDT_matrix.size() ) + { + applyMatrix( _IDT_matrix ); + } + + if ( _CAT_matrix.size() ) + { + applyMatrix( _CAT_matrix ); + + _imageBuffer = OIIO::ImageBufAlgo::colormatrixtransform( + _imageBuffer, XYZ_acesrgb_transposed_4 ); + } + + _imageBuffer = OIIO::ImageBufAlgo::mul( _imageBuffer, headroom ); + return true; +} + +bool ImageConverter::save( const std::string &output_filename ) +{ + const float chromaticities[] = { 0.7347, 0.2653, 0, 1, + 0.0001, -0.077, 0.32168, 0.33767 }; + + OIIO::ImageSpec imageSpec = _inputFull; + imageSpec.set_format( OIIO::TypeDesc::HALF ); + imageSpec["acesImageContainerFlag"] = 1; + imageSpec["compression"] = "none"; + imageSpec.attribute( + "chromaticities", + OIIO::TypeDesc( OIIO::TypeDesc::FLOAT, 8 ), + chromaticities ); + + auto imageOutput = OIIO::ImageOutput::create( "exr" ); + bool result = imageOutput->open( output_filename, imageSpec ); + result = _imageBuffer.write( imageOutput.get() ); + return result; +} + +void ImageConverter::prepareIDT_spectral( + bool calc_white_balance, bool calc_matrix ) +{ + std::string lower_illuminant( illuminant ); + + if ( lower_illuminant.length() == 0 ) + lower_illuminant = "na"; + else + { + std::transform( + lower_illuminant.begin(), + lower_illuminant.end(), + lower_illuminant.begin(), + []( unsigned char c ) { return std::tolower( c ); } ); + } + + Idt idt; + + auto illum_paths = collectDataFiles( "illuminant" ); + int res = idt.loadIlluminant( illum_paths, lower_illuminant ); + + bool found_camera = false; + auto camera_paths = collectDataFiles( "camera" ); + + for ( auto &path: camera_paths ) + { + if ( idt.loadCameraSpst( + path, _cameraMake.c_str(), _cameraModel.c_str() ) ) + { + found_camera = true; + break; + } + } + + auto training = findFile( "training/training_spectral.json" ); + if ( training.length() ) + { + idt.loadTrainingData( training ); + } + + auto cmf = findFile( "cmf/cmf_1931.json" ); + if ( cmf.length() ) + { + idt.loadCMF( cmf ); + } + + if ( lower_illuminant != "na" ) + { + idt.chooseIllumType( lower_illuminant.c_str(), headroom ); + } + else + { + std::vector cam_mul( 3 ); + for ( int i = 0; i < 3; i++ ) + cam_mul[i] = _inputFull.find_attribute( "raw:cam_mul" ) + ->get_float_indexed( i ); + idt.chooseIllumSrc( cam_mul, headroom ); + } + + if ( idt.calIDT() ) + { + if ( calc_white_balance ) + { + _WB_mults = idt.getWB(); + } + + if ( calc_matrix ) + { + _IDT_matrix = idt.getIDT(); + _CAT_matrix.resize( 0 ); + } + } +} + +void ImageConverter::prepareIDT_DNG() +{ + Metadata metadata; + metadata.neutralRGB.resize( 3 ); + metadata.xyz2rgbMatrix1.resize( 9 ); + metadata.xyz2rgbMatrix2.resize( 9 ); + metadata.cameraCalibration1.resize( 9 ); + metadata.cameraCalibration2.resize( 9 ); + + metadata.baselineExposure = + _inputFull.get_float_attribute( "raw:dng:baseline_exposure" ); + metadata.calibrationIlluminant1 = + _inputFull.get_int_attribute( "raw:dng:calibration_illuminant1" ); + metadata.calibrationIlluminant2 = + _inputFull.get_int_attribute( "raw:dng:calibration_illuminant2" ); + + for ( int i = 0; i < 3; i++ ) + { + metadata.neutralRGB[i] = + 1.0 / + _inputFull.find_attribute( "raw:cam_mul" )->get_float_indexed( i ); + + for ( int j = 0; j < 3; j++ ) + { + metadata.xyz2rgbMatrix1[i * 3 + j] = + _inputFull.find_attribute( "raw:dng:color_matrix1" ) + ->get_float_indexed( i * 3 + j ); + metadata.xyz2rgbMatrix2[i * 3 + j] = + _inputFull.find_attribute( "raw:dng:color_matrix2" ) + ->get_float_indexed( i * 3 + j ); + metadata.cameraCalibration1[i * 3 + j] = + _inputFull.find_attribute( "raw:dng:camera_calibration1" ) + ->get_float_indexed( i * 4 + j ); + metadata.cameraCalibration2[i * 3 + j] = + _inputFull.find_attribute( "raw:dng:camera_calibration2" ) + ->get_float_indexed( i * 4 + j ); + } + } + + DNGIdt *dng = new DNGIdt( metadata ); + + _IDT_matrix = dng->getDNGIDTMatrix(); + + // Do not apply CAT for DNG + _CAT_matrix.resize( 0 ); + // _CAT_matrix = dng->getDNGCATMatrix(); +} + +void ImageConverter::prepareIDT_nonDNG() +{ + _IDT_matrix.resize( 0 ); + + vector dIV( d65, d65 + 3 ); + vector dOV( d60, d60 + 3 ); + _CAT_matrix = getCAT( dIV, dOV ); +} + +} // namespace rta diff --git a/unittest/testDNGIdt.cpp b/unittest/testDNGIdt.cpp index d885890..b29473b 100644 --- a/unittest/testDNGIdt.cpp +++ b/unittest/testDNGIdt.cpp @@ -88,9 +88,57 @@ BOOST_AUTO_TEST_CASE( TestIDT_RobertsonLength ) BOOST_CHECK_CLOSE( rLength, 0.060234937, 1e-5 ); }; -BOOST_AUTO_TEST_CASE( TestIDT_LightSourceToColorTemp ) +DNGIdt *openFile( std::string path, LibRaw &rawProcessor ) { + boost::filesystem::path pathToRaw = boost::filesystem::absolute( + "../../unittest/materials/blackmagic_cinema_camera_cinemadng.dng" ); + int ret = rawProcessor.open_file( ( pathToRaw.string() ).c_str() ); + ret = rawProcessor.unpack(); +#define R rawProcessor.imgdata.rawdata + + Metadata metadata; + metadata.neutralRGB.resize( 3 ); + metadata.xyz2rgbMatrix1.resize( 9 ); + metadata.xyz2rgbMatrix2.resize( 9 ); + metadata.cameraCalibration1.resize( 9 ); + metadata.cameraCalibration2.resize( 9 ); + +#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION( 0, 20, 0 ) + metadata.baselineExposure = + static_cast( R.color.dng_levels.baseline_exposure ); +#else + metadata.baselineExposure = + static_cast( R.color.baseline_exposure ); +#endif + metadata.calibrationIlluminant1 = + static_cast( R.color.dng_color[0].illuminant ); + metadata.calibrationIlluminant2 = + static_cast( R.color.dng_color[1].illuminant ); + + FORI( 3 ) + { + metadata.neutralRGB[i] = + 1.0 / static_cast( R.color.cam_mul[i] ); + } + + FORIJ( 3, 3 ) + { + metadata.xyz2rgbMatrix1[i * 3 + j] = + static_cast( ( R.color.dng_color[0].colormatrix )[i][j] ); + metadata.xyz2rgbMatrix2[i * 3 + j] = + static_cast( ( R.color.dng_color[1].colormatrix )[i][j] ); + metadata.cameraCalibration1[i * 3 + j] = + static_cast( ( R.color.dng_color[0].calibration )[i][j] ); + metadata.cameraCalibration2[i * 3 + j] = + static_cast( ( R.color.dng_color[1].calibration )[i][j] ); + } + + return new DNGIdt( metadata ); +} + +BOOST_AUTO_TEST_CASE( TestIDT_LightSourceToColorTemp ) +{ DNGIdt *di = new DNGIdt(); unsigned short tag = 17; double ct = di->lightSourceToColorTemp( tag ); @@ -101,13 +149,10 @@ BOOST_AUTO_TEST_CASE( TestIDT_LightSourceToColorTemp ) BOOST_AUTO_TEST_CASE( TestIDT_XYZToColorTemperature ) { - LibRaw rawProcessor; - boost::filesystem::path pathToRaw = boost::filesystem::absolute( - "../../unittest/materials/blackmagic_cinema_camera_cinemadng.dng" ); - int ret = rawProcessor.open_file( ( pathToRaw.string() ).c_str() ); - ret = rawProcessor.unpack(); - - DNGIdt *di = new DNGIdt( rawProcessor.imgdata.rawdata ); + LibRaw rawProcessor; + DNGIdt *di = openFile( + "../../unittest/materials/blackmagic_cinema_camera_cinemadng.dng", + rawProcessor ); double XYZ[3] = { 0.9731171910, 1.0174927152, 0.9498565880 }; vector XYZVector( XYZ, XYZ + 3 ); double cct = di->XYZToColorTemperature( XYZVector ); @@ -120,13 +165,11 @@ BOOST_AUTO_TEST_CASE( TestIDT_XYZToColorTemperature ) BOOST_AUTO_TEST_CASE( TestIDT_XYZtoCameraWeightedMatrix ) { - LibRaw rawProcessor; - boost::filesystem::path pathToRaw = boost::filesystem::absolute( - "../../unittest/materials/blackmagic_cinema_camera_cinemadng.dng" ); - int ret = rawProcessor.open_file( ( pathToRaw.string() ).c_str() ); - ret = rawProcessor.unpack(); + LibRaw rawProcessor; + DNGIdt *di = openFile( + "../../unittest/materials/blackmagic_cinema_camera_cinemadng.dng", + rawProcessor ); - DNGIdt *di = new DNGIdt( rawProcessor.imgdata.rawdata ); double mirs[3] = { 158.8461538462, 350.1400560224, 153.8461538462 }; double matrix[9] = { 1.0165710542, -0.2791973987, -0.0801820653, -0.4881171650, 1.3469051835, 0.1100471308, @@ -142,13 +185,11 @@ BOOST_AUTO_TEST_CASE( TestIDT_XYZtoCameraWeightedMatrix ) BOOST_AUTO_TEST_CASE( TestIDT_FindXYZtoCameraMtx ) { - LibRaw rawProcessor; - boost::filesystem::path pathToRaw = boost::filesystem::absolute( - "../../unittest/materials/blackmagic_cinema_camera_cinemadng.dng" ); - int ret = rawProcessor.open_file( ( pathToRaw.string() ).c_str() ); - ret = rawProcessor.unpack(); + LibRaw rawProcessor; + DNGIdt *di = openFile( + "../../unittest/materials/blackmagic_cinema_camera_cinemadng.dng", + rawProcessor ); - DNGIdt *di = new DNGIdt( rawProcessor.imgdata.rawdata ); double neutralRGB[3] = { 0.6289999865, 1.0000000000, 0.7904000305 }; double matrix[9] = { 1.0616656923, -0.3124143737, -0.0661770211, -0.4772957633, 1.3614785395, 0.1001599918, @@ -192,17 +233,14 @@ BOOST_AUTO_TEST_CASE( TestIDT_MatrixRGBtoXYZ ) BOOST_AUTO_TEST_CASE( TestIDT_GetDNGCATMatrix ) { - - LibRaw rawProcessor; - boost::filesystem::path pathToRaw = boost::filesystem::absolute( - "../../unittest/materials/blackmagic_cinema_camera_cinemadng.dng" ); - int ret = rawProcessor.open_file( ( pathToRaw.string() ).c_str() ); - ret = rawProcessor.unpack(); - - DNGIdt *di = new DNGIdt( rawProcessor.imgdata.rawdata ); - double matrix[3][3] = { { 0.9907763427, -0.0022862289, 0.0209908807 }, - { -0.0017882434, 0.9941341374, 0.0083008330 }, - { 0.0003777587, 0.0015609315, 1.1063201101 } }; + LibRaw rawProcessor; + DNGIdt *di = openFile( + "../../unittest/materials/blackmagic_cinema_camera_cinemadng.dng", + rawProcessor ); + + double matrix[3][3] = { { 0.9907763427, -0.0022862289, 0.0209908807 }, + { -0.0017882434, 0.9941341374, 0.0083008330 }, + { 0.0003777587, 0.0015609315, 1.1063201101 } }; vector> result = di->getDNGCATMatrix(); rawProcessor.recycle(); @@ -214,17 +252,14 @@ BOOST_AUTO_TEST_CASE( TestIDT_GetDNGCATMatrix ) BOOST_AUTO_TEST_CASE( TestIDT_GetDNGIDTMatrix ) { - - LibRaw rawProcessor; - boost::filesystem::path pathToRaw = boost::filesystem::absolute( - "../../unittest/materials/blackmagic_cinema_camera_cinemadng.dng" ); - int ret = rawProcessor.open_file( ( pathToRaw.string() ).c_str() ); - ret = rawProcessor.unpack(); - - DNGIdt *di = new DNGIdt( rawProcessor.imgdata.rawdata ); - double matrix[3][3] = { { 1.0536466144, 0.0039044182, 0.0049084502 }, - { -0.4899562165, 1.3614787986, 0.1020844728 }, - { -0.0024498461, 0.0060497128, 1.0139159537 } }; + LibRaw rawProcessor; + DNGIdt *di = openFile( + "../../unittest/materials/blackmagic_cinema_camera_cinemadng.dng", + rawProcessor ); + + double matrix[3][3] = { { 1.0536466144, 0.0039044182, 0.0049084502 }, + { -0.4899562165, 1.3614787986, 0.1020844728 }, + { -0.0024498461, 0.0060497128, 1.0139159537 } }; vector> result = di->getDNGIDTMatrix(); rawProcessor.recycle();