diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 992bf4db3..1e054b33d 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -22,7 +22,7 @@ jobs: mkdir build date +%s > build/stamp brew uninstall --ignore-dependencies libtiff - brew install libtiff gtk+3 gtkmm3 gtk-mac-integration adwaita-icon-theme libsigc++@2 little-cms2 libiptcdata fftw lensfun expat pkgconfig llvm shared-mime-info exiv2 | tee -a depslog + brew install libtiff gtk+3 gtkmm3 gtk-mac-integration adwaita-icon-theme libsigc++@2 little-cms2 libiptcdata fftw lensfun expat pkgconfig llvm shared-mime-info exiv2 automake | tee -a depslog date -u echo "----====Pourage====----" cat depslog | grep Pouring @@ -42,6 +42,7 @@ jobs: export REF=${GITHUB_REF##*/} export C_FLAGS=$(echo -e $C_FLAGS | tr -d '\n') cd build && date -u && date +%s > configstamp + curl -L https://github.com/Homebrew/homebrew-core/raw/679923b4eb48a8dc7ecc1f05d06063cd79b3fc00/Formula/libomp.rb -o libomp.rb && brew install --formula libomp.rb cmake \ -DCMAKE_BUILD_TYPE="Release" \ -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON \ @@ -66,7 +67,6 @@ jobs: -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \ -DOSX_CONTINUOUS=ON \ .. - curl -L https://github.com/Homebrew/homebrew-core/raw/679923b4eb48a8dc7ecc1f05d06063cd79b3fc00/Formula/libomp.rb -o libomp.rb && brew install --formula libomp.rb zsh -c 'echo "Configured in $(printf "%0.2f" $(($[$(date +%s)-$(cat configstamp)]/$((60.))))) minutes"' - name: Compile RawTherapee run: | diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 1143c948d..146b4144b 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -43,6 +43,7 @@ jobs: cc:p pkgconf:p cmake:p + autotools:p ninja:p gtkmm3:p lcms2:p diff --git a/CMakeLists.txt b/CMakeLists.txt index abb86a108..820ff6f5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,6 +226,7 @@ option(WITH_LTO "Build with link-time optimizations" OFF) option(WITH_SAN "Build with run-time sanitizer" OFF) option(WITH_PROF "Build with profiling instrumentation" OFF) option(WITH_SYSTEM_KLT "Build using system KLT library." OFF) +option(WITH_SYSTEM_LIBRAW "Build using system LibRaw library." OFF) option(OPTION_OMP "Build with OpenMP support" ON) option( STRICT_MUTEX @@ -522,6 +523,13 @@ foreach(l ${_exiv2_libs}) set(EXIV2_LIBRARIES ${EXIV2_LIBRARIES} ${_el}) endforeach() +if(NOT WITH_SYSTEM_LIBRAW) + set(LIBRAW_LIBRARIES "${CMAKE_CURRENT_BINARY_DIR}/rtengine/libraw/lib/.libs/libraw_r.a") + if(WIN32) + set(LIBRAW_LIBRARIES ${LIBRAW_LIBRARIES} -lws2_32) + endif() +endif() + if(WIN32) add_definitions(-DWIN32) add_definitions(-D_WIN32) @@ -551,6 +559,9 @@ find_package(ZLIB REQUIRED) if(WITH_SYSTEM_KLT) find_package(KLT REQUIRED) endif() +if(WITH_SYSTEM_LIBRAW) + pkg_check_modules(LIBRAW REQUIRED libraw_r>=0.21) +endif() # Check for libcanberra-gtk3 (sound events on Linux): if(UNIX AND (NOT APPLE)) diff --git a/rtengine/CMakeLists.txt b/rtengine/CMakeLists.txt index 5a9b2d953..793f24601 100644 --- a/rtengine/CMakeLists.txt +++ b/rtengine/CMakeLists.txt @@ -45,6 +45,11 @@ endif() if(EXIV2_INCLUDE_DIRS) include_directories("${EXIV2_INCLUDE_DIRS}") endif() +if(NOT WITH_SYSTEM_LIBRAW) + include_directories("${CMAKE_SOURCE_DIR}/rtengine/libraw") +else() + include_directories("${LIBRAW_INCLUDE_DIRS}") +endif() link_directories( "${EXPAT_LIBRARY_DIRS}" @@ -245,10 +250,16 @@ target_link_libraries(rtengine ${RSVG_LIBRARIES} ${KLT_LIBRARIES} ${EXIV2_LIBRARIES} + ${LIBRAW_LIBRARIES} ) if(OpenMP_FOUND) target_link_libraries(rtengine ${OpenMP_CXX_LIBRARIES}) endif() +# Configure LibRaw +if(NOT WITH_SYSTEM_LIBRAW) + include(LibRaw.cmake) +endif() + install(FILES ${CAMCONSTSFILE} DESTINATION "${DATADIR}" PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) diff --git a/rtengine/LibRaw.cmake b/rtengine/LibRaw.cmake new file mode 100644 index 000000000..433312212 --- /dev/null +++ b/rtengine/LibRaw.cmake @@ -0,0 +1,124 @@ +# LibRaw has its own configuration script and uses make to build. Here we add +# the LibRaw configuration commands so they run during the CMake configuration. +# Also, add a target which always runs make. + +set(LIBRAW_DIR "${CMAKE_CURRENT_BINARY_DIR}/libraw") +set(LIBRAW_LIB_DIR "${LIBRAW_DIR}/lib") +set(LIBRAW_PHANTOM_FILE "${LIBRAW_LIB_DIR}/phantom_file") +if(DEFINED ENV{SHELL}) + set(SHELL "$ENV{SHELL}") +else() + set(SHELL "sh") +endif() + +add_custom_target( + LibRaw ALL + DEPENDS ${LIBRAW_PHANTOM_FILE} # Ensures target always executes. +) + +# Configuration flags. +set(CONFIGURE_FLAGS "--disable-examples") +set(LIBRAW_CXX_FLAGS "${CXX_FLAGS} -std=gnu++11 -Wno-error=unknown-pragmas") +# Let the configure script handle OpenMP flags. +string(REPLACE "${OpenMP_CXX_FLAGS}" "" LIBRAW_CXX_FLAGS "${LIBRAW_CXX_FLAGS}") +if(OPTION_OMP) + set(CONFIGURE_FLAGS "${CONFIGURE_FLAGS} --enable-openmp") +else() + set(CONFIGURE_FLAGS "${CONFIGURE_FLAGS} --disable-openmp") +endif() +set(CONFIGURE_FLAGS "${CONFIGURE_FLAGS} CC=\"${CMAKE_C_COMPILER}\"") +set(CONFIGURE_FLAGS "${CONFIGURE_FLAGS} CXX=\"${CMAKE_CXX_COMPILER}\"") +set(CONFIGURE_FLAGS "${CONFIGURE_FLAGS} CXXFLAGS=\"${LIBRAW_CXX_FLAGS}\"") + +# Configuration commands. +message(STATUS "Configuring LibRaw") +execute_process( + COMMAND cp -p -R "${CMAKE_CURRENT_SOURCE_DIR}/libraw" . + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + RESULT_VARIABLE PROCESS_RESULT + COMMAND_ECHO STDOUT +) +if(PROCESS_RESULT AND NOT PROCESS_RESULT EQUAL 0) + message(FATAL_ERROR "Could not copy LibRaw files into build directory") +endif() +execute_process( + COMMAND "${SHELL}" -l -c "autoreconf -v --install" + WORKING_DIRECTORY "${LIBRAW_DIR}" + RESULT_VARIABLE PROCESS_RESULT + COMMAND_ECHO STDOUT +) +if(PROCESS_RESULT AND NOT PROCESS_RESULT EQUAL 0) + message(FATAL_ERROR "Could not generate LibRaw configuration script") +endif() +execute_process( + COMMAND "${SHELL}" -l -c "./configure ${CONFIGURE_FLAGS}" + WORKING_DIRECTORY "${LIBRAW_DIR}" + RESULT_VARIABLE PROCESS_RESULT + COMMAND_ECHO STDOUT +) +if(PROCESS_RESULT AND NOT PROCESS_RESULT EQUAL 0) + execute_process( + COMMAND cat config.log + WORKING_DIRECTORY "${LIBRAW_DIR}" + COMMAND_ECHO STDOUT + ) + message(FATAL_ERROR "LibRaw configure failed") +endif() + +# Build flags. +set(LIBRAW_MAKE_FLAGS "") +if(CMAKE_GENERATOR MATCHES ".*Makefiles.*") + set(LIBRAW_MAKE_COMMAND "$(MAKE)") +else() + # If not using Makefiles, set number of jobs equal to logical processors + # count. Not necessary for make because of the jobserver. + execute_process( + COMMAND "${SHELL}" -l -c "nproc" + OUTPUT_VARIABLE LOGICAL_PROCESSORS + RESULT_VARIABLE PROCESS_RESULT + ERROR_QUIET + ) + if(PROCESS_RESULT AND NOT PROCESS_RESULT EQUAL 0) + execute_process( + COMMAND "${SHELL}" -l -c "sysctl -n hw.ncpu" + OUTPUT_VARIABLE LOGICAL_PROCESSORS + RESULT_VARIABLE PROCESS_RESULT + ERROR_QUIET + ) + endif() + if(PROCESS_RESULT AND NOT PROCESS_RESULT EQUAL 0) + execute_process( + COMMAND "${SHELL}" -l -c "getconf _NPROCESSORS_ONLN" + OUTPUT_VARIABLE LOGICAL_PROCESSORS + RESULT_VARIABLE PROCESS_RESULT + ERROR_QUIET + ) + endif() + if(PROCESS_RESULT AND NOT PROCESS_RESULT EQUAL 0) + set(LOGICAL_PROCESSORS "1") + endif() + string(STRIP "${LOGICAL_PROCESSORS}" LOGICAL_PROCESSORS) + set(LIBRAW_MAKE_FLAGS "${LIBRAW_MAKE_FLAGS} -j${LOGICAL_PROCESSORS}") + + set(LIBRAW_MAKE_COMMAND "make") +endif() + +# Build commands. +add_custom_command( + OUTPUT "${LIBRAW_PHANTOM_FILE}" "${LIBRAW_LIB_DIR}/.libs/libraw_r.a" + COMMAND cp -p -R "${CMAKE_CURRENT_SOURCE_DIR}/libraw" .. + COMMAND "${SHELL}" -l -c "${LIBRAW_MAKE_COMMAND} ${LIBRAW_MAKE_FLAGS}" + COMMENT "Building LibRaw" + WORKING_DIRECTORY libraw + VERBATIM +) + +# Add a `make clean-libraw` command because there's no good way to automatically +# clean the LibRaw build with `make`clean`. +add_custom_target( + clean-libraw + COMMAND make clean + COMMAND rm -rf lib + WORKING_DIRECTORY libraw +) + diff --git a/rtengine/dcraw.h b/rtengine/dcraw.h index 4f1db420c..4501aef67 100644 --- a/rtengine/dcraw.h +++ b/rtengine/dcraw.h @@ -67,6 +67,7 @@ public: ,getbithuff(this,ifp,zero_after_ff) ,nikbithuff(ifp) { + shrink=0; memset(&hbd, 0, sizeof(hbd)); aber[0]=aber[1]=aber[2]=aber[3]=1; gamm[0]=0.45;gamm[1]=4.5;gamm[2]=gamm[3]=gamm[4]=gamm[5]=0; diff --git a/rtengine/init.cc b/rtengine/init.cc index 04faa98a8..731c26a20 100644 --- a/rtengine/init.cc +++ b/rtengine/init.cc @@ -44,6 +44,7 @@ const Settings* settings; MyMutex* lcmsMutex = nullptr; MyMutex *fftwMutex = nullptr; +MyMutex *librawMutex = nullptr; int init (const Settings* s, const Glib::ustring& baseDir, const Glib::ustring& userSettingsDir, bool loadAll) { @@ -120,6 +121,8 @@ int init (const Settings* s, const Glib::ustring& baseDir, const Glib::ustring& delete lcmsMutex; lcmsMutex = new MyMutex; fftwMutex = new MyMutex; + delete librawMutex; + librawMutex = new MyMutex; return 0; } diff --git a/rtengine/panasonic_decoders.cc b/rtengine/panasonic_decoders.cc index bbbfb7c20..c68ec153f 100644 --- a/rtengine/panasonic_decoders.cc +++ b/rtengine/panasonic_decoders.cc @@ -58,6 +58,9 @@ unsigned DCraw::pana_bits_t::operator() (int nbits, unsigned *bytes) } } +namespace +{ + class pana_cs6_page_decoder { unsigned int pixelbuffer[14], lastoffset, maxoffset; @@ -99,6 +102,8 @@ void pana_cs6_page_decoder::read_page() } #undef wbuffer +} + void DCraw::panasonic_load_raw() { int enc_blck_size = RT_pana_info.bpp == 12 ? 10 : 9; diff --git a/rtengine/rawimage.cc b/rtengine/rawimage.cc index dc026d83a..d96993a0d 100644 --- a/rtengine/rawimage.cc +++ b/rtengine/rawimage.cc @@ -1,6 +1,8 @@ /* * This file is part of RawTherapee. * + * LibRaw integration adapted from ART. + * * Created on: 20/nov/2010 */ @@ -11,17 +13,24 @@ #include #endif +#include "image8.h" #include "rawimage.h" #include "settings.h" #include "camconst.h" #include "utils.h" #include "rtengine.h" +#include "libraw/libraw/libraw.h" namespace rtengine { + +extern MyMutex *librawMutex; + + RawImage::RawImage(const Glib::ustring &name) - : data(nullptr) + : DCraw() + , data(nullptr) , prefilters(0) , filename(name) , rotate_deg(0) @@ -29,9 +38,12 @@ RawImage::RawImage(const Glib::ustring &name) , allocation(nullptr) { memset(maximum_c4, 0, sizeof(maximum_c4)); + memset(white, 0, sizeof(white)); RT_matrix_from_constant = ThreeValBool::X; RT_blacklevel_from_constant = ThreeValBool::X; RT_whitelevel_from_constant = ThreeValBool::X; + memset(make, 0, sizeof(make)); + memset(model, 0, sizeof(model)); } RawImage::~RawImage() @@ -41,7 +53,7 @@ RawImage::~RawImage() ifp = nullptr; } - if (image) { + if (image && decoder == Decoder::DCRAW) { free(image); } @@ -66,6 +78,18 @@ RawImage::~RawImage() } } +void RawImage::pre_interpolate() +{ + int w = width, h = height; + if (decoder == Decoder::LIBRAW) { + width = iwidth; + height = iheight; + } + DCraw::pre_interpolate(); + width = w; + height = h; +} + eSensorType RawImage::getSensorType() const { if (isBayer()) { @@ -467,7 +491,176 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog // set the number of the frame to extract. If the number is larger then number of existing frames - 1, dcraw will handle that correctly shot_select = imageNum; - identify(); + + libraw.reset(new LibRaw()); + int libraw_error = [&]() -> int { + libraw->imgdata.params.use_camera_wb = 1; + + int err = libraw->open_buffer(ifp->data, ifp->size); + if (err == LIBRAW_FILE_UNSUPPORTED || err == LIBRAW_TOO_BIG) { + // fallback to the internal one + return err; + } else if (err != LIBRAW_SUCCESS && strncmp(libraw->imgdata.idata.software, "make_arq", 8) == 0) { + return err; + } else if (err == LIBRAW_FILE_UNSUPPORTED && (strncmp(libraw->unpack_function_name(), "sony_arq_load_raw", 17) == 0 || strncmp(libraw->imgdata.idata.software, "HDRMerge", 8) == 0)) { + return err; + } else if (err != LIBRAW_SUCCESS) { + decoder = Decoder::LIBRAW; + return err; + } else if (libraw->is_floating_point() && libraw->imgdata.idata.dng_version) { + return err; + } + + auto &d = libraw->imgdata.idata; + is_raw = d.raw_count; + strncpy(make, d.normalized_make, sizeof(make)-1); + make[sizeof(make)-1] = 0; + strncpy(model, d.normalized_model, sizeof(model)-1); + model[sizeof(model)-1] = 0; + RT_software = d.software; + dng_version = d.dng_version; + filters = d.filters; + is_foveon = d.is_foveon; + colors = d.colors; + tiff_bps = 0; + + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 6; ++j) { + xtrans[i][j] = d.xtrans[i][j]; + xtrans_abs[i][j] = d.xtrans_abs[i][j]; + } + } + auto &s = libraw->imgdata.sizes; + raw_width = s.raw_width; + raw_height = s.raw_height; + width = s.width; + height = s.height; + top_margin = s.top_margin; + left_margin = s.left_margin; + iheight = s.iheight; + iwidth = s.iwidth; + flip = s.flip; + + auto &o = libraw->imgdata.other; + iso_speed = o.iso_speed; + shutter = o.shutter; + aperture = o.aperture; + focal_len = o.focal_len; + timestamp = o.timestamp; + shot_order = o.shot_order; + + auto &io = libraw->imgdata.rawdata.ioparams; + shrink = io.shrink; + zero_is_bad = io.zero_is_bad; + fuji_width = io.fuji_width; + raw_color = io.raw_color; + mix_green = io.mix_green; + + auto &cd = libraw->imgdata.color; + black = cd.black; + maximum = cd.maximum; + tiff_bps = cd.raw_bps; + + for (size_t i = 0; i < sizeof(cblack)/sizeof(unsigned); ++i) { + cblack[i] = cd.cblack[i]; + } + for (int i = 0; i < 4; ++i) { + cam_mul[i] = cd.cam_mul[i]; + pre_mul[i] = cd.pre_mul[i]; + } + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 4; ++j) { + cmatrix[i][j] = cd.cmatrix[i][j]; + rgb_cam[i][j] = cd.rgb_cam[i][j]; + } + } + + for (int i = 0; i < 8; ++i) { + for (int j = 0; j < 8; ++j) { + white[i][j] = cd.white[i][j]; + } + } + + for (int i = 0; i < 8; ++i) { + for (int j = 0; j < 4; ++j) { + mask[i][j] = s.mask[i][j]; + } + } + + auto &mkn = libraw->imgdata.makernotes; + + // if (!strcmp(make, "Panasonic") && mkn.panasonic.BlackLevelDim > 0) { + // memset(cblack, 0, sizeof(cblack)); + // if (mkn.panasonic.BlackLevelDim >= 4) { + // for (size_t i = 0; i < 4; ++i) { + // cblack[i] = mkn.panasonic.BlackLevel[i]; + // } + // black = 0; + // } else { + // black = mkn.panasonic.BlackLevel[0]; + // } + // } else + if (!strcmp(make, "Canon") && isBayer() && !dng_version) { + if (mkn.canon.AverageBlackLevel) { + memset(cblack, 0, sizeof(cblack)); + for (size_t i = 0; i < 4; ++i) { + cblack[i] = mkn.canon.ChannelBlackLevel[i]; + } + black = 0; + } + if (mkn.canon.SpecularWhiteLevel) { + maximum = mkn.canon.SpecularWhiteLevel; + } else if (mkn.canon.NormalWhiteLevel) { + maximum = mkn.canon.NormalWhiteLevel; + } + } + while (tiff_bps < 16 && (size_t(1) << size_t(tiff_bps)) < maximum) { + ++tiff_bps; + } + + if (dng_version) { + RT_whitelevel_from_constant = ThreeValBool::F; + RT_blacklevel_from_constant = ThreeValBool::F; + if (!isBayer() && !isXtrans()) { + RT_matrix_from_constant = ThreeValBool::F; + } + } else if (strcmp(make, "Panasonic") != 0) { + RT_whitelevel_from_constant = ThreeValBool::T; + RT_blacklevel_from_constant = ThreeValBool::T; + } + + if (is_foveon) { + raw_width = width; + raw_height = height; + top_margin = 0; + left_margin = 0; + } + + decoder = Decoder::LIBRAW; + return err; + }(); + + if (libraw_error && verbose) { + printf("LibRaw could not load image."); + if (decoder == Decoder::DCRAW) { + printf(" Falling back to dcraw."); + } + printf("\n"); + } + + if (decoder == Decoder::LIBRAW) { + if (libraw_error) { + return libraw_error; + } + } else { + libraw->recycle(); + } + + if (decoder == Decoder::DCRAW) { + identify(); + } + // in case dcraw didn't handle the above mentioned case... shot_select = std::min(shot_select, std::max(is_raw, 1u) - 1); @@ -482,7 +675,7 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog return 2; } - if (!strcmp(make, "Fujifilm") && raw_height * raw_width * 2u != raw_size) { + if (decoder == Decoder::DCRAW && !strcmp(make, "Fujifilm") && raw_height * raw_width * 2u != raw_size) { if (raw_width * raw_height * 7u / 4u == raw_size) { load_raw = &RawImage::fuji_14bit_load_raw; } else { @@ -514,29 +707,87 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog iheight = height; iwidth = width; - if (filters || colors == 1) { - raw_image = (ushort *) calloc ((static_cast(raw_height) + 7u) * static_cast(raw_width), 2); - merror(raw_image, "main()"); - } + if (decoder == Decoder::DCRAW) { + if (filters || colors == 1) { + raw_image = (ushort *) calloc ((static_cast(raw_height) + 7u) * static_cast(raw_width), 2); + merror(raw_image, "main()"); + } - // dcraw needs this global variable to hold pixel data - image = (dcrawImage_t)calloc (static_cast(height) * static_cast(width) * sizeof * image + meta_length, 1); - if(!image) { - return 200; - } - meta_data = (char *) (image + static_cast(height) * static_cast(width)); + // dcraw needs this global variable to hold pixel data + image = (dcrawImage_t)calloc (static_cast(height) * static_cast(width) * sizeof * image + meta_length, 1); + if(!image) { + return 200; + } + meta_data = (char *) (image + static_cast(height) * static_cast(width)); - /* Issue 2467 - if (setjmp (failure)) { - if (image) { free (image); image=NULL; } - if (raw_image) { free(raw_image); raw_image=NULL; } - fclose(ifp); ifp=NULL; - return 100; - } - */ - // Load raw pixels data - fseek(ifp, data_offset, SEEK_SET); - (this->*load_raw)(); + /* Issue 2467 + if (setjmp (failure)) { + if (image) { free (image); image=NULL; } + if (raw_image) { free(raw_image); raw_image=NULL; } + fclose(ifp); ifp=NULL; + return 100; + } + */ + // Load raw pixels data + fseek(ifp, data_offset, SEEK_SET); + (this->*load_raw)(); + } else if (decoder == Decoder::LIBRAW) { + libraw->imgdata.rawparams.shot_select = shot_select; + + int err = libraw->open_buffer(ifp->data, ifp->size); + if (err) { + return err; + } + { +#ifdef LIBRAW_USE_OPENMP + MyMutex::MyLock lock(*librawMutex); +#endif + err = libraw->unpack(); + } + if (err) { + return err; + } + + auto &rd = libraw->imgdata.rawdata; + raw_image = rd.raw_image; + if (rd.float_image) { + float_raw_image = new float[raw_width * raw_height]; + for (int y = 0; y < raw_height; ++y) { + for (int x = 0; x < raw_width; ++x) { + size_t idx = y * raw_width + x; + float_raw_image[idx] = rd.float_image[idx]; + } + } + } else { +#ifdef LIBRAW_USE_OPENMP + MyMutex::MyLock lock(*librawMutex); +#endif + float_raw_image = nullptr; + err = libraw->raw2image(); + if (err) { + return err; + } + image = libraw->imgdata.image; + } + + // get our custom camera matrices, but don't mess with black/white levels yet + // (if we have custom levels in json files, we will get them later) + auto bl = RT_blacklevel_from_constant; + auto wl = RT_whitelevel_from_constant; + RT_blacklevel_from_constant = ThreeValBool::F; + RT_whitelevel_from_constant = ThreeValBool::F; + + adobe_coeff(make, model); + + RT_blacklevel_from_constant = bl; + RT_whitelevel_from_constant = wl; + + if (libraw->imgdata.color.profile_length) { + profile_length = libraw->imgdata.color.profile_length; + profile_data = new char[profile_length]; + memcpy(profile_data, libraw->imgdata.color.profile, profile_length); + } + } if (!float_raw_image) { // apply baseline exposure only for float DNGs RT_baseline_exposure = 0; @@ -551,6 +802,10 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog bool raw_crop_cc = false; int orig_raw_width = width; int orig_raw_height = height; + // For raw crop when using LibRaw. + int raw_top_margin = 0; + int raw_left_margin = 0; + bool adjust_margins = false; if (raw_image) { orig_raw_width = raw_width; @@ -561,33 +816,82 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog int lm, tm, w, h; cc->get_rawCrop(raw_width, raw_height, lm, tm, w, h); - if (isXtrans()) { - shiftXtransMatrix(6 - ((top_margin - tm) % 6), 6 - ((left_margin - lm) % 6)); + if ((w < 0 || h < 0) && decoder != Decoder::DCRAW) { + raw_crop_cc = false; } else { - if (((int)top_margin - tm) & 1) { // we have an odd border difference - filters = (filters << 4) | (filters >> 28); // left rotate filters by 4 bits + // protect against DNG files that are already cropped + if (int(raw_width) <= w+lm) { + lm = max(int(raw_width) - w, 0); + } + if (int(raw_height) <= h+tm) { + tm = max(int(raw_height) - h, 0); } - } - left_margin = lm; - top_margin = tm; + if (decoder == Decoder::DCRAW) { + if (isXtrans()) { + shiftXtransMatrix(6 - ((top_margin - tm) % 6), 6 - ((left_margin - lm) % 6)); + } else { + if (((int)top_margin - tm) & 1) { // we have an odd border difference + filters = (filters << 4) | (filters >> 28); // left rotate filters by 4 bits + } + } - if (w < 0) { - iwidth += w; - iwidth -= left_margin; - width += w; - width -= left_margin; - } else if (w > 0) { - iwidth = width = min((int)width, w); - } + left_margin = lm; + top_margin = tm; + } else { + if (lm < left_margin) { + lm = left_margin; + } + if (tm < top_margin) { + tm = top_margin; + } + // make sure we do not rotate filters + if (isXtrans()) { + if ((tm - top_margin) % 6) { + tm = top_margin; + } + if ((lm - left_margin) % 6) { + lm = left_margin; + } + } else { + if ((tm - top_margin) & 1) { + tm = top_margin; + } + if ((lm - left_margin) & 1) { + lm = left_margin; + } + } + raw_left_margin = lm - left_margin; + raw_top_margin = tm - top_margin; + } - if (h < 0) { - iheight += h; - iheight -= top_margin; - height += h; - height -= top_margin; - } else if (h > 0) { - iheight = height = min((int)height, h); + if (w < 0) { + iwidth += w; + iwidth -= left_margin; + width += w; + width -= left_margin; + } else if (w > 0) { + width = min((int)width, w); + if (decoder == Decoder::DCRAW) { + iwidth = width; + } else if (width > iwidth) { + width = iwidth; + } + } + + if (h < 0) { + iheight += h; + iheight -= top_margin; + height += h; + height -= top_margin; + } else if (h > 0) { + height = min((int)height, h); + if (decoder == Decoder::DCRAW) { + iheight = height; + } else if (height > iheight) { + height = iheight; + } + } } } @@ -597,11 +901,14 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog } } - crop_masked_pixels(); - free(raw_image); + if (decoder == Decoder::DCRAW) { + crop_masked_pixels(); + free(raw_image); + } raw_image = nullptr; + adjust_margins = !float_raw_image; //true; } else { - if (get_maker() == "Sigma" && cc && cc->has_rawCrop(width, height)) { // foveon images + if (decoder == Decoder::DCRAW && get_maker() == "Sigma" && cc && cc->has_rawCrop(width, height)) { // foveon images raw_crop_cc = true; int lm, tm, w, h; cc->get_rawCrop(width, height, lm, tm, w, h); @@ -611,21 +918,27 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog if (w < 0) { width += w; width -= left_margin; + iwidth += w; + iwidth -= left_margin; } else if (w > 0) { width = min((int)width, w); + iwidth = width; } if (h < 0) { height += h; height -= top_margin; + iheight += h; + iheight -= top_margin; } else if (h > 0) { height = min((int)height, h); + iheight = height; } } } // Load embedded profile - if (profile_length) { + if (decoder == Decoder::DCRAW && profile_length) { profile_data = new char[profile_length]; fseek(ifp, profile_offset, SEEK_SET); fread(profile_data, 1, profile_length, ifp); @@ -690,23 +1003,30 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog for (int c = 0; c < 4; c++) { if (static_cast(cblack[c]) < black_c4[c]) { cblack[c] = black_c4[c]; + cblack[4] = cblack[5] = 0; } } if (settings->verbose) { + const char *decoder_name = decoder == Decoder::DCRAW ? "dcraw" : decoder == Decoder::LIBRAW ? "libraw" : "unknown"; if (cc) { printf("constants exists for \"%s %s\" in camconst.json\n", make, model); } else { - printf("no constants in camconst.json exists for \"%s %s\" (relying only on dcraw defaults)\n", make, model); + printf("no constants in camconst.json exists for \"%s %s\" (relying only on %s defaults)\n", make, model, decoder_name); } printf("raw dimensions: %d x %d\n", orig_raw_width, orig_raw_height); - printf("black levels: R:%d G1:%d B:%d G2:%d (%s)\n", get_cblack(0), get_cblack(1), get_cblack(2), get_cblack(3), - black_from_cc ? "provided by camconst.json" : "provided by dcraw"); - printf("white levels: R:%d G1:%d B:%d G2:%d (%s)\n", get_white(0), get_white(1), get_white(2), get_white(3), - white_from_cc ? "provided by camconst.json" : "provided by dcraw"); - printf("raw crop: %d %d %d %d (provided by %s)\n", left_margin, top_margin, iwidth, iheight, raw_crop_cc ? "camconst.json" : "dcraw"); - printf("color matrix provided by %s\n", (cc && cc->has_dcrawMatrix()) ? "camconst.json" : "dcraw"); + printf("black levels: R:%d G1:%d B:%d G2:%d (provided by %s)\n", get_cblack(0), get_cblack(1), get_cblack(2), get_cblack(3), + black_from_cc ? "camconst.json" : decoder_name); + printf("white levels: R:%d G1:%d B:%d G2:%d (provided by %s)\n", get_white(0), get_white(1), get_white(2), get_white(3), + white_from_cc ? "camconst.json" : decoder_name); + printf("raw crop: %d %d %d %d (provided by %s)\n", left_margin, top_margin, iwidth, iheight, raw_crop_cc ? "camconst.json" : decoder_name); + printf("color matrix provided by %s\n", (cc && cc->has_dcrawMatrix()) ? "camconst.json" : decoder_name); + } + + if (adjust_margins) { + top_margin = raw_top_margin; + left_margin = raw_left_margin; } } @@ -779,7 +1099,7 @@ float** RawImage::compress_image(unsigned int frameNum, bool freeImage) for (int row = 0; row < height; row++) for (int col = 0; col < width; col++) { - this->data[row][col] = image[row * width + col][FC(row, col)]; + this->data[row][col] = image[(row + top_margin) * iwidth + col + left_margin][FC(row, col)]; } } else if (isXtrans()) { #ifdef _OPENMP @@ -788,7 +1108,7 @@ float** RawImage::compress_image(unsigned int frameNum, bool freeImage) for (int row = 0; row < height; row++) for (int col = 0; col < width; col++) { - this->data[row][col] = image[row * width + col][XTRANSFC(row, col)]; + this->data[row][col] = image[(row + top_margin) * iwidth + col + left_margin][XTRANSFC(row, col)]; } } else if (colors == 1) { #ifdef _OPENMP @@ -797,7 +1117,7 @@ float** RawImage::compress_image(unsigned int frameNum, bool freeImage) for (int row = 0; row < height; row++) for (int col = 0; col < width; col++) { - this->data[row][col] = image[row * width + col][0]; + this->data[row][col] = image[row * iwidth + col][0]; } } else { if((get_maker() == "Sigma" || get_maker() == "Pentax" || get_maker() == "Sony") && dng_version) { // Hack to prevent sigma dng files and dng files from PixelShift2DNG from crashing @@ -817,7 +1137,11 @@ float** RawImage::compress_image(unsigned int frameNum, bool freeImage) } if (freeImage) { - free(image); // we don't need this anymore + if (decoder == Decoder::DCRAW) { + free(image); // we don't need this anymore + } else if (decoder == Decoder::LIBRAW) { + libraw->recycle(); + } image = nullptr; } @@ -872,6 +1196,89 @@ RawImage::get_thumbSwap() const return (order == 0x4949) == (ntohs(0x1234) == 0x1234); } + +bool RawImage::checkThumbOk() const +{ + if (!is_supportedThumb()) { + return false; + } + + if (get_thumbOffset() >= get_file()->size) { + return false; + } + + const ssize_t length = + fdata (get_thumbOffset(), get_file())[1] != 0xD8 && is_ppmThumb() + ? get_thumbWidth() * get_thumbHeight() * (get_thumbBPS() / 8) * 3 + : get_thumbLength(); + + return get_thumbOffset() + length <= get_file()->size; +} + + +Image8 *RawImage::getThumbnail() const +{ + if (decoder == Decoder::DCRAW) { + if (!checkThumbOk()) { + return nullptr; + } + + Image8 *img = new Image8(); + img->setSampleFormat(IIOSF_UNSIGNED_CHAR); + img->setSampleArrangement(IIOSA_CHUNKY); + + const char *data = reinterpret_cast(fdata(get_thumbOffset(), get_file())); + + int err = 1; + if ((unsigned char)data[1] == 0xd8) { + err = img->loadJPEGFromMemory(data, get_thumbLength()); + } else if (is_ppmThumb()) { + err = img->loadPPMFromMemory(data, get_thumbWidth(), get_thumbHeight(), get_thumbSwap(), get_thumbBPS()); + } + + // did we succeed? + if (err) { + delete img; + img = nullptr; + } + + return img; + } + + if (!ifp) { + return nullptr; + } else { + int err = libraw->unpack_thumb(); + if (err) { + return nullptr; + } + auto &t = libraw->imgdata.thumbnail; + if (!t.thumb) { + return nullptr; + } else if (t.tformat != LIBRAW_THUMBNAIL_JPEG && t.tformat != LIBRAW_THUMBNAIL_BITMAP) { + return nullptr; + } else { + Image8 *img = new Image8(); + img->setSampleFormat(IIOSF_UNSIGNED_CHAR); + img->setSampleArrangement(IIOSA_CHUNKY); + if (t.tformat == LIBRAW_THUMBNAIL_JPEG) { + err = img->loadJPEGFromMemory(t.thumb, t.tlength); + } else { + err = img->loadPPMFromMemory(t.thumb, t.twidth, t.theight, false, 8); + } + if (err) { + delete img; + return nullptr; + } else { + return img; + } + } + } + + return nullptr; +} + + } //namespace rtengine bool diff --git a/rtengine/rawimage.h b/rtengine/rawimage.h index 2b1cd2156..19d3a4bc6 100644 --- a/rtengine/rawimage.h +++ b/rtengine/rawimage.h @@ -21,14 +21,23 @@ #include #include #include +#include #include #include "dcraw.h" #include "imageformat.h" + +class LibRaw; + + namespace rtengine { + +class Image8; + + class RawImage: public DCraw { public: @@ -57,11 +66,18 @@ public: double getBaselineExposure() const { return RT_baseline_exposure; } protected: + enum class Decoder { + DCRAW, + LIBRAW, + }; + Glib::ustring filename; // complete filename int rotate_deg; // 0,90,180,270 degree of rotation: info taken by dcraw from exif char* profile_data; // Embedded ICC color profile float* allocation; // pointer to allocated memory int maximum_c4[4]; + Decoder decoder{Decoder::DCRAW}; + std::unique_ptr libraw; bool isFoveon() const { return is_foveon; @@ -261,10 +277,7 @@ public: public: // dcraw functions - void pre_interpolate() - { - DCraw::pre_interpolate(); - } + void pre_interpolate(); public: bool ISRED(unsigned row, unsigned col) const @@ -304,6 +317,11 @@ public: { return dng_version; } + +public: + bool checkThumbOk() const; + Image8 *getThumbnail() const; + }; } diff --git a/rtengine/rtthumbnail.cc b/rtengine/rtthumbnail.cc index bc832661a..f497dce47 100644 --- a/rtengine/rtthumbnail.cc +++ b/rtengine/rtthumbnail.cc @@ -495,32 +495,14 @@ Thumbnail* Thumbnail::loadQuickFromRaw (const Glib::ustring& fname, eSensorType sensorType = ri->getSensorType(); - Image8* img = new Image8 (); - // No sample format detection occurred earlier, so we set them here, - // as they are mandatory for the setScanline method - img->setSampleFormat (IIOSF_UNSIGNED_CHAR); - img->setSampleArrangement (IIOSA_CHUNKY); - - int err = 1; - - // See if it is something we support - if (checkRawImageThumb (*ri)) { - const char* data ((const char*)fdata (ri->get_thumbOffset(), ri->get_file())); - - if ( (unsigned char)data[1] == 0xd8 ) { - err = img->loadJPEGFromMemory (data, ri->get_thumbLength()); - } else if (ri->is_ppmThumb()) { - err = img->loadPPMFromMemory (data, ri->get_thumbWidth(), ri->get_thumbHeight(), ri->get_thumbSwap(), ri->get_thumbBPS()); - } - } + Image8 *img = ri->getThumbnail(); // did we succeed? - if ( err ) { + if (!img) { if (settings->verbose) { std::cout << "Could not extract thumb from " << fname.c_str() << std::endl; } delete tpp; - delete img; delete ri; return nullptr; } @@ -627,6 +609,11 @@ Thumbnail* Thumbnail::loadFromRaw (const Glib::ustring& fname, eSensorType &sens int width = ri->get_width(); int height = ri->get_height(); + int iwidth = ri->get_iwidth(); + int iheight = ri->get_iheight(); + int left_margin = ri->get_leftmargin(); + int top_margin = ri->get_topmargin(); + rtengine::Thumbnail* tpp = new rtengine::Thumbnail; tpp->isRaw = true; @@ -717,19 +704,19 @@ Thumbnail* Thumbnail::loadFromRaw (const Glib::ustring& fname, eSensorType &sens if (ri->getSensorType() == ST_BAYER) { // demosaicing! (sort of) for (int row = 1, y = 0; row < height - 1 && y < tmph; row += vskip, y++) { - rofs = row * width; + rofs = (row + top_margin) * iwidth; for (int col = firstgreen, x = 0; col < width - 1 && x < tmpw; col += hskip, x++) { - int ofs = rofs + col; + int ofs = rofs + col + left_margin; int g = image[ofs][1]; int r, b; if (FISRED (filter, row, col + 1)) { r = (image[ofs + 1 ][0] + image[ofs - 1 ][0]) >> 1; - b = (image[ofs + width][2] + image[ofs - width][2]) >> 1; + b = (image[ofs + iwidth][2] + image[ofs - iwidth][2]) >> 1; } else { b = (image[ofs + 1 ][2] + image[ofs - 1 ][2]) >> 1; - r = (image[ofs + width][0] + image[ofs - width][0]) >> 1; + r = (image[ofs + iwidth][0] + image[ofs - iwidth][0]) >> 1; } tmpImg->r (y, x) = r; @@ -739,28 +726,28 @@ Thumbnail* Thumbnail::loadFromRaw (const Glib::ustring& fname, eSensorType &sens } } else if (ri->get_colors() == 1) { for (int row = 1, y = 0; row < height - 1 && y < tmph; row += vskip, y++) { - rofs = row * width; + rofs = (row + top_margin) * iwidth; for (int col = firstgreen, x = 0; col < width - 1 && x < tmpw; col += hskip, x++) { - int ofs = rofs + col; + int ofs = rofs + col + left_margin; tmpImg->r (y, x) = tmpImg->g (y, x) = tmpImg->b (y, x) = image[ofs][0]; } } } else { if (ri->getSensorType() == ST_FUJI_XTRANS) { for ( int row = 1, y = 0; row < height - 1 && y < tmph; row += vskip, y++) { - rofs = row * width; + rofs = (row + top_margin) * iwidth; for ( int col = 1, x = 0; col < width - 1 && x < tmpw; col += hskip, x++ ) { - int ofs = rofs + col; + int ofs = rofs + col + left_margin; float sum[3] = {}; int c; for (int v = -1; v <= 1; v++) { for (int h = -1; h <= 1; h++) { c = ri->XTRANSFC (row + v, col + h); - sum[c] += image[ofs + v * width + h][c]; + sum[c] += image[ofs + v * iwidth + h][c]; } } @@ -788,11 +775,11 @@ Thumbnail* Thumbnail::loadFromRaw (const Glib::ustring& fname, eSensorType &sens } } } else { - int iwidth = ri->get_iwidth(); - int iheight = ri->get_iheight(); - int left_margin = ri->get_leftmargin(); + // int iwidth = ri->get_iwidth(); + // int iheight = ri->get_iheight(); + // int left_margin = ri->get_leftmargin(); firstgreen += left_margin; - int top_margin = ri->get_topmargin(); + // int top_margin = ri->get_topmargin(); int wmax = tmpw; int hmax = tmph;