diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 296037812..f6f83f567 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -159,7 +159,7 @@ jobs: "libstdc++-6.dll" \ "libsystre-0.dll" \ "libthai-0.dll" \ - "libtiff-5.dll" \ + "libtiff-6.dll" \ "libtre-5.dll" \ "libwebp-7.dll" \ "libwinpthread-1.dll" \ diff --git a/README.md b/README.md index ae9efd9c8..21f219a83 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -![RawTherapee logo](https://www.rawtherapee.com/images/logos/rawtherapee_logo_discuss.png) +![RawTherapee logo](https://raw.githubusercontent.com/Beep6581/RawTherapee/dev/rtdata/images/rt-logo-text-black.svg) + +![RawTherapee screenshot](http://rawtherapee.com/images/carousel/100_rt59_provence_local_maskxxx.jpg) RawTherapee is a powerful, cross-platform raw photo processing program, released as [libre software](https://en.wikipedia.org/wiki/Free_software) under the [GNU General Public License Version 3](https://opensource.org/licenses/gpl-3.0.html). It is written mostly in C++ using a [GTK+](https://www.gtk.org) front-end. It uses a patched version of [dcraw](https://www.dechifro.org/dcraw/) for reading raw files, with an in-house solution which adds the highest quality support for certain camera models unsupported by dcraw and enhances the accuracy of certain raw files already supported by dcraw. It is notable for the advanced control it gives the user over the demosaicing and development process. diff --git a/rtdata/dcpprofiles/FUJIFILM DBP for GX680.dcp b/rtdata/dcpprofiles/FUJIFILM DBP for GX680.dcp new file mode 100644 index 000000000..dcfc174c7 Binary files /dev/null and b/rtdata/dcpprofiles/FUJIFILM DBP for GX680.dcp differ diff --git a/rtdata/languages/default b/rtdata/languages/default index 61cfb97a4..2631278e5 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -61,6 +61,7 @@ EXIFFILTER_IMAGETYPE;Image type EXIFFILTER_ISO;ISO EXIFFILTER_LENS;Lens EXIFFILTER_METADATAFILTER;Enable metadata filters +EXIFFILTER_PATH;File path EXIFFILTER_SHUTTER;Shutter EXIFPANEL_ADDEDIT;Add/Edit EXIFPANEL_ADDEDITHINT;Add new tag or edit tag. @@ -165,6 +166,7 @@ FILEBROWSER_POPUPREMOVE;Delete permanently FILEBROWSER_POPUPREMOVEINCLPROC;Delete permanently, including queue-processed version FILEBROWSER_POPUPRENAME;Rename FILEBROWSER_POPUPSELECTALL;Select all +FILEBROWSER_POPUPSORTBY;Sort Files FILEBROWSER_POPUPTRASH;Move to trash FILEBROWSER_POPUPUNRANK;Unrank FILEBROWSER_POPUPUNTRASH;Remove from trash @@ -1406,6 +1408,7 @@ HISTORY_MSG_DEHAZE_STRENGTH;Dehaze - Strength HISTORY_MSG_DUALDEMOSAIC_AUTO_CONTRAST;Dual demosaic - Auto threshold HISTORY_MSG_DUALDEMOSAIC_CONTRAST;Dual demosaic - Contrast threshold HISTORY_MSG_EDGEFFECT;Edge Attenuation response +HISTORY_MSG_FF_FROMMETADATA;Flat-Field - From Metadata HISTORY_MSG_FILMNEGATIVE_BALANCE;FN - Reference output HISTORY_MSG_FILMNEGATIVE_COLORSPACE;Film negative color space HISTORY_MSG_FILMNEGATIVE_ENABLED;Film Negative @@ -1736,6 +1739,7 @@ PARTIALPASTE_EXPOSURE;Exposure PARTIALPASTE_FILMNEGATIVE;Film negative PARTIALPASTE_FILMSIMULATION;Film simulation PARTIALPASTE_FLATFIELDAUTOSELECT;Flat-field auto-selection +PARTIALPASTE_FLATFIELDFROMMETADATA;Flat-field from Metadata PARTIALPASTE_FLATFIELDBLURRADIUS;Flat-field blur radius PARTIALPASTE_FLATFIELDBLURTYPE;Flat-field blur type PARTIALPASTE_FLATFIELDCLIPCONTROL;Flat-field clip control @@ -2065,6 +2069,13 @@ SAVEDLG_WARNFILENAME;File will be named SHCSELECTOR_TOOLTIP;Click right mouse button to reset the position of those 3 sliders. SOFTPROOF_GAMUTCHECK_TOOLTIP;Highlight pixels with out-of-gamut colors with respect to:\n- the printer profile, if one is set and soft-proofing is enabled,\n- the output profile, if a printer profile is not set and soft-proofing is enabled,\n- the monitor profile, if soft-proofing is disabled. SOFTPROOF_TOOLTIP;Soft-proofing simulates the appearance of the image:\n- when printed, if a printer profile is set in Preferences > Color Management,\n- when viewed on a display that uses the current output profile, if a printer profile is not set. +SORT_ASCENDING;Ascending +SORT_BY_NAME;By Name +SORT_BY_DATE;By Date +SORT_BY_EXIF;By EXIF +SORT_BY_RANK;By Rank +SORT_BY_LABEL;By Color Label +SORT_DESCENDING;Descending TC_PRIM_BLUX;Bx TC_PRIM_BLUY;By TC_PRIM_GREX;Gx @@ -2478,6 +2489,7 @@ TP_FLATFIELD_BT_VERTHORIZ;Vertical + Horizontal TP_FLATFIELD_BT_VERTICAL;Vertical TP_FLATFIELD_CLIPCONTROL;Clip control TP_FLATFIELD_CLIPCONTROL_TOOLTIP;Clip control avoids clipped highlights caused by applying the flat field. If there are already clipped highlights before applying the flat field, value 0 is used. +TP_FLATFIELD_FROMMETADATA;From Metadata TP_FLATFIELD_LABEL;Flat-Field TP_GENERAL_11SCALE_TOOLTIP;The effects of this tool are only visible or only accurate at a preview scale of 1:1. TP_GRADIENT_CENTER;Center diff --git a/rtengine/CMakeLists.txt b/rtengine/CMakeLists.txt index 0fab84b55..c657d6f9d 100644 --- a/rtengine/CMakeLists.txt +++ b/rtengine/CMakeLists.txt @@ -176,6 +176,18 @@ if(LENSFUN_HAS_LOAD_DIRECTORY) set_source_files_properties(rtlensfun.cc PROPERTIES COMPILE_DEFINITIONS RT_LENSFUN_HAS_LOAD_DIRECTORY) endif() +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.0") + # procparams.cc takes a long time to compile with optimizations starting + # with GCC 12.1 due to PTA (see issue #6548) + get_source_file_property(PROCPARAMS_COMPILE_OPTIONS procparams.cc COMPILE_OPTIONS) + if(PROCPARAMS_COMPILE_OPTIONS STREQUAL "NOTFOUND") + set(PROCPARAMS_COMPILE_OPTIONS "") + else() + set(PROCPARAMS_COMPILE_OPTIONS "${PROCPARAMS_COMPILE_OPTIONS};") + endif() + set(PROCPARAMS_COMPILE_OPTIONS "${PROCPARAMS_COMPILE_OPTIONS}-fno-tree-pta") + set_source_files_properties(procparams.cc PROPERTIES COMPILE_OPTIONS ${PROCPARAMS_COMPILE_OPTIONS}) +endif() if(WITH_BENCHMARK) add_definitions(-DBENCHMARK) diff --git a/rtengine/array2D.h b/rtengine/array2D.h index 10d797999..eee6c3210 100644 --- a/rtengine/array2D.h +++ b/rtengine/array2D.h @@ -248,6 +248,14 @@ public: return *this; } + // import from flat data + void operator()(std::size_t w, std::size_t h, const T* const copy) + { + ar_realloc(w, h); + for (std::size_t y = 0; y < h; ++y) { + std::copy(copy + y * w, copy + y * w + w, rows.data()[y]); + } + } int getWidth() const { diff --git a/rtengine/camconst.cc b/rtengine/camconst.cc index aab2a252c..64fc4d4ba 100644 --- a/rtengine/camconst.cc +++ b/rtengine/camconst.cc @@ -28,8 +28,6 @@ namespace rtengine CameraConst::CameraConst() : pdafOffset(0) { memset(dcraw_matrix, 0, sizeof(dcraw_matrix)); - memset(raw_crop, 0, sizeof(raw_crop)); - memset(raw_mask, 0, sizeof(raw_mask)); white_max = 0; globalGreenEquilibration = -1; } @@ -192,6 +190,68 @@ CameraConst* CameraConst::parseEntry(const void *cJSON_, const char *make_model) std::unique_ptr cc(new CameraConst); cc->make_model = make_model; + const auto get_raw_crop = + [](int w, int h, const cJSON *ji, CameraConst *cc) -> bool + { + std::array rc; + + if (ji->type != cJSON_Array) { + //fprintf(stderr, "\"raw_crop\" must be an array\n"); + return false; + } + + int i; + + for (i = 0, ji = ji->child; i < 4 && ji != nullptr; i++, ji = ji->next) { + if (ji->type != cJSON_Number) { + //fprintf(stderr, "\"raw_crop\" array must contain numbers\n"); + return false; + } + + //cc->raw_crop[i] = ji->valueint; + rc[i] = ji->valueint; + } + + if (i != 4 || ji != nullptr) { + //fprintf(stderr, "\"raw_crop\" must contain 4 numbers\n"); + return false; + } + + cc->raw_crop[std::make_pair(w, h)] = rc; + return true; + }; + + const auto get_masked_areas = + [](int w, int h, const cJSON *ji, CameraConst *cc) -> bool + { + std::array, 2> rm; + + if (ji->type != cJSON_Array) { + //fprintf(stderr, "\"masked_areas\" must be an array\n"); + return false; + } + + int i; + + for (i = 0, ji = ji->child; i < 2 * 4 && ji != nullptr; i++, ji = ji->next) { + if (ji->type != cJSON_Number) { + //fprintf(stderr, "\"masked_areas\" array must contain numbers\n"); + return false; + } + + //cc->raw_mask[i / 4][i % 4] = ji->valueint; + rm[i / 4][i % 4] = ji->valueint; + } + + if (i % 4 != 0) { + //fprintf(stderr, "\"masked_areas\" array length must be divisable by 4\n"); + return false; + } + + cc->raw_mask[std::make_pair(w, h)] = rm; + return true; + }; + const cJSON *ji = cJSON_GetObjectItem(js, "dcraw_matrix"); if (ji) { @@ -216,24 +276,32 @@ CameraConst* CameraConst::parseEntry(const void *cJSON_, const char *make_model) if (ji) { if (ji->type != cJSON_Array) { - fprintf(stderr, "\"raw_crop\" must be an array\n"); + fprintf(stderr, "invalid entry for raw_crop.\n"); return nullptr; - } - - int i; - - for (i = 0, ji = ji->child; i < 4 && ji; i++, ji = ji->next) { - if (ji->type != cJSON_Number) { - fprintf(stderr, "\"raw_crop\" array must contain numbers\n"); - return nullptr; + } else if (!get_raw_crop(0, 0, ji, cc.get())) { + cJSON *je; + cJSON_ArrayForEach(je, ji) { + if (!cJSON_IsObject(je)) { + fprintf(stderr, "invalid entry for raw_crop.\n"); + return nullptr; + } else { + auto js = cJSON_GetObjectItem(je, "frame"); + if (!js || js->type != cJSON_Array || + cJSON_GetArraySize(js) != 2 || + !cJSON_IsNumber(cJSON_GetArrayItem(js, 0)) || + !cJSON_IsNumber(cJSON_GetArrayItem(js, 1))) { + fprintf(stderr, "invalid entry for raw_crop.\n"); + return nullptr; + } + int w = cJSON_GetArrayItem(js, 0)->valueint; + int h = cJSON_GetArrayItem(js, 1)->valueint; + js = cJSON_GetObjectItem(je, "crop"); + if (!js || !get_raw_crop(w, h, js, cc.get())) { + fprintf(stderr, "invalid entry for raw_crop.\n"); + return nullptr; + } + } } - - cc->raw_crop[i] = ji->valueint; - } - - if (i != 4 || ji) { - fprintf(stderr, "\"raw_crop\" must contain 4 numbers\n"); - return nullptr; } } @@ -241,24 +309,32 @@ CameraConst* CameraConst::parseEntry(const void *cJSON_, const char *make_model) if (ji) { if (ji->type != cJSON_Array) { - fprintf(stderr, "\"masked_areas\" must be an array\n"); + fprintf(stderr, "invalid entry for masked_areas.\n"); return nullptr; - } - - int i; - - for (i = 0, ji = ji->child; i < 2 * 4 && ji; i++, ji = ji->next) { - if (ji->type != cJSON_Number) { - fprintf(stderr, "\"masked_areas\" array must contain numbers\n"); - return nullptr; + } else if (!get_masked_areas(0, 0, ji, cc.get())) { + cJSON *je; + cJSON_ArrayForEach(je, ji) { + if (!cJSON_IsObject(je)) { + fprintf(stderr, "invalid entry for masked_areas.\n"); + return nullptr; + } else { + auto js = cJSON_GetObjectItem(je, "frame"); + if (!js || js->type != cJSON_Array || + cJSON_GetArraySize(js) != 2 || + !cJSON_IsNumber(cJSON_GetArrayItem(js, 0)) || + !cJSON_IsNumber(cJSON_GetArrayItem(js, 1))) { + fprintf(stderr, "invalid entry for masked_areas.\n"); + return nullptr; + } + int w = cJSON_GetArrayItem(js, 0)->valueint; + int h = cJSON_GetArrayItem(js, 1)->valueint; + js = cJSON_GetObjectItem(je, "areas"); + if (!js || !get_masked_areas(w, h, js, cc.get())) { + fprintf(stderr, "invalid entry for masked_areas.\n"); + return nullptr; + } + } } - - cc->raw_mask[i / 4][i % 4] = ji->valueint; - } - - if (i % 4 != 0) { - fprintf(stderr, "\"masked_areas\" array length must be divisible by 4\n"); - return nullptr; } } @@ -399,29 +475,41 @@ void CameraConst::update_pdafOffset(int other) pdafOffset = other; } -bool CameraConst::has_rawCrop() const + +bool CameraConst::has_rawCrop(int raw_width, int raw_height) const { - return raw_crop[0] != 0 || raw_crop[1] != 0 || raw_crop[2] != 0 || raw_crop[3] != 0; + return raw_crop.find(std::make_pair(raw_width, raw_height)) != raw_crop.end() || raw_crop.find(std::make_pair(0, 0)) != raw_crop.end(); } -void CameraConst::get_rawCrop(int& left_margin, int& top_margin, int& width, int& height) const + +void CameraConst::get_rawCrop(int raw_width, int raw_height, int &left_margin, int &top_margin, int &width, int &height) const { - left_margin = raw_crop[0]; - top_margin = raw_crop[1]; - width = raw_crop[2]; - height = raw_crop[3]; + auto it = raw_crop.find(std::make_pair(raw_width, raw_height)); + if (it == raw_crop.end()) { + it = raw_crop.find(std::make_pair(0, 0)); + } + if (it != raw_crop.end()) { + left_margin = it->second[0]; + top_margin = it->second[1]; + width = it->second[2]; + height = it->second[3]; + } else { + left_margin = top_margin = width = height = 0; + } } -bool CameraConst::has_rawMask(int idx) const + +bool CameraConst::has_rawMask(int raw_width, int raw_height, int idx) const { if (idx < 0 || idx > 1) { return false; } - return (raw_mask[idx][0] | raw_mask[idx][1] | raw_mask[idx][2] | raw_mask[idx][3]) != 0; + return raw_mask.find(std::make_pair(raw_width, raw_height)) != raw_mask.end() || raw_mask.find(std::make_pair(0, 0)) != raw_mask.end(); } -void CameraConst::get_rawMask(int idx, int& top, int& left, int& bottom, int& right) const + +void CameraConst::get_rawMask(int raw_width, int raw_height, int idx, int &top, int &left, int &bottom, int &right) const { top = left = bottom = right = 0; @@ -429,10 +517,17 @@ void CameraConst::get_rawMask(int idx, int& top, int& left, int& bottom, int& ri return; } - top = raw_mask[idx][0]; - left = raw_mask[idx][1]; - bottom = raw_mask[idx][2]; - right = raw_mask[idx][3]; + auto it = raw_mask.find(std::make_pair(raw_width, raw_height)); + if (it == raw_mask.end()) { + it = raw_mask.find(std::make_pair(0, 0)); + } + + if (it != raw_mask.end()) { + top = it->second[idx][0]; + left = it->second[idx][1]; + bottom = it->second[idx][2]; + right = it->second[idx][3]; + } } void CameraConst::update_Levels(const CameraConst *other) @@ -464,9 +559,7 @@ void CameraConst::update_Crop(CameraConst *other) return; } - if (other->has_rawCrop()) { - other->get_rawCrop(raw_crop[0], raw_crop[1], raw_crop[2], raw_crop[3]); - } + raw_crop.insert(other->raw_crop.begin(), other->raw_crop.end()); } bool CameraConst::get_Levels(camera_const_levels & lvl, int bw, int iso, float fnumber) const diff --git a/rtengine/camconst.h b/rtengine/camconst.h index aa0702439..273bdd7a1 100644 --- a/rtengine/camconst.h +++ b/rtengine/camconst.h @@ -1,9 +1,11 @@ -/* +/* -*- C++ -*- + * * This file is part of RawTherapee. */ #pragma once #include +#include #include #include @@ -17,17 +19,17 @@ class ustring; namespace rtengine { -struct camera_const_levels { - int levels[4]; -}; - class CameraConst final { private: + struct camera_const_levels { + int levels[4]; + }; + std::string make_model; short dcraw_matrix[12]; - int raw_crop[4]; - int raw_mask[2][4]; + std::map, std::array> raw_crop; + std::map, std::array, 2>> raw_mask; int white_max; std::map mLevels[2]; std::map mApertureScaling; @@ -47,10 +49,10 @@ public: const short *get_dcrawMatrix(void) const; const std::vector& get_pdafPattern() const; int get_pdafOffset() const {return pdafOffset;}; - bool has_rawCrop(void) const; - void get_rawCrop(int& left_margin, int& top_margin, int& width, int& height) const; - bool has_rawMask(int idx) const; - void get_rawMask(int idx, int& top, int& left, int& bottom, int& right) const; + bool has_rawCrop(int raw_width, int raw_height) const; + void get_rawCrop(int raw_width, int raw_height, int& left_margin, int& top_margin, int& width, int& height) const; + bool has_rawMask(int raw_width, int raw_height, int idx) const; + void get_rawMask(int raw_width, int raw_height, int idx, int& top, int& left, int& bottom, int& right) const; int get_BlackLevel(int idx, int iso_speed) const; int get_WhiteLevel(int idx, int iso_speed, float fnumber) const; bool has_globalGreenEquilibration() const; @@ -77,4 +79,5 @@ public: const CameraConst *get(const char make[], const char model[]) const; }; -} +} // namespace rtengine + diff --git a/rtengine/camconst.json b/rtengine/camconst.json index 189d8f3f4..7a143e850 100644 --- a/rtengine/camconst.json +++ b/rtengine/camconst.json @@ -70,6 +70,14 @@ Examples: // cropped so the "negative number" way is not totally safe. "raw_crop": [ 10, 20, 4000, 3000 ], + // multi-aspect support (added 2020-12-03) + // "frame" defines the full dimensions the crop applies to + // (with [0, 0] being the fallback crop if none of the other applies) + "raw_crop" : [ + { "frame" : [4100, 3050], "crop": [10, 20, 4050, 3020] }, + { "frame" : [0, 0], "crop": [10, 20, 4000, 3000] } + ] + // Almost same as MaskedAreas DNG tag, used for black level measuring. Here up to two areas can be defined // by tetrads of numbers: "masked_areas": [ 51, 2, 3804, 156, 51, 5794, 3804, 5792 ], @@ -84,6 +92,14 @@ Examples: // instead, to take care of possible light leaks from the light sensing area to the optically black (masked) // area or sensor imperfections at the outer borders. + // multi-aspect support (added 2020-12-03) + // "frame" defines the full dimensions the masked areas apply to + // (with [0, 0] being the fallback crop if none of the other applies) + "masked_areas" : [ + { "frame" : [4100, 3050], "areas": [10, 20, 4050, 3020] }, + { "frame" : [0, 0], "areas": [10, 20, 4000, 3000] } + ] + // list of indices of the rows with on-sensor PDAF pixels, for cameras that have such features. The indices here form a pattern that is repeated for the whole height of the sensor. The values are relative to the "pdaf_offset" value (see below) "pdaf_pattern" : [ 0,12,36,54,72,90,114,126,144,162,180,204,216,240,252,270,294,306,324,342,366,384,396,414,432,450,474,492,504,522,540,564,576,594,606,630 ], // index of the first row of the PDAF pattern in the sensor (0 is the topmost row). Allowed to be negative for convenience (this means that the first repetition of the pattern doesn't start from the first row) @@ -1210,33 +1226,50 @@ Camera constants: { // Quality C "make_model": "Canon EOS R3", - "dcraw_matrix" : [9423,-2839,-1195,-4532,12377,2415,-483,1374,5276] + "dcraw_matrix" : [ 9423, -2839, -1195, -4532, 12377, 2415, -483, 1374, 5276 ], + "raw_crop": [ 160, 120, 6024, 4024 ] }, { // Quality C "make_model": "Canon EOS R5", "dcraw_matrix" : [9766, -2953, -1254, -4276, 12116, 2433, -437, 1336, 5131], - "raw_crop" : [ 128, 96, 8224, 5490 ], - "masked_areas" : [ 94, 20, 5578, 122 ], + "raw_crop" : [ + { "frame" : [ 8352, 5586 ], "crop" : [ 128, 96, 8224, 5490 ] }, + { "frame" : [ 5248, 3510 ], "crop" : [ 128, 96, 5120, 3382 ] } + ], + "masked_areas" : [ + { "frame" : [ 8352, 5586 ], "areas": [ 94, 20, 5578, 122 ] }, + { "frame" : [ 5248, 3510 ], "areas": [ 94, 20, 3510, 122 ] } + ], "ranges" : { "white" : 16382 } }, { // Quality C "make_model": "Canon EOS R6", "dcraw_matrix" : [8293, -1611, -1132, -4759, 12710, 2275, -1013, 2415, 5508], - "raw_crop": [ 72, 38, 5496, 3670 ], - "masked_areas" : [ 40, 10, 5534, 70 ], + "raw_crop": [ + { "frame": [5568, 3708], "crop" : [ 72, 38, 5496, 3670 ] }, + { "frame": [3584, 2386], "crop" : [ 156, 108, 3404, 2270 ] } + ], + "masked_areas" : [ + { "frame": [5568, 3708], "areas": [ 40, 10, 5534, 70 ] }, + { "frame": [3584, 2386], "areas": [ 40, 10, 2374, 110 ] } + ], "ranges" : { "white" : 16382 } }, { // Quality C "make_model": "Canon EOS R7", - "dcraw_matrix" : [10424, -3138, -1300, -4221, 11938, 2584, -547, 1658, 6183] + "dcraw_matrix" : [10424, -3138, -1300, -4221, 11938, 2584, -547, 1658, 6183], + "raw_crop": [ 144, 72, 6984, 4660 ], + "masked_areas" : [ 70, 20, 4724, 138 ] }, { // Quality C "make_model": "Canon EOS R10", - "dcraw_matrix" : [9269, -2012, -1107, -3990, 11762, 2527, -569, 2093, 4913] + "dcraw_matrix" : [9269, -2012, -1107, -3990, 11762, 2527, -569, 2093, 4913], + "raw_crop": [ 144, 40, 6048, 4020 ], + "masked_areas" : [ 38, 20, 4052, 138 ] }, { // Quality C, CHDK DNGs, raw frame correction @@ -1377,7 +1410,11 @@ Camera constants: { // Quality C "make_model": [ "FUJIFILM GFX 100", "FUJIFILM GFX100S" ], "dcraw_matrix" : [ 16212, -8423, -1583, -4336, 12583, 1937, -195, 726, 6199 ], // taken from ART - "raw_crop": [ 0, 2, 11664, 8734 ] + "raw_crop": [ + // multi-aspect crop to account for 16-shot pixel shift images + { "frame" : [11808, 8754], "crop" : [ 0, 2, 11664, 8734 ] }, + { "frame" : [23616, 17508], "crop" : [ 0, 4, 23328, 17468 ] } + ] }, { // Quality B @@ -1549,6 +1586,12 @@ Camera constants: "raw_crop": [ 0, 5, 7752, 5184 ] }, + { // Quality C + "make_model": "FUJIFILM DBP for GX680", + "dcraw_matrix": [ 12741, -4916, -1420, -8510, 16791, 1715, -1767, 2302, 7771 ], // same as S2Pro as per LibRaw + "ranges": { "white": 4096, "black": 132 } + }, + { // Quality C, Leica C-Lux names can differ? "make_model" : [ "LEICA C-LUX", "LEICA CAM-DC25" ], "dcraw_matrix" : [7790, -2736, -755, -3452, 11870, 1769, -628, 1647, 4898] @@ -2988,7 +3031,10 @@ Camera constants: { // Quality B, correction for frame width "make_model": [ "Sony ILCE-7S", "Sony ILCE-7SM2" ], "dcraw_matrix": [ 5838,-1430,-246,-3497,11477,2297,-748,1885,5778 ], // DNG_v9.2 D65 - "raw_crop": [ 0, 0, 4254, 2848 ], + "raw_crop" : [ + { "frame" : [ 2816, 1872 ], "crop" : [ 0, 0, 2792, 1872 ] }, + { "frame" : [ 4254, 2848 ], "crop" : [ 0, 0, 4254, 2848 ] } + ], "ranges": { "black": 512, "white": 16300 } }, diff --git a/rtengine/dcraw.cc b/rtengine/dcraw.cc index a42b377d4..8eca727b4 100644 --- a/rtengine/dcraw.cc +++ b/rtengine/dcraw.cc @@ -2464,6 +2464,30 @@ void CLASS unpacked_load_raw() } } +// RT - from LibRaw +void CLASS unpacked_load_raw_FujiDBP() +/* +for Fuji DBP for GX680, aka DX-2000 + DBP_tile_width = 688; + DBP_tile_height = 3856; + DBP_n_tiles = 8; +*/ +{ + int scan_line, tile_n; + int nTiles = 8; + tile_width = raw_width / nTiles; + ushort *tile; + tile = (ushort *) calloc(raw_height, tile_width * 2); + for (tile_n = 0; tile_n < nTiles; tile_n++) { + read_shorts(tile, tile_width * raw_height); + for (scan_line = 0; scan_line < raw_height; scan_line++) { + memcpy(&raw_image[scan_line * raw_width + tile_n * tile_width], + &tile[scan_line * tile_width], tile_width * 2); + } + } + free(tile); + fseek(ifp, -2, SEEK_CUR); // avoid EOF error +} // RT void CLASS sony_arq_load_raw() @@ -6319,12 +6343,13 @@ void CLASS parse_mos (int offset) void CLASS linear_table (unsigned len) { - int i; - if (len > 0x1000) len = 0x1000; - read_shorts (curve, len); - for (i=len; i < 0x1000; i++) - curve[i] = curve[i-1]; - maximum = curve[0xfff]; + const unsigned maxLen = std::min(0x10000ull, 1ull << tiff_bps); + len = std::min(len, maxLen); + read_shorts(curve, len); + maximum = curve[len - 1]; + for (std::size_t i = len; i < maxLen; ++i) { + curve[i] = maximum; + } } void CLASS parse_kodak_ifd (int base) @@ -6913,7 +6938,6 @@ it under the terms of the one of two licenses as you choose: unsigned oldOrder = order; order = 0x4d4d; // always big endian per definition in https://www.adobe.com/content/dam/acom/en/products/photoshop/pdfs/dng_spec_1.4.0.0.pdf chapter 7 unsigned ntags = get4(); // read the number of opcodes - if (ntags < ifp->size / 12) { // rough check for wrong value (happens for example with DNG files from DJI FC6310) while (ntags-- && !ifp->eof) { unsigned opcode = get4(); @@ -6932,8 +6956,48 @@ it under the terms of the one of two licenses as you choose: break; } case 51009: /* OpcodeList2 */ - meta_offset = ftell(ifp); - break; + { + meta_offset = ftell(ifp); + const unsigned oldOrder = order; + order = 0x4d4d; // always big endian per definition in https://www.adobe.com/content/dam/acom/en/products/photoshop/pdfs/dng_spec_1.4.0.0.pdf chapter 7 + unsigned ntags = get4(); // read the number of opcodes + if (ntags < ifp->size / 12) { // rough check for wrong value (happens for example with DNG files from DJI FC6310) + while (ntags-- && !ifp->eof) { + unsigned opcode = get4(); + if (opcode == 9 && gainMaps.size() < 4) { + fseek(ifp, 4, SEEK_CUR); // skip 4 bytes as we know that the opcode 4 takes 4 byte + fseek(ifp, 8, SEEK_CUR); // skip 8 bytes as they don't interest us currently + GainMap gainMap; + gainMap.Top = get4(); + gainMap.Left = get4(); + gainMap.Bottom = get4(); + gainMap.Right = get4(); + gainMap.Plane = get4(); + gainMap.Planes = get4(); + gainMap.RowPitch = get4(); + gainMap.ColPitch = get4(); + gainMap.MapPointsV = get4(); + gainMap.MapPointsH = get4(); + gainMap.MapSpacingV = getreal(12); + gainMap.MapSpacingH = getreal(12); + gainMap.MapOriginV = getreal(12); + gainMap.MapOriginH = getreal(12); + gainMap.MapPlanes = get4(); + const std::size_t n = static_cast(gainMap.MapPointsV) * static_cast(gainMap.MapPointsH) * static_cast(gainMap.MapPlanes); + gainMap.MapGain.reserve(n); + for (std::size_t i = 0; i < n; ++i) { + gainMap.MapGain.push_back(getreal(11)); + } + gainMaps.push_back(std::move(gainMap)); + } else { + fseek(ifp, 8, SEEK_CUR); // skip 8 bytes as they don't interest us currently + fseek(ifp, get4(), SEEK_CUR); + } + } + } + order = oldOrder; + break; + } case 64772: /* Kodak P-series */ if (len < 13) break; fseek (ifp, 16, SEEK_CUR); @@ -10066,6 +10130,9 @@ canon_a5: } else if (!strcmp(model, "X-Pro3") || !strcmp(model, "X-T3") || !strcmp(model, "X-T30") || !strcmp(model, "X-T4") || !strcmp(model, "X100V") || !strcmp(model, "X-S10")) { width = raw_width = 6384; height = raw_height = 4182; + } else if (!strcmp(model, "DBP for GX680")) { // Special case for #4204 + width = raw_width = 5504; + height = raw_height = 3856; } top_margin = (raw_height - height) >> 2 << 1; left_margin = (raw_width - width ) >> 2 << 1; @@ -10073,6 +10140,16 @@ canon_a5: if (width == 4032 || width == 4952 || width == 6032 || width == 8280) left_margin = 0; if (width == 3328 && (width -= 66)) left_margin = 34; if (width == 4936) left_margin = 4; + if (width == 5504) { // #4204, taken from LibRaw + left_margin = 32; + top_margin = 8; + width = raw_width - 2*left_margin; + height = raw_height - 2*top_margin; + load_raw = &CLASS unpacked_load_raw_FujiDBP; + filters = 0x16161616; + load_flags = 0; + flip = 6; + } if (!strcmp(model,"HS50EXR") || !strcmp(model,"F900EXR")) { width += 2; @@ -11041,6 +11118,70 @@ void CLASS nikon_14bit_load_raw() free(buf); } +bool CLASS isGainMapSupported() const { + if (!(dng_version && isBayer())) { + return false; + } + const auto n = gainMaps.size(); + if (n != 4) { // we need 4 gainmaps for bayer files + if (rtengine::settings->verbose) { + std::cout << "GainMap has " << n << " maps, but 4 are needed" << std::endl; + } + return false; + } + unsigned int check = 0; + bool noOp = true; + for (const auto &m : gainMaps) { + if (m.MapGain.size() < 1) { + if (rtengine::settings->verbose) { + std::cout << "GainMap has invalid size of " << m.MapGain.size() << std::endl; + } + return false; + } + if (m.MapGain.size() != static_cast(m.MapPointsV) * static_cast(m.MapPointsH) * static_cast(m.MapPlanes)) { + if (rtengine::settings->verbose) { + std::cout << "GainMap has size of " << m.MapGain.size() << ", but needs " << m.MapPointsV * m.MapPointsH * m.MapPlanes << std::endl; + } + return false; + } + if (m.RowPitch != 2 || m.ColPitch != 2) { + if (rtengine::settings->verbose) { + std::cout << "GainMap needs Row/ColPitch of 2/2, but has " << m.RowPitch << "/" << m.ColPitch << std::endl; + } + return false; + } + if (m.Top == 0){ + if (m.Left == 0) { + check += 1; + } else if (m.Left == 1) { + check += 2; + } + } else if (m.Top == 1) { + if (m.Left == 0) { + check += 4; + } else if (m.Left == 1) { + check += 8; + } + } + for (size_t i = 0; noOp && i < m.MapGain.size(); ++i) { + if (m.MapGain[i] != 1.f) { // we have at least one value != 1.f => map is not a nop + noOp = false; + } + } + } + if (noOp || check != 15) { // all maps are nops or the structure of the combination of 4 maps is not correct + if (rtengine::settings->verbose) { + if (noOp) { + std::cout << "GainMap is a nop" << std::endl; + } else { + std::cout << "GainMap has unsupported type : " << check << std::endl; + } + } + return false; + } + return true; +} + /* RT: Delete from here */ /*RT*/#undef SQR /*RT*/#undef MAX diff --git a/rtengine/dcraw.h b/rtengine/dcraw.h index 849012cb7..e0a6cda92 100644 --- a/rtengine/dcraw.h +++ b/rtengine/dcraw.h @@ -19,9 +19,12 @@ #pragma once +#include + #include "myfile.h" #include - +#include "dnggainmap.h" +#include "settings.h" class DCraw { @@ -165,6 +168,8 @@ protected: PanasonicRW2Info(): bpp(0), encoding(0) {} }; PanasonicRW2Info RT_pana_info; + std::vector gainMaps; + public: struct CanonCR3Data { // contents of tag CMP1 for relevant track in CR3 file @@ -193,6 +198,18 @@ public: int crx_track_selected; short CR3_CTMDtag; }; + + bool isBayer() const + { + return (filters != 0 && filters != 9); + } + + const std::vector& getGainMaps() const { + return gainMaps; + } + + bool isGainMapSupported() const; + struct CanonLevelsData { unsigned cblack[4]; unsigned white; @@ -200,6 +217,7 @@ public: bool white_ok; CanonLevelsData(): cblack{0}, white{0}, black_ok(false), white_ok(false) {} }; + protected: CanonCR3Data RT_canon_CR3_data; @@ -432,6 +450,7 @@ void parse_hasselblad_gain(); void hasselblad_load_raw(); void leaf_hdr_load_raw(); void unpacked_load_raw(); +void unpacked_load_raw_FujiDBP(); void sinar_4shot_load_raw(); void imacon_full_load_raw(); void packed_load_raw(); diff --git a/rtengine/dnggainmap.h b/rtengine/dnggainmap.h new file mode 100644 index 000000000..25a01fd0f --- /dev/null +++ b/rtengine/dnggainmap.h @@ -0,0 +1,43 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2021 Ingo Weyrich + * + * RawTherapee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RawTherapee is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ + +#pragma once + +#include +#include + +struct GainMap +{ + std::uint32_t Top; + std::uint32_t Left; + std::uint32_t Bottom; + std::uint32_t Right; + std::uint32_t Plane; + std::uint32_t Planes; + std::uint32_t RowPitch; + std::uint32_t ColPitch; + std::uint32_t MapPointsV; + std::uint32_t MapPointsH; + double MapSpacingV; + double MapSpacingH; + double MapOriginV; + double MapOriginH; + std::uint32_t MapPlanes; + std::vector MapGain; +}; diff --git a/rtengine/dynamicprofile.cc b/rtengine/dynamicprofile.cc index 28516a1ee..6dbd2d0f0 100644 --- a/rtengine/dynamicprofile.cc +++ b/rtengine/dynamicprofile.cc @@ -77,7 +77,7 @@ bool DynamicProfileRule::operator< (const DynamicProfileRule &other) const } -bool DynamicProfileRule::matches (const rtengine::FramesMetaData *im) const +bool DynamicProfileRule::matches (const rtengine::FramesMetaData *im, const Glib::ustring& filename) const { return (iso (im->getISOSpeed()) && fnumber (im->getFNumber()) @@ -86,6 +86,7 @@ bool DynamicProfileRule::matches (const rtengine::FramesMetaData *im) const && expcomp (im->getExpComp()) && camera (im->getCamera()) && lens (im->getLens()) + && path (filename) && imagetype(im->getImageType(0))); } @@ -214,6 +215,7 @@ bool DynamicProfileRules::loadRules() get_double_range (rule.expcomp, kf, group, "expcomp"); get_optional (rule.camera, kf, group, "camera"); get_optional (rule.lens, kf, group, "lens"); + get_optional (rule.path, kf, group, "path"); get_optional (rule.imagetype, kf, group, "imagetype"); try { @@ -247,6 +249,7 @@ bool DynamicProfileRules::storeRules() set_double_range (kf, group, "expcomp", rule.expcomp); set_optional (kf, group, "camera", rule.camera); set_optional (kf, group, "lens", rule.lens); + set_optional (kf, group, "path", rule.path); set_optional (kf, group, "imagetype", rule.imagetype); kf.set_string (group, "profilepath", rule.profilepath); } diff --git a/rtengine/dynamicprofile.h b/rtengine/dynamicprofile.h index d91b91aee..654db3a8e 100644 --- a/rtengine/dynamicprofile.h +++ b/rtengine/dynamicprofile.h @@ -51,7 +51,7 @@ public: }; DynamicProfileRule(); - bool matches (const rtengine::FramesMetaData *im) const; + bool matches (const rtengine::FramesMetaData *im, const Glib::ustring& filename) const; bool operator< (const DynamicProfileRule &other) const; int serial_number; @@ -62,6 +62,7 @@ public: Range expcomp; Optional camera; Optional lens; + Optional path; Optional imagetype; Glib::ustring profilepath; }; diff --git a/rtengine/imagedata.cc b/rtengine/imagedata.cc index 3c10e7dc0..fb2fcaf3a 100644 --- a/rtengine/imagedata.cc +++ b/rtengine/imagedata.cc @@ -19,6 +19,7 @@ #include #include +#include #include @@ -57,7 +58,8 @@ template T getFromFrame( const std::vector>& frames, std::size_t frame, - const std::function& function + const std::function& function, + T defval = {} ) { if (frame < frames.size()) { @@ -66,7 +68,7 @@ T getFromFrame( if (!frames.empty()) { return function(*frames[0]); } - return {}; + return defval; } const std::string& validateUft8(const std::string& str, const std::string& on_error = "???") @@ -85,11 +87,21 @@ FramesMetaData* FramesMetaData::fromFile(const Glib::ustring& fname, std::unique return new FramesData(fname, std::move(rml), firstFrameOnly); } -FrameData::FrameData(rtexif::TagDirectory* frameRootDir_, rtexif::TagDirectory* rootDir, rtexif::TagDirectory* firstRootDir) : +static struct tm timeFromTS(const time_t ts) +{ +#if !defined(WIN32) + struct tm tm; + return *gmtime_r(&ts, &tm); +#else + return *gmtime(&ts); +#endif +} + +FrameData::FrameData(rtexif::TagDirectory* frameRootDir_, rtexif::TagDirectory* rootDir, rtexif::TagDirectory* firstRootDir, time_t ts) : frameRootDir(frameRootDir_), iptc(nullptr), - time{}, - timeStamp{}, + time(timeFromTS(ts)), + timeStamp(ts), iso_speed(0), aperture(0.), focal_len(0.), @@ -1068,7 +1080,8 @@ tm FramesData::getDateTime(unsigned int frame) const [](const FrameData& frame_data) { return frame_data.getDateTime(); - } + }, + modTime ); } @@ -1080,7 +1093,8 @@ time_t FramesData::getDateTimeAsTS(unsigned int frame) const [](const FrameData& frame_data) { return frame_data.getDateTimeAsTS(); - } + }, + modTimeStamp ); } @@ -1366,6 +1380,11 @@ failure: FramesData::FramesData(const Glib::ustring& fname, std::unique_ptr rml, bool firstFrameOnly) : iptc(nullptr), dcrawFrameCount(0) { + GStatBuf statbuf = {}; + g_stat(fname.c_str(), &statbuf); + modTimeStamp = statbuf.st_mtime; + modTime = timeFromTS(modTimeStamp); + if (rml && (rml->exifBase >= 0 || rml->ciffBase >= 0)) { FILE* f = g_fopen(fname.c_str(), "rb"); @@ -1384,7 +1403,7 @@ FramesData::FramesData(const Glib::ustring& fname, std::unique_ptr(new FrameData(currFrame, currFrame->getRoot(), roots.at(0)))); + frames.push_back(std::unique_ptr(new FrameData(currFrame, currFrame->getRoot(), roots.at(0), modTimeStamp))); } for (auto currRoot : roots) { @@ -1410,7 +1429,7 @@ FramesData::FramesData(const Glib::ustring& fname, std::unique_ptr(new FrameData(currFrame, currFrame->getRoot(), roots.at(0)))); + frames.push_back(std::unique_ptr(new FrameData(currFrame, currFrame->getRoot(), roots.at(0), modTimeStamp))); } rewind(exifManager.f); // Not sure this is necessary @@ -1430,7 +1449,7 @@ FramesData::FramesData(const Glib::ustring& fname, std::unique_ptr(new FrameData(currFrame, currFrame->getRoot(), roots.at(0)))); + frames.push_back(std::unique_ptr(new FrameData(currFrame, currFrame->getRoot(), roots.at(0), modTimeStamp))); } for (auto currRoot : roots) { diff --git a/rtengine/imagedata.h b/rtengine/imagedata.h index 4bf9bdf5b..752fafab3 100644 --- a/rtengine/imagedata.h +++ b/rtengine/imagedata.h @@ -72,7 +72,7 @@ protected: public: - FrameData (rtexif::TagDirectory* frameRootDir, rtexif::TagDirectory* rootDir, rtexif::TagDirectory* firstRootDir); + FrameData (rtexif::TagDirectory* frameRootDir, rtexif::TagDirectory* rootDir, rtexif::TagDirectory* firstRootDir, time_t ts = 0); virtual ~FrameData (); bool getPixelShift () const; @@ -109,6 +109,8 @@ private: std::vector roots; IptcData* iptc; unsigned int dcrawFrameCount; + struct tm modTime; + time_t modTimeStamp; public: explicit FramesData (const Glib::ustring& fname, std::unique_ptr rml = nullptr, bool firstFrameOnly = false); diff --git a/rtengine/imagesource.h b/rtengine/imagesource.h index 6cb279efc..a8ea8f851 100644 --- a/rtengine/imagesource.h +++ b/rtengine/imagesource.h @@ -137,6 +137,7 @@ public: virtual ImageMatrices* getImageMatrices () = 0; virtual bool isRAW () const = 0; + virtual bool isGainMapSupported () const = 0; virtual DCPProfile* getDCP (const procparams::ColorManagementParams &cmp, DCPProfileApplyState &as) { return nullptr; diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 8d377d30c..df354bfd8 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -407,7 +407,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) // If high detail (=100%) is newly selected, do a demosaic update, since the last was just with FAST if (imageTypeListener) { - imageTypeListener->imageTypeChanged(imgsrc->isRAW(), imgsrc->getSensorType() == ST_BAYER, imgsrc->getSensorType() == ST_FUJI_XTRANS, imgsrc->isMono()); + imageTypeListener->imageTypeChanged(imgsrc->isRAW(), imgsrc->getSensorType() == ST_BAYER, imgsrc->getSensorType() == ST_FUJI_XTRANS, imgsrc->isMono(), imgsrc->isGainMapSupported()); } if ((todo & M_RAW) diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 04ece8bc3..c9c420b44 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -5633,6 +5633,7 @@ bool RAWParams::PreprocessWB::operator !=(const PreprocessWB& other) const RAWParams::RAWParams() : df_autoselect(false), ff_AutoSelect(false), + ff_FromMetaData(false), ff_BlurRadius(32), ff_BlurType(getFlatFieldBlurTypeString(FlatFieldBlurType::AREA)), ff_AutoClipControl(false), @@ -5658,6 +5659,7 @@ bool RAWParams::operator ==(const RAWParams& other) const && df_autoselect == other.df_autoselect && ff_file == other.ff_file && ff_AutoSelect == other.ff_AutoSelect + && ff_FromMetaData == other.ff_FromMetaData && ff_BlurRadius == other.ff_BlurRadius && ff_BlurType == other.ff_BlurType && ff_AutoClipControl == other.ff_AutoClipControl @@ -7484,6 +7486,7 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo saveToKeyfile(!pedited || pedited->raw.df_autoselect, "RAW", "DarkFrameAuto", raw.df_autoselect, keyFile); saveToKeyfile(!pedited || pedited->raw.ff_file, "RAW", "FlatFieldFile", relativePathIfInside(fname, fnameAbsolute, raw.ff_file), keyFile); saveToKeyfile(!pedited || pedited->raw.ff_AutoSelect, "RAW", "FlatFieldAutoSelect", raw.ff_AutoSelect, keyFile); + saveToKeyfile(!pedited || pedited->raw.ff_FromMetaData, "RAW", "FlatFieldFromMetaData", raw.ff_FromMetaData, keyFile); saveToKeyfile(!pedited || pedited->raw.ff_BlurRadius, "RAW", "FlatFieldBlurRadius", raw.ff_BlurRadius, keyFile); saveToKeyfile(!pedited || pedited->raw.ff_BlurType, "RAW", "FlatFieldBlurType", raw.ff_BlurType, keyFile); saveToKeyfile(!pedited || pedited->raw.ff_AutoClipControl, "RAW", "FlatFieldAutoClipControl", raw.ff_AutoClipControl, keyFile); @@ -10130,6 +10133,7 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) } assignFromKeyfile(keyFile, "RAW", "FlatFieldAutoSelect", pedited, raw.ff_AutoSelect, pedited->raw.ff_AutoSelect); + assignFromKeyfile(keyFile, "RAW", "FlatFieldFromMetaData", pedited, raw.ff_FromMetaData, pedited->raw.ff_FromMetaData); assignFromKeyfile(keyFile, "RAW", "FlatFieldBlurRadius", pedited, raw.ff_BlurRadius, pedited->raw.ff_BlurRadius); assignFromKeyfile(keyFile, "RAW", "FlatFieldBlurType", pedited, raw.ff_BlurType, pedited->raw.ff_BlurType); assignFromKeyfile(keyFile, "RAW", "FlatFieldAutoClipControl", pedited, raw.ff_AutoClipControl, pedited->raw.ff_AutoClipControl); diff --git a/rtengine/procparams.h b/rtengine/procparams.h index d730316e2..309bcb2ab 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -2440,6 +2440,7 @@ struct RAWParams { Glib::ustring ff_file; bool ff_AutoSelect; + bool ff_FromMetaData; int ff_BlurRadius; Glib::ustring ff_BlurType; bool ff_AutoClipControl; diff --git a/rtengine/profilestore.cc b/rtengine/profilestore.cc index 7d937e736..f83ddd385 100644 --- a/rtengine/profilestore.cc +++ b/rtengine/profilestore.cc @@ -508,7 +508,7 @@ void ProfileStore::dumpFolderList() printf ("\n"); } -PartialProfile *ProfileStore::loadDynamicProfile (const FramesMetaData *im) +PartialProfile *ProfileStore::loadDynamicProfile (const FramesMetaData *im, const Glib::ustring& filename) { if (storeState == STORESTATE_NOTINITIALIZED) { parseProfilesOnce(); @@ -521,7 +521,7 @@ PartialProfile *ProfileStore::loadDynamicProfile (const FramesMetaData *im) } for (auto rule : dynamicRules) { - if (rule.matches (im)) { + if (rule.matches (im, filename)) { if (settings->verbose) { printf ("found matching profile %s\n", rule.profilepath.c_str()); } diff --git a/rtengine/profilestore.h b/rtengine/profilestore.h index 460facb72..e8e48c17f 100644 --- a/rtengine/profilestore.h +++ b/rtengine/profilestore.h @@ -209,7 +209,7 @@ public: void addListener (ProfileStoreListener *listener); void removeListener (ProfileStoreListener *listener); - rtengine::procparams::PartialProfile* loadDynamicProfile (const rtengine::FramesMetaData *im); + rtengine::procparams::PartialProfile* loadDynamicProfile (const rtengine::FramesMetaData *im, const Glib::ustring& filename); void dumpFolderList(); }; diff --git a/rtengine/rawimage.cc b/rtengine/rawimage.cc index 2354f343a..8478d56ab 100644 --- a/rtengine/rawimage.cc +++ b/rtengine/rawimage.cc @@ -548,11 +548,18 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog CameraConstantsStore* ccs = CameraConstantsStore::getInstance(); const CameraConst *cc = ccs->get(make, model); + bool raw_crop_cc = false; + int orig_raw_width = width; + int orig_raw_height = height; if (raw_image) { - if (cc && cc->has_rawCrop()) { + orig_raw_width = raw_width; + orig_raw_height = raw_height; + + if (cc && cc->has_rawCrop(raw_width, raw_height)) { + raw_crop_cc = true; int lm, tm, w, h; - cc->get_rawCrop(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)); @@ -584,9 +591,9 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog } } - if (cc && cc->has_rawMask(0)) { - for (int i = 0; i < 8 && cc->has_rawMask(i); i++) { - cc->get_rawMask(i, mask[i][0], mask[i][1], mask[i][2], mask[i][3]); + if (cc && cc->has_rawMask(orig_raw_width, orig_raw_height, 0)) { + for (int i = 0; i < 2 && cc->has_rawMask(orig_raw_width, orig_raw_height, i); i++) { + cc->get_rawMask(orig_raw_width, orig_raw_height, i, mask[i][0], mask[i][1], mask[i][2], mask[i][3]); } } @@ -594,9 +601,10 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog free(raw_image); raw_image = nullptr; } else { - if (get_maker() == "Sigma" && cc && cc->has_rawCrop()) { // foveon images + if (get_maker() == "Sigma" && cc && cc->has_rawCrop(width, height)) { // foveon images + raw_crop_cc = true; int lm, tm, w, h; - cc->get_rawCrop(lm, tm, w, h); + cc->get_rawCrop(width, height, lm, tm, w, h); left_margin = lm; top_margin = tm; @@ -692,11 +700,12 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog printf("no constants in camconst.json exists for \"%s %s\" (relying only on dcraw defaults)\n", make, model); } + 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, (cc && cc->has_rawCrop()) ? "camconst.json" : "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"); } } diff --git a/rtengine/rawimage.h b/rtengine/rawimage.h index 871267dac..2b1cd2156 100644 --- a/rtengine/rawimage.h +++ b/rtengine/rawimage.h @@ -245,11 +245,6 @@ public: return zero_is_bad == 1; } - bool isBayer() const - { - return (filters != 0 && filters != 9); - } - bool isXtrans() const { return filters == 9; diff --git a/rtengine/rawimagesource.cc b/rtengine/rawimagesource.cc index 48d7b0904..550c59e9c 100644 --- a/rtengine/rawimagesource.cc +++ b/rtengine/rawimagesource.cc @@ -39,6 +39,7 @@ #include "rawimage.h" #include "rawimagesource_i.h" #include "rawimagesource.h" +#include "rescale.h" #include "rt_math.h" #include "rtengine.h" #include "rtlensfun.h" @@ -1347,7 +1348,6 @@ void RawImageSource::preprocess (const RAWParams &raw, const LensProfParams &le rif = ffm.searchFlatField(idata->getMake(), idata->getModel(), idata->getLens(), idata->getFocalLen(), idata->getFNumber(), idata->getDateTimeAsTS()); } - bool hasFlatField = (rif != nullptr); if (hasFlatField && settings->verbose) { @@ -1387,6 +1387,9 @@ void RawImageSource::preprocess (const RAWParams &raw, const LensProfParams &le } //FLATFIELD end + if (raw.ff_FromMetaData && isGainMapSupported()) { + applyDngGainMap(c_black, ri->getGainMaps()); + } // Always correct camera badpixels from .badpixels file const std::vector *bp = DFManager::getInstance().getBadPixels(ri->get_maker(), ri->get_model(), idata->getSerialNumber()); @@ -6259,6 +6262,36 @@ void RawImageSource::getRawValues(int x, int y, int rotate, int &R, int &G, int } } +bool RawImageSource::isGainMapSupported() const { + return ri->isGainMapSupported(); +} + +void RawImageSource::applyDngGainMap(const float black[4], const std::vector &gainMaps) { + // now we can apply each gain map to raw_data + array2D mvals[2][2]; + for (auto &m : gainMaps) { + mvals[m.Top & 1][m.Left & 1](m.MapPointsH, m.MapPointsV, m.MapGain.data()); + } + + // now we assume, col_scale and row scale is the same for all maps + const float col_scale = float(gainMaps[0].MapPointsH-1) / float(W); + const float row_scale = float(gainMaps[0].MapPointsV-1) / float(H); + +#ifdef _OPENMP + #pragma omp parallel for schedule(dynamic, 16) +#endif + for (std::size_t y = 0; y < static_cast(H); ++y) { + const float rowBlack[2] = {black[FC(y,0)], black[FC(y,1)]}; + const float ys = y * row_scale; + float xs = 0.f; + for (std::size_t x = 0; x < static_cast(W); ++x, xs += col_scale) { + const float f = getBilinearValue(mvals[y & 1][x & 1], xs, ys); + const float b = rowBlack[x & 1]; + rawData[y][x] = rtengine::max((rawData[y][x] - b) * f + b, 0.f); + } + } +} + void RawImageSource::cleanup () { delete phaseOneIccCurve; diff --git a/rtengine/rawimagesource.h b/rtengine/rawimagesource.h index 41a400dd9..d7549fe71 100644 --- a/rtengine/rawimagesource.h +++ b/rtengine/rawimagesource.h @@ -24,6 +24,7 @@ #include "array2D.h" #include "colortemp.h" +#include "dnggainmap.h" #include "iimage.h" #include "imagesource.h" #include "procparams.h" @@ -177,6 +178,8 @@ public: return true; } + bool isGainMapSupported() const override; + void setProgressListener (ProgressListener* pl) override { plistener = pl; @@ -304,6 +307,7 @@ protected: void vflip (Imagefloat* im); void getRawValues(int x, int y, int rotate, int &R, int &G, int &B) override; void captureSharpening(const procparams::CaptureSharpeningParams &sharpeningParams, bool showMask, double &conrastThreshold, double &radius) override; + void applyDngGainMap(const float black[4], const std::vector &gainMaps); }; } diff --git a/rtengine/rtengine.h b/rtengine/rtengine.h index b9fc916f6..989ca3354 100644 --- a/rtengine/rtengine.h +++ b/rtengine/rtengine.h @@ -493,7 +493,7 @@ class ImageTypeListener { public: virtual ~ImageTypeListener() = default; - virtual void imageTypeChanged(bool isRaw, bool isBayer, bool isXtrans, bool is_Mono = false) = 0; + virtual void imageTypeChanged(bool isRaw, bool isBayer, bool isXtrans, bool is_Mono = false, bool isGainMapSupported = false) = 0; }; class AutoContrastListener diff --git a/rtengine/stdimagesource.h b/rtengine/stdimagesource.h index 9b95fe34e..f83c58a04 100644 --- a/rtengine/stdimagesource.h +++ b/rtengine/stdimagesource.h @@ -100,6 +100,11 @@ public: return false; } + bool isGainMapSupported() const override + { + return false; + } + void setProgressListener (ProgressListener* pl) override { plistener = pl; diff --git a/rtengine/winutils.h b/rtengine/winutils.h new file mode 100644 index 000000000..757849dd1 --- /dev/null +++ b/rtengine/winutils.h @@ -0,0 +1,124 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2021 Lawrence Lee + * + * RawTherapee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RawTherapee is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ +#pragma once + +#ifdef WIN32 + +#include +#include + +#include "noncopyable.h" + + +/** + * Wrapper for pointers to memory allocated by HeapAlloc. + * + * Memory is automatically freed when the object goes out of scope. + */ +template +class WinHeapPtr : public rtengine::NonCopyable +{ +private: + const T ptr; + +public: + WinHeapPtr() = delete; + + /** Allocates the specified number of bytes in the process heap. */ + explicit WinHeapPtr(SIZE_T bytes): ptr(static_cast(HeapAlloc(GetProcessHeap(), 0, bytes))) {}; + + ~WinHeapPtr() + { + // HeapFree does a null check. + HeapFree(GetProcessHeap(), 0, static_cast(ptr)); + } + + T operator ->() const + { + return ptr; + } + + operator T() const + { + return ptr; + } +}; + +/** + * Wrapper for HLOCAL pointers to memory allocated by LocalAlloc. + * + * Memory is automatically freed when the object goes out of scope. + */ +template +class WinLocalPtr : public rtengine::NonCopyable +{ +private: + const T ptr; + +public: + WinLocalPtr() = delete; + + /** Wraps a raw pointer. */ + WinLocalPtr(T pointer): ptr(pointer) {}; + + ~WinLocalPtr() + { + // LocalFree does a null check. + LocalFree(static_cast(ptr)); + } + + T operator ->() const + { + return ptr; + } + + operator T() const + { + return ptr; + } +}; + +/** + * Wrapper for HANDLEs. + * + * Handles are automatically closed when the object goes out of scope. + */ +class WinHandle : public rtengine::NonCopyable +{ +private: + const HANDLE handle; + +public: + WinHandle() = delete; + + /** Wraps a HANDLE. */ + WinHandle(HANDLE handle): handle(handle) {}; + + ~WinHandle() + { + CloseHandle(handle); + } + + operator HANDLE() const + { + return handle; + } +}; + +#endif diff --git a/rtgui/batchqueueentry.cc b/rtgui/batchqueueentry.cc index 31a6f40c7..9fe4dd605 100644 --- a/rtgui/batchqueueentry.cc +++ b/rtgui/batchqueueentry.cc @@ -34,7 +34,7 @@ bool BatchQueueEntry::iconsLoaded(false); Glib::RefPtr BatchQueueEntry::savedAsIcon; BatchQueueEntry::BatchQueueEntry (rtengine::ProcessingJob* pjob, const rtengine::procparams::ProcParams& pparams, Glib::ustring fname, int prevw, int prevh, Thumbnail* thm, bool overwrite) : - ThumbBrowserEntryBase(fname), + ThumbBrowserEntryBase(fname, thm), opreview(nullptr), origpw(prevw), origph(prevh), diff --git a/rtgui/dynamicprofilepanel.cc b/rtgui/dynamicprofilepanel.cc index 865603b3a..feb6aea70 100644 --- a/rtgui/dynamicprofilepanel.cc +++ b/rtgui/dynamicprofilepanel.cc @@ -42,6 +42,7 @@ DynamicProfilePanel::EditDialog::EditDialog (const Glib::ustring &title, Gtk::Wi add_optional (M ("EXIFFILTER_CAMERA"), has_camera_, camera_); add_optional (M ("EXIFFILTER_LENS"), has_lens_, lens_); + add_optional (M ("EXIFFILTER_PATH"), has_path_, path_); imagetype_ = Gtk::manage (new MyComboBoxText()); imagetype_->append(Glib::ustring("(") + M("DYNPROFILEEDITOR_IMGTYPE_ANY") + ")"); @@ -93,6 +94,9 @@ void DynamicProfilePanel::EditDialog::set_rule ( has_lens_->set_active (rule.lens.enabled); lens_->set_text (rule.lens.value); + has_path_->set_active (rule.path.enabled); + path_->set_text (rule.path.value); + if (!rule.imagetype.enabled) { imagetype_->set_active(0); } else if (rule.imagetype.value == "STD") { @@ -136,6 +140,9 @@ DynamicProfileRule DynamicProfilePanel::EditDialog::get_rule() ret.lens.enabled = has_lens_->get_active(); ret.lens.value = lens_->get_text(); + ret.path.enabled = has_path_->get_active(); + ret.path.value = path_->get_text(); + ret.imagetype.enabled = imagetype_->get_active_row_number() > 0; switch (imagetype_->get_active_row_number()) { case 1: @@ -296,6 +303,16 @@ DynamicProfilePanel::DynamicProfilePanel(): *this, &DynamicProfilePanel::render_lens)); } + cell = Gtk::manage (new Gtk::CellRendererText()); + cols_count = treeview_.append_column (M ("EXIFFILTER_PATH"), *cell); + col = treeview_.get_column (cols_count - 1); + + if (col) { + col->set_cell_data_func ( + *cell, sigc::mem_fun ( + *this, &DynamicProfilePanel::render_path)); + } + cell = Gtk::manage (new Gtk::CellRendererText()); cols_count = treeview_.append_column (M ("EXIFFILTER_IMAGETYPE"), *cell); col = treeview_.get_column (cols_count - 1); @@ -375,6 +392,7 @@ void DynamicProfilePanel::update_rule (Gtk::TreeModel::Row row, row[columns_.expcomp] = rule.expcomp; row[columns_.camera] = rule.camera; row[columns_.lens] = rule.lens; + row[columns_.path] = rule.path; row[columns_.imagetype] = rule.imagetype; row[columns_.profilepath] = rule.profilepath; } @@ -398,6 +416,7 @@ DynamicProfileRule DynamicProfilePanel::to_rule (Gtk::TreeModel::Row row, ret.expcomp = row[columns_.expcomp]; ret.camera = row[columns_.camera]; ret.lens = row[columns_.lens]; + ret.path = row[columns_.path]; ret.profilepath = row[columns_.profilepath]; ret.imagetype = row[columns_.imagetype]; return ret; @@ -510,6 +529,12 @@ void DynamicProfilePanel::render_lens ( RENDER_OPTIONAL_ (lens); } +void DynamicProfilePanel::render_path ( + Gtk::CellRenderer *cell, const Gtk::TreeModel::iterator &iter) +{ + RENDER_OPTIONAL_ (path); +} + void DynamicProfilePanel::render_imagetype ( Gtk::CellRenderer *cell, const Gtk::TreeModel::iterator &iter) { diff --git a/rtgui/dynamicprofilepanel.h b/rtgui/dynamicprofilepanel.h index 972ca1c4a..03b9e7c62 100644 --- a/rtgui/dynamicprofilepanel.h +++ b/rtgui/dynamicprofilepanel.h @@ -55,6 +55,7 @@ private: add (expcomp); add (camera); add (lens); + add (path); add (profilepath); add (imagetype); } @@ -66,6 +67,7 @@ private: Gtk::TreeModelColumn> expcomp; Gtk::TreeModelColumn camera; Gtk::TreeModelColumn lens; + Gtk::TreeModelColumn path; Gtk::TreeModelColumn imagetype; Gtk::TreeModelColumn profilepath; }; @@ -78,6 +80,7 @@ private: void render_expcomp (Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter); void render_camera (Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter); void render_lens (Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter); + void render_path (Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter); void render_imagetype (Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter); void render_profilepath (Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter); @@ -114,6 +117,9 @@ private: Gtk::CheckButton *has_lens_; Gtk::Entry *lens_; + Gtk::CheckButton *has_path_; + Gtk::Entry *path_; + MyComboBoxText *imagetype_; ProfileStoreComboBox *profilepath_; diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index bde08b80f..3a180cc58 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -45,6 +45,8 @@ #ifdef WIN32 #include "windows.h" + +#include "../rtengine/winutils.h" #endif using namespace rtengine::procparams; @@ -135,6 +137,235 @@ bool find_default_monitor_profile (GdkWindow *rootwin, Glib::ustring &defprof, G } #endif +bool hasUserOnlyPermission(const Glib::ustring &dirname) +{ +#if defined(__linux__) || defined(__APPLE__) + const Glib::RefPtr file = Gio::File::create_for_path(dirname); + const Glib::RefPtr file_info = file->query_info("owner::user,unix::mode"); + + if (!file_info) { + return false; + } + + const Glib::ustring owner = file_info->get_attribute_string("owner::user"); + const guint32 mode = file_info->get_attribute_uint32("unix::mode"); + + return (mode & 0777) == 0700 && owner == Glib::get_user_name(); +#elif defined(WIN32) + const Glib::RefPtr file = Gio::File::create_for_path(dirname); + const Glib::RefPtr file_info = file->query_info("owner::user"); + if (!file_info) { + return false; + } + + // Current user must be the owner. + const Glib::ustring user_name = Glib::get_user_name(); + const Glib::ustring owner = file_info->get_attribute_string("owner::user"); + if (user_name != owner) { + return false; + } + + // Get security descriptor and discretionary access control list. + PACL dacl = nullptr; + PSECURITY_DESCRIPTOR sec_desc_raw_ptr = nullptr; + auto win_error = GetNamedSecurityInfo( + dirname.c_str(), + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, + nullptr, + nullptr, + &dacl, + nullptr, + &sec_desc_raw_ptr + ); + const WinLocalPtr sec_desc_ptr(sec_desc_raw_ptr); + if (win_error != ERROR_SUCCESS) { + return false; + } + + // Must not inherit permissions. + SECURITY_DESCRIPTOR_CONTROL sec_desc_control; + DWORD revision; + if (!( + GetSecurityDescriptorControl(sec_desc_ptr, &sec_desc_control, &revision) + && sec_desc_control & SE_DACL_PROTECTED + )) { + return false; + } + + // Check that there is one entry allowing full access. + ULONG acl_entry_count; + PEXPLICIT_ACCESS acl_entry_list_raw = nullptr; + win_error = GetExplicitEntriesFromAcl(dacl, &acl_entry_count, &acl_entry_list_raw); + const WinLocalPtr acl_entry_list(acl_entry_list_raw); + if (win_error != ERROR_SUCCESS || acl_entry_count != 1) { + return false; + } + const EXPLICIT_ACCESS &ace = acl_entry_list[0]; + if ( + ace.grfAccessMode != GRANT_ACCESS + || (ace.grfAccessPermissions & FILE_ALL_ACCESS) != FILE_ALL_ACCESS + || ace.Trustee.TrusteeForm != TRUSTEE_IS_SID // Should already be SID, but double check. + ) { + return false; + } + + // ACE must be for the current user. + HANDLE process_token_raw; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &process_token_raw)) { + return false; + } + const WinHandle process_token(process_token_raw); + DWORD actual_token_info_size = 0; + GetTokenInformation(process_token, TokenUser, nullptr, 0, &actual_token_info_size); + if (!actual_token_info_size) { + return false; + } + const WinHeapPtr user_token_ptr(actual_token_info_size); + if (!user_token_ptr || !GetTokenInformation( + process_token, + TokenUser, + user_token_ptr, + actual_token_info_size, + &actual_token_info_size + )) { + return false; + } + return EqualSid(ace.Trustee.ptstrName, user_token_ptr->User.Sid); +#endif + return false; +} + +/** + * Sets read and write permissions, and optionally the execute permission, for + * the user and no permissions for others. + */ +void setUserOnlyPermission(const Glib::RefPtr file, bool execute) +{ +#if defined(__linux__) || defined(__APPLE__) + const Glib::RefPtr file_info = file->query_info("unix::mode"); + if (!file_info) { + return; + } + + guint32 mode = file_info->get_attribute_uint32("unix::mode"); + mode = (mode & ~0777) | (execute ? 0700 : 0600); + try { + file->set_attribute_uint32("unix::mode", mode, Gio::FILE_QUERY_INFO_NONE); + } catch (Gio::Error &) { + } +#elif defined(WIN32) + // Get the current user's SID. + HANDLE process_token_raw; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &process_token_raw)) { + return; + } + const WinHandle process_token(process_token_raw); + DWORD actual_token_info_size = 0; + GetTokenInformation(process_token, TokenUser, nullptr, 0, &actual_token_info_size); + if (!actual_token_info_size) { + return; + } + const WinHeapPtr user_token_ptr(actual_token_info_size); + if (!user_token_ptr || !GetTokenInformation( + process_token, + TokenUser, + user_token_ptr, + actual_token_info_size, + &actual_token_info_size + )) { + return; + } + const PSID user_sid = user_token_ptr->User.Sid; + + // Get a handle to the file. + const Glib::ustring filename = file->get_path(); + const HANDLE file_handle_raw = CreateFile( + filename.c_str(), + READ_CONTROL | WRITE_DAC, + 0, + nullptr, + OPEN_EXISTING, + execute ? FILE_FLAG_BACKUP_SEMANTICS : FILE_ATTRIBUTE_NORMAL, + nullptr + ); + if (file_handle_raw == INVALID_HANDLE_VALUE) { + return; + } + const WinHandle file_handle(file_handle_raw); + + // Create the user-only permission and set it. + EXPLICIT_ACCESS ea = { + .grfAccessPermissions = FILE_ALL_ACCESS, + .grfAccessMode = GRANT_ACCESS, + .grfInheritance = NO_INHERITANCE, + }; + BuildTrusteeWithSid(&(ea.Trustee), user_sid); + PACL new_dacl_raw = nullptr; + auto win_error = SetEntriesInAcl(1, &ea, nullptr, &new_dacl_raw); + if (win_error != ERROR_SUCCESS) { + return; + } + const WinLocalPtr new_dacl(new_dacl_raw); + SetSecurityInfo( + file_handle, + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, + nullptr, + nullptr, + new_dacl, + nullptr + ); +#endif +} + +/** + * Gets the path to the temp directory, creating it if necessary. + */ +Glib::ustring getTmpDirectory() +{ +#if defined(__linux__) || defined(__APPLE__) || defined(WIN32) + static Glib::ustring recent_dir = ""; + const Glib::ustring tmp_dir_root = Glib::get_tmp_dir(); + const Glib::ustring subdir_base = + Glib::ustring::compose("rawtherapee-%1", Glib::get_user_name()); + Glib::ustring dir = Glib::build_filename(tmp_dir_root, subdir_base); + + // Returns true if the directory doesn't exist or has the right permissions. + auto is_usable_dir = [](const Glib::ustring &dir_path) { + return !Glib::file_test(dir_path, Glib::FILE_TEST_EXISTS) || (Glib::file_test(dir_path, Glib::FILE_TEST_IS_DIR) && hasUserOnlyPermission(dir_path)); + }; + + if (!(is_usable_dir(dir) || recent_dir.empty())) { + // Try to reuse the random suffix directory. + dir = recent_dir; + } + + if (!is_usable_dir(dir)) { + // Create new directory with random suffix. + gchar *const rand_dir = g_dir_make_tmp((subdir_base + "-XXXXXX").c_str(), nullptr); + if (!rand_dir) { + return tmp_dir_root; + } + dir = recent_dir = rand_dir; + g_free(rand_dir); + Glib::RefPtr file = Gio::File::create_for_path(dir); + setUserOnlyPermission(file, true); + } else if (!Glib::file_test(dir, Glib::FILE_TEST_EXISTS)) { + // Create the directory. + Glib::RefPtr file = Gio::File::create_for_path(dir); + bool dir_created = file->make_directory(); + if (!dir_created) { + return tmp_dir_root; + } + setUserOnlyPermission(file, true); + } + + return dir; +#else + return Glib::get_tmp_dir(); +#endif +} } class EditorPanel::ColorManagementToolbar @@ -2116,7 +2347,7 @@ bool EditorPanel::idle_sendToGimp ( ProgressConnector *p dirname = options.editor_custom_out_dir; break; default: // Options::EDITOR_OUT_DIR_TEMP - dirname = Glib::get_tmp_dir(); + dirname = getTmpDirectory(); break; } Glib::ustring fullFileName = Glib::build_filename(dirname, shortname); @@ -2183,6 +2414,8 @@ bool EditorPanel::idle_sentToGimp (ProgressConnector *pc, rtengine::IImagef parent->setProgress (0.); bool success = false; + setUserOnlyPermission(Gio::File::create_for_path(filename), false); + success = ExtProgStore::openInExternalEditor(filename, external_editor_info); if (!success) { diff --git a/rtgui/filebrowser.cc b/rtgui/filebrowser.cc index 66c84d86e..ac4a27dec 100644 --- a/rtgui/filebrowser.cc +++ b/rtgui/filebrowser.cc @@ -167,6 +167,41 @@ FileBrowser::FileBrowser () : pmenu->attach (*Gtk::manage(selall = new Gtk::MenuItem (M("FILEBROWSER_POPUPSELECTALL"))), 0, 1, p, p + 1); p++; + /*********************** + * sort + ***********************/ + const std::array cnameSortOrders = { + M("SORT_ASCENDING"), + M("SORT_DESCENDING"), + }; + + const std::array cnameSortMethods = { + M("SORT_BY_NAME"), + M("SORT_BY_DATE"), + M("SORT_BY_EXIF"), + M("SORT_BY_RANK"), + M("SORT_BY_LABEL"), + }; + + pmenu->attach (*Gtk::manage(menuSort = new Gtk::MenuItem (M("FILEBROWSER_POPUPSORTBY"))), 0, 1, p, p + 1); + p++; + Gtk::Menu* submenuSort = Gtk::manage (new Gtk::Menu ()); + Gtk::RadioButtonGroup sortOrderGroup, sortMethodGroup; + for (size_t i = 0; i < cnameSortOrders.size(); i++) { + submenuSort->attach (*Gtk::manage(sortOrder[i] = new Gtk::RadioMenuItem (sortOrderGroup, cnameSortOrders[i])), 0, 1, p, p + 1); + p++; + sortOrder[i]->set_active (i == options.sortDescending); + } + submenuSort->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1); + p++; + for (size_t i = 0; i < cnameSortMethods.size(); i++) { + submenuSort->attach (*Gtk::manage(sortMethod[i] = new Gtk::RadioMenuItem (sortMethodGroup, cnameSortMethods[i])), 0, 1, p, p + 1); + p++; + sortMethod[i]->set_active (i == options.sortMethod); + } + submenuSort->show_all (); + menuSort->set_submenu (*submenuSort); + /*********************** * rank ***********************/ @@ -427,6 +462,14 @@ FileBrowser::FileBrowser () : inspect->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), inspect)); } + for (int i = 0; i < 2; i++) { + sortOrder[i]->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), sortOrder[i])); + } + + for (int i = 0; i < Options::SORT_METHOD_COUNT; i++) { + sortMethod[i]->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), sortMethod[i])); + } + for (int i = 0; i < 6; i++) { rank[i]->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), rank[i])); } @@ -610,27 +653,7 @@ void FileBrowser::addEntry_ (FileBrowserEntry* entry) entry->getThumbButtonSet()->setButtonListener(this); entry->resize(getThumbnailHeight()); entry->filtered = !checkFilter(entry); - - // find place in abc order - { - MYWRITERLOCK(l, entryRW); - - fd.insert( - std::lower_bound( - fd.begin(), - fd.end(), - entry, - [](const ThumbBrowserEntryBase* a, const ThumbBrowserEntryBase* b) - { - return *a < *b; - } - ), - entry - ); - - initEntry(entry); - } - redraw(entry); + insertEntry(entry); } FileBrowserEntry* FileBrowser::delEntry (const Glib::ustring& fname) @@ -724,6 +747,18 @@ void FileBrowser::menuItemActivated (Gtk::MenuItem* m) return; } + for (int i = 0; i < 2; i++) + if (m == sortOrder[i]) { + sortOrderRequested (i); + return; + } + + for (int i = 0; i < Options::SORT_METHOD_COUNT; i++) + if (m == sortMethod[i]) { + sortMethodRequested (i); + return; + } + for (int i = 0; i < 6; i++) if (m == rank[i]) { rankingRequested (mselected, i); @@ -1632,6 +1667,18 @@ void FileBrowser::fromTrashRequested (std::vector tbe) applyFilter (filter); } +void FileBrowser::sortMethodRequested (int method) +{ + options.sortMethod = Options::SortMethod(method); + resort (); +} + +void FileBrowser::sortOrderRequested (int order) +{ + options.sortDescending = !!order; + resort (); +} + void FileBrowser::rankingRequested (std::vector tbe, int rank) { diff --git a/rtgui/filebrowser.h b/rtgui/filebrowser.h index 4602ba9bb..0df1cf9eb 100644 --- a/rtgui/filebrowser.h +++ b/rtgui/filebrowser.h @@ -83,9 +83,12 @@ protected: Gtk::MenuItem* open; Gtk::MenuItem* inspect; Gtk::MenuItem* selall; + Gtk::RadioMenuItem* sortMethod[Options::SORT_METHOD_COUNT]; + Gtk::RadioMenuItem* sortOrder[2]; Gtk::MenuItem* copyTo; Gtk::MenuItem* moveTo; + Gtk::MenuItem* menuSort; Gtk::MenuItem* menuRank; Gtk::MenuItem* menuLabel; Gtk::MenuItem* menuFileOperations; @@ -131,6 +134,8 @@ protected: void toTrashRequested (std::vector tbe); void fromTrashRequested (std::vector tbe); + void sortMethodRequested (int method); + void sortOrderRequested (int order); void rankingRequested (std::vector tbe, int rank); void colorlabelRequested (std::vector tbe, int colorlabel); void requestRanking (int rank); diff --git a/rtgui/filebrowserentry.cc b/rtgui/filebrowserentry.cc index bf3f11a79..b89fe340d 100644 --- a/rtgui/filebrowserentry.cc +++ b/rtgui/filebrowserentry.cc @@ -45,10 +45,8 @@ Glib::RefPtr FileBrowserEntry::hdr; Glib::RefPtr FileBrowserEntry::ps; FileBrowserEntry::FileBrowserEntry (Thumbnail* thm, const Glib::ustring& fname) - : ThumbBrowserEntryBase (fname), wasInside(false), iatlistener(nullptr), press_x(0), press_y(0), action_x(0), action_y(0), rot_deg(0.0), landscape(true), cropParams(new rtengine::procparams::CropParams), cropgl(nullptr), state(SNormal), crop_custom_ratio(0.f) + : ThumbBrowserEntryBase (fname, thm), wasInside(false), iatlistener(nullptr), press_x(0), press_y(0), action_x(0), action_y(0), rot_deg(0.0), landscape(true), cropParams(new rtengine::procparams::CropParams), cropgl(nullptr), state(SNormal), crop_custom_ratio(0.f) { - thumbnail = thm; - feih = new FileBrowserEntryIdleHelper; feih->fbentry = this; feih->destroyed = false; diff --git a/rtgui/flatfield.cc b/rtgui/flatfield.cc index 71fa0aab6..f493ba0b9 100644 --- a/rtgui/flatfield.cc +++ b/rtgui/flatfield.cc @@ -18,6 +18,7 @@ */ #include +#include "eventmapper.h" #include "flatfield.h" #include "guiutils.h" @@ -32,6 +33,9 @@ using namespace rtengine::procparams; FlatField::FlatField () : FoldableToolPanel(this, "flatfield", M("TP_FLATFIELD_LABEL")) { + auto m = ProcEventMapper::getInstance(); + EvFlatFieldFromMetaData = m->newEvent(DARKFRAME, "HISTORY_MSG_FF_FROMMETADATA"); + hbff = Gtk::manage(new Gtk::Box()); flatFieldFile = Gtk::manage(new MyFileChooserButton(M("TP_FLATFIELD_LABEL"), Gtk::FILE_CHOOSER_ACTION_OPEN)); bindCurrentFolder (*flatFieldFile, options.lastFlatfieldDir); @@ -42,6 +46,8 @@ FlatField::FlatField () : FoldableToolPanel(this, "flatfield", M("TP_FLATFIELD_L hbff->pack_start(*flatFieldFile); hbff->pack_start(*flatFieldFileReset, Gtk::PACK_SHRINK); flatFieldAutoSelect = Gtk::manage(new Gtk::CheckButton((M("TP_FLATFIELD_AUTOSELECT")))); + flatFieldFromMetaData = Gtk::manage(new CheckBox((M("TP_FLATFIELD_FROMMETADATA")), multiImage)); + flatFieldFromMetaData->setCheckBoxListener (this); ffInfo = Gtk::manage(new Gtk::Label("-")); setExpandAlignProperties(ffInfo, true, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER); flatFieldBlurRadius = Gtk::manage(new Adjuster (M("TP_FLATFIELD_BLURRADIUS"), 0, 200, 2, 32)); @@ -70,8 +76,10 @@ FlatField::FlatField () : FoldableToolPanel(this, "flatfield", M("TP_FLATFIELD_L flatFieldClipControl->show(); flatFieldClipControl->set_tooltip_markup (M("TP_FLATFIELD_CLIPCONTROL_TOOLTIP")); - pack_start( *hbff, Gtk::PACK_SHRINK); + pack_start( *flatFieldFromMetaData, Gtk::PACK_SHRINK); + pack_start( *Gtk::manage( new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL)), Gtk::PACK_SHRINK, 0 ); pack_start( *flatFieldAutoSelect, Gtk::PACK_SHRINK); + pack_start( *hbff, Gtk::PACK_SHRINK); pack_start( *ffInfo, Gtk::PACK_SHRINK); pack_start( *hbffbt, Gtk::PACK_SHRINK); pack_start( *flatFieldBlurRadius, Gtk::PACK_SHRINK); @@ -128,12 +136,14 @@ void FlatField::read(const rtengine::procparams::ProcParams* pp, const ParamsEdi } flatFieldAutoSelect->set_active (pp->raw.ff_AutoSelect); + flatFieldFromMetaData->set_active (pp->raw.ff_FromMetaData); flatFieldBlurRadius->setValue (pp->raw.ff_BlurRadius); flatFieldClipControl->setValue (pp->raw.ff_clipControl); flatFieldClipControl->setAutoValue (pp->raw.ff_AutoClipControl); if(pedited ) { flatFieldAutoSelect->set_inconsistent (!pedited->raw.ff_AutoSelect); + flatFieldFromMetaData->set_inconsistent (!pedited->raw.ff_FromMetaData); flatFieldBlurRadius->setEditedState( pedited->raw.ff_BlurRadius ? Edited : UnEdited ); flatFieldClipControl->setEditedState( pedited->raw.ff_clipControl ? Edited : UnEdited ); flatFieldClipControl->setAutoInconsistent(multiImage && !pedited->raw.ff_AutoClipControl); @@ -214,6 +224,7 @@ void FlatField::write( rtengine::procparams::ProcParams* pp, ParamsEdited* pedit { pp->raw.ff_file = flatFieldFile->get_filename(); pp->raw.ff_AutoSelect = flatFieldAutoSelect->get_active(); + pp->raw.ff_FromMetaData = flatFieldFromMetaData->get_active(); pp->raw.ff_BlurRadius = flatFieldBlurRadius->getIntValue(); pp->raw.ff_clipControl = flatFieldClipControl->getIntValue(); pp->raw.ff_AutoClipControl = flatFieldClipControl->getAutoValue(); @@ -227,6 +238,7 @@ void FlatField::write( rtengine::procparams::ProcParams* pp, ParamsEdited* pedit if (pedited) { pedited->raw.ff_file = ffChanged; pedited->raw.ff_AutoSelect = !flatFieldAutoSelect->get_inconsistent(); + pedited->raw.ff_FromMetaData = !flatFieldFromMetaData->get_inconsistent(); pedited->raw.ff_BlurRadius = flatFieldBlurRadius->getEditedState (); pedited->raw.ff_clipControl = flatFieldClipControl->getEditedState (); pedited->raw.ff_AutoClipControl = !flatFieldClipControl->getAutoInconsistent(); @@ -352,6 +364,13 @@ void FlatField::flatFieldBlurTypeChanged () } } +void FlatField::checkBoxToggled (CheckBox* c, CheckValue newval) +{ + if (listener && c == flatFieldFromMetaData) { + listener->panelChanged (EvFlatFieldFromMetaData, flatFieldFromMetaData->getLastActive() ? M("GENERAL_ENABLED") : M("GENERAL_DISABLED")); + } +} + void FlatField::flatFieldAutoSelectChanged() { if (batchMode) { @@ -419,3 +438,18 @@ void FlatField::flatFieldAutoClipValueChanged(int n) } ); } + +void FlatField::setGainMap(bool enabled) { + flatFieldFromMetaData->set_sensitive(enabled); + if (!enabled) { + idle_register.add( + [this, enabled]() -> bool + { + disableListener(); + flatFieldFromMetaData->setValue(false); + enableListener(); + return false; + } + ); + } +} diff --git a/rtgui/flatfield.h b/rtgui/flatfield.h index 0d6f167e1..be46d5a1d 100644 --- a/rtgui/flatfield.h +++ b/rtgui/flatfield.h @@ -23,6 +23,7 @@ #include #include "adjuster.h" +#include "checkbox.h" #include "guiutils.h" #include "toolpanel.h" @@ -42,7 +43,7 @@ public: // add other info here }; -class FlatField final : public ToolParamBlock, public AdjusterListener, public FoldableToolPanel, public rtengine::FlatFieldAutoClipListener +class FlatField final : public ToolParamBlock, public AdjusterListener, public CheckBoxListener, public FoldableToolPanel, public rtengine::FlatFieldAutoClipListener { protected: @@ -52,6 +53,7 @@ protected: Gtk::Label *ffInfo; Gtk::Button *flatFieldFileReset; Gtk::CheckButton* flatFieldAutoSelect; + CheckBox* flatFieldFromMetaData; Adjuster* flatFieldClipControl; Adjuster* flatFieldBlurRadius; MyComboBoxText* flatFieldBlurType; @@ -64,8 +66,10 @@ protected: Glib::ustring lastShortcutPath; bool b_filter_asCurrent; bool israw; + rtengine::ProcEvent EvFlatFieldFromMetaData; IdleRegister idle_register; + public: FlatField (); @@ -90,4 +94,6 @@ public: ffp = p; }; void flatFieldAutoClipValueChanged(int n = 0) override; + void checkBoxToggled(CheckBox* c, CheckValue newval) override; + void setGainMap(bool enabled); }; diff --git a/rtgui/main-cli.cc b/rtgui/main-cli.cc index 8375ffe8b..cebced274 100644 --- a/rtgui/main-cli.cc +++ b/rtgui/main-cli.cc @@ -743,7 +743,7 @@ int processLineParams ( int argc, char **argv ) if (options.defProfRaw == DEFPROFILE_DYNAMIC) { rawParams->deleteInstance(); delete rawParams; - rawParams = ProfileStore::getInstance()->loadDynamicProfile (ii->getMetaData()); + rawParams = ProfileStore::getInstance()->loadDynamicProfile (ii->getMetaData(), inputFile); } std::cout << " Merging default raw processing profile." << std::endl; @@ -752,7 +752,7 @@ int processLineParams ( int argc, char **argv ) if (options.defProfImg == DEFPROFILE_DYNAMIC) { imgParams->deleteInstance(); delete imgParams; - imgParams = ProfileStore::getInstance()->loadDynamicProfile (ii->getMetaData()); + imgParams = ProfileStore::getInstance()->loadDynamicProfile (ii->getMetaData(), inputFile); } std::cout << " Merging default non-raw processing profile." << std::endl; diff --git a/rtgui/options.cc b/rtgui/options.cc index e1836594e..c29e65d71 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -687,6 +687,8 @@ void Options::setDefaults() lastICCProfCreatorDir = ""; gimpPluginShowInfoDialog = true; maxRecentFolders = 15; + sortMethod = SORT_BY_NAME; + sortDescending = false; rtSettings.lensfunDbDirectory = ""; // set also in main.cc and main-cli.cc cropGuides = CROP_GUIDE_FULL; cropAutoFit = false; @@ -1303,6 +1305,19 @@ void Options::readFromFile(Glib::ustring fname) if (keyFile.has_key("File Browser", "RecentFolders")) { recentFolders = keyFile.get_string_list("File Browser", "RecentFolders"); } + + if (keyFile.has_key("File Browser", "SortMethod")) { + int v = keyFile.get_integer("File Browser", "SortMethod"); + if (v < int(0) || v >= int(SORT_METHOD_COUNT)) { + sortMethod = SORT_BY_NAME; + } else { + sortMethod = SortMethod(v); + } + } + + if (keyFile.has_key("File Browser", "SortDescending")) { + sortDescending = keyFile.get_boolean("File Browser", "SortDescending"); + } } if (keyFile.has_group("Clipping Indication")) { @@ -2389,6 +2404,8 @@ void Options::saveToFile(Glib::ustring fname) keyFile.set_string_list("File Browser", "RecentFolders", temp); } + keyFile.set_integer("File Browser", "SortMethod", sortMethod); + keyFile.set_boolean("File Browser", "SortDescending", sortDescending); keyFile.set_integer("Clipping Indication", "HighlightThreshold", highlightThreshold); keyFile.set_integer("Clipping Indication", "ShadowThreshold", shadowThreshold); keyFile.set_boolean("Clipping Indication", "BlinkClipped", blinkClipped); diff --git a/rtgui/options.h b/rtgui/options.h index d6b546d40..361f98b4a 100644 --- a/rtgui/options.h +++ b/rtgui/options.h @@ -465,6 +465,17 @@ public: size_t maxRecentFolders; // max. number of recent folders stored in options file std::vector recentFolders; // List containing all recent folders + enum SortMethod { + SORT_BY_NAME, + SORT_BY_DATE, + SORT_BY_EXIF, + SORT_BY_RANK, + SORT_BY_LABEL, + SORT_METHOD_COUNT, + }; + SortMethod sortMethod; // remembers current state of file browser + bool sortDescending; + Options (); diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index a7963b7dc..9c7a92f39 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -519,6 +519,7 @@ void ParamsEdited::set(bool v) raw.df_autoselect = v; raw.ff_file = v; raw.ff_AutoSelect = v; + raw.ff_FromMetaData = v; raw.ff_BlurRadius = v; raw.ff_BlurType = v; raw.ff_AutoClipControl = v; @@ -1930,6 +1931,7 @@ void ParamsEdited::initFrom(const std::vector& raw.df_autoselect = raw.df_autoselect && p.raw.df_autoselect == other.raw.df_autoselect; raw.ff_file = raw.ff_file && p.raw.ff_file == other.raw.ff_file; raw.ff_AutoSelect = raw.ff_AutoSelect && p.raw.ff_AutoSelect == other.raw.ff_AutoSelect; + raw.ff_FromMetaData = raw.ff_FromMetaData && p.raw.ff_FromMetaData == other.raw.ff_FromMetaData; raw.ff_BlurRadius = raw.ff_BlurRadius && p.raw.ff_BlurRadius == other.raw.ff_BlurRadius; raw.ff_BlurType = raw.ff_BlurType && p.raw.ff_BlurType == other.raw.ff_BlurType; raw.ff_AutoClipControl = raw.ff_AutoClipControl && p.raw.ff_AutoClipControl == other.raw.ff_AutoClipControl; @@ -6644,6 +6646,10 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.raw.ff_AutoSelect = mods.raw.ff_AutoSelect; } + if (raw.ff_FromMetaData) { + toEdit.raw.ff_FromMetaData = mods.raw.ff_FromMetaData; + } + if (raw.ff_BlurRadius) { toEdit.raw.ff_BlurRadius = mods.raw.ff_BlurRadius; } @@ -7375,7 +7381,7 @@ bool RAWParamsEdited::XTransSensor::isUnchanged() const bool RAWParamsEdited::isUnchanged() const { return bayersensor.isUnchanged() && xtranssensor.isUnchanged() && ca_autocorrect && ca_avoidcolourshift && caautoiterations && cared && cablue && hotPixelFilter && deadPixelFilter && hotdeadpix_thresh && darkFrame - && df_autoselect && ff_file && ff_AutoSelect && ff_BlurRadius && ff_BlurType && exPos && ff_AutoClipControl && ff_clipControl; + && df_autoselect && ff_file && ff_AutoSelect && ff_FromMetaData && ff_BlurRadius && ff_BlurType && exPos && ff_AutoClipControl && ff_clipControl; } bool LensProfParamsEdited::isUnchanged() const diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 0c0c79f7c..3c108f33d 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -1489,6 +1489,7 @@ struct RAWParamsEdited { bool df_autoselect; bool ff_file; bool ff_AutoSelect; + bool ff_FromMetaData; bool ff_BlurRadius; bool ff_BlurType; bool ff_AutoClipControl; diff --git a/rtgui/partialpastedlg.cc b/rtgui/partialpastedlg.cc index a9f79d854..81847adc0 100644 --- a/rtgui/partialpastedlg.cc +++ b/rtgui/partialpastedlg.cc @@ -301,6 +301,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren //--- ff_file = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_FLATFIELDFILE"))); ff_AutoSelect = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_FLATFIELDAUTOSELECT"))); + ff_FromMetaData = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_FLATFIELDFROMMETADATA"))); ff_BlurType = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_FLATFIELDBLURTYPE"))); ff_BlurRadius = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_FLATFIELDBLURRADIUS"))); ff_ClipControl = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_FLATFIELDCLIPCONTROL"))); @@ -423,6 +424,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren vboxes[8]->pack_start (*Gtk::manage (new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL)), Gtk::PACK_SHRINK, 0); vboxes[8]->pack_start (*ff_file, Gtk::PACK_SHRINK, 2); vboxes[8]->pack_start (*ff_AutoSelect, Gtk::PACK_SHRINK, 2); + vboxes[8]->pack_start (*ff_FromMetaData, Gtk::PACK_SHRINK, 2); vboxes[8]->pack_start (*ff_BlurType, Gtk::PACK_SHRINK, 2); vboxes[8]->pack_start (*ff_BlurRadius, Gtk::PACK_SHRINK, 2); vboxes[8]->pack_start (*ff_ClipControl, Gtk::PACK_SHRINK, 2); @@ -574,6 +576,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren //--- ff_fileConn = ff_file->signal_toggled().connect (sigc::bind (sigc::mem_fun(*raw, &Gtk::CheckButton::set_inconsistent), true)); ff_AutoSelectConn = ff_AutoSelect->signal_toggled().connect (sigc::bind (sigc::mem_fun(*raw, &Gtk::CheckButton::set_inconsistent), true)); + ff_FromMetaDataConn = ff_FromMetaData->signal_toggled().connect (sigc::bind (sigc::mem_fun(*raw, &Gtk::CheckButton::set_inconsistent), true)); ff_BlurTypeConn = ff_BlurType->signal_toggled().connect (sigc::bind (sigc::mem_fun(*raw, &Gtk::CheckButton::set_inconsistent), true)); ff_BlurRadiusConn = ff_BlurRadius->signal_toggled().connect (sigc::bind (sigc::mem_fun(*raw, &Gtk::CheckButton::set_inconsistent), true)); ff_ClipControlConn = ff_ClipControl->signal_toggled().connect (sigc::bind (sigc::mem_fun(*raw, &Gtk::CheckButton::set_inconsistent), true)); @@ -655,6 +658,7 @@ void PartialPasteDlg::rawToggled () ConnectionBlocker df_AutoSelectBlocker(df_AutoSelectConn); ConnectionBlocker ff_fileBlocker(ff_fileConn); ConnectionBlocker ff_AutoSelectBlocker(ff_AutoSelectConn); + ConnectionBlocker ff_FromMetaDataBlocker(ff_FromMetaDataConn); ConnectionBlocker ff_BlurTypeBlocker(ff_BlurTypeConn); ConnectionBlocker ff_BlurRadiusBlocker(ff_BlurRadiusConn); ConnectionBlocker ff_ClipControlBlocker(ff_ClipControlConn); @@ -685,6 +689,7 @@ void PartialPasteDlg::rawToggled () df_AutoSelect->set_active (raw->get_active ()); ff_file->set_active (raw->get_active ()); ff_AutoSelect->set_active (raw->get_active ()); + ff_FromMetaData->set_active (raw->get_active ()); ff_BlurType->set_active (raw->get_active ()); ff_BlurRadius->set_active (raw->get_active ()); ff_ClipControl->set_active (raw->get_active ()); @@ -1173,6 +1178,10 @@ void PartialPasteDlg::applyPaste (rtengine::procparams::ProcParams* dstPP, Param filterPE.raw.ff_AutoSelect = falsePE.raw.ff_AutoSelect; } + if (!ff_FromMetaData->get_active ()) { + filterPE.raw.ff_FromMetaData = falsePE.raw.ff_FromMetaData; + } + if (!ff_BlurRadius->get_active ()) { filterPE.raw.ff_BlurRadius = falsePE.raw.ff_BlurRadius; } diff --git a/rtgui/partialpastedlg.h b/rtgui/partialpastedlg.h index 19e1eb462..dcf44bb72 100644 --- a/rtgui/partialpastedlg.h +++ b/rtgui/partialpastedlg.h @@ -214,6 +214,7 @@ public: Gtk::CheckButton* df_AutoSelect; Gtk::CheckButton* ff_file; Gtk::CheckButton* ff_AutoSelect; + Gtk::CheckButton* ff_FromMetaData; Gtk::CheckButton* ff_BlurRadius; Gtk::CheckButton* ff_BlurType; Gtk::CheckButton* ff_ClipControl; @@ -230,7 +231,7 @@ public: sigc::connection distortionConn, cacorrConn, vignettingConn, lcpConn; sigc::connection coarserotConn, finerotConn, cropConn, resizeConn, prsharpeningConn, perspectiveConn, commonTransConn; sigc::connection metadataConn, exifchConn, iptcConn, icmConn; - sigc::connection df_fileConn, df_AutoSelectConn, ff_fileConn, ff_AutoSelectConn, ff_BlurRadiusConn, ff_BlurTypeConn, ff_ClipControlConn; + sigc::connection df_fileConn, df_AutoSelectConn, ff_fileConn, ff_AutoSelectConn, ff_FromMetaDataConn, ff_BlurRadiusConn, ff_BlurTypeConn, ff_ClipControlConn; sigc::connection raw_caredblueConn, raw_ca_autocorrectConn, raw_ca_avoid_colourshiftconn, raw_hotpix_filtConn, raw_deadpix_filtConn, raw_pdaf_lines_filterConn, raw_linenoiseConn, raw_greenthreshConn, raw_ccStepsConn, raw_methodConn, raw_borderConn, raw_imagenumConn, raw_dcb_iterationsConn, raw_lmmse_iterationsConn, raw_pixelshiftConn, raw_dcb_enhanceConn, raw_exposConn, raw_blackConn; sigc::connection filmNegativeConn; sigc::connection captureSharpeningConn; diff --git a/rtgui/thumbbrowserbase.cc b/rtgui/thumbbrowserbase.cc index 06c662e51..8f3499c2a 100644 --- a/rtgui/thumbbrowserbase.cc +++ b/rtgui/thumbbrowserbase.cc @@ -1091,6 +1091,25 @@ bool ThumbBrowserBase::Internal::on_scroll_event (GdkEventScroll* event) } +void ThumbBrowserBase::resort () +{ + { + MYWRITERLOCK(l, entryRW); + + std::sort( + fd.begin(), + fd.end(), + [](const ThumbBrowserEntryBase* a, const ThumbBrowserEntryBase* b) + { + bool lt = a->compare(*b, options.sortMethod); + return options.sortDescending ? !lt : lt; + } + ); + } + + redraw (); +} + void ThumbBrowserBase::redraw (ThumbBrowserEntryBase* entry) { @@ -1218,9 +1237,30 @@ void ThumbBrowserBase::enableTabMode(bool enable) } } -void ThumbBrowserBase::initEntry (ThumbBrowserEntryBase* entry) +void ThumbBrowserBase::insertEntry (ThumbBrowserEntryBase* entry) { - entry->setOffset ((int)(hscroll.get_value()), (int)(vscroll.get_value())); + // find place in sort order + { + MYWRITERLOCK(l, entryRW); + + fd.insert( + std::lower_bound( + fd.begin(), + fd.end(), + entry, + [](const ThumbBrowserEntryBase* a, const ThumbBrowserEntryBase* b) + { + bool lt = a->compare(*b, options.sortMethod); + return options.sortDescending ? !lt : lt; + } + ), + entry + ); + + entry->setOffset ((int)(hscroll.get_value()), (int)(vscroll.get_value())); + } + + redraw (); } void ThumbBrowserBase::getScrollPosition (double& h, double& v) diff --git a/rtgui/thumbbrowserbase.h b/rtgui/thumbbrowserbase.h index 2d41cdfab..8c1ec49c8 100644 --- a/rtgui/thumbbrowserbase.h +++ b/rtgui/thumbbrowserbase.h @@ -208,12 +208,13 @@ public: return fd; } void on_style_updated () override; + void resort (); // re-apply sort method void redraw (ThumbBrowserEntryBase* entry = nullptr); // arrange files and draw area void refreshThumbImages (); // refresh thumbnail sizes, re-generate thumbnail images, arrange and draw void refreshQuickThumbImages (); // refresh thumbnail sizes, re-generate thumbnail images, arrange and draw void refreshEditedState (const std::set& efiles); - void initEntry (ThumbBrowserEntryBase* entry); + void insertEntry (ThumbBrowserEntryBase* entry); void getScrollPosition (double& h, double& v); void setScrollPosition (double h, double v); diff --git a/rtgui/thumbbrowserentrybase.cc b/rtgui/thumbbrowserentrybase.cc index 306b491be..3d1e6bdc4 100644 --- a/rtgui/thumbbrowserentrybase.cc +++ b/rtgui/thumbbrowserentrybase.cc @@ -119,7 +119,7 @@ Glib::ustring getPaddedName(const Glib::ustring& name) } -ThumbBrowserEntryBase::ThumbBrowserEntryBase (const Glib::ustring& fname) : +ThumbBrowserEntryBase::ThumbBrowserEntryBase (const Glib::ustring& fname, Thumbnail *thm) : fnlabw(0), fnlabh(0), dtlabw(0), @@ -153,7 +153,8 @@ ThumbBrowserEntryBase::ThumbBrowserEntryBase (const Glib::ustring& fname) : bbPreview(nullptr), cursor_type(CSUndefined), collate_name(getPaddedName(dispname).casefold_collate_key()), - thumbnail(nullptr), + collate_exif(getPaddedName(thm->getExifString()).casefold_collate_key()), + thumbnail(thm), filename(fname), selected(false), drawable(false), diff --git a/rtgui/thumbbrowserentrybase.h b/rtgui/thumbbrowserentrybase.h index 764f806fd..3db03a96e 100644 --- a/rtgui/thumbbrowserentrybase.h +++ b/rtgui/thumbbrowserentrybase.h @@ -26,6 +26,8 @@ #include "guiutils.h" #include "lwbuttonset.h" #include "threadutils.h" +#include "options.h" +#include "thumbnail.h" #include "../rtengine/coord2d.h" @@ -95,6 +97,7 @@ protected: private: const std::string collate_name; + const std::string collate_exif; public: @@ -117,7 +120,7 @@ public: bool updatepriority; eWithFilename withFilename; - explicit ThumbBrowserEntryBase (const Glib::ustring& fname); + explicit ThumbBrowserEntryBase (const Glib::ustring& fname, Thumbnail *thm); virtual ~ThumbBrowserEntryBase (); void setParent (ThumbBrowserBase* l) @@ -174,9 +177,32 @@ public: void setPosition (int x, int y, int w, int h); void setOffset (int x, int y); - bool operator <(const ThumbBrowserEntryBase& other) const + bool compare (const ThumbBrowserEntryBase& other, Options::SortMethod method) const { - return collate_name < other.collate_name; + int cmp = 0; + switch (method){ + case Options::SORT_BY_NAME: + return collate_name < other.collate_name; + case Options::SORT_BY_DATE: + cmp = thumbnail->getDateTime().compare(other.thumbnail->getDateTime()); + break; + case Options::SORT_BY_EXIF: + cmp = collate_exif.compare(other.collate_exif); + break; + case Options::SORT_BY_RANK: + cmp = thumbnail->getRank() - other.thumbnail->getRank(); + break; + case Options::SORT_BY_LABEL: + cmp = thumbnail->getColorLabel() - other.thumbnail->getColorLabel(); + break; + case Options::SORT_METHOD_COUNT: abort(); + } + + // Always fall back to sorting by name + if (!cmp) + cmp = collate_name.compare(other.collate_name); + + return cmp < 0; } virtual void refreshThumbnailImage () = 0; diff --git a/rtgui/thumbnail.cc b/rtgui/thumbnail.cc index cc8e9ad81..30766ebc9 100644 --- a/rtgui/thumbnail.cc +++ b/rtgui/thumbnail.cc @@ -31,6 +31,7 @@ #include "../rtengine/procparams.h" #include "../rtengine/rtthumbnail.h" #include +#include #include "../rtengine/dynamicprofile.h" #include "../rtengine/profilestore.h" @@ -266,7 +267,7 @@ rtengine::procparams::ProcParams* Thumbnail::createProcParamsForUpdate(bool retu // Should we ask all frame's MetaData ? imageMetaData = rtengine::FramesMetaData::fromFile (fname, nullptr, true); } - PartialProfile *pp = ProfileStore::getInstance()->loadDynamicProfile(imageMetaData); + PartialProfile *pp = ProfileStore::getInstance()->loadDynamicProfile(imageMetaData, fname); delete imageMetaData; int err = pp->pparams->save(outFName); pp->deleteInstance(); @@ -718,11 +719,44 @@ rtengine::IImage8* Thumbnail::upgradeThumbImage (const rtengine::procparams::Pro void Thumbnail::generateExifDateTimeStrings () { + if (cfs.timeValid) { + std::string dateFormat = options.dateFormat; + std::ostringstream ostr; + bool spec = false; - exifString = ""; - dateTimeString = ""; + for (size_t i = 0; i < dateFormat.size(); i++) + if (spec && dateFormat[i] == 'y') { + ostr << cfs.year; + spec = false; + } else if (spec && dateFormat[i] == 'm') { + ostr << (int)cfs.month; + spec = false; + } else if (spec && dateFormat[i] == 'd') { + ostr << (int)cfs.day; + spec = false; + } else if (dateFormat[i] == '%') { + spec = true; + } else { + ostr << (char)dateFormat[i]; + spec = false; + } + + ostr << " " << (int)cfs.hour; + ostr << ":" << std::setw(2) << std::setfill('0') << (int)cfs.min; + ostr << ":" << std::setw(2) << std::setfill('0') << (int)cfs.sec; + + dateTimeString = ostr.str (); + dateTime = Glib::DateTime::create_local(cfs.year, cfs.month, cfs.day, + cfs.hour, cfs.min, cfs.sec); + } + + if (!dateTime.gobj() || !cfs.timeValid) { + dateTimeString = ""; + dateTime = Glib::DateTime::create_now_utc(0); + } if (!cfs.exifValid) { + exifString = ""; return; } @@ -731,33 +765,6 @@ void Thumbnail::generateExifDateTimeStrings () if (options.fbShowExpComp && cfs.expcomp != "0.00" && !cfs.expcomp.empty()) { // don't show exposure compensation if it is 0.00EV;old cache files do not have ExpComp, so value will not be displayed. exifString = Glib::ustring::compose ("%1 %2EV", exifString, cfs.expcomp); // append exposure compensation to exifString } - - std::string dateFormat = options.dateFormat; - std::ostringstream ostr; - bool spec = false; - - for (size_t i = 0; i < dateFormat.size(); i++) - if (spec && dateFormat[i] == 'y') { - ostr << cfs.year; - spec = false; - } else if (spec && dateFormat[i] == 'm') { - ostr << (int)cfs.month; - spec = false; - } else if (spec && dateFormat[i] == 'd') { - ostr << (int)cfs.day; - spec = false; - } else if (dateFormat[i] == '%') { - spec = true; - } else { - ostr << (char)dateFormat[i]; - spec = false; - } - - ostr << " " << (int)cfs.hour; - ostr << ":" << std::setw(2) << std::setfill('0') << (int)cfs.min; - ostr << ":" << std::setw(2) << std::setfill('0') << (int)cfs.sec; - - dateTimeString = ostr.str (); } const Glib::ustring& Thumbnail::getExifString () const @@ -772,6 +779,12 @@ const Glib::ustring& Thumbnail::getDateTimeString () const return dateTimeString; } +const Glib::DateTime& Thumbnail::getDateTime () const +{ + + return dateTime; +} + void Thumbnail::getAutoWB (double& temp, double& green, double equal, double tempBias) { if (cfs.redAWBMul != -1.0) { @@ -802,6 +815,16 @@ int Thumbnail::infoFromImage (const Glib::ustring& fname, std::unique_ptrgetDateTimeAsTS() > 0) { + cfs.year = 1900 + idata->getDateTime().tm_year; + cfs.month = idata->getDateTime().tm_mon + 1; + cfs.day = idata->getDateTime().tm_mday; + cfs.hour = idata->getDateTime().tm_hour; + cfs.min = idata->getDateTime().tm_min; + cfs.sec = idata->getDateTime().tm_sec; + cfs.timeValid = true; + } + if (idata->hasExif()) { cfs.shutter = idata->getShutterSpeed (); cfs.fnumber = idata->getFNumber (); @@ -814,18 +837,11 @@ int Thumbnail::infoFromImage (const Glib::ustring& fname, std::unique_ptrgetPixelShift (); cfs.frameCount = idata->getFrameCount (); cfs.sampleFormat = idata->getSampleFormat (); - cfs.year = 1900 + idata->getDateTime().tm_year; - cfs.month = idata->getDateTime().tm_mon + 1; - cfs.day = idata->getDateTime().tm_mday; - cfs.hour = idata->getDateTime().tm_hour; - cfs.min = idata->getDateTime().tm_min; - cfs.sec = idata->getDateTime().tm_sec; - cfs.timeValid = true; - cfs.exifValid = true; cfs.lens = idata->getLens(); cfs.camMake = idata->getMake(); cfs.camModel = idata->getModel(); cfs.rating = idata->getRating(); + cfs.exifValid = true; if (idata->getOrientation() == "Rotate 90 CW") { deg = 90; diff --git a/rtgui/thumbnail.h b/rtgui/thumbnail.h index cda69f030..491151028 100644 --- a/rtgui/thumbnail.h +++ b/rtgui/thumbnail.h @@ -22,6 +22,7 @@ #include #include +#include #include "cacheimagedata.h" #include "threadutils.h" @@ -73,6 +74,7 @@ class Thumbnail // exif & date/time strings Glib::ustring exifString; Glib::ustring dateTimeString; + Glib::DateTime dateTime; bool initial_; @@ -124,6 +126,7 @@ public: const Glib::ustring& getExifString () const; const Glib::ustring& getDateTimeString () const; + const Glib::DateTime& getDateTime () const; void getCamWB (double& temp, double& green) const; void getAutoWB (double& temp, double& green, double equal, double tempBias); void getSpotWB (int x, int y, int rect, double& temp, double& green); diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index 9c14aeb6e..fe8b4f1bf 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -343,12 +343,12 @@ ToolPanelCoordinator::~ToolPanelCoordinator () delete toolBar; } -void ToolPanelCoordinator::imageTypeChanged(bool isRaw, bool isBayer, bool isXtrans, bool isMono) +void ToolPanelCoordinator::imageTypeChanged(bool isRaw, bool isBayer, bool isXtrans, bool isMono, bool isGainMapSupported) { if (isRaw) { if (isBayer) { idle_register.add( - [this]() -> bool + [this, isGainMapSupported]() -> bool { rawPanelSW->set_sensitive(true); sensorxtrans->FoldableToolPanel::hide(); @@ -362,6 +362,7 @@ void ToolPanelCoordinator::imageTypeChanged(bool isRaw, bool isBayer, bool isXtr preprocessWB->FoldableToolPanel::show(); preprocess->FoldableToolPanel::show(); flatfield->FoldableToolPanel::show(); + flatfield->setGainMap(isGainMapSupported); pdSharpening->FoldableToolPanel::show(); retinex->FoldableToolPanel::setGrayedOut(false); return false; @@ -369,7 +370,7 @@ void ToolPanelCoordinator::imageTypeChanged(bool isRaw, bool isBayer, bool isXtr ); } else if (isXtrans) { idle_register.add( - [this]() -> bool + [this, isGainMapSupported]() -> bool { rawPanelSW->set_sensitive(true); sensorxtrans->FoldableToolPanel::show(); @@ -383,6 +384,7 @@ void ToolPanelCoordinator::imageTypeChanged(bool isRaw, bool isBayer, bool isXtr preprocessWB->FoldableToolPanel::show(); preprocess->FoldableToolPanel::show(); flatfield->FoldableToolPanel::show(); + flatfield->setGainMap(isGainMapSupported); pdSharpening->FoldableToolPanel::show(); retinex->FoldableToolPanel::setGrayedOut(false); return false; @@ -390,7 +392,7 @@ void ToolPanelCoordinator::imageTypeChanged(bool isRaw, bool isBayer, bool isXtr ); } else if (isMono) { idle_register.add( - [this]() -> bool + [this, isGainMapSupported]() -> bool { rawPanelSW->set_sensitive(true); sensorbayer->FoldableToolPanel::hide(); @@ -403,6 +405,7 @@ void ToolPanelCoordinator::imageTypeChanged(bool isRaw, bool isBayer, bool isXtr preprocessWB->FoldableToolPanel::hide(); preprocess->FoldableToolPanel::hide(); flatfield->FoldableToolPanel::show(); + flatfield->setGainMap(isGainMapSupported); pdSharpening->FoldableToolPanel::show(); retinex->FoldableToolPanel::setGrayedOut(false); return false; diff --git a/rtgui/toolpanelcoord.h b/rtgui/toolpanelcoord.h index 65e2f1e8f..cd4131ff4 100644 --- a/rtgui/toolpanelcoord.h +++ b/rtgui/toolpanelcoord.h @@ -260,7 +260,7 @@ public: void unsetTweakOperator (rtengine::TweakOperator *tOperator) override; // FilmNegProvider interface - void imageTypeChanged (bool isRaw, bool isBayer, bool isXtrans, bool isMono = false) override; + void imageTypeChanged (bool isRaw, bool isBayer, bool isXtrans, bool isMono = false, bool isGainMapSupported = false) override; // profilechangelistener interface void profileChange(