From 96ad7051cfbe8893f97194f637ea467aa332e081 Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Mon, 4 Nov 2024 22:57:55 -0500 Subject: [PATCH 01/21] Refactor aspect ratios out of crop.cc --- rtgui/CMakeLists.txt | 1 + rtgui/aspectratios.cc | 65 +++++++++++++++++++++++++++++++++++++++++++ rtgui/aspectratios.h | 37 ++++++++++++++++++++++++ rtgui/crop.cc | 39 +++----------------------- 4 files changed, 107 insertions(+), 35 deletions(-) create mode 100644 rtgui/aspectratios.cc create mode 100644 rtgui/aspectratios.h diff --git a/rtgui/CMakeLists.txt b/rtgui/CMakeLists.txt index cd2b01895..6e4908bcb 100644 --- a/rtgui/CMakeLists.txt +++ b/rtgui/CMakeLists.txt @@ -13,6 +13,7 @@ set(CLISOURCEFILES set(NONCLISOURCEFILES adjuster.cc alignedmalloc.cc + aspectratios.cc batchqueue.cc batchqueuebuttonset.cc batchqueueentry.cc diff --git a/rtgui/aspectratios.cc b/rtgui/aspectratios.cc new file mode 100644 index 000000000..e9e525dbb --- /dev/null +++ b/rtgui/aspectratios.cc @@ -0,0 +1,65 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2004-2010 Gabor Horvath + * + * 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 . + * + * 2024-2024 Daniel Gao + */ + +#include "aspectratios.h" + +namespace { + +static const std::vector ASPECT_RATIOS { + {"3:2", 3.0 / 2.0}, // L1.5, P0.666... + {"4:3", 4.0 / 3.0}, // L1.333..., P0.75 + {"16:9", 16.0 / 9.0}, // L1.777..., P0.5625 + {"16:10", 16.0 / 10.0}, // L1.6, P0.625 + {"1:1", 1.0 / 1.0}, // L1, P1 + {"2:1", 2.0 / 1.0}, // L2, P0.5 + {"3:1", 3.0 / 1.0}, // L3, P0.333... + {"4:1", 4.0 / 1.0}, // L4, P0.25 + {"5:1", 5.0 / 1.0}, // L5, P0.2 + {"6:1", 6.0 / 1.0}, // L6, P0.1666... + {"7:1", 7.0 / 1.0}, // L7, P0.142... + {"4:5", 4.0 / 5.0}, // L1.25, P0.8 + {"5:7", 5.0 / 7.0}, // L1.4, P0.714... + {"6:7", 6.0 / 7.0}, // L1.166..., P0.857... + {"6:17", 6.0 / 17.0}, // L2.833..., P0.352... + {"24:65 - XPAN", 24.0 / 65.0}, // L2.708..., P0.369... + {"1.414 - DIN EN ISO 216", 1.414}, // L1.414, P0.707... + {"3.5:5", 3.5 / 5.0}, // L1.428..., P0.7 + {"8.5:11 - US Letter", 8.5 / 11.0}, // L1.294..., P0.772... + {"9.5:12", 9.5 / 12.0}, // L1.263..., P0.791... + {"10:12", 10.0 / 12.0}, // L1.2, P0.833... + {"11:14", 11.0 / 14.0}, // L1.272..., P0.785... + {"11:17 - Tabloid", 11.0 / 17.0}, // L1.545..., P0.647... + {"13:19", 13.0 / 19.0}, // L1.461..., P0.684... + {"17:22", 17.0 / 22.0}, // L1.294..., P0.772... + {"45:35 - ePassport", 45.0 / 35.0}, // L1.285,... P0.777... + {"64:27", 64.0 / 27.0}, // L2.370..., P0.421... + {"13:18", 13.0 / 18.0}, // L1.384..., P0.722... +}; + +} // namespace + +void fillAspectRatios(std::vector& ratios) { + ratios.reserve(ratios.size() + ASPECT_RATIOS.size()); + + for (const auto& ratio : ASPECT_RATIOS) { + ratios.push_back(ratio); + } +} diff --git a/rtgui/aspectratios.h b/rtgui/aspectratios.h new file mode 100644 index 000000000..3f190522a --- /dev/null +++ b/rtgui/aspectratios.h @@ -0,0 +1,37 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2004-2010 Gabor Horvath + * + * 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 . + * + * 2024-2024 Daniel Gao + */ + +#pragma once + +#include + +#include + +struct AspectRatio { + Glib::ustring label; + double value; +}; + +// Pushes known aspect ratios to the given vector. +// +// Callers should populate ratios for entries like "As Imange" and "Current" +// before calling this function. +void fillAspectRatios(std::vector& ratios); diff --git a/rtgui/crop.cc b/rtgui/crop.cc index 0bbbdbf1f..46aef2219 100644 --- a/rtgui/crop.cc +++ b/rtgui/crop.cc @@ -20,6 +20,7 @@ #include "crop.h" +#include "aspectratios.h" #include "options.h" #include "rtimage.h" @@ -57,37 +58,10 @@ public: CropRatios() : ratios{ {M("GENERAL_ASIMAGE"), 0.0}, - {M("GENERAL_CURRENT"), -1.0}, - {"3:2", 3.0 / 2.0}, // L1.5, P0.666... - {"4:3", 4.0 / 3.0}, // L1.333..., P0.75 - {"16:9", 16.0 / 9.0}, // L1.777..., P0.5625 - {"16:10", 16.0 / 10.0}, // L1.6, P0.625 - {"1:1", 1.0 / 1.0}, // L1, P1 - {"2:1", 2.0 / 1.0}, // L2, P0.5 - {"3:1", 3.0 / 1.0}, // L3, P0.333... - {"4:1", 4.0 / 1.0}, // L4, P0.25 - {"5:1", 5.0 / 1.0}, // L5, P0.2 - {"6:1", 6.0 / 1.0}, // L6, P0.1666... - {"7:1", 7.0 / 1.0}, // L7, P0.142... - {"4:5", 4.0 / 5.0}, // L1.25, P0.8 - {"5:7", 5.0 / 7.0}, // L1.4, P0.714... - {"6:7", 6.0 / 7.0}, // L1.166..., P0.857... - {"6:17", 6.0 / 17.0}, // L2.833..., P0.352... - {"24:65 - XPAN", 24.0 / 65.0}, // L2.708..., P0.369... - {"1.414 - DIN EN ISO 216", 1.414}, // L1.414, P0.707... - {"3.5:5", 3.5 / 5.0}, // L1.428..., P0.7 - {"8.5:11 - US Letter", 8.5 / 11.0}, // L1.294..., P0.772... - {"9.5:12", 9.5 / 12.0}, // L1.263..., P0.791... - {"10:12", 10.0 / 12.0}, // L1.2, P0.833... - {"11:14", 11.0 / 14.0}, // L1.272..., P0.785... - {"11:17 - Tabloid", 11.0 / 17.0}, // L1.545..., P0.647... - {"13:19", 13.0 / 19.0}, // L1.461..., P0.684... - {"17:22", 17.0 / 22.0}, // L1.294..., P0.772... - {"45:35 - ePassport", 45.0 / 35.0}, // L1.285,... P0.777... - {"64:27", 64.0 / 27.0}, // L2.370..., P0.421... - {"13:18", 13.0 / 18.0}, // L1.384..., P0.722... + {M("GENERAL_CURRENT"), -1.0} } { + fillAspectRatios(ratios); } std::vector getLabels() const @@ -117,12 +91,7 @@ public: } private: - struct CropRatio { - Glib::ustring label; - double value; - }; - - std::vector ratios; + std::vector ratios; }; Crop::Crop(): From 03a73eb3a4b977db8b8ad55af53b1897ed0d8329 Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Mon, 4 Nov 2024 16:38:43 -0500 Subject: [PATCH 02/21] Add framing tool GUI * Added under the Resize tool like PR sharpening * Adds FramingParams and FramingParamsEdited structs * No parameter read/write implemented * No support for batch mode * No adjuster listeners implemented * Signals and callback functions are defined but do nothing --- rtdata/languages/default | 33 ++- rtengine/procparams.cc | 50 ++++ rtengine/procparams.h | 58 +++++ rtgui/CMakeLists.txt | 1 + rtgui/batchtoolpanelcoord.cc | 4 + rtgui/framing.cc | 466 +++++++++++++++++++++++++++++++++++ rtgui/framing.h | 141 +++++++++++ rtgui/paramsedited.h | 23 ++ rtgui/prsharpening.cc | 2 +- rtgui/resize.cc | 9 +- rtgui/resize.h | 5 +- rtgui/toollocationpref.cc | 2 + rtgui/toolpanelcoord.cc | 12 + rtgui/toolpanelcoord.h | 3 + 14 files changed, 798 insertions(+), 11 deletions(-) create mode 100644 rtgui/framing.cc create mode 100644 rtgui/framing.h diff --git a/rtdata/languages/default b/rtdata/languages/default index fdcab9568..b68b25b8a 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1587,8 +1587,8 @@ HISTORY_MSG_TONE_EQUALIZER_ENABLED;Tone equalizer HISTORY_MSG_TONE_EQUALIZER_PIVOT;Tone equalizer - Pivot HISTORY_MSG_TONE_EQUALIZER_REGULARIZATION;Tone equalizer - Regularization HISTORY_MSG_TONE_EQUALIZER_SHOW_COLOR_MAP;Tone equalizer - Tonal map -HISTORY_MSG_TRANS_SCALE;Geometry - Scale HISTORY_MSG_TRANS_METHOD;Geometry - Method +HISTORY_MSG_TRANS_SCALE;Geometry - Scale HISTORY_MSG_WAVBALCHROM;Equalizer chrominance HISTORY_MSG_WAVBALLUM;Equalizer luminance HISTORY_MSG_WAVBL;Blur levels @@ -2709,6 +2709,35 @@ 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_FRAMING_ABSOLUTE_HEIGHT;Border Height +TP_FRAMING_ABSOLUTE_WIDTH;Border Width +TP_FRAMING_ALLOW_UPSCALING;Allow upscaling to frame +TP_FRAMING_ASPECT_RATIO;Aspect Ratio: +TP_FRAMING_BASIS;Basis +TP_FRAMING_BASIS_AUTO;Auto +TP_FRAMING_BASIS_HEIGHT;Height +TP_FRAMING_BASIS_LONG_SIDE;Long Edge +TP_FRAMING_BASIS_SHORT_SIDE;Short Edge +TP_FRAMING_BASIS_WIDTH;Width +TP_FRAMING_BLUE;Blue +TP_FRAMING_BORDER_COLOR;Border Color +TP_FRAMING_BORDER_SIZE;Size +TP_FRAMING_BORDER_SIZE_ABSOLUTE;Absolute +TP_FRAMING_BORDER_SIZE_METHOD;Sizing: +TP_FRAMING_BORDER_SIZE_RELATIVE;Relative +TP_FRAMING_FRAMED_HEIGHT;Framed Height +TP_FRAMING_FRAMED_WIDTH;Framed Width +TP_FRAMING_GREEN;Green +TP_FRAMING_LABEL;Framing +TP_FRAMING_LIMIT_MINIMUM;Limit minimum size +TP_FRAMING_METHOD;Method: +TP_FRAMING_METHOD_BBOX;Bounding Box +TP_FRAMING_METHOD_FIXED;Fixed Frame +TP_FRAMING_METHOD_STANDARD;Standard +TP_FRAMING_MIN_HEIGHT;Minimum Height +TP_FRAMING_MIN_WIDTH;Minimum Width +TP_FRAMING_ORIENTATION;Orientation: +TP_FRAMING_RED;Red 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 TP_GRADIENT_CENTER_X;Center X @@ -2876,11 +2905,11 @@ TP_LABCURVE_LCREDSK_TOOLTIP;If enabled, the LC Curve affects only red and skin-t TP_LABCURVE_RSTPROTECTION;Red and skin-tones protection TP_LABCURVE_RSTPRO_TOOLTIP;Works on the Chromaticity slider and the CC curve. TP_LENSGEOM_AUTOCROP;Auto-Crop -TP_LENSGEOM_SCALE;Scale TP_LENSGEOM_FILL;Auto-fill TP_LENSGEOM_LABEL;Lens / Geometry TP_LENSGEOM_LIN;Linear TP_LENSGEOM_LOG;Logarithmic +TP_LENSGEOM_SCALE;Scale TP_LENSPROFILE_CORRECTION_AUTOMATCH;Automatically selected TP_LENSPROFILE_CORRECTION_LCPFILE;LCP file TP_LENSPROFILE_CORRECTION_MANUAL;Manually selected diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 63882d816..0a0c9bd68 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -2425,6 +2425,56 @@ bool ResizeParams::operator !=(const ResizeParams& other) const return !(*this == other); } +FramingParams::FramingParams() : + enabled(false), + framingMethod(FramingMethod::STANDARD), + aspectRatio(0), + orientation(Orientation::AS_IMAGE), + framedWidth(800), + framedHeight(600), + allowUpscaling(false), + borderSizingMethod(BorderSizing::PERCENTAGE), + basis(Basis::AUTO), + relativeBorderSize(0.1), + minSizeEnabled(false), + minWidth(0), + minHeight(0), + absWidth(0), + absHeight(0), + borderRed(255), + borderGreen(255), + borderBlue(255) +{ +} + +bool FramingParams::operator ==(const FramingParams& other) const +{ + return + enabled == other.enabled + && framingMethod == other.framingMethod + && aspectRatio == other.aspectRatio + && orientation == other.orientation + && framedWidth == other.framedWidth + && framedHeight == other.framedHeight + && allowUpscaling == other.allowUpscaling + && borderSizingMethod == other.borderSizingMethod + && basis == other.basis + && relativeBorderSize == other.relativeBorderSize + && minSizeEnabled == other.minSizeEnabled + && minWidth == other.minWidth + && minHeight == other.minHeight + && absWidth == other.absWidth + && absHeight == other.absHeight + && borderRed == other.borderRed + && borderGreen == other.borderGreen + && borderBlue == other.borderBlue; +} + +bool FramingParams::operator !=(const FramingParams& other) const +{ + return !(*this == other); +} + const Glib::ustring ColorManagementParams::NoICMString = Glib::ustring("No ICM: sRGB output"); const Glib::ustring ColorManagementParams::NoProfileString = Glib::ustring("(none)"); diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 80a9100d1..7c2833ba1 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -1984,6 +1984,64 @@ struct ResizeParams { bool operator !=(const ResizeParams& other) const; }; +struct FramingParams { + // How is framed size determined? + enum class FramingMethod { + STANDARD, // Unconstrained framed size + BBOX, // Framed size within bounding box + FIXED_SIZE // Fixed framed size + }; + + // Orientation of framed image + enum class Orientation { + AS_IMAGE, + LANDSCAPE, + PORTRAIT + }; + + // How to size border? + enum class BorderSizing { + PERCENTAGE, // Percentage of image size + FIXED_SIZE // Fixed pixel dimensions + }; + + // Which dimension to use for percentage based border sizing? + enum class Basis { + AUTO, // Determine by aspect ratio of image and frame + WIDTH, + HEIGHT, + LONG, // Use long side of image + SHORT // Use short side of image + }; + + FramingParams(); + + bool enabled; + + FramingMethod framingMethod; + double aspectRatio; // 0 - Use aspect ratio of image + Orientation orientation; + int framedWidth; + int framedHeight; + bool allowUpscaling; + + BorderSizing borderSizingMethod; + Basis basis; + double relativeBorderSize; + bool minSizeEnabled; + int minWidth; + int minHeight; + int absWidth; + int absHeight; + + int borderRed; + int borderGreen; + int borderBlue; + + bool operator ==(const FramingParams& other) const; + bool operator !=(const FramingParams& other) const; +}; + /** * Parameters entry */ diff --git a/rtgui/CMakeLists.txt b/rtgui/CMakeLists.txt index 6e4908bcb..289992653 100644 --- a/rtgui/CMakeLists.txt +++ b/rtgui/CMakeLists.txt @@ -77,6 +77,7 @@ set(NONCLISOURCEFILES filterpanel.cc flatcurveeditorsubgroup.cc flatfield.cc + framing.cc gradient.cc guiutils.cc histogrampanel.cc diff --git a/rtgui/batchtoolpanelcoord.cc b/rtgui/batchtoolpanelcoord.cc index 219054ca4..35e4f7892 100644 --- a/rtgui/batchtoolpanelcoord.cc +++ b/rtgui/batchtoolpanelcoord.cc @@ -467,9 +467,13 @@ void BatchToolPanelCoordinator::panelChanged(const rtengine::ProcEvent& event, c crop->write (&pparams, &pparamsEdited); resize->update (pparams.crop.enabled, pparams.crop.w, pparams.crop.h, w, h); resize->write (&pparams, &pparamsEdited); + framing->update (pparams.crop.enabled, pparams.crop.w, pparams.crop.h, w, h); + framing->write (&pparams, &pparamsEdited); } else if (event == rtengine::EvCrop) { resize->update (pparams.crop.enabled, pparams.crop.w, pparams.crop.h); resize->write (&pparams, &pparamsEdited); + framing->update (pparams.crop.enabled, pparams.crop.w, pparams.crop.h, w, h); + framing->write (&pparams, &pparamsEdited); } } else { // Compensate rotation on flip diff --git a/rtgui/framing.cc b/rtgui/framing.cc new file mode 100644 index 000000000..fa38f91f6 --- /dev/null +++ b/rtgui/framing.cc @@ -0,0 +1,466 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2004-2010 Gabor Horvath + * + * 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 . + * + * 2024-2024 Daniel Gao + */ + +#include "framing.h" + +#include "aspectratios.h" +#include "resize.h" + +#include + +namespace { + +// Framing method combo box data +constexpr int INDEX_STANDARD = 0; +constexpr int INDEX_BBOX = 1; +constexpr int INDEX_FIXED = 2; +constexpr std::array FRAMING_METHODS = { + "TP_FRAMING_METHOD_STANDARD", + "TP_FRAMING_METHOD_BBOX", + "TP_FRAMING_METHOD_FIXED" +}; + +// Orientation combo box data +constexpr int INDEX_AS_IMAGE = 0; +constexpr int INDEX_LANDSCAPE = 1; +constexpr int INDEX_PORTRAIT = 2; +constexpr std::array ORIENTATION = { + "GENERAL_ASIMAGE", + "GENERAL_LANDSCAPE", + "GENERAL_PORTRAIT" +}; + +// Border sizing method combo box data +constexpr int INDEX_SIZE_RELATIVE = 0; +constexpr int INDEX_SIZE_ABSOLUTE = 1; +constexpr std::array BORDER_SIZE_METHODS = { + "TP_FRAMING_BORDER_SIZE_RELATIVE", + "TP_FRAMING_BORDER_SIZE_ABSOLUTE" +}; + +// Relative sizing basis combo box data +constexpr int INDEX_BASIS_AUTO = 0; +constexpr int INDEX_BASIS_WIDTH = 1; +constexpr int INDEX_BASIS_HEIGHT = 2; +constexpr int INDEX_BASIS_LONG = 3; +constexpr int INDEX_BASIS_SHORT = 4; +constexpr std::array BORDER_SIZE_BASIS = { + "TP_FRAMING_BASIS_AUTO", + "TP_FRAMING_BASIS_WIDTH", + "TP_FRAMING_BASIS_HEIGHT", + "TP_FRAMING_BASIS_LONG_SIDE", + "TP_FRAMING_BASIS_SHORT_SIDE" +}; + +constexpr int INITIAL_IMG_WIDTH = 800; +constexpr int INITIAL_IMG_HEIGHT = 600; + +constexpr int ROW_SPACING = 4; +constexpr float FRAME_LABEL_ALIGN_X = 0.025; +constexpr float FRAME_LABEL_ALIGN_Y = 0.5; + +Gtk::Label* createGridLabel(const char* text) +{ + Gtk::Label* label = Gtk::manage(new Gtk::Label(M(text))); + label->set_halign(Gtk::ALIGN_START); + return label; +} + +MySpinButton* createSpinButton() +{ + MySpinButton* button = Gtk::manage(new MySpinButton()); + button->set_width_chars(5); + button->set_digits(0); + button->set_increments(1, 100); + setExpandAlignProperties(button, false, false, Gtk::ALIGN_END, Gtk::ALIGN_CENTER); + return button; +} + +} // namespace + +const Glib::ustring Framing::TOOL_NAME = "framing"; + +class Framing::AspectRatios +{ +public: + static constexpr int INDEX_CURRENT = 0; + + AspectRatios() : + ratios{{M("GENERAL_CURRENT")}} + { + fillAspectRatios(ratios); + } + + void fillCombo(MyComboBoxText* combo) + { + for (const auto& aspectRatio : ratios) { + combo->append(aspectRatio.label); + } + combo->set_active(INDEX_CURRENT); + } + +private: + std::vector ratios; +}; + +Framing::DimensionGui::DimensionGui(Gtk::Box* parent, const char* text) +{ + box = Gtk::manage(new Gtk::Box()); + Gtk::Label* label = Gtk::manage(new Gtk::Label(M(text))); + setExpandAlignProperties(label, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); + value = createSpinButton(); + box->pack_start(*label); + box->pack_start(*value); + parent->pack_start(*box); +} + +void Framing::DimensionGui::connect(Framing& framing, CallbackFunc callback) +{ + connection = value->signal_value_changed().connect(sigc::mem_fun(framing, callback), true); +} + +Framing::Framing() : + FoldableToolPanel(this, TOOL_NAME, M("TP_FRAMING_LABEL"), false, true), + aspectRatioData(new AspectRatios), + imgWidth(INITIAL_IMG_WIDTH), + imgHeight(INITIAL_IMG_HEIGHT) +{ + setupFramingMethodGui(); + pack_start(*Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL))); + setupBorderSizeGui(); + pack_start(*Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL))); + setupBorderColorsGui(); +} + +Framing::~Framing() { + idleRegister.destroy(); +} + +void Framing::setupFramingMethodGui() +{ + Gtk::Grid* combos = Gtk::manage(new Gtk::Grid()); + combos->set_row_spacing(ROW_SPACING); + + framingMethod = Gtk::manage(new MyComboBoxText()); + for (auto label : FRAMING_METHODS) { + framingMethod->append(M(label)); + } + framingMethod->set_active(INDEX_STANDARD); + framingMethod->set_hexpand(); + framingMethod->set_halign(Gtk::ALIGN_FILL); + + combos->attach(*createGridLabel("TP_FRAMING_METHOD"), 0, 0); + combos->attach(*framingMethod, 1, 0); + + aspectRatioLabel = createGridLabel("TP_FRAMING_ASPECT_RATIO"); + aspectRatio = Gtk::manage(new MyComboBoxText()); + aspectRatioData->fillCombo(aspectRatio); + aspectRatio->set_hexpand(); + aspectRatio->set_halign(Gtk::ALIGN_FILL); + + combos->attach(*aspectRatioLabel, 0, 1); + combos->attach(*aspectRatio, 1, 1); + + orientationLabel = createGridLabel("TP_FRAMING_ORIENTATION"); + orientation = Gtk::manage(new MyComboBoxText()); + for (auto label : ORIENTATION) { + orientation->append(M(label)); + } + orientation->set_active(INDEX_AS_IMAGE); + orientation->set_hexpand(); + orientation->set_halign(Gtk::ALIGN_FILL); + + combos->attach(*orientationLabel, 0, 2); + combos->attach(*orientation, 1, 2); + pack_start(*combos); + + width = DimensionGui(this, "TP_FRAMING_FRAMED_WIDTH"); + width.setRange(Resize::MIN_SIZE, Resize::MAX_SCALE * imgWidth); + width.setValue(imgWidth); + height = DimensionGui(this, "TP_FRAMING_FRAMED_HEIGHT"); + height.setRange(Resize::MIN_SIZE, Resize::MAX_SCALE * imgHeight); + height.setValue(imgHeight); + + allowUpscaling = Gtk::manage(new Gtk::CheckButton(M("TP_FRAMING_ALLOW_UPSCALING"))); + pack_start(*allowUpscaling); + + updateFramingMethodGui(); + + framingMethodChanged = framingMethod->signal_changed().connect( + sigc::mem_fun(*this, &Framing::onFramingMethodChanged)); + aspectRatioChanged = aspectRatio->signal_changed().connect( + sigc::mem_fun(*this, &Framing::onAspectRatioChanged)); + orientationChanged = orientation->signal_changed().connect( + sigc::mem_fun(*this, &Framing::onOrientationChanged)); + width.connect(*this, &Framing::onWidthChanged); + height.connect(*this, &Framing::onHeightChanged); + allowUpscalingConnection = allowUpscaling->signal_toggled().connect( + sigc::mem_fun(*this, &Framing::onAllowUpscalingToggled)); +} + +void Framing::setupBorderSizeGui() +{ + Gtk::Grid* combos = Gtk::manage(new Gtk::Grid()); + combos->set_row_spacing(ROW_SPACING); + + borderSizeMethod = Gtk::manage(new MyComboBoxText()); + for (auto label : BORDER_SIZE_METHODS) { + borderSizeMethod->append(M(label)); + } + borderSizeMethod->set_active(INDEX_SIZE_RELATIVE); + borderSizeMethod->set_hexpand(); + borderSizeMethod->set_halign(Gtk::ALIGN_FILL); + + combos->attach(*createGridLabel("TP_FRAMING_BORDER_SIZE_METHOD"), 0, 0); + combos->attach(*borderSizeMethod, 1, 0); + + basisLabel = createGridLabel("TP_FRAMING_BASIS"); + basis = Gtk::manage(new MyComboBoxText()); + for (auto label : BORDER_SIZE_BASIS) { + basis->append(M(label)); + } + basis->set_active(INDEX_BASIS_AUTO); + basis->set_hexpand(); + basis->set_halign(Gtk::ALIGN_FILL); + + combos->attach(*basisLabel, 0, 1); + combos->attach(*basis, 1, 1); + + pack_start(*combos); + + relativeBorderSize = Gtk::manage(new Adjuster(M("TP_FRAMING_BORDER_SIZE"), 0, 1, 0.01, 0.1)); + pack_start(*relativeBorderSize); + + minSizeFrame = Gtk::manage(new Gtk::Frame()); + minSizeFrame->set_label_align(FRAME_LABEL_ALIGN_X, FRAME_LABEL_ALIGN_Y); + minSizeEnabled = Gtk::manage(new Gtk::CheckButton(M("TP_FRAMING_LIMIT_MINIMUM"))); + minSizeFrame->set_label_widget(*minSizeEnabled); + + minSizeFrameContent = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + + minWidth = DimensionGui(minSizeFrameContent, "TP_FRAMING_MIN_WIDTH"); + minWidth.setRange(0, imgWidth); + minWidth.setValue(0); + minHeight = DimensionGui(minSizeFrameContent, "TP_FRAMING_MIN_HEIGHT"); + minHeight.setRange(0, imgHeight); + minHeight.setValue(0); + + minSizeFrame->add(*minSizeFrameContent); + pack_start(*minSizeFrame); + + absWidth = DimensionGui(this, "TP_FRAMING_ABSOLUTE_WIDTH"); + absWidth.setRange(0, imgWidth); + absWidth.setValue(0); + absHeight = DimensionGui(this, "TP_FRAMING_ABSOLUTE_HEIGHT"); + absHeight.setRange(0, imgHeight); + absHeight.setValue(0); + + updateBorderSizeGui(); + + borderSizeMethodChanged = borderSizeMethod->signal_changed().connect( + sigc::mem_fun(*this, &Framing::onBorderSizeMethodChanged)); + basisChanged = basis->signal_changed().connect( + sigc::mem_fun(*this, &Framing::onBasisChanged)); + relativeBorderSize->setAdjusterListener(this); + minSizeEnabledConnection = minSizeEnabled->signal_toggled().connect( + sigc::mem_fun(*this, &Framing::onMinSizeToggled)); + minWidth.connect(*this, &Framing::onMinWidthChanged); + minHeight.connect(*this, &Framing::onMinHeightChanged); + absWidth.connect(*this, &Framing::onAbsWidthChanged); + absHeight.connect(*this, &Framing::onAbsHeightChanged); +} + +void Framing::setupBorderColorsGui() +{ + Gtk::Frame* const frame = Gtk::manage(new Gtk::Frame()); + + Gtk::Label* const label = Gtk::manage(new Gtk::Label(M("TP_FRAMING_BORDER_COLOR"))); + frame->set_label_align(FRAME_LABEL_ALIGN_X, FRAME_LABEL_ALIGN_Y); + frame->set_label_widget(*label); + + Gtk::Box* const box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + redAdj = Gtk::manage(new Adjuster(M("TP_FRAMING_RED"), 0, 255, 1, 255)); + box->add(*redAdj); + greenAdj = Gtk::manage(new Adjuster(M("TP_FRAMING_GREEN"), 0, 255, 1, 255)); + box->add(*greenAdj); + blueAdj = Gtk::manage(new Adjuster(M("TP_FRAMING_BLUE"), 0, 255, 1, 255)); + box->add(*blueAdj); + + frame->add(*box); + pack_start(*frame); + + redAdj->setAdjusterListener(this); + greenAdj->setAdjusterListener(this); + blueAdj->setAdjusterListener(this); +} + +void Framing::read(const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited) +{ +} + +void Framing::write(rtengine::procparams::ProcParams* pp, ParamsEdited* pedited) +{ +} + +void Framing::setDefaults(const rtengine::procparams::ProcParams* defParams, const ParamsEdited* pedited) +{ +} + +void Framing::setBatchMode(bool batchMode) +{ + ToolPanel::setBatchMode(batchMode); +} + +void Framing::update(bool isCropped, int croppedWidth, int croppedHeight, + int originalWidth, int originalHeight) +{ + if (originalWidth && originalHeight) { + imgWidth = originalWidth; + imgHeight = originalHeight; + } + + setDimensions(); +} + +void Framing::setDimensions() +{ + idleRegister.add([this]() -> bool { + width.value->set_range(Resize::MIN_SIZE, Resize::MAX_SCALE * imgWidth); + height.value->set_range(Resize::MIN_SIZE, Resize::MAX_SCALE * imgHeight); + + return false; + }); +} + +void Framing::updateFramingMethodGui() +{ + if (batchMode) return; + + int activeRow = framingMethod->get_active_row_number(); + if (activeRow == INDEX_STANDARD) { + aspectRatioLabel->show(); + aspectRatio->show(); + orientationLabel->show(); + orientation->show(); + width.hide(); + height.hide(); + allowUpscaling->hide(); + } else if (activeRow == INDEX_BBOX) { + aspectRatioLabel->show(); + aspectRatio->show(); + orientationLabel->show(); + orientation->show(); + width.show(); + height.show(); + allowUpscaling->hide(); + } else if (activeRow == INDEX_FIXED) { + aspectRatioLabel->hide(); + aspectRatio->hide(); + orientationLabel->hide(); + orientation->hide(); + width.show(); + height.show(); + allowUpscaling->show(); + } +} + +void Framing::updateBorderSizeGui() +{ + if (batchMode) return; + + int activeRow = borderSizeMethod->get_active_row_number(); + if (activeRow == INDEX_SIZE_RELATIVE) { + basisLabel->show(); + basis->show(); + relativeBorderSize->show(); + minSizeFrame->show(); + absWidth.hide(); + absHeight.hide(); + } else if (activeRow == INDEX_SIZE_ABSOLUTE) { + basisLabel->hide(); + basis->hide(); + relativeBorderSize->hide(); + minSizeFrame->hide(); + absWidth.show(); + absHeight.show(); + } + + minSizeFrameContent->set_sensitive(minSizeEnabled->get_active()); +} + +void Framing::adjusterChanged(Adjuster* adj, double newVal) +{ +} + +void Framing::onFramingMethodChanged() +{ + updateFramingMethodGui(); +} + +void Framing::onAspectRatioChanged() +{ +} + +void Framing::onOrientationChanged() +{ +} + +void Framing::onWidthChanged() +{ +} + +void Framing::onHeightChanged() +{ +} + +void Framing::onAllowUpscalingToggled() +{ +} + +void Framing::onBorderSizeMethodChanged() +{ + updateBorderSizeGui(); +} + +void Framing::onBasisChanged() +{ +} + +void Framing::onMinSizeToggled() +{ + updateBorderSizeGui(); +} + +void Framing::onMinWidthChanged() +{ +} + +void Framing::onMinHeightChanged() +{ +} + +void Framing::onAbsWidthChanged() +{ +} + +void Framing::onAbsHeightChanged() +{ +} \ No newline at end of file diff --git a/rtgui/framing.h b/rtgui/framing.h new file mode 100644 index 000000000..90d8d85b9 --- /dev/null +++ b/rtgui/framing.h @@ -0,0 +1,141 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2004-2010 Gabor Horvath + * + * 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 . + * + * 2024-2024 Daniel Gao + */ + +#pragma once + +#include "adjuster.h" +#include "guiutils.h" +#include "toolpanel.h" + +#include + +class Framing final : + public ToolParamBlock, + public AdjusterListener, + public FoldableToolPanel +{ +public: + static const Glib::ustring TOOL_NAME; + + Framing(); + ~Framing(); + + void read(const rtengine::procparams::ProcParams* pp, + const ParamsEdited* pedited = nullptr) override; + void write(rtengine::procparams::ProcParams* pp, + ParamsEdited* pedited = nullptr) override; + void setDefaults(const rtengine::procparams::ProcParams* defParams, + const ParamsEdited* pedited = nullptr) override; + void setBatchMode(bool batchMode) override; + + void update(bool isCropped, int croppedWidth, int croppedHeight, + int originalWidth = 0, int originalHeight = 0); + + // AdjusterListener + void adjusterChanged(Adjuster* adj, double newVal) override; + + // Signal connections + void onFramingMethodChanged(); + void onAspectRatioChanged(); + void onOrientationChanged(); + void onWidthChanged(); + void onHeightChanged(); + void onAllowUpscalingToggled(); + void onBorderSizeMethodChanged(); + void onBasisChanged(); + void onMinSizeToggled(); + void onMinWidthChanged(); + void onMinHeightChanged(); + void onAbsWidthChanged(); + void onAbsHeightChanged(); + +private: + class AspectRatios; + + // Helper struct for repeated patterns + struct DimensionGui + { + using CallbackFunc = void(Framing::*)(); + + DimensionGui() = default; + DimensionGui(Gtk::Box* parent, const char* text); + + void setValue(int newValue) { value->set_value(newValue); } + void setRange(int min, int max) { value->set_range(min, max); } + void connect(Framing& framing, CallbackFunc callback); + + void show() { box->show(); } + void hide() { box->hide(); } + + Gtk::Box* box; + MySpinButton* value; + sigc::connection connection; + }; + + void setupFramingMethodGui(); + void setupBorderSizeGui(); + void setupBorderColorsGui(); + + void setDimensions(); + void updateFramingMethodGui(); + void updateBorderSizeGui(); + + // Framing method + MyComboBoxText* framingMethod; + sigc::connection framingMethodChanged; + Gtk::Label* aspectRatioLabel; + MyComboBoxText* aspectRatio; + sigc::connection aspectRatioChanged; + Gtk::Label* orientationLabel; + MyComboBoxText* orientation; + sigc::connection orientationChanged; + DimensionGui width; + DimensionGui height; + Gtk::CheckButton* allowUpscaling; + sigc::connection allowUpscalingConnection; + + // Border sizing + MyComboBoxText* borderSizeMethod; + sigc::connection borderSizeMethodChanged; + Gtk::Label* basisLabel; + MyComboBoxText* basis; + sigc::connection basisChanged; + Adjuster* relativeBorderSize; + Gtk::Frame* minSizeFrame; + Gtk::Box* minSizeFrameContent; + Gtk::CheckButton* minSizeEnabled; + sigc::connection minSizeEnabledConnection; + DimensionGui minWidth; + DimensionGui minHeight; + DimensionGui absWidth; + DimensionGui absHeight; + + // Border colors + Adjuster* redAdj; + Adjuster* greenAdj; + Adjuster* blueAdj; + + IdleRegister idleRegister; + std::unique_ptr aspectRatioData; + + int imgWidth; + int imgHeight; +}; \ No newline at end of file diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index f3cefe653..a610b147a 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -1307,6 +1307,29 @@ struct ResizeParamsEdited { bool allowUpscaling; }; +struct FramingParamsEdited { + bool enabled; + bool framingMethod; + bool aspectRatio; + bool orientation; + bool framedWidth; + bool framedHeight; + bool allowUpscaling; + + bool borderSizingMethod; + bool basis; + bool relativeBorderSize; + bool minSizeEnabled; + bool minWidth; + bool minHeight; + bool absWidth; + bool absHeight; + + bool borderRed; + bool borderGreen; + bool borderBlue; +}; + class SpotParamsEdited { public: diff --git a/rtgui/prsharpening.cc b/rtgui/prsharpening.cc index d3c936fa2..834e1f486 100644 --- a/rtgui/prsharpening.cc +++ b/rtgui/prsharpening.cc @@ -35,7 +35,7 @@ PrSharpening::PrSharpening () : FoldableToolPanel(this, TOOL_NAME, M("TP_PRSHARP milestones.push_back( GradientMilestone(0.0, 0.0, 0.0, 0.0) ); milestones.push_back( GradientMilestone(1.0, 1.0, 1.0, 1.0) ); - //setEnabledTooltipMarkup(M("TP_PRSHARPENING_TOOLTIP")); + setEnabledTooltipMarkup(M("TP_PRSHARPENING_TOOLTIP")); Gtk::Box* hb = Gtk::manage (new Gtk::Box ()); hb->show (); diff --git a/rtgui/resize.cc b/rtgui/resize.cc index de9f6b4d1..ca4221757 100644 --- a/rtgui/resize.cc +++ b/rtgui/resize.cc @@ -146,22 +146,22 @@ Resize::Resize () : FoldableToolPanel(this, TOOL_NAME, M("TP_RESIZE_LABEL"), fal w->set_digits (0); w->set_increments (1, 100); - w->set_range (32, MAX_SCALE * maxw); + w->set_range (MIN_SIZE, MAX_SCALE * maxw); w->set_value (800); // Doesn't seem to have any effect (overwritten in Resize::read) h->set_digits (0); h->set_increments (1, 100); - h->set_range (32, MAX_SCALE * maxh); + h->set_range (MIN_SIZE, MAX_SCALE * maxh); h->set_value (600); // Doesn't seem to have any effect (overwritten in Resize::read) le->set_digits (0); le->set_increments (1, 100); - le->set_range (32, MAX_SCALE * maxw); + le->set_range (MIN_SIZE, MAX_SCALE * maxw); le->set_value (900); se->set_digits (0); se->set_increments (1, 100); - se->set_range (32, MAX_SCALE * maxh); + se->set_range (MIN_SIZE, MAX_SCALE * maxh); se->set_value (900); wconn = w->signal_value_changed().connect ( sigc::mem_fun(*this, &Resize::entryWChanged), true); @@ -173,7 +173,6 @@ Resize::Resize () : FoldableToolPanel(this, TOOL_NAME, M("TP_RESIZE_LABEL"), fal sconn = spec->signal_changed().connect ( sigc::mem_fun(*this, &Resize::specChanged) ); getSubToolsContainer()->hide(); - getSubToolsContainer()->set_tooltip_markup (M("TP_PRSHARPENING_TOOLTIP")); show_all(); } diff --git a/rtgui/resize.h b/rtgui/resize.h index 674bbb34f..cd83b1418 100644 --- a/rtgui/resize.h +++ b/rtgui/resize.h @@ -22,7 +22,6 @@ #include "adjuster.h" #include "guiutils.h" -#include "guiutils.h" #include "toolpanel.h" class Resize final : @@ -33,6 +32,8 @@ class Resize final : { public: static const Glib::ustring TOOL_NAME; + static constexpr int MAX_SCALE = 16; // 16 to match the main preview max scale of 1600% + static constexpr int MIN_SIZE = 32; Resize (); ~Resize () override; @@ -85,6 +86,4 @@ private: sigc::connection sconn, aconn, wconn, hconn, leconn, seconn; bool wDirty, hDirty, leDirty, seDirty; IdleRegister idle_register; - - static constexpr int MAX_SCALE = 16; // 16 to match the main preview max scale of 1600% }; diff --git a/rtgui/toollocationpref.cc b/rtgui/toollocationpref.cc index 4352e1b07..d15f00de1 100644 --- a/rtgui/toollocationpref.cc +++ b/rtgui/toollocationpref.cc @@ -133,6 +133,8 @@ Glib::ustring getToolTitleKey(Tool tool) return "TP_RESIZE_LABEL"; case Tool::PR_SHARPENING: return "TP_PRSHARPENING_LABEL"; + case Tool::FRAMING: + return "TP_FRAMING_LABEL"; case Tool::CROP_TOOL: return "TP_CROP_LABEL"; case Tool::ICM: diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index 4dd1cca22..a02993ddc 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -164,6 +164,9 @@ const std::vector TRANSFORM_PANEL_TOOLS = { { .id = Tool::PR_SHARPENING, }, + { + .id = Tool::FRAMING, + }, }, }, { @@ -324,6 +327,7 @@ ToolPanelCoordinator::ToolPanelCoordinator (bool batch) : ipc (nullptr), favorit blackwhite = Gtk::manage(new BlackWhite()); resize = Gtk::manage(new Resize()); prsharpening = Gtk::manage(new PrSharpening()); + framing = Gtk::manage(new Framing()); crop = Gtk::manage(new Crop()); icm = Gtk::manage(new ICMPanel()); metadata = Gtk::manage(new MetaDataPanel()); @@ -638,6 +642,8 @@ std::string ToolPanelCoordinator::getToolName(Tool tool) return Resize::TOOL_NAME; case Tool::PR_SHARPENING: return PrSharpening::TOOL_NAME; + case Tool::FRAMING: + return Framing::TOOL_NAME; case Tool::CROP_TOOL: return Crop::TOOL_NAME; case Tool::ICM: @@ -1117,9 +1123,13 @@ void ToolPanelCoordinator::panelChanged(const rtengine::ProcEvent& event, const crop->write(params); resize->update(params->crop.enabled, params->crop.w, params->crop.h, ipc->getFullWidth(), ipc->getFullHeight()); resize->write(params); + framing->update(params->crop.enabled, params->crop.w, params->crop.h, ipc->getFullWidth(), ipc->getFullHeight()); + framing->write(params); } else if (event == rtengine::EvCrop) { resize->update(params->crop.enabled, params->crop.w, params->crop.h); resize->write(params); + framing->update(params->crop.enabled, params->crop.w, params->crop.h, ipc->getFullWidth(), ipc->getFullHeight()); + framing->write(params); } /* @@ -2001,6 +2011,8 @@ FoldableToolPanel *ToolPanelCoordinator::getFoldableToolPanel(Tool tool) const return resize; case Tool::PR_SHARPENING: return prsharpening; + case Tool::FRAMING: + return framing; case Tool::CROP_TOOL: return crop; case Tool::ICM: diff --git a/rtgui/toolpanelcoord.h b/rtgui/toolpanelcoord.h index 6801664f6..31044a9b0 100644 --- a/rtgui/toolpanelcoord.h +++ b/rtgui/toolpanelcoord.h @@ -45,6 +45,7 @@ #include "filmnegative.h" #include "filmsimulation.h" #include "flatfield.h" +#include "framing.h" #include "gradient.h" #include "guiutils.h" #include "hsvequalizer.h" @@ -131,6 +132,7 @@ protected: ChMixer* chmixer; BlackWhite* blackwhite; Resize* resize; + Framing* framing; PrSharpening* prsharpening; ICMPanel* icm; Crop* crop; @@ -282,6 +284,7 @@ public: BLACK_WHITE, RESIZE_TOOL, PR_SHARPENING, + FRAMING, CROP_TOOL, ICM, WAVELET, From b472fbf2ab32c52a40b1443f38f7f67ee300c713 Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Thu, 7 Nov 2024 11:31:59 -0500 Subject: [PATCH 03/21] Add RAII listener blocking utility * Added for ToolPanel listener and Adjuster * Follows style of ConnectionBlocker for sigc::connection --- rtgui/guiutils.cc | 30 ++++++++++++++++++++++++++++++ rtgui/guiutils.h | 22 ++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/rtgui/guiutils.cc b/rtgui/guiutils.cc index 2bbdcd8cd..1060c4ac4 100644 --- a/rtgui/guiutils.cc +++ b/rtgui/guiutils.cc @@ -25,6 +25,8 @@ #include "rtimage.h" #include "rtscalable.h" #include "multilangmgr.h" +#include "adjuster.h" +#include "toolpanel.h" #include @@ -77,6 +79,34 @@ void IdleRegister::destroy() mutex.unlock(); } +BlockAdjusterEvents::BlockAdjusterEvents(Adjuster* adjuster) : adj(adjuster) +{ + if (adj) { + adj->block(true); + } +} + +BlockAdjusterEvents::~BlockAdjusterEvents() +{ + if (adj) { + adj->block(false); + } +} + +DisableListener::DisableListener(ToolPanel* panelToDisable) : panel(panelToDisable) +{ + if (panel) { + panel->disableListener(); + } +} + +DisableListener::~DisableListener() +{ + if (panel) { + panel->enableListener(); + } +} + Glib::ustring escapeHtmlChars(const Glib::ustring &src) { diff --git a/rtgui/guiutils.h b/rtgui/guiutils.h index 169d0ae11..aae2989a0 100644 --- a/rtgui/guiutils.h +++ b/rtgui/guiutils.h @@ -45,7 +45,9 @@ struct CropParams; } +class Adjuster; class RTImage; +class ToolPanel; Glib::ustring escapeHtmlChars(const Glib::ustring &src); bool removeIfThere (Gtk::Container* cont, Gtk::Widget* w, bool increference = true); @@ -161,6 +163,26 @@ private: bool wasBlocked; }; +class BlockAdjusterEvents +{ +public: + explicit BlockAdjusterEvents(Adjuster* adjuster); + ~BlockAdjusterEvents(); + +private: + Adjuster* adj; +}; + +class DisableListener +{ +public: + explicit DisableListener(ToolPanel* panelToDisable); + ~DisableListener(); + +private: + ToolPanel* panel; +}; + /** * @brief Glue box to control visibility of the MyExpender's content ; also handle the frame around it */ From 8e68eccb857cab4d17a9349f1d65dd1218f83515 Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Thu, 7 Nov 2024 14:28:51 -0500 Subject: [PATCH 04/21] Add framing tool ProcParams * Adds FramingParams parameter struct to ProcParams * Implement saving and writing to struct from framing tool GUI --- rtengine/procparams.h | 1 + rtgui/framing.cc | 326 +++++++++++++++++++++++++++++++++++++++++- rtgui/framing.h | 6 + rtgui/paramsedited.h | 1 + 4 files changed, 331 insertions(+), 3 deletions(-) diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 7c2833ba1..ae22fe799 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -2822,6 +2822,7 @@ public: ChannelMixerParams chmixer; ///< Channel mixer parameters BlackWhiteParams blackwhite; ///< Black& White parameters ResizeParams resize; ///< Resize parameters + FramingParams framing; ///< Framing parameters SpotParams spot; ///< Spot removal tool ColorManagementParams icm; ///< profiles/color spaces used during the image processing RAWParams raw; ///< RAW parameters before demosaicing diff --git a/rtgui/framing.cc b/rtgui/framing.cc index fa38f91f6..eafd9dffc 100644 --- a/rtgui/framing.cc +++ b/rtgui/framing.cc @@ -22,11 +22,20 @@ #include "framing.h" #include "aspectratios.h" +#include "paramsedited.h" #include "resize.h" -#include +#include "../rtengine/procparams.h" -namespace { +#include +#include + +namespace +{ + +using rtengine::procparams::FramingParams; + +constexpr int EMPTY_COMBO_INDEX = -1; // Framing method combo box data constexpr int INDEX_STANDARD = 0; @@ -38,6 +47,36 @@ constexpr std::array FRAMING_METHODS = { "TP_FRAMING_METHOD_FIXED" }; +int mapFramingMethod(FramingParams::FramingMethod framingMethod) +{ + using FramingMethod = FramingParams::FramingMethod; + switch (framingMethod) { + case FramingMethod::STANDARD: + return INDEX_STANDARD; + case FramingMethod::BBOX: + return INDEX_BBOX; + case FramingMethod::FIXED_SIZE: + return INDEX_FIXED; + default: + return INDEX_STANDARD; + } +} + +FramingParams::FramingMethod mapFramingMethod(int comboIndex) +{ + using FramingMethod = FramingParams::FramingMethod; + switch (comboIndex) { + case INDEX_STANDARD: + return FramingMethod::STANDARD; + case INDEX_BBOX: + return FramingMethod::BBOX; + case INDEX_FIXED: + return FramingMethod::FIXED_SIZE; + default: + return FramingMethod::STANDARD; + } +} + // Orientation combo box data constexpr int INDEX_AS_IMAGE = 0; constexpr int INDEX_LANDSCAPE = 1; @@ -48,6 +87,36 @@ constexpr std::array ORIENTATION = { "GENERAL_PORTRAIT" }; +int mapOrientation(FramingParams::Orientation orientation) +{ + using Orientation = FramingParams::Orientation; + switch (orientation) { + case Orientation::AS_IMAGE: + return INDEX_AS_IMAGE; + case Orientation::LANDSCAPE: + return INDEX_LANDSCAPE; + case Orientation::PORTRAIT: + return INDEX_PORTRAIT; + default: + return INDEX_AS_IMAGE; + } +} + +FramingParams::Orientation mapOrientation(int comboIndex) +{ + using Orientation = FramingParams::Orientation; + switch (comboIndex) { + case INDEX_AS_IMAGE: + return Orientation::AS_IMAGE; + case INDEX_LANDSCAPE: + return Orientation::LANDSCAPE; + case INDEX_PORTRAIT: + return Orientation::PORTRAIT; + default: + return Orientation::AS_IMAGE; + } +} + // Border sizing method combo box data constexpr int INDEX_SIZE_RELATIVE = 0; constexpr int INDEX_SIZE_ABSOLUTE = 1; @@ -56,6 +125,32 @@ constexpr std::array BORDER_SIZE_METHODS = { "TP_FRAMING_BORDER_SIZE_ABSOLUTE" }; +int mapBorderSizeMethod(FramingParams::BorderSizing sizing) +{ + using BorderSizing = FramingParams::BorderSizing; + switch (sizing) { + case BorderSizing::PERCENTAGE: + return INDEX_SIZE_RELATIVE; + case BorderSizing::FIXED_SIZE: + return INDEX_SIZE_ABSOLUTE; + default: + return INDEX_SIZE_RELATIVE; + } +} + +FramingParams::BorderSizing mapBorderSizeMethod(int comboIndex) +{ + using BorderSizing = FramingParams::BorderSizing; + switch (comboIndex) { + case INDEX_SIZE_RELATIVE: + return BorderSizing::PERCENTAGE; + case INDEX_SIZE_ABSOLUTE: + return BorderSizing::FIXED_SIZE; + default: + return BorderSizing::PERCENTAGE; + } +} + // Relative sizing basis combo box data constexpr int INDEX_BASIS_AUTO = 0; constexpr int INDEX_BASIS_WIDTH = 1; @@ -70,6 +165,44 @@ constexpr std::array BORDER_SIZE_BASIS = { "TP_FRAMING_BASIS_SHORT_SIDE" }; +int mapBasis(FramingParams::Basis basis) +{ + using Basis = FramingParams::Basis; + switch(basis) { + case Basis::AUTO: + return INDEX_BASIS_AUTO; + case Basis::WIDTH: + return INDEX_BASIS_WIDTH; + case Basis::HEIGHT: + return INDEX_BASIS_HEIGHT; + case Basis::LONG: + return INDEX_BASIS_LONG; + case Basis::SHORT: + return INDEX_BASIS_SHORT; + default: + return INDEX_BASIS_AUTO; + } +} + +FramingParams::Basis mapBasis(int comboIndex) +{ + using Basis = FramingParams::Basis; + switch(comboIndex) { + case INDEX_BASIS_AUTO: + return Basis::AUTO; + case INDEX_BASIS_WIDTH: + return Basis::WIDTH; + case INDEX_BASIS_HEIGHT: + return Basis::HEIGHT; + case INDEX_BASIS_LONG: + return Basis::LONG; + case INDEX_BASIS_SHORT: + return Basis::SHORT; + default: + return Basis::AUTO; + } +} + constexpr int INITIAL_IMG_WIDTH = 800; constexpr int INITIAL_IMG_HEIGHT = 600; @@ -109,7 +242,7 @@ public: fillAspectRatios(ratios); } - void fillCombo(MyComboBoxText* combo) + void fillCombo(MyComboBoxText* combo) const { for (const auto& aspectRatio : ratios) { combo->append(aspectRatio.label); @@ -117,6 +250,23 @@ public: combo->set_active(INDEX_CURRENT); } + double value(int index) + { + return ratios.at(index).value; + } + + int findIndex(double aspectRatio) const + { + if (aspectRatio == 0) return INDEX_CURRENT; + + for (size_t i = 1; i < ratios.size(); i++) { + if (ratios[i].value == aspectRatio) return i; + } + + // Couldn't find a matching value + return INDEX_CURRENT; + } + private: std::vector ratios; }; @@ -314,14 +464,184 @@ void Framing::setupBorderColorsGui() void Framing::read(const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited) { + DisableListener disableListener(this); + std::vector blockers; + blockers.reserve(13); + blockers.emplace_back(framingMethodChanged); + blockers.emplace_back(aspectRatioChanged); + blockers.emplace_back(orientationChanged); + blockers.emplace_back(width.connection); + blockers.emplace_back(height.connection); + blockers.emplace_back(allowUpscalingConnection); + blockers.emplace_back(borderSizeMethodChanged); + blockers.emplace_back(basisChanged); + blockers.emplace_back(minSizeEnabledConnection); + blockers.emplace_back(minWidth.connection); + blockers.emplace_back(minHeight.connection); + blockers.emplace_back(absWidth.connection); + blockers.emplace_back(absHeight.connection); + BlockAdjusterEvents blockRelative(relativeBorderSize); + BlockAdjusterEvents blockRed(redAdj); + BlockAdjusterEvents blockGreen(greenAdj); + BlockAdjusterEvents blockBlue(blueAdj); + + readParams(pp); + readEdited(pedited); + + updateFramingMethodGui(); + updateBorderSizeGui(); +} + +void Framing::readParams(const rtengine::procparams::ProcParams* pp) +{ + const rtengine::procparams::FramingParams& params = pp->framing; + + setEnabled(params.enabled); + + framingMethod->set_active(mapFramingMethod(params.framingMethod)); + aspectRatio->set_active(aspectRatioData->findIndex(params.aspectRatio)); + orientation->set_active(mapOrientation(params.orientation)); + width.setValue(params.framedWidth); + height.setValue(params.framedHeight); + allowUpscaling->set_active(params.allowUpscaling); + + borderSizeMethod->set_active(mapBorderSizeMethod(params.borderSizingMethod)); + basis->set_active(mapBasis(params.basis)); + relativeBorderSize->setValue(params.relativeBorderSize); + minSizeEnabled->set_active(params.minSizeEnabled); + minWidth.setValue(params.minWidth); + minHeight.setValue(params.minHeight); + absWidth.setValue(params.absWidth); + absHeight.setValue(params.absHeight); + + redAdj->setValue(params.borderRed); + greenAdj->setValue(params.borderGreen); + blueAdj->setValue(params.borderBlue); +} + +void Framing::readEdited(const ParamsEdited* pedited) +{ + if (!pedited) return; + + const FramingParamsEdited& edits = pedited->framing; + + set_inconsistent(multiImage && !edits.enabled); + + if (!edits.framingMethod) { + framingMethod->set_active(EMPTY_COMBO_INDEX); + } + if (!edits.aspectRatio) { + aspectRatio->set_active(EMPTY_COMBO_INDEX); + } + if (!edits.orientation) { + orientation->set_active(EMPTY_COMBO_INDEX); + } + width.isDirty = edits.framedWidth; + height.isDirty = edits.framedHeight; + allowUpscaling->set_inconsistent(edits.allowUpscaling); + + if (!edits.borderSizingMethod) { + borderSizeMethod->set_active(EMPTY_COMBO_INDEX); + } + if (!edits.basis) { + basis->set_active(EMPTY_COMBO_INDEX); + } + relativeBorderSize->setEditedState(edits.relativeBorderSize ? Edited : UnEdited); + minSizeEnabled->set_inconsistent(edits.minSizeEnabled); + minWidth.isDirty = edits.minWidth; + minHeight.isDirty = edits.minHeight; + absWidth.isDirty = edits.absWidth; + absHeight.isDirty = edits.absHeight; + + redAdj->setEditedState(edits.borderRed ? Edited : UnEdited); + greenAdj->setEditedState(edits.borderGreen ? Edited : UnEdited); + blueAdj->setEditedState(edits.borderBlue ? Edited : UnEdited); } void Framing::write(rtengine::procparams::ProcParams* pp, ParamsEdited* pedited) { + writeParams(pp); + writeEdited(pedited); +} + +void Framing::writeParams(rtengine::procparams::ProcParams* pp) +{ + rtengine::procparams::FramingParams& params = pp->framing; + + params.enabled = getEnabled(); + + params.framingMethod = mapFramingMethod(framingMethod->get_active_row_number()); + params.aspectRatio = aspectRatioData->value(aspectRatio->get_active_row_number()); + params.orientation = mapOrientation(orientation->get_active_row_number()); + params.framedWidth = width.value->get_value_as_int(); + params.framedHeight = height.value->get_value_as_int(); + params.allowUpscaling = allowUpscaling->get_active(); + + params.borderSizingMethod = mapBorderSizeMethod(borderSizeMethod->get_active_row_number()); + params.basis = mapBasis(basis->get_active_row_number()); + params.relativeBorderSize = relativeBorderSize->getValue(); + params.minSizeEnabled = minSizeEnabled->get_active(); + params.minWidth = minWidth.value->get_value_as_int(); + params.minHeight = minHeight.value->get_value_as_int(); + params.absWidth = absWidth.value->get_value_as_int(); + params.absHeight = absHeight.value->get_value_as_int(); + + params.borderRed = redAdj->getValue(); + params.borderGreen = greenAdj->getValue(); + params.borderBlue = blueAdj->getValue(); +} + +void Framing::writeEdited(ParamsEdited* pedited) +{ + if (!pedited) return; + + FramingParamsEdited& edits = pedited->framing; + + edits.enabled = !get_inconsistent(); + + edits.framingMethod = framingMethod->get_active_row_number() != EMPTY_COMBO_INDEX; + edits.aspectRatio = aspectRatio->get_active_row_number() != EMPTY_COMBO_INDEX; + edits.orientation = orientation->get_active_row_number() != EMPTY_COMBO_INDEX; + edits.framedWidth = width.isDirty; + edits.framedHeight = height.isDirty; + edits.allowUpscaling = !allowUpscaling->get_inconsistent(); + + edits.borderSizingMethod = borderSizeMethod->get_active_row_number() != EMPTY_COMBO_INDEX; + edits.basis = basis->get_active_row_number() != EMPTY_COMBO_INDEX; + edits.relativeBorderSize = relativeBorderSize->getEditedState(); + edits.minSizeEnabled = !minSizeEnabled->get_inconsistent(); + edits.minWidth = minWidth.isDirty; + edits.minHeight = minHeight.isDirty; + edits.absWidth = absWidth.isDirty; + edits.absHeight = absHeight.isDirty; + + edits.borderRed = redAdj->getEditedState(); + edits.borderGreen = greenAdj->getEditedState(); + edits.borderBlue = blueAdj->getEditedState(); } void Framing::setDefaults(const rtengine::procparams::ProcParams* defParams, const ParamsEdited* pedited) { + const FramingParams& params = defParams->framing; + + relativeBorderSize->setDefault(params.relativeBorderSize); + redAdj->setDefault(params.borderRed); + greenAdj->setDefault(params.borderGreen); + blueAdj->setDefault(params.borderBlue); + + if (pedited) { + const FramingParamsEdited& edits = pedited->framing; + + relativeBorderSize->setDefaultEditedState(edits.relativeBorderSize ? Edited : UnEdited); + redAdj->setDefaultEditedState(edits.borderRed ? Edited : UnEdited); + greenAdj->setDefaultEditedState(edits.borderGreen ? Edited : UnEdited); + blueAdj->setDefaultEditedState(edits.borderBlue ? Edited : UnEdited); + } else { + relativeBorderSize->setDefaultEditedState(Irrelevant); + redAdj->setDefaultEditedState(Irrelevant); + greenAdj->setDefaultEditedState(Irrelevant); + blueAdj->setDefaultEditedState(Irrelevant); + } } void Framing::setBatchMode(bool batchMode) diff --git a/rtgui/framing.h b/rtgui/framing.h index 90d8d85b9..4b0516e38 100644 --- a/rtgui/framing.h +++ b/rtgui/framing.h @@ -88,12 +88,18 @@ private: Gtk::Box* box; MySpinButton* value; sigc::connection connection; + bool isDirty; }; void setupFramingMethodGui(); void setupBorderSizeGui(); void setupBorderColorsGui(); + void readParams(const rtengine::procparams::ProcParams* pp); + void readEdited(const ParamsEdited* pedited); + void writeParams(rtengine::procparams::ProcParams* pp); + void writeEdited(ParamsEdited* pedited); + void setDimensions(); void updateFramingMethodGui(); void updateBorderSizeGui(); diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index a610b147a..68b362bf0 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -1705,6 +1705,7 @@ struct ParamsEdited { ChannelMixerParamsEdited chmixer; BlackWhiteParamsEdited blackwhite; ResizeParamsEdited resize; + FramingParamsEdited framing; SpotParamsEdited spot; ColorManagementParamsEdited icm; RAWParamsEdited raw; From d1148539bb32a33986a75f938766206108342d6b Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Thu, 7 Nov 2024 17:41:18 -0500 Subject: [PATCH 05/21] Implement load/save for FramingParams * Implement writing FramingParams to keyfile * Implement reading FramingParams from keyfile --- rtengine/procparams.cc | 178 +++++++++++++++++++++++++++++++++++++++++ rtgui/framing.cc | 12 +++ 2 files changed, 190 insertions(+) diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 0a0c9bd68..4dad02ed8 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -427,6 +427,178 @@ bool saveToKeyfile( return false; } +namespace ProfileKeys { + +#define DEFINE_KEY(VAR, NAME) \ + constexpr const char* VAR = NAME; + +DEFINE_KEY(TOOL_ENABLED, "Enabled"); + +namespace Framing +{ + DEFINE_KEY(TOOL_NAME, "Framing"); + + // Fields + DEFINE_KEY(FRAMING_METHOD, "FramingMethod"); + DEFINE_KEY(ASPECT_RATIO, "AspectRatio"); + DEFINE_KEY(ORIENTATION, "Orientation"); + DEFINE_KEY(FRAMED_WIDTH, "FramedWidth"); + DEFINE_KEY(FRAMED_HEIGHT, "FramedHeight"); + DEFINE_KEY(ALLOW_UPSCALING, "AllowUpscaling"); + + DEFINE_KEY(BORDER_SIZING_METHOD, "BorderSizingMethod"); + DEFINE_KEY(BASIS, "Basis"); + DEFINE_KEY(RELATIVE_BORDER_SIZE, "RelativeBorderSize"); + DEFINE_KEY(MIN_SIZE_ENABLED, "MinSizeEnabled"); + DEFINE_KEY(MIN_WIDTH, "MinWidth"); + DEFINE_KEY(MIN_HEIGHT, "MinHeight"); + DEFINE_KEY(ABS_WIDTH, "AbsWidth"); + DEFINE_KEY(ABS_HEIGHT, "AbsHeight"); + + DEFINE_KEY(BORDER_RED, "BorderRed"); + DEFINE_KEY(BORDER_GREEN, "BorderGreen"); + DEFINE_KEY(BORDER_BLUE, "BorderBlue"); + + // Enum mappings + DEFINE_KEY(FRAMING_METHOD_STANDARD, "Standard"); + DEFINE_KEY(FRAMING_METHOD_BBOX, "BoundingBox"); + DEFINE_KEY(FRAMING_METHOD_FIXED_SIZE, "FixedSize"); + DEFINE_KEY(ORIENT_AS_IMAGE, "AsImage"); + DEFINE_KEY(ORIENT_LANDSCAPE, "Landscape"); + DEFINE_KEY(ORIENT_PORTRAIT, "Portait"); + DEFINE_KEY(BORDER_SIZING_PERCENTAGE, "Percentage"); + DEFINE_KEY(BORDER_SIZING_FIXED_SIZE, "FixedSize"); + DEFINE_KEY(BASIS_AUTO, "Auto"); + DEFINE_KEY(BASIS_WIDTH, "Width"); + DEFINE_KEY(BASIS_HEIGHT, "Height"); + DEFINE_KEY(BASIS_LONG, "Long"); + DEFINE_KEY(BASIS_SHORT, "Short"); +} // namespace Framing + +} // namespace ProfileKeys + +void loadFramingParams( + const Glib::KeyFile& keyFile, + rtengine::procparams::FramingParams& params, + FramingParamsEdited& edited +) +{ + using namespace ProfileKeys; + using namespace ProfileKeys::Framing; + using FramingParams = rtengine::procparams::FramingParams; + + const Glib::ustring group{TOOL_NAME}; + if (!keyFile.has_group(group)) return; + + assignFromKeyfile(keyFile, group, TOOL_ENABLED, params.enabled, edited.enabled); + + using FramingMethod = FramingParams::FramingMethod; + const std::map framingMethodMapping = { + {FRAMING_METHOD_STANDARD, FramingMethod::STANDARD}, + {FRAMING_METHOD_BBOX, FramingMethod::BBOX}, + {FRAMING_METHOD_FIXED_SIZE, FramingMethod::FIXED_SIZE} + }; + assignFromKeyfile(keyFile, group, FRAMING_METHOD, framingMethodMapping, params.framingMethod, edited.framingMethod); + assignFromKeyfile(keyFile, group, ASPECT_RATIO, params.aspectRatio, edited.aspectRatio); + using Orientation = FramingParams::Orientation; + const std::map orientationMapping = { + {ORIENT_AS_IMAGE, Orientation::AS_IMAGE}, + {ORIENT_LANDSCAPE, Orientation::LANDSCAPE}, + {ORIENT_PORTRAIT, Orientation::PORTRAIT}, + }; + assignFromKeyfile(keyFile, group, ORIENTATION, orientationMapping, params.orientation, edited.orientation); + assignFromKeyfile(keyFile, group, FRAMED_WIDTH, params.framedWidth, edited.framedWidth); + assignFromKeyfile(keyFile, group, FRAMED_HEIGHT, params.framedHeight, edited.framedHeight); + assignFromKeyfile(keyFile, group, ALLOW_UPSCALING, params.allowUpscaling, edited.allowUpscaling); + + using BorderSizing = FramingParams::BorderSizing; + const std::map borderSizingMapping = { + {BORDER_SIZING_PERCENTAGE, BorderSizing::PERCENTAGE}, + {BORDER_SIZING_FIXED_SIZE, BorderSizing::FIXED_SIZE} + }; + assignFromKeyfile(keyFile, group, BORDER_SIZING_METHOD, borderSizingMapping, params.borderSizingMethod, edited.borderSizingMethod); + using Basis = FramingParams::Basis; + const std::map basisMapping = { + {BASIS_AUTO, Basis::AUTO}, + {BASIS_WIDTH, Basis::WIDTH}, + {BASIS_HEIGHT, Basis::HEIGHT}, + {BASIS_LONG, Basis::LONG}, + {BASIS_SHORT, Basis::SHORT} + }; + assignFromKeyfile(keyFile, group, BASIS, basisMapping, params.basis, edited.basis); + assignFromKeyfile(keyFile, group, RELATIVE_BORDER_SIZE, params.relativeBorderSize, edited.relativeBorderSize); + assignFromKeyfile(keyFile, group, MIN_SIZE_ENABLED, params.minSizeEnabled, edited.minSizeEnabled); + assignFromKeyfile(keyFile, group, MIN_WIDTH, params.minWidth, edited.minWidth); + assignFromKeyfile(keyFile, group, MIN_HEIGHT, params.minHeight, edited.minHeight); + assignFromKeyfile(keyFile, group, ABS_WIDTH, params.absWidth, edited.absWidth); + assignFromKeyfile(keyFile, group, ABS_HEIGHT, params.absHeight, edited.absHeight); + + assignFromKeyfile(keyFile, group, BORDER_RED, params.borderRed, edited.borderRed); + assignFromKeyfile(keyFile, group, BORDER_GREEN, params.borderGreen, edited.borderGreen); + assignFromKeyfile(keyFile, group, BORDER_BLUE, params.borderBlue, edited.borderBlue); +} + +void saveFramingParams( + Glib::KeyFile& keyFile, + const rtengine::procparams::FramingParams& params, + const ParamsEdited* pedited +) +{ + using namespace ProfileKeys; + using namespace ProfileKeys::Framing; + using FramingParams = rtengine::procparams::FramingParams; + + const Glib::ustring group{TOOL_NAME}; + + const FramingParamsEdited& edited = pedited->framing; + + saveToKeyfile(!pedited || edited.enabled, group, TOOL_ENABLED, params.enabled, keyFile); + + using FramingMethod = FramingParams::FramingMethod; + const std::map framingMethodMapping = { + {FramingMethod::STANDARD, FRAMING_METHOD_STANDARD}, + {FramingMethod::BBOX, FRAMING_METHOD_BBOX}, + {FramingMethod::FIXED_SIZE, FRAMING_METHOD_FIXED_SIZE} + }; + saveToKeyfile(!pedited || edited.framingMethod, group, FRAMING_METHOD, framingMethodMapping, params.framingMethod, keyFile); + saveToKeyfile(!pedited || edited.aspectRatio, group, ASPECT_RATIO, params.aspectRatio, keyFile); + using Orientation = FramingParams::Orientation; + const std::map orientationMapping = { + {Orientation::AS_IMAGE, ORIENT_AS_IMAGE}, + {Orientation::LANDSCAPE, ORIENT_LANDSCAPE}, + {Orientation::PORTRAIT, ORIENT_PORTRAIT}, + }; + saveToKeyfile(!pedited || edited.orientation, group, ORIENTATION, orientationMapping, params.orientation, keyFile); + saveToKeyfile(!pedited || edited.framedWidth, group, FRAMED_WIDTH, params.framedWidth, keyFile); + saveToKeyfile(!pedited || edited.framedHeight, group, FRAMED_HEIGHT, params.framedHeight, keyFile); + saveToKeyfile(!pedited || edited.allowUpscaling, group, ALLOW_UPSCALING, params.allowUpscaling, keyFile); + + using BorderSizing = FramingParams::BorderSizing; + const std::map borderSizingMapping = { + {BorderSizing::PERCENTAGE, BORDER_SIZING_PERCENTAGE}, + {BorderSizing::FIXED_SIZE, BORDER_SIZING_FIXED_SIZE} + }; + saveToKeyfile(!pedited || edited.borderSizingMethod, group, BORDER_SIZING_METHOD, borderSizingMapping, params.borderSizingMethod, keyFile); + using Basis = FramingParams::Basis; + const std::map basisMapping = { + {Basis::AUTO, BASIS_AUTO}, + {Basis::WIDTH, BASIS_WIDTH}, + {Basis::HEIGHT, BASIS_HEIGHT}, + {Basis::LONG, BASIS_LONG}, + {Basis::SHORT, BASIS_SHORT} + }; + saveToKeyfile(!pedited || edited.basis, group, BASIS, basisMapping, params.basis, keyFile); + saveToKeyfile(!pedited || edited.relativeBorderSize, group, RELATIVE_BORDER_SIZE, params.relativeBorderSize, keyFile); + saveToKeyfile(!pedited || edited.minSizeEnabled, group, MIN_SIZE_ENABLED, params.minSizeEnabled, keyFile); + saveToKeyfile(!pedited || edited.minWidth, group, MIN_WIDTH, params.minWidth, keyFile); + saveToKeyfile(!pedited || edited.minHeight, group, MIN_HEIGHT, params.minHeight, keyFile); + saveToKeyfile(!pedited || edited.absWidth, group, ABS_WIDTH, params.absWidth, keyFile); + saveToKeyfile(!pedited || edited.absHeight, group, ABS_HEIGHT, params.absHeight, keyFile); + + saveToKeyfile(!pedited || edited.borderRed, group, BORDER_RED, params.borderRed, keyFile); + saveToKeyfile(!pedited || edited.borderGreen, group, BORDER_GREEN, params.borderGreen, keyFile); + saveToKeyfile(!pedited || edited.borderBlue, group, BORDER_BLUE, params.borderBlue, keyFile); +} } // namespace @@ -6401,6 +6573,8 @@ void ProcParams::setDefaults() resize = {}; + framing = {}; + icm = {}; wavelet = {}; @@ -7740,6 +7914,8 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo saveToKeyfile(!pedited || pedited->resize.shortedge, "Resize", "ShortEdge", resize.shortedge, keyFile); saveToKeyfile(!pedited || pedited->resize.allowUpscaling, "Resize", "AllowUpscaling", resize.allowUpscaling, keyFile); + saveFramingParams(keyFile, framing, pedited); + // Post demosaic sharpening saveToKeyfile(!pedited || pedited->pdsharpening.enabled, "PostDemosaicSharpening", "Enabled", pdsharpening.enabled, keyFile); saveToKeyfile(!pedited || pedited->pdsharpening.contrast, "PostDemosaicSharpening", "Contrast", pdsharpening.contrast, keyFile); @@ -10261,6 +10437,8 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) } } + loadFramingParams(keyFile, framing, pedited->framing); + if (keyFile.has_group ("Spot removal")) { assignFromKeyfile(keyFile, "Spot removal", "Enabled", spot.enabled, pedited->spot.enabled); int i = 0; diff --git a/rtgui/framing.cc b/rtgui/framing.cc index eafd9dffc..b687e2508 100644 --- a/rtgui/framing.cc +++ b/rtgui/framing.cc @@ -502,7 +502,9 @@ void Framing::readParams(const rtengine::procparams::ProcParams* pp) aspectRatio->set_active(aspectRatioData->findIndex(params.aspectRatio)); orientation->set_active(mapOrientation(params.orientation)); width.setValue(params.framedWidth); + width.isDirty = false; height.setValue(params.framedHeight); + height.isDirty = false; allowUpscaling->set_active(params.allowUpscaling); borderSizeMethod->set_active(mapBorderSizeMethod(params.borderSizingMethod)); @@ -510,9 +512,13 @@ void Framing::readParams(const rtengine::procparams::ProcParams* pp) relativeBorderSize->setValue(params.relativeBorderSize); minSizeEnabled->set_active(params.minSizeEnabled); minWidth.setValue(params.minWidth); + minWidth.isDirty = false; minHeight.setValue(params.minHeight); + minHeight.isDirty = false; absWidth.setValue(params.absWidth); + absWidth.isDirty = false; absHeight.setValue(params.absHeight); + absHeight.isDirty = false; redAdj->setValue(params.borderRed); greenAdj->setValue(params.borderGreen); @@ -745,10 +751,12 @@ void Framing::onOrientationChanged() void Framing::onWidthChanged() { + width.isDirty = true; } void Framing::onHeightChanged() { + height.isDirty = true; } void Framing::onAllowUpscalingToggled() @@ -771,16 +779,20 @@ void Framing::onMinSizeToggled() void Framing::onMinWidthChanged() { + minWidth.isDirty = true; } void Framing::onMinHeightChanged() { + minHeight.isDirty = true; } void Framing::onAbsWidthChanged() { + absWidth.isDirty = true; } void Framing::onAbsHeightChanged() { + absHeight.isDirty = true; } \ No newline at end of file From 114a4680de8d7bce6b55f2b56a6e9c3309ea9e38 Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Thu, 7 Nov 2024 18:22:59 -0500 Subject: [PATCH 06/21] Implement FramingParamsEdited operations * Implements edited param manipulation operations for framing tool --- rtgui/paramsedited.cc | 130 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 3 deletions(-) diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index 7a92194ba..a77c48d3a 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -25,6 +25,124 @@ #include "../rtengine/procparams.h" +namespace +{ + +using namespace rtengine::procparams; + +void setAll(FramingParamsEdited& framing, bool v) +{ + framing.enabled = v; + framing.framingMethod = v; + framing.aspectRatio = v; + framing.orientation = v; + framing.framedWidth = v; + framing.framedHeight = v; + framing.allowUpscaling = v; + + framing.borderSizingMethod = v; + framing.basis = v; + framing.relativeBorderSize = v; + framing.minSizeEnabled = v; + framing.minWidth = v; + framing.minHeight = v; + framing.absWidth = v; + framing.absHeight = v; + + framing.borderRed = v; + framing.borderGreen = v; + framing.borderBlue = v; +} + +void initFrom(FramingParamsEdited& edits, const ProcParams& params, const ProcParams& otherParams) +{ + const FramingParams& curr = params.framing; + const FramingParams& other = otherParams.framing; + + edits.enabled &= curr.enabled == other.enabled; + edits.framingMethod &= curr.framingMethod == other.framingMethod; + edits.aspectRatio &= curr.aspectRatio == other.aspectRatio; + edits.orientation &= curr.orientation == other.orientation; + edits.framedWidth &= curr.framedWidth == other.framedWidth; + edits.framedHeight &= curr.framedHeight == other.framedHeight; + edits.allowUpscaling &= curr.allowUpscaling == other.allowUpscaling; + + edits.borderSizingMethod &= curr.borderSizingMethod == other.borderSizingMethod; + edits.basis &= curr.basis == other.basis; + edits.relativeBorderSize &= curr.relativeBorderSize == other.relativeBorderSize; + edits.minSizeEnabled &= curr.minSizeEnabled == other.minSizeEnabled; + edits.minWidth &= curr.minWidth == other.minWidth; + edits.minHeight &= curr.minHeight == other.minHeight; + edits.absWidth &= curr.absWidth == other.absWidth; + edits.absHeight &= curr.absHeight == other.absHeight; + + edits.borderRed &= curr.borderRed == other.borderRed; + edits.borderGreen &= curr.borderGreen == other.borderGreen; + edits.borderBlue &= curr.borderBlue == other.borderBlue; +} + +void combine(FramingParams& toEdit, const FramingParams& mod, const FramingParamsEdited& edits) +{ + if (edits.enabled) { + toEdit.enabled = mod.enabled; + } + if (edits.framingMethod) { + toEdit.framingMethod = mod.framingMethod; + } + if (edits.aspectRatio) { + toEdit.aspectRatio = mod.aspectRatio; + } + if (edits.orientation) { + toEdit.orientation = mod.orientation; + } + if (edits.framedWidth) { + toEdit.framedWidth = mod.framedWidth; + } + if (edits.framedHeight) { + toEdit.framedHeight = mod.framedHeight; + } + if (edits.allowUpscaling) { + toEdit.allowUpscaling = mod.allowUpscaling; + } + + if (edits.borderSizingMethod) { + toEdit.borderSizingMethod = mod.borderSizingMethod; + } + if (edits.basis) { + toEdit.basis = mod.basis; + } + if (edits.relativeBorderSize) { + toEdit.relativeBorderSize = mod.relativeBorderSize; + } + if (edits.minSizeEnabled) { + toEdit.minSizeEnabled = mod.minSizeEnabled; + } + if (edits.minWidth) { + toEdit.minWidth = mod.minWidth; + } + if (edits.minHeight) { + toEdit.minHeight = mod.minHeight; + } + if (edits.absWidth) { + toEdit.absWidth = mod.absWidth; + } + if (edits.absHeight) { + toEdit.absHeight = mod.absHeight; + } + + if (edits.borderRed) { + toEdit.borderRed = mod.borderRed; + } + if (edits.borderGreen) { + toEdit.borderGreen = mod.borderGreen; + } + if (edits.borderBlue) { + toEdit.borderBlue = mod.borderBlue; + } +} + +} // namespace + ParamsEdited::ParamsEdited(bool value) { @@ -446,7 +564,6 @@ void ParamsEdited::set(bool v) blackwhite.autoc = v; blackwhite.algo = v; - resize.scale = v; resize.appliesTo = v; resize.method = v; @@ -456,11 +573,13 @@ void ParamsEdited::set(bool v) resize.longedge = v; resize.shortedge = v; resize.enabled = v; + resize.allowUpscaling = v; + + ::setAll(framing, v); spot.enabled = v; spot.entries = v; - resize.allowUpscaling = v; icm.inputProfile = v; icm.toneCurve = v; icm.applyLookTable = v; @@ -1984,9 +2103,12 @@ void ParamsEdited::initFrom(const std::vector& resize.longedge = resize.longedge && p.resize.longedge == other.resize.longedge; resize.shortedge = resize.shortedge && p.resize.shortedge == other.resize.shortedge; resize.enabled = resize.enabled && p.resize.enabled == other.resize.enabled; + resize.allowUpscaling = resize.allowUpscaling && p.resize.allowUpscaling == other.resize.allowUpscaling; + + ::initFrom(framing, p, other); + spot.enabled = spot.enabled && p.spot.enabled == other.spot.enabled; spot.entries = spot.entries && p.spot.entries == other.spot.entries; - resize.allowUpscaling = resize.allowUpscaling && p.resize.allowUpscaling == other.resize.allowUpscaling; icm.inputProfile = icm.inputProfile && p.icm.inputProfile == other.icm.inputProfile; icm.toneCurve = icm.toneCurve && p.icm.toneCurve == other.icm.toneCurve; icm.applyLookTable = icm.applyLookTable && p.icm.applyLookTable == other.icm.applyLookTable; @@ -6856,6 +6978,8 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.resize.allowUpscaling = mods.resize.allowUpscaling; } + ::combine(toEdit.framing, mods.framing, framing); + if (icm.inputProfile) { toEdit.icm.inputProfile = mods.icm.inputProfile; } From 757a0a032af6f7e73ff333fbd84b03a335655d7e Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Thu, 7 Nov 2024 18:31:54 -0500 Subject: [PATCH 07/21] Implement partial profile for framing tool * Update partial profile load/save GUI to include framing tool * Allows for loading/saving framing tool parameters from the clipboard --- rtdata/languages/default | 1 + rtgui/partialpastedlg.cc | 9 +++++++++ rtgui/partialpastedlg.h | 3 ++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index b68b25b8a..b7366c5e6 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1864,6 +1864,7 @@ PARTIALPASTE_FLATFIELDBLURTYPE;Flat-field blur type PARTIALPASTE_FLATFIELDCLIPCONTROL;Flat-field clip control PARTIALPASTE_FLATFIELDFILE;Flat-field file PARTIALPASTE_FLATFIELDFROMMETADATA;Flat-field from Metadata +PARTIALPASTE_FRAMING;Framing PARTIALPASTE_GRADIENT;Graduated filter PARTIALPASTE_HSVEQUALIZER;HSV equalizer PARTIALPASTE_ICMSETTINGS;Color management settings diff --git a/rtgui/partialpastedlg.cc b/rtgui/partialpastedlg.cc index 3f03d81f6..d98c46489 100644 --- a/rtgui/partialpastedlg.cc +++ b/rtgui/partialpastedlg.cc @@ -266,6 +266,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren crop = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_CROP"))); resize = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_RESIZE"))); prsharpening = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_PRSHARPENING"))); + framing = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_FRAMING"))); perspective = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_PERSPECTIVE"))); commonTrans = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_COMMONTRANSFORMPARAMS"))); @@ -384,6 +385,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren vboxes[4]->pack_start (*crop, Gtk::PACK_SHRINK, 2); vboxes[4]->pack_start (*resize, Gtk::PACK_SHRINK, 2); vboxes[4]->pack_start (*prsharpening, Gtk::PACK_SHRINK, 2); + vboxes[4]->pack_start (*framing, Gtk::PACK_SHRINK, 2); vboxes[4]->pack_start (*perspective, Gtk::PACK_SHRINK, 2); vboxes[4]->pack_start (*commonTrans, Gtk::PACK_SHRINK, 2); @@ -547,6 +549,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren cropConn = crop->signal_toggled().connect (sigc::bind (sigc::mem_fun(*composition, &Gtk::CheckButton::set_inconsistent), true)); resizeConn = resize->signal_toggled().connect (sigc::bind (sigc::mem_fun(*composition, &Gtk::CheckButton::set_inconsistent), true)); prsharpeningConn = prsharpening->signal_toggled().connect (sigc::bind (sigc::mem_fun(*composition, &Gtk::CheckButton::set_inconsistent), true)); + framingConn = framing->signal_toggled().connect (sigc::bind (sigc::mem_fun(*composition, &Gtk::CheckButton::set_inconsistent), true)); perspectiveConn = perspective->signal_toggled().connect (sigc::bind (sigc::mem_fun(*composition, &Gtk::CheckButton::set_inconsistent), true)); commonTransConn = commonTrans->signal_toggled().connect (sigc::bind (sigc::mem_fun(*composition, &Gtk::CheckButton::set_inconsistent), true)); @@ -823,6 +826,7 @@ void PartialPasteDlg::compositionToggled () ConnectionBlocker cropBlocker(cropConn); ConnectionBlocker resizeBlocker(resizeConn); ConnectionBlocker prsharpeningBlocker(prsharpeningConn); + ConnectionBlocker framingBlocker(framingConn); ConnectionBlocker perspectiveBlocker(perspectiveConn); ConnectionBlocker commonTransBlocker(commonTransConn); @@ -833,6 +837,7 @@ void PartialPasteDlg::compositionToggled () crop->set_active (composition->get_active ()); resize->set_active (composition->get_active ()); prsharpening->set_active (composition->get_active ()); + framing->set_active (composition->get_active ()); perspective->set_active (composition->get_active ()); commonTrans->set_active (composition->get_active ()); } @@ -1043,6 +1048,10 @@ void PartialPasteDlg::applyPaste (rtengine::procparams::ProcParams* dstPP, Param filterPE.prsharpening = falsePE.prsharpening; } + if (!framing->get_active ()) { + filterPE.framing = falsePE.framing; + } + if (!perspective->get_active ()) { filterPE.perspective = falsePE.perspective; } diff --git a/rtgui/partialpastedlg.h b/rtgui/partialpastedlg.h index 1dee55a6e..10df7ed57 100644 --- a/rtgui/partialpastedlg.h +++ b/rtgui/partialpastedlg.h @@ -180,6 +180,7 @@ public: Gtk::CheckButton* crop; Gtk::CheckButton* resize; Gtk::CheckButton* prsharpening; + Gtk::CheckButton* framing; Gtk::CheckButton* perspective; Gtk::CheckButton* commonTrans; @@ -231,7 +232,7 @@ public: sigc::connection spotConn, sharpenConn, gradsharpenConn, microcontrastConn, impdenConn, dirpyrdenConn, defringeConn, epdConn, fattalConn, dirpyreqConn, waveletConn, retinexConn, dehazeConn; sigc::connection vibranceConn, chmixerConn, hsveqConn, rgbcurvesConn, chmixerbwConn, colortoningConn, filmSimulationConn, softlightConn; sigc::connection distortionConn, cacorrConn, vignettingConn, lcpConn; - sigc::connection coarserotConn, finerotConn, cropConn, resizeConn, prsharpeningConn, perspectiveConn, commonTransConn; + sigc::connection coarserotConn, finerotConn, cropConn, resizeConn, prsharpeningConn, framingConn, perspectiveConn, commonTransConn; sigc::connection metadataConn, exifchConn, iptcConn, icmConn; 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; From 29945f4a35f29900f5ea9ca23aef73f836424419 Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Thu, 7 Nov 2024 22:31:41 -0500 Subject: [PATCH 08/21] Implement ProcEvents for framing tool * Add new events to ProcEvents * Add new events to refresh map under RESIZE category * Add new strings for displaying events in history panel * Hook up GUI events with emitting ProcEvents --- rtdata/languages/default | 18 +++++++ rtengine/procevents.h | 18 +++++++ rtengine/procparams.h | 5 +- rtengine/refreshmap.cc | 20 +++++++- rtgui/framing.cc | 102 ++++++++++++++++++++++++++++++++++++++- rtgui/framing.h | 2 + 6 files changed, 162 insertions(+), 3 deletions(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index b7366c5e6..525195a53 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1380,6 +1380,24 @@ HISTORY_MSG_1147;Local - Jz BlackEv WhiteEv HISTORY_MSG_1148;Local - Jz Sigmoid HISTORY_MSG_1149;Local - Q Sigmoid HISTORY_MSG_1150;Local - Log encoding Q instead Sigmoid Q +HISTORY_MSG_1151;Framing +HISTORY_MSG_1152;Framing - Method +HISTORY_MSG_1153;Framing - Aspect Ratio +HISTORY_MSG_1154;Framing - Orientation +HISTORY_MSG_1155;Framing - Framed Width +HISTORY_MSG_1156;Framing - Framed Height +HISTORY_MSG_1157;Framing - Upscaling +HISTORY_MSG_1158;Framing - Border Sizing Method +HISTORY_MSG_1159;Framing - Basis +HISTORY_MSG_1160;Framing - Relative Size +HISTORY_MSG_1161;Framing - Min Size +HISTORY_MSG_1162;Framing - Min Width +HISTORY_MSG_1163;Framing - Min Height +HISTORY_MSG_1164;Framing - Border Width +HISTORY_MSG_1165;Framing - Border Height +HISTORY_MSG_1166;Framing - Border (R) +HISTORY_MSG_1167;Framing - Border (G) +HISTORY_MSG_1168;Framing - Border (B) HISTORY_MSG_BLSHAPE;Blur by level HISTORY_MSG_BLURCWAV;Blur chroma HISTORY_MSG_BLURWAV;Blur luminance diff --git a/rtengine/procevents.h b/rtengine/procevents.h index a1408e405..c573de8c0 100644 --- a/rtengine/procevents.h +++ b/rtengine/procevents.h @@ -1177,6 +1177,24 @@ enum ProcEventCode { Evlocallabsigjz = 1147, Evlocallabsigq = 1148, Evlocallablogcie = 1149, + EvFramingEnabled = 1150, + EvFramingMethod = 1151, + EvFramingAspectRatio = 1152, + EvFramingOrientation = 1153, + EvFramingFramedWidth = 1154, + EvFramingFramedHeight = 1155, + EvFramingAllowUpscaling = 1156, + EvFramingBorderSizingMethod = 1157, + EvFramingBasis = 1158, + EvFramingRelativeBorderSize = 1159, + EvFramingMinSizeEnabled = 1160, + EvFramingMinWidth = 1161, + EvFramingMinHeight = 1162, + EvFramingAbsWidth = 1163, + EvFramingAbsHeight = 1164, + EvFramingBorderRed = 1165, + EvFramingBorderGreen = 1166, + EvFramingBorderBlue = 1167, NUMOFEVENTS }; diff --git a/rtengine/procparams.h b/rtengine/procparams.h index ae22fe799..f61ca6505 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -2014,12 +2014,15 @@ struct FramingParams { SHORT // Use short side of image }; + // Indicates to use the image aspect ratio for border + static constexpr double AS_IMAGE_ASPECT_RATIO = 0.0; + FramingParams(); bool enabled; FramingMethod framingMethod; - double aspectRatio; // 0 - Use aspect ratio of image + double aspectRatio; Orientation orientation; int framedWidth; int framedHeight; diff --git a/rtengine/refreshmap.cc b/rtengine/refreshmap.cc index 5147e09ce..da5cbe3e0 100644 --- a/rtengine/refreshmap.cc +++ b/rtengine/refreshmap.cc @@ -1185,7 +1185,25 @@ int refreshmap[rtengine::NUMOFEVENTS] = { AUTOEXP, //Evlocallabforcebw AUTOEXP, //Evlocallabsigjz AUTOEXP, //Evlocallabsigq - AUTOEXP //Evlocallablogcie + AUTOEXP, //Evlocallablogcie + RESIZE, // EvFramingEnabled + RESIZE, // EvFramingFramingMethod + RESIZE, // EvFramingAspectRatio + RESIZE, // EvFramingOrientation + RESIZE, // EvFramingFramedWidth + RESIZE, // EvFramingFramedHeight + RESIZE, // EvFramingAllowUpscaling + RESIZE, // EvFramingBorderSizingMethod + RESIZE, // EvFramingBasis + RESIZE, // EvFramingRelativeBorderSize + RESIZE, // EvFramingMinSizeEnabled + RESIZE, // EvFramingMinWidth + RESIZE, // EvFramingMinHeight + RESIZE, // EvFramingAbsWidth + RESIZE, // EvFramingAbsHeight + RESIZE, // EvFramingBorderRed + RESIZE, // EvFramingBorderGreen + RESIZE // EvFramingBorderBlue }; diff --git a/rtgui/framing.cc b/rtgui/framing.cc index b687e2508..cff506a46 100644 --- a/rtgui/framing.cc +++ b/rtgui/framing.cc @@ -28,11 +28,13 @@ #include "../rtengine/procparams.h" #include +#include #include namespace { +using namespace rtengine; using rtengine::procparams::FramingParams; constexpr int EMPTY_COMBO_INDEX = -1; @@ -257,7 +259,7 @@ public: int findIndex(double aspectRatio) const { - if (aspectRatio == 0) return INDEX_CURRENT; + if (aspectRatio == FramingParams::AS_IMAGE_ASPECT_RATIO) return INDEX_CURRENT; for (size_t i = 1; i < ratios.size(); i++) { if (ratios[i].value == aspectRatio) return i; @@ -655,6 +657,19 @@ void Framing::setBatchMode(bool batchMode) ToolPanel::setBatchMode(batchMode); } +void Framing::enabledChanged() +{ + if (listener) { + if (get_inconsistent()) { + listener->panelChanged(EvFramingEnabled, M("GENERAL_UNCHANGED")); + } else if (getEnabled()) { + listener->panelChanged(EvFramingEnabled, M("GENERAL_ENABLED")); + } else { + listener->panelChanged(EvFramingEnabled, M("GENERAL_DISABLED")); + } + } +} + void Framing::update(bool isCropped, int croppedWidth, int croppedHeight, int originalWidth, int originalHeight) { @@ -734,65 +749,150 @@ void Framing::updateBorderSizeGui() void Framing::adjusterChanged(Adjuster* adj, double newVal) { + if (listener && (getEnabled() || batchMode)) { + Glib::ustring costr; + if (adj == relativeBorderSize) { + costr = Glib::ustring::format(std::setw(3), std::fixed, std::setprecision(2), + adj->getValue()); + } else { + costr = Glib::ustring::format(static_cast(adj->getValue())); + } + + if (adj == relativeBorderSize) { + listener->panelChanged(EvFramingRelativeBorderSize, costr); + } else if (adj == redAdj) { + listener->panelChanged(EvFramingBorderRed, costr); + } else if (adj == greenAdj) { + listener->panelChanged(EvFramingBorderGreen, costr); + } else if (adj == blueAdj) { + listener->panelChanged(EvFramingBorderBlue, costr); + } + } } void Framing::onFramingMethodChanged() { updateFramingMethodGui(); + + if (listener && (getEnabled() || batchMode)) { + listener->panelChanged(EvFramingMethod, framingMethod->get_active_text()); + } } void Framing::onAspectRatioChanged() { + if (listener && (getEnabled() || batchMode)) { + listener->panelChanged(EvFramingAspectRatio, aspectRatio->get_active_text()); + } } void Framing::onOrientationChanged() { + if (listener && (getEnabled() || batchMode)) { + listener->panelChanged(EvFramingOrientation, orientation->get_active_text()); + } } void Framing::onWidthChanged() { width.isDirty = true; + + if (listener && (getEnabled() || batchMode)) { + listener->panelChanged(EvFramingFramedWidth, + Glib::ustring::format(width.value->get_value_as_int())); + } } void Framing::onHeightChanged() { height.isDirty = true; + + if (listener && (getEnabled() || batchMode)) { + listener->panelChanged(EvFramingFramedHeight, + Glib::ustring::format(height.value->get_value_as_int())); + } } void Framing::onAllowUpscalingToggled() { + if (listener && (getEnabled() || batchMode)) { + if (allowUpscaling->get_inconsistent()) { + listener->panelChanged(EvFramingAllowUpscaling, M("GENERAL_UNCHANGED")); + } else if (allowUpscaling->get_active()) { + listener->panelChanged(EvFramingAllowUpscaling, M("GENERAL_ENABLED")); + } else { + listener->panelChanged(EvFramingAllowUpscaling, M("GENERAL_DISABLED")); + } + } } void Framing::onBorderSizeMethodChanged() { updateBorderSizeGui(); + + if (listener && (getEnabled() || batchMode)) { + listener->panelChanged(EvFramingBorderSizingMethod, borderSizeMethod->get_active_text()); + } } void Framing::onBasisChanged() { + if (listener && (getEnabled() || batchMode)) { + listener->panelChanged(EvFramingBasis, basis->get_active_text()); + } } void Framing::onMinSizeToggled() { updateBorderSizeGui(); + + if (listener && (getEnabled() || batchMode)) { + if (minSizeEnabled->get_inconsistent()) { + listener->panelChanged(EvFramingMinSizeEnabled, M("GENERAL_UNCHANGED")); + } else if (minSizeEnabled->get_active()) { + listener->panelChanged(EvFramingMinSizeEnabled, M("GENERAL_ENABLED")); + } else { + listener->panelChanged(EvFramingMinSizeEnabled, M("GENERAL_DISABLED")); + } + } } void Framing::onMinWidthChanged() { minWidth.isDirty = true; + + if (listener && (getEnabled() || batchMode)) { + listener->panelChanged(EvFramingMinWidth, + Glib::ustring::format(minWidth.value->get_value_as_int())); + } } void Framing::onMinHeightChanged() { minHeight.isDirty = true; + + if (listener && (getEnabled() || batchMode)) { + listener->panelChanged(EvFramingMinHeight, + Glib::ustring::format(minHeight.value->get_value_as_int())); + } } void Framing::onAbsWidthChanged() { absWidth.isDirty = true; + + if (listener && (getEnabled() || batchMode)) { + listener->panelChanged(EvFramingAbsWidth, + Glib::ustring::format(absWidth.value->get_value_as_int())); + } } void Framing::onAbsHeightChanged() { absHeight.isDirty = true; + + if (listener && (getEnabled() || batchMode)) { + listener->panelChanged(EvFramingAbsHeight, + Glib::ustring::format(absHeight.value->get_value_as_int())); + } } \ No newline at end of file diff --git a/rtgui/framing.h b/rtgui/framing.h index 4b0516e38..9f2cdab79 100644 --- a/rtgui/framing.h +++ b/rtgui/framing.h @@ -38,6 +38,7 @@ public: Framing(); ~Framing(); + // FoldableToolPanel void read(const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr) override; void write(rtengine::procparams::ProcParams* pp, @@ -45,6 +46,7 @@ public: void setDefaults(const rtengine::procparams::ProcParams* defParams, const ParamsEdited* pedited = nullptr) override; void setBatchMode(bool batchMode) override; + void enabledChanged() override; void update(bool isCropped, int croppedWidth, int croppedHeight, int originalWidth = 0, int originalHeight = 0); From b88ad569c2cd53fe8a3cebfa2b1deac802dfce0f Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Sat, 9 Nov 2024 20:38:59 -0500 Subject: [PATCH 09/21] Tweak framing tool GUI * Set sensitivity of aspect ratio and orientation combo boxes * Sensitivity determined by absolute/relative sizing method * Show allow upscaling box for BBOX framing method --- rtgui/framing.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rtgui/framing.cc b/rtgui/framing.cc index cff506a46..677a63050 100644 --- a/rtgui/framing.cc +++ b/rtgui/framing.cc @@ -711,7 +711,7 @@ void Framing::updateFramingMethodGui() orientation->show(); width.show(); height.show(); - allowUpscaling->hide(); + allowUpscaling->show(); } else if (activeRow == INDEX_FIXED) { aspectRatioLabel->hide(); aspectRatio->hide(); @@ -735,6 +735,9 @@ void Framing::updateBorderSizeGui() minSizeFrame->show(); absWidth.hide(); absHeight.hide(); + + aspectRatio->set_sensitive(true); + orientation->set_sensitive(true); } else if (activeRow == INDEX_SIZE_ABSOLUTE) { basisLabel->hide(); basis->hide(); @@ -742,6 +745,9 @@ void Framing::updateBorderSizeGui() minSizeFrame->hide(); absWidth.show(); absHeight.show(); + + aspectRatio->set_sensitive(false); + orientation->set_sensitive(false); } minSizeFrameContent->set_sensitive(minSizeEnabled->get_active()); From a96dc4cdde786a95cfadc8c80a29541630f09a8b Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Mon, 11 Nov 2024 14:03:22 -0500 Subject: [PATCH 10/21] Calculate required image and frame sizes * Refactor simpleprocess.cc to be more clear on resize dimensions * Implement image and frame sizing calculations * Resizes the image based on adjusted framing calculations * Missing functionality to draw border around image after resizing --- rtengine/improcfun.h | 21 +- rtengine/ipresize.cc | 623 +++++++++++++++++++++++++++++++++++++- rtengine/simpleprocess.cc | 67 +++- 3 files changed, 687 insertions(+), 24 deletions(-) diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index 6b15ac4d7..578704a50 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -213,12 +213,31 @@ enum class BlurType { void sharpening(LabImage* lab, const procparams::SharpeningParams &sharpenParam, bool showMask = false); void sharpeningcam(CieImage* ncie, float** buffer, bool showMask = false); void transform(Imagefloat* original, Imagefloat* transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH, const FramesMetaData *metadata, int rawRotationDeg, bool fullImage, bool useOriginalBuffer = false); - float resizeScale(const procparams::ProcParams* params, int fw, int fh, int &imw, int &imh); void lab2monitorRgb(LabImage* lab, Image8* image); + + double resizeScale(const procparams::ProcParams* params, int fw, int fh, int &imw, int &imh); void resize(Imagefloat* src, Imagefloat* dst, float dScale); void Lanczos(const LabImage* src, LabImage* dst, float scale); void Lanczos(const Imagefloat* src, Imagefloat* dst, float scale); + struct FramingArgs { + const procparams::ProcParams* params = nullptr; + int cropWidth = 0; + int cropHeight = 0; + int resizeWidth = 0; + int resizeHeight = 0; + double resizeScale = 1.0f; + }; + struct FramingData { + bool enabled = false; + int imgWidth = 0; + int imgHeight = 0; + double scale = 1.0; + int framedWidth = 0; + int framedHeight = 0; + }; + FramingData framing(const FramingArgs& args) const; + void deconvsharpening(float** luminance, float** buffer, const float* const * blend, int W, int H, const procparams::SharpeningParams &sharpenParam, double Scale); void deconvsharpeningloc(float** luminance, float** buffer, int W, int H, float** loctemp, int damp, double radi, int ite, int amo, int contrast, double blurrad, int sk); diff --git a/rtengine/ipresize.cc b/rtengine/ipresize.cc index b6bdc5ede..784d77055 100644 --- a/rtengine/ipresize.cc +++ b/rtengine/ipresize.cc @@ -33,6 +33,582 @@ # include #endif +namespace +{ + +using ProcParams = rtengine::procparams::ProcParams; +using FramingParams = rtengine::procparams::FramingParams; + +using Basis = FramingParams::Basis; +using BorderSizing = FramingParams::BorderSizing; +using FramingMethod = FramingParams::FramingMethod; + +enum class Orientation { LANDSCAPE, PORTRAIT }; +enum class Side { WIDTH, HEIGHT }; + +struct Dimensions { + double width; + double height; + + Dimensions() : width(0), height(0) {} + Dimensions(double w, double h) : width(w), height(h) {} + + bool isDegenerate() const { return width <= 0.0 || height <= 0.0; } + + double aspectRatio() const { + if (isDegenerate()) return 1.0; + else return static_cast(width) / static_cast(height); + } + + Orientation orient() const { + return width >= height ? Orientation::LANDSCAPE : Orientation::PORTRAIT; + } + + bool inside(const Dimensions& other) const { + return width <= other.width && height <= other.height; + } + + void rotate(Orientation newOrient) { + if (newOrient != orient()) { + std::swap(width, height); + } + } + + bool operator==(const Dimensions& other) const { + return width == other.width && height == other.height; + } + bool operator!=(const Dimensions& other) const { return !(*this == other); } + + Dimensions intersect(const Dimensions& other) const { + return Dimensions(std::min(width, other.width), std::min(height, other.height)); + } + + void debug(const char* prefix) const { + printf("%s w=%f h=%f ar=%f\n", prefix, width, height, aspectRatio()); + } +}; + +struct ResizeArgs { + Dimensions size; + double scale = 1.0; + + ResizeArgs(const Dimensions& aSize, double aScale) : size(aSize), scale(aScale) {} +}; + +class Framing { +public: + Framing(const ProcParams& params, int fullWidth, int fullHeight); + + ResizeArgs adjustResizeForFraming(const ResizeArgs& resize) const; + Dimensions computeFramedSize(const Dimensions& imgSize) const; + +private: + Dimensions clampResize(const Dimensions& imgSize, const Dimensions& bounds) const; + ResizeArgs adjustResize(const ResizeArgs& resize, const Dimensions& newSize) const; + Dimensions computeRelativeImageBBoxInFrame(const Dimensions& imgSize, + const Dimensions& framedSize) const; + ResizeArgs resizeForFixedFrame(const ResizeArgs& resize) const; + ResizeArgs resizeForBBox(const ResizeArgs& resize) const; + Dimensions computeSizeWithBorders(const Dimensions& imgSize) const; + + const ProcParams& allParams; + const FramingParams framing; // Make a copy to sanitize inputs + const Dimensions postCropImageSize; + const Dimensions maxUpscalingBBox; +}; + +// Downscaling limit is 32x32px +constexpr double MIN_DOWNSCALE_PX = 32.0; +// Upscaling limit is 16x image size +constexpr double MAX_UPSCALE_FACTOR = 16.0; + +int computeSize(int dim, double scale) +{ + return static_cast(static_cast(dim) * scale + 0.5); +} + +Orientation orient(const FramingParams& params, const Dimensions& imgSize) { + switch (params.orientation) { + case FramingParams::Orientation::LANDSCAPE: + return Orientation::LANDSCAPE; + case FramingParams::Orientation::PORTRAIT: + return Orientation::PORTRAIT; + case FramingParams::Orientation::AS_IMAGE: + default: + return imgSize.orient(); + } +} + +double flipAspectRatioByOrientation(double aspectRatio, Orientation orient) +{ + switch (orient) { + case Orientation::LANDSCAPE: + return aspectRatio >= 1.0 ? aspectRatio : 1.0 / aspectRatio; + case Orientation::PORTRAIT: + return aspectRatio <= 1.0 ? aspectRatio : 1.0 / aspectRatio; + default: + return aspectRatio; + } +} + +Side autoPickBasis(const FramingParams& params, const Dimensions& imgSize) +{ + if (imgSize.isDegenerate()) { + if (imgSize.width <= 0) return Side::HEIGHT; + else return Side::WIDTH; + } + + Orientation imgOrient = imgSize.orient(); + double imgAspectRatio = imgSize.aspectRatio(); + Orientation frameOrient = orient(params, imgSize); + double frameAspectRatio = flipAspectRatioByOrientation(params.aspectRatio, frameOrient); + + if (frameOrient == imgOrient) { + // Pick the more constrained side (i.e. hits 0 border width first) + return imgAspectRatio >= frameAspectRatio ? Side::WIDTH : Side::HEIGHT; + } else if (imgOrient == Orientation::LANDSCAPE) { + // Image in landscape, frame in portrait + return Side::WIDTH; + } else { + // Image in portrait, frame in landscape + return Side::HEIGHT; + } +} + +Side pickReferenceSide(const FramingParams& params, const Dimensions& imgSize) +{ + switch (params.basis) { + case Basis::WIDTH: + return Side::WIDTH; + case Basis::HEIGHT: + return Side::HEIGHT; + case Basis::LONG: + return imgSize.width >= imgSize.height ? Side::WIDTH : Side::HEIGHT; + case Basis::SHORT: + return imgSize.width <= imgSize.height ? Side::WIDTH : Side::HEIGHT; + case Basis::AUTO: + default: + return autoPickBasis(params, imgSize); + } +} + +constexpr bool INSIDE_BBOX = true; +constexpr bool OUTSIDE_BBOX = false; + +Dimensions clampToBBox(const Dimensions& img, const Dimensions& bbox, bool clampInside) { + double widthScale = 1.0; + double heightScale = 1.0; + if (bbox.width > 0) { + widthScale = img.width / bbox.width; + } + if (bbox.height > 0) { + heightScale = img.height / bbox.height; + } + + Dimensions newSize = img; + + if (clampInside) { + // If a side exceeds the bbox, scale down to bbox + double scale = std::max(widthScale, heightScale); + if (scale > 1.0) { + if (widthScale >= heightScale) { + newSize.width = bbox.width; + newSize.height = img.height / widthScale; + } else { + newSize.width = img.width / heightScale; + newSize.height = bbox.height; + } + } + } else { + // If a side is within the bbox, scale up to bbox + double scale = std::min(widthScale, heightScale); + if (scale < 1.0) { + if (widthScale <= heightScale) { + newSize.width = bbox.width; + newSize.height = img.height / widthScale; + } else { + newSize.width = img.width / heightScale; + newSize.height = bbox.height; + } + } + } + + return newSize; +} + +Dimensions downscaleToTouchBBox(const Dimensions& img, const Dimensions& bbox) { + if (bbox.isDegenerate()) return Dimensions(0, 0); + if (!bbox.inside(img)) return img; + + double widthScale = img.width / bbox.width; + double heightScale = img.height / bbox.height; + + Dimensions downscaled; + if (widthScale <= heightScale) { + downscaled.width = bbox.width; + downscaled.height = img.height / widthScale; + } else { + downscaled.height = bbox.height; + downscaled.width = img.width / heightScale; + } + return downscaled; +} + +Dimensions upscaleToBBox(const Dimensions& img, const Dimensions& bbox) { + if (bbox.isDegenerate()) return Dimensions(0, 0); + if (!img.inside(bbox)) return img; + + double widthScale = img.width / bbox.width; + double heightScale = img.height / bbox.height; + + Dimensions upscaled; + if (widthScale >= heightScale) { + upscaled.width = bbox.width; + upscaled.height = img.height / widthScale; + } else { + upscaled.height = bbox.height; + upscaled.width = img.width / heightScale; + } + + return upscaled; +} + +double orientAspectRatio(const FramingParams& framing, const Dimensions& imgSize) +{ + double aspectRatio = framing.aspectRatio; + Orientation borderOrient = orient(framing, imgSize); + if ((borderOrient == Orientation::PORTRAIT && aspectRatio > 1.0) || + (borderOrient == Orientation::LANDSCAPE && aspectRatio < 1.0)) { + aspectRatio = 1.0 / aspectRatio; + } + return aspectRatio; +} + +Dimensions fromAspectRatio(const Dimensions& size, double aspectRatio) +{ + Dimensions result; + if (aspectRatio >= 1.0) { + result.height = size.height; + result.width = result.height * aspectRatio; + } else { + result.width = size.width; + result.height = result.width / aspectRatio; + } + return result; +} + +FramingParams sanitize(const FramingParams& dirty) +{ + FramingParams framing = dirty; + framing.framedWidth = std::max(static_cast(MIN_DOWNSCALE_PX), framing.framedWidth); + framing.framedHeight = std::max(static_cast(MIN_DOWNSCALE_PX), framing.framedHeight); + framing.relativeBorderSize = std::max(0.0, std::min(1.0, framing.relativeBorderSize)); + framing.minWidth = std::max(0, framing.minWidth); + framing.minHeight = std::max(0, framing.minHeight); + framing.absWidth = std::max(0, framing.absWidth); + framing.absHeight = std::max(0, framing.absHeight); + return framing; +} + +Framing::Framing(const ProcParams& params, int fullWidth, int fullHeight) : + allParams(params), + framing(sanitize(params.framing)), + postCropImageSize(params.crop.enabled ? + Dimensions(params.crop.w, params.crop.h) : + Dimensions(fullWidth, fullHeight)), + maxUpscalingBBox(Dimensions( + computeSize(postCropImageSize.width, MAX_UPSCALE_FACTOR), + computeSize(postCropImageSize.height, MAX_UPSCALE_FACTOR))) +{ +} + +Dimensions Framing::clampResize(const Dimensions& imgSize, const Dimensions& bounds) const +{ + // Don't adjust above upscaling limit. + // + // If the upscaling limit is contained inside the target bounds, scale + // down the bounds to outside the upscaling limit. This is needed since + // scaling the bounds to fit inside the upscaling bbox may artificially + // reduce the upscaling limit due to aspect ratio differences. + Dimensions clampedBounds = maxUpscalingBBox.inside(bounds) ? + downscaleToTouchBBox(bounds, maxUpscalingBBox) : + clampToBBox(bounds, maxUpscalingBBox, INSIDE_BBOX); + + if (!imgSize.inside(clampedBounds)) { + // Downscale large images to fit inside bounds (only if above limit) + + Dimensions minSize(MIN_DOWNSCALE_PX, MIN_DOWNSCALE_PX); + if (!minSize.inside(imgSize)) { + // Skip images below downscaling limit + return imgSize; + } else if (!minSize.inside(clampedBounds)) { + // Go as small as possible without exceeding downscaling limit + return downscaleToTouchBBox(imgSize, minSize); + } else { + // Downscale large images to fit inside bounds + return clampToBBox(imgSize, clampedBounds, INSIDE_BBOX); + } + } else { + // Consider upscaling... + if (!framing.allowUpscaling || + imgSize.width == clampedBounds.width || + imgSize.height == clampedBounds.height) { + return imgSize; + } else { + return upscaleToBBox(imgSize, clampedBounds); + } + } +} + +ResizeArgs Framing::adjustResize(const ResizeArgs& resize, const Dimensions& bbox) const +{ + Dimensions newSize = clampResize(resize.size, bbox); + double newScale = newSize.width / resize.size.width; + return ResizeArgs(newSize, newScale); +} + +Dimensions Framing::computeRelativeImageBBoxInFrame(const Dimensions& imgSize, + const Dimensions& framedSize) const +{ + if (imgSize.isDegenerate() || framedSize.isDegenerate()) { + return Dimensions(0, 0); + } + + double imgAspectRatio = imgSize.aspectRatio(); + // Compute the width:height ratio of the border size for the requested + // image size and framed size. + // + // We do this by creating a dummy image. Then, scale the framed size to be + // larger than the dummy image such that there is a non-zero difference for + // widths and heights. + double borderAspectRatio = [&]() + { + Dimensions fakeImage = fromAspectRatio(framedSize, imgAspectRatio); + Dimensions bigFrame = clampToBBox(framedSize, fakeImage, OUTSIDE_BBOX); + bigFrame.width *= 2.0; + bigFrame.height *= 2.0; + + Dimensions diff(bigFrame.width - fakeImage.width, bigFrame.height - fakeImage.height); + return diff.aspectRatio(); + }(); + + Side side = pickReferenceSide(framing, imgSize); + double scale = framing.relativeBorderSize; + + // Compute image and border lengths on basis side + double frameBasis = side == Side::WIDTH ? framedSize.width : framedSize.height; + double frameOther = side == Side::WIDTH ? framedSize.height : framedSize.width; + // frame_len = img_len + 2 * scale * img_len = (1 + 2 * scale) * img_len + double imgFrameScale = (1.0 + 2.0 * scale); + double imgBasis = frameBasis / imgFrameScale; + // border_len = (scale * img_len) + // = frame_len / (1 / scale + 2) + // = frame_len * scale / (1 + 2 * scale) + double borderBasis = frameBasis * scale / imgFrameScale; + + // Compute image and border lengths for the non-basis side. + double imgBasisToOther = side == Side::WIDTH ? 1.0 / imgAspectRatio : imgAspectRatio; + double borderBasisToOther = side == Side::WIDTH ? 1.0 / borderAspectRatio : borderAspectRatio; + double imgOther = imgBasis * imgBasisToOther; + double borderOther = borderBasis * borderBasisToOther; + + double maxImageBasis = frameBasis; + double maxImageOther = frameOther; + if (framing.minSizeEnabled) { + double minBorderBasis = static_cast( + side == Side::WIDTH ? framing.minWidth : framing.minHeight); + double minBorderOther = static_cast( + side == Side::WIDTH ? framing.minHeight : framing.minWidth); + + if (borderOther < minBorderOther) { + maxImageOther = std::floor(frameOther - 2.0 * minBorderOther); + } + if (borderBasis < minBorderBasis) { + maxImageBasis = std::floor(frameBasis - 2.0 * minBorderBasis); + } + } + + if (imgOther > maxImageOther) { + imgOther = maxImageOther; + imgBasis = imgOther / imgBasisToOther; + } + if (imgBasis > maxImageBasis) { + imgBasis = maxImageBasis; + imgOther = imgBasis * imgBasisToOther; + } + + if (side == Side::WIDTH) { + return Dimensions(imgBasis, imgOther); + } else { + return Dimensions(imgOther, imgBasis); + } +} + +ResizeArgs Framing::adjustResizeForFraming(const ResizeArgs& resize) const +{ + if (!framing.enabled) return resize; + + switch (framing.framingMethod) { + case FramingMethod::BBOX: + return resizeForBBox(resize); + case FramingMethod::FIXED_SIZE: + return resizeForFixedFrame(resize); + case FramingMethod::STANDARD: + default: + // No limits on framed size so do nothing + return resize; + } +} + +ResizeArgs Framing::resizeForFixedFrame(const ResizeArgs& args) const +{ + double framedWidth = framing.framedWidth; + double framedHeight = framing.framedHeight; + Dimensions frameSize(framedWidth, framedHeight); + + Dimensions bbox; + if (framing.borderSizingMethod == BorderSizing::FIXED_SIZE) { + auto length = [](double frame, double border) { + return std::max(0.0, frame - 2.0 * border); + }; + + bbox = { + length(framedWidth, framing.absWidth), + length(framedHeight, framing.absHeight) + }; + } else { + bbox = computeRelativeImageBBoxInFrame(args.size, frameSize); + } + + return adjustResize(args, bbox); +} + +ResizeArgs Framing::resizeForBBox(const ResizeArgs& args) const +{ + Dimensions boundary(framing.framedWidth, framing.framedHeight); + + Dimensions bbox; + if (framing.borderSizingMethod == BorderSizing::FIXED_SIZE) { + auto length = [](double frame, double border) { + return std::max(0.0, frame - 2.0 * border); + }; + + bbox = { + length(boundary.width, framing.absWidth), + length(boundary.height, framing.absHeight) + }; + } else { + // For the requested aspect ratio, it must fit inside the requested + // bounding box + double aspectRatio = orientAspectRatio(framing, args.size); + Dimensions ratioBBox = fromAspectRatio(boundary, aspectRatio); + ratioBBox = clampToBBox(ratioBBox, boundary, INSIDE_BBOX); + + // Now we have the true max bounds for the framed image. Determine how the + // original image fits inside these bounds. This process is the same as + // in the fixed frame mode. + bbox = computeRelativeImageBBoxInFrame(args.size, ratioBBox); + } + + return adjustResize(args, bbox); +} + +Dimensions Framing::computeFramedSize(const Dimensions& imgSize) const +{ + if (!framing.enabled) return imgSize; + + // For constrained frame sizes, check if the image size (without frame) + // exceeds the constrained size. This indicates that a combination of + // parameters caused the downscaling limit to be hit. In which case, + // just return the original image size (i.e. don't insert border). + // + // If the image fits the constrained size, assume that previous resize + // calculations were correct and trim off any excess borders. The excess + // may be from rounding errors or hitting some downscaling limit. + switch (framing.framingMethod) { + case FramingMethod::BBOX: + { + Dimensions fixed(framing.framedWidth, framing.framedHeight); + if (imgSize.inside(fixed)) { + Dimensions framedSize = computeSizeWithBorders(imgSize); + return clampToBBox(framedSize, fixed, INSIDE_BBOX); + } else { + return imgSize; + } + } + case FramingMethod::FIXED_SIZE: + { + Dimensions fixed(framing.framedWidth, framing.framedHeight); + return imgSize.inside(fixed) ? fixed : imgSize; + } + case FramingMethod::STANDARD: + default: + return computeSizeWithBorders(imgSize); + } +} + +Dimensions Framing::computeSizeWithBorders(const Dimensions& imgSize) const +{ + auto length = [](double img, double border) { + return img + 2.0 * border; + }; + auto evalBorder = [](double side, double scale, double img) { + double otherSide = side * scale; + double totalBorder = otherSide - img; + return std::max(totalBorder, 0.0) * 0.5; + }; + + if (framing.borderSizingMethod == BorderSizing::FIXED_SIZE) { + return Dimensions(length(imgSize.width, framing.absWidth), + length(imgSize.height, framing.absHeight)); + } + + Side side = pickReferenceSide(framing, imgSize); + double aspectRatio = orientAspectRatio(framing, imgSize); + + // Compute the size with borders given the requested reference side length + // and aspect ratio + Dimensions borderSize; + double scale = framing.relativeBorderSize; + if (side == Side::WIDTH) { + borderSize.width = imgSize.width * scale; + double totalWidth = length(imgSize.width, borderSize.width); + borderSize.height = evalBorder(totalWidth, 1.0 / aspectRatio, imgSize.height); + } else { + borderSize.height = imgSize.height * scale; + double totalHeight = length(imgSize.height, borderSize.height); + borderSize.width = evalBorder(totalHeight, aspectRatio, imgSize.width); + } + + if (framing.minSizeEnabled) { + Dimensions minSize(static_cast(framing.minWidth), + static_cast(framing.minHeight)); + borderSize = clampToBBox(borderSize, minSize, OUTSIDE_BBOX); + } + + Dimensions framedSize(length(imgSize.width, borderSize.width), + length(imgSize.height, borderSize.height)); + + // Check if the computed frame size satsifies the requested aspect ratio + // without cutting off the original image. If the image is cut off, use + // the smallest frame that preserves the original image and still + // satisfies the requested aspect ratio. + Dimensions minFramedSize = fromAspectRatio(imgSize, aspectRatio); + if (framing.minSizeEnabled) { + Dimensions limit = imgSize; + limit.width += 2.0 * framing.minWidth; + limit.height += 2.0 * framing.minHeight; + minFramedSize = clampToBBox(minFramedSize, limit, OUTSIDE_BBOX); + } + + if (minFramedSize.inside(framedSize)) { + return framedSize; + } else { + return minFramedSize; + } +} + +} // namespace namespace rtengine { @@ -333,7 +909,7 @@ void ImProcFunctions::Lanczos (const LabImage* src, LabImage* dst, float scale) delete[] wwh; } -float ImProcFunctions::resizeScale (const ProcParams* params, int fw, int fh, int &imw, int &imh) +double ImProcFunctions::resizeScale (const ProcParams* params, int fw, int fh, int &imw, int &imh) { imw = fw; imh = fh; @@ -406,10 +982,6 @@ float ImProcFunctions::resizeScale (const ProcParams* params, int fw, int fh, in break; } - if (fabs (dScale - 1.0) <= 1e-5) { - return 1.0; - } - if (params->crop.enabled && params->resize.appliesTo == "Full image") { imw = params->crop.w; imh = params->crop.h; @@ -418,9 +990,13 @@ float ImProcFunctions::resizeScale (const ProcParams* params, int fw, int fh, in imh = refh; } - imw = (int) ( (double)imw * dScale + 0.5 ); - imh = (int) ( (double)imh * dScale + 0.5 ); - return (float)dScale; + if (fabs (dScale - 1.0) <= 1e-5) { + return 1.0; + } else { + imw = computeSize(imw, dScale); + imh = computeSize(imh, dScale); + return dScale; + } } void ImProcFunctions::resize (Imagefloat* src, Imagefloat* dst, float dScale) @@ -458,4 +1034,35 @@ void ImProcFunctions::resize (Imagefloat* src, Imagefloat* dst, float dScale) #endif } +ImProcFunctions::FramingData ImProcFunctions::framing(const FramingArgs& args) const +{ + FramingData result; + result.enabled = false; + result.imgWidth = args.resizeWidth; + result.imgHeight = args.resizeHeight; + result.scale = args.resizeScale; + result.framedWidth = args.resizeWidth; + result.framedHeight = args.resizeHeight; + + if (!args.params || !args.params->resize.enabled) return result; + if (!args.params->framing.enabled) return result; + + // For these calculations, try to keep everything as doubles to minimize + // rounding errors from intermediate steps! + + Framing util(*params, args.cropWidth, args.cropHeight); + ResizeArgs resize(Dimensions(args.resizeWidth, args.resizeHeight), args.resizeScale); + ResizeArgs adjusted = util.adjustResizeForFraming(resize); + Dimensions framedSize = util.computeFramedSize(adjusted.size); + + result.enabled = true; + result.imgWidth = std::round(adjusted.size.width); + result.imgHeight = std::round(adjusted.size.height); + result.scale = adjusted.scale; + result.framedWidth = std::round(framedSize.width); + result.framedHeight = std::round(framedSize.height); + + return result; } + +} // namespace rtengine \ No newline at end of file diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index fa15285a7..0c695c6a0 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -1893,20 +1893,50 @@ private: pl->setProgress(0.60); } - int imw, imh; - double tmpScale = ipf.resizeScale(¶ms, fw, fh, imw, imh); - bool labResize = params.resize.enabled && params.resize.method != "Nearest" && (tmpScale != 1.0 || params.prsharpening.enabled); - LabImage *tmplab; - // crop and convert to rgb16 int cx = 0, cy = 0, cw = labView->W, ch = labView->H; - if (params.crop.enabled) { cx = params.crop.x; cy = params.crop.y; cw = params.crop.w; ch = params.crop.h; + } + ImProcFunctions::FramingArgs framingArgs; + framingArgs.params = ¶ms; + framingArgs.cropWidth = cw; + framingArgs.cropHeight = ch; + { + int imw, imh; + double tmpScale = ipf.resizeScale(¶ms, fw, fh, imw, imh); + framingArgs.resizeWidth = imw; + framingArgs.resizeHeight = imh; + framingArgs.resizeScale = tmpScale; + + // If upscaling is not permitted, keep original sizing + if ((cw < imw || ch < imh) && !params.resize.allowUpscaling) { + framingArgs.resizeWidth = cw; + framingArgs.resizeHeight = ch; + framingArgs.resizeScale = 1.0; + } + } + + // If framing is not enabled, resize values simply pass through to output + ImProcFunctions::FramingData framingData = ipf.framing(framingArgs); + printf("Framing Parameters (enabled=%s)\n", framingData.enabled ? "yes" : "no"); + printf(" Crop: w=%d h=%d\n", cw, ch); + printf(" Original resize: w=%d h=%d s=%f\n", + framingArgs.resizeWidth, framingArgs.resizeHeight, framingArgs.resizeScale); + printf(" Framed image size: w=%d h=%d s=%f\n", + framingData.imgWidth, framingData.imgHeight, framingData.scale); + printf(" Total size: w=%d h=%d\n", + framingData.framedWidth, framingData.framedHeight); + + bool labResize = params.resize.enabled && params.resize.method != "Nearest" && + (framingData.scale != 1.0 || params.prsharpening.enabled || framingData.enabled); + + LabImage *tmplab = nullptr; + if (params.crop.enabled) { if (labResize) { // crop lab data tmplab = new LabImage(cw, ch); @@ -1926,11 +1956,12 @@ private: } if (labResize) { // resize lab data - if ((labView->W != imw || labView->H != imh) && - (params.resize.allowUpscaling || (labView->W >= imw && labView->H >= imh))) { + int imw = framingData.imgWidth; + int imh = framingData.imgHeight; + if (labView->W != imw || labView->H != imh) { // resize image tmplab = new LabImage(imw, imh); - ipf.Lanczos(labView, tmplab, tmpScale); + ipf.Lanczos(labView, tmplab, framingData.scale); delete labView; labView = tmplab; } @@ -1984,14 +2015,20 @@ private: pl->setProgress(0.70); } - if (tmpScale != 1.0 && params.resize.method == "Nearest" && - (params.resize.allowUpscaling || (readyImg->getWidth() >= imw && readyImg->getHeight() >= imh))) { // resize rgb data (gamma applied) - Imagefloat* tempImage = new Imagefloat(imw, imh); - ipf.resize(readyImg, tempImage, tmpScale); - delete readyImg; - readyImg = tempImage; + if (framingData.scale != 1.0 && params.resize.method == "Nearest") { + int imw = framingData.imgWidth; + int imh = framingData.imgHeight; + if (readyImg->getWidth() != imw || readyImg->getHeight() != imh) { + // resize rgb data (gamma applied) + Imagefloat* tempImage = new Imagefloat(imw, imh); + ipf.resize(readyImg, tempImage, framingData.scale); + delete readyImg; + readyImg = tempImage; + } } + // TODO: Blit framing border + image + Exiv2Metadata info(imgsrc->getFileName()); switch (params.metadata.mode) { From 7ccab91434bc75596826e124513a66b315ad5587 Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Mon, 11 Nov 2024 22:13:38 -0500 Subject: [PATCH 11/21] Implement drawing border for framing tool * Draws border after all resize operations are complete * Update the RGB sliders for 16-bit channels --- rtengine/improcfun.h | 2 ++ rtengine/ipresize.cc | 59 ++++++++++++++++++++++++++++++++++++++- rtengine/simpleprocess.cc | 4 ++- rtgui/framing.cc | 6 ++-- 4 files changed, 66 insertions(+), 5 deletions(-) diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index 578704a50..6200f177d 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -237,6 +237,8 @@ enum class BlurType { int framedHeight = 0; }; FramingData framing(const FramingArgs& args) const; + Imagefloat* drawFrame(Imagefloat* rgb, const procparams::FramingParams& params, + const FramingData& dims) const; void deconvsharpening(float** luminance, float** buffer, const float* const * blend, int W, int H, const procparams::SharpeningParams &sharpenParam, double Scale); void deconvsharpeningloc(float** luminance, float** buffer, int W, int H, float** loctemp, int damp, double radi, int ite, int amo, int contrast, double blurrad, int sk); diff --git a/rtengine/ipresize.cc b/rtengine/ipresize.cc index 784d77055..16adff2ca 100644 --- a/rtengine/ipresize.cc +++ b/rtengine/ipresize.cc @@ -20,6 +20,7 @@ #include "improcfun.h" #include "alignedbuffer.h" +#include "color.h" #include "imagefloat.h" #include "labimage.h" #include "opthelper.h" @@ -1065,4 +1066,60 @@ ImProcFunctions::FramingData ImProcFunctions::framing(const FramingArgs& args) c return result; } -} // namespace rtengine \ No newline at end of file +// Draws the border around the input image. +// It should be called after gamma correction. +Imagefloat* ImProcFunctions::drawFrame(Imagefloat* rgb, const FramingParams& params, + const FramingData& dims) const +{ + if (rgb->getWidth() > dims.framedWidth || rgb->getHeight() > dims.framedHeight) { + return rgb; + } + if (rgb->getWidth() == dims.framedWidth && rgb->getHeight() == dims.framedHeight) { + return rgb; + } + + Imagefloat* framed = new Imagefloat(dims.framedWidth, dims.framedHeight); + + auto clip = [](int v) { return std::max(0, std::min(v, 65535)); }; + + float r = Color::gamma2curve[clip(params.borderRed)]; + float g = Color::gamma2curve[clip(params.borderGreen)]; + float b = Color::gamma2curve[clip(params.borderBlue)]; + +#ifdef _OPENMP + #pragma omp parallel for if (multiThread) +#endif + for (int i = 0; i < framed->getHeight(); i++) { + for (int j = 0; j < framed->getWidth(); j++) { + framed->r(i, j) = r; + framed->g(i, j) = g; + framed->b(i, j) = b; + } + } + + auto offset = [](int inner, int outer) { + double u = inner; + double v = outer; + return static_cast(std::round((v - u) / 2.0)); + }; + int rowOffset = offset(rgb->getHeight(), framed->getHeight()); + int colOffset = offset(rgb->getWidth(), framed->getWidth()); + +#ifdef _OPENMP + #pragma omp parallel for if (multiThread) +#endif + for (int i = 0; i < rgb->getHeight(); i++) { + for (int j = 0; j < rgb->getWidth(); j++) { + int row = i + rowOffset; + int col = j + colOffset; + + framed->r(row, col) = rgb->r(i, j); + framed->g(row, col) = rgb->g(i, j); + framed->b(row, col) = rgb->b(i, j); + } + } + + return framed; +} + +} // namespace rtengine diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 0c695c6a0..d5a94ea98 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -2027,7 +2027,9 @@ private: } } - // TODO: Blit framing border + image + if (framingData.enabled) { + readyImg = ipf.drawFrame(readyImg, params.framing, framingData); + } Exiv2Metadata info(imgsrc->getFileName()); diff --git a/rtgui/framing.cc b/rtgui/framing.cc index 677a63050..033e17578 100644 --- a/rtgui/framing.cc +++ b/rtgui/framing.cc @@ -449,11 +449,11 @@ void Framing::setupBorderColorsGui() frame->set_label_widget(*label); Gtk::Box* const box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); - redAdj = Gtk::manage(new Adjuster(M("TP_FRAMING_RED"), 0, 255, 1, 255)); + redAdj = Gtk::manage(new Adjuster(M("TP_FRAMING_RED"), 0, 65535, 1, 65535)); box->add(*redAdj); - greenAdj = Gtk::manage(new Adjuster(M("TP_FRAMING_GREEN"), 0, 255, 1, 255)); + greenAdj = Gtk::manage(new Adjuster(M("TP_FRAMING_GREEN"), 0, 65535, 1, 65535)); box->add(*greenAdj); - blueAdj = Gtk::manage(new Adjuster(M("TP_FRAMING_BLUE"), 0, 255, 1, 255)); + blueAdj = Gtk::manage(new Adjuster(M("TP_FRAMING_BLUE"), 0, 65535, 1, 65535)); box->add(*blueAdj); frame->add(*box); From 55480f40ea41678b81d470adf33cc6b37be148ff Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Tue, 12 Nov 2024 22:30:48 -0500 Subject: [PATCH 12/21] Fix framing tool batch mode behavior * Add adjuster add/set options to preferences * Update width/height limits * Trim adjuster values * Batch mode fixes * Display all settings with sensitivity * Add "(Unchanged)" option to combo boxes * Fix checked button toggle modes --- rtgui/addsetids.h | 4 + rtgui/batchtoolpanelcoord.cc | 14 +++- rtgui/framing.cc | 158 ++++++++++++++++++++++++++++------- rtgui/framing.h | 9 +- rtgui/paramsedited.cc | 22 +++-- rtgui/preferences.cc | 7 ++ rtgui/toolpanelcoord.cc | 4 +- 7 files changed, 175 insertions(+), 43 deletions(-) diff --git a/rtgui/addsetids.h b/rtgui/addsetids.h index 2a123a9f7..0753e8473 100644 --- a/rtgui/addsetids.h +++ b/rtgui/addsetids.h @@ -134,6 +134,10 @@ enum { ADDSET_SHARP_EDGETOL, ADDSET_SHARP_HALOCTRL, ADDSET_RESIZE_SCALE, + ADDSET_FRAMING_RELATIVE_SCALE, + ADDSET_FRAMING_BORDER_RED, + ADDSET_FRAMING_BORDER_GREEN, + ADDSET_FRAMING_BORDER_BLUE, ADDSET_EPD_STRENGTH, ADDSET_EPD_GAMMA, ADDSET_EPD_EDGESTOPPING, diff --git a/rtgui/batchtoolpanelcoord.cc b/rtgui/batchtoolpanelcoord.cc index 35e4f7892..4b6e16dca 100644 --- a/rtgui/batchtoolpanelcoord.cc +++ b/rtgui/batchtoolpanelcoord.cc @@ -151,6 +151,7 @@ void BatchToolPanelCoordinator::initSession () colorappearance->setAdjusterBehavior (false, false, false, false, false, false, false, false, false, false, false, false, false, false, false); rotate->setAdjusterBehavior (false); resize->setAdjusterBehavior (false); + framing->setAdjusterBehavior (false, false, false, false); distortion->setAdjusterBehavior (false, false); perspective->setAdjusterBehavior (false, false, false, false, false, false, false); gradient->setAdjusterBehavior (false, false, false, false); @@ -196,6 +197,11 @@ void BatchToolPanelCoordinator::initSession () colorappearance->setAdjusterBehavior (options.baBehav[ADDSET_CAT_DEGREE], options.baBehav[ADDSET_CAT_ADAPTSCENE], options.baBehav[ADDSET_CAT_ADAPTVIEWING], options.baBehav[ADDSET_CAT_BADPIX], options.baBehav[ADDSET_CAT_LIGHT], options.baBehav[ADDSET_CAT_CHROMA], options.baBehav[ADDSET_CAT_CONTRAST], options.baBehav[ADDSET_CAT_RSTPRO], options.baBehav[ADDSET_CAT_BRIGHT], options.baBehav[ADDSET_CAT_CONTRAST_Q], options.baBehav[ADDSET_CAT_CHROMA_S], options.baBehav[ADDSET_CAT_CHROMA_M], options.baBehav[ADDSET_CAT_HUE],options.baBehav[ADDSET_CAT_DEGREEOUT], options.baBehav[ADDSET_CAT_TEMPOUT] ); rotate->setAdjusterBehavior (options.baBehav[ADDSET_ROTATE_DEGREE]); resize->setAdjusterBehavior (options.baBehav[ADDSET_RESIZE_SCALE]); + framing->setAdjusterBehavior ( + options.baBehav[ADDSET_FRAMING_RELATIVE_SCALE], + options.baBehav[ADDSET_FRAMING_BORDER_RED], + options.baBehav[ADDSET_FRAMING_BORDER_GREEN], + options.baBehav[ADDSET_FRAMING_BORDER_BLUE]); distortion->setAdjusterBehavior ( options.baBehav[ADDSET_DIST_AMOUNT], options.baBehav[ADDSET_DIST_FOCAL_LENGTH] @@ -324,6 +330,10 @@ void BatchToolPanelCoordinator::initSession () if (options.baBehav[ADDSET_DEHAZE_STRENGTH]) { pparams.dehaze.strength = 0; } if (options.baBehav[ADDSET_ROTATE_DEGREE]) { pparams.rotate.degree = 0; } if (options.baBehav[ADDSET_RESIZE_SCALE]) { pparams.resize.scale = 0; } + if (options.baBehav[ADDSET_FRAMING_RELATIVE_SCALE]) { pparams.framing.relativeBorderSize = 0; } + if (options.baBehav[ADDSET_FRAMING_BORDER_RED]) { pparams.framing.borderRed = 0; } + if (options.baBehav[ADDSET_FRAMING_BORDER_GREEN]) { pparams.framing.borderGreen = 0; } + if (options.baBehav[ADDSET_FRAMING_BORDER_BLUE]) { pparams.framing.borderBlue = 0; } if (options.baBehav[ADDSET_DIST_AMOUNT]) { pparams.distortion.amount = 0; } if (options.baBehav[ADDSET_PERSPECTIVE]) { pparams.perspective.horizontal = pparams.perspective.vertical = 0; } if (options.baBehav[ADDSET_PERSP_CAM_FOCAL_LENGTH]) { pparams.perspective.camera_focal_length = pparams.perspective.camera_crop_factor = 0; } @@ -467,12 +477,12 @@ void BatchToolPanelCoordinator::panelChanged(const rtengine::ProcEvent& event, c crop->write (&pparams, &pparamsEdited); resize->update (pparams.crop.enabled, pparams.crop.w, pparams.crop.h, w, h); resize->write (&pparams, &pparamsEdited); - framing->update (pparams.crop.enabled, pparams.crop.w, pparams.crop.h, w, h); + framing->update (w, h); framing->write (&pparams, &pparamsEdited); } else if (event == rtengine::EvCrop) { resize->update (pparams.crop.enabled, pparams.crop.w, pparams.crop.h); resize->write (&pparams, &pparamsEdited); - framing->update (pparams.crop.enabled, pparams.crop.w, pparams.crop.h, w, h); + framing->update (w, h); framing->write (&pparams, &pparamsEdited); } } else { diff --git a/rtgui/framing.cc b/rtgui/framing.cc index 033e17578..f21e8ed1d 100644 --- a/rtgui/framing.cc +++ b/rtgui/framing.cc @@ -43,6 +43,7 @@ constexpr int EMPTY_COMBO_INDEX = -1; constexpr int INDEX_STANDARD = 0; constexpr int INDEX_BBOX = 1; constexpr int INDEX_FIXED = 2; +constexpr int INDEX_FRAMING_METHOD_UNCHANGED = 3; constexpr std::array FRAMING_METHODS = { "TP_FRAMING_METHOD_STANDARD", "TP_FRAMING_METHOD_BBOX", @@ -83,6 +84,7 @@ FramingParams::FramingMethod mapFramingMethod(int comboIndex) constexpr int INDEX_AS_IMAGE = 0; constexpr int INDEX_LANDSCAPE = 1; constexpr int INDEX_PORTRAIT = 2; +constexpr int INDEX_ORIENTATION_UNCHANGED = 3; constexpr std::array ORIENTATION = { "GENERAL_ASIMAGE", "GENERAL_LANDSCAPE", @@ -122,6 +124,7 @@ FramingParams::Orientation mapOrientation(int comboIndex) // Border sizing method combo box data constexpr int INDEX_SIZE_RELATIVE = 0; constexpr int INDEX_SIZE_ABSOLUTE = 1; +constexpr int INDEX_SIZE_UNCHANGED = 2; constexpr std::array BORDER_SIZE_METHODS = { "TP_FRAMING_BORDER_SIZE_RELATIVE", "TP_FRAMING_BORDER_SIZE_ABSOLUTE" @@ -159,6 +162,7 @@ constexpr int INDEX_BASIS_WIDTH = 1; constexpr int INDEX_BASIS_HEIGHT = 2; constexpr int INDEX_BASIS_LONG = 3; constexpr int INDEX_BASIS_SHORT = 4; +constexpr int INDEX_BASIS_UNCHANGED = 5; constexpr std::array BORDER_SIZE_BASIS = { "TP_FRAMING_BASIS_AUTO", "TP_FRAMING_BASIS_WIDTH", @@ -205,8 +209,8 @@ FramingParams::Basis mapBasis(int comboIndex) } } -constexpr int INITIAL_IMG_WIDTH = 800; -constexpr int INITIAL_IMG_HEIGHT = 600; +constexpr int INITIAL_IMG_WIDTH = 100000; +constexpr int INITIAL_IMG_HEIGHT = 100000; constexpr int ROW_SPACING = 4; constexpr float FRAME_LABEL_ALIGN_X = 0.025; @@ -252,7 +256,9 @@ public: combo->set_active(INDEX_CURRENT); } - double value(int index) + int unchangedIndex() const { return ratios.size(); } + + double value(int index) const { return ratios.at(index).value; } @@ -293,7 +299,9 @@ Framing::Framing() : FoldableToolPanel(this, TOOL_NAME, M("TP_FRAMING_LABEL"), false, true), aspectRatioData(new AspectRatios), imgWidth(INITIAL_IMG_WIDTH), - imgHeight(INITIAL_IMG_HEIGHT) + imgHeight(INITIAL_IMG_HEIGHT), + lastAllowUpscaling(false), + lastMinSizeEnabled(false) { setupFramingMethodGui(); pack_start(*Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL))); @@ -467,21 +475,23 @@ void Framing::setupBorderColorsGui() void Framing::read(const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited) { DisableListener disableListener(this); - std::vector blockers; - blockers.reserve(13); - blockers.emplace_back(framingMethodChanged); - blockers.emplace_back(aspectRatioChanged); - blockers.emplace_back(orientationChanged); - blockers.emplace_back(width.connection); - blockers.emplace_back(height.connection); - blockers.emplace_back(allowUpscalingConnection); - blockers.emplace_back(borderSizeMethodChanged); - blockers.emplace_back(basisChanged); - blockers.emplace_back(minSizeEnabledConnection); - blockers.emplace_back(minWidth.connection); - blockers.emplace_back(minHeight.connection); - blockers.emplace_back(absWidth.connection); - blockers.emplace_back(absHeight.connection); + + std::array blockers = { + ConnectionBlocker(framingMethodChanged), + ConnectionBlocker(aspectRatioChanged), + ConnectionBlocker(orientationChanged), + ConnectionBlocker(width.connection), + ConnectionBlocker(height.connection), + ConnectionBlocker(allowUpscalingConnection), + ConnectionBlocker(borderSizeMethodChanged), + ConnectionBlocker(basisChanged), + ConnectionBlocker(minSizeEnabledConnection), + ConnectionBlocker(minWidth.connection), + ConnectionBlocker(minHeight.connection), + ConnectionBlocker(absWidth.connection), + ConnectionBlocker(absHeight.connection) + }; + BlockAdjusterEvents blockRelative(relativeBorderSize); BlockAdjusterEvents blockRed(redAdj); BlockAdjusterEvents blockGreen(greenAdj); @@ -492,6 +502,7 @@ void Framing::read(const rtengine::procparams::ProcParams* pp, const ParamsEdite updateFramingMethodGui(); updateBorderSizeGui(); + setDimensions(); } void Framing::readParams(const rtengine::procparams::ProcParams* pp) @@ -508,11 +519,13 @@ void Framing::readParams(const rtengine::procparams::ProcParams* pp) height.setValue(params.framedHeight); height.isDirty = false; allowUpscaling->set_active(params.allowUpscaling); + lastAllowUpscaling = params.allowUpscaling; borderSizeMethod->set_active(mapBorderSizeMethod(params.borderSizingMethod)); basis->set_active(mapBasis(params.basis)); relativeBorderSize->setValue(params.relativeBorderSize); minSizeEnabled->set_active(params.minSizeEnabled); + lastMinSizeEnabled = params.minSizeEnabled; minWidth.setValue(params.minWidth); minWidth.isDirty = false; minHeight.setValue(params.minHeight); @@ -607,15 +620,15 @@ void Framing::writeEdited(ParamsEdited* pedited) edits.enabled = !get_inconsistent(); - edits.framingMethod = framingMethod->get_active_row_number() != EMPTY_COMBO_INDEX; - edits.aspectRatio = aspectRatio->get_active_row_number() != EMPTY_COMBO_INDEX; - edits.orientation = orientation->get_active_row_number() != EMPTY_COMBO_INDEX; + edits.framingMethod = framingMethod->get_active_row_number() != INDEX_FRAMING_METHOD_UNCHANGED; + edits.aspectRatio = aspectRatio->get_active_row_number() != aspectRatioData->unchangedIndex(); + edits.orientation = orientation->get_active_row_number() != INDEX_ORIENTATION_UNCHANGED; edits.framedWidth = width.isDirty; edits.framedHeight = height.isDirty; edits.allowUpscaling = !allowUpscaling->get_inconsistent(); - edits.borderSizingMethod = borderSizeMethod->get_active_row_number() != EMPTY_COMBO_INDEX; - edits.basis = basis->get_active_row_number() != EMPTY_COMBO_INDEX; + edits.borderSizingMethod = borderSizeMethod->get_active_row_number() != INDEX_SIZE_UNCHANGED; + edits.basis = basis->get_active_row_number() != INDEX_BASIS_UNCHANGED; edits.relativeBorderSize = relativeBorderSize->getEditedState(); edits.minSizeEnabled = !minSizeEnabled->get_inconsistent(); edits.minWidth = minWidth.isDirty; @@ -652,9 +665,27 @@ void Framing::setDefaults(const rtengine::procparams::ProcParams* defParams, con } } +void Framing::trimValues(rtengine::procparams::ProcParams* pp) +{ + relativeBorderSize->trimValue(pp->framing.relativeBorderSize); + redAdj->trimValue(pp->framing.borderRed); + greenAdj->trimValue(pp->framing.borderGreen); + blueAdj->trimValue(pp->framing.borderBlue); +} + void Framing::setBatchMode(bool batchMode) { + framingMethod->append(M("GENERAL_UNCHANGED")); + aspectRatio->append(M("GENERAL_UNCHANGED")); + orientation->append(M("GENERAL_UNCHANGED")); + borderSizeMethod->append(M("GENERAL_UNCHANGED")); + basis->append(M("GENERAL_UNCHANGED")); + ToolPanel::setBatchMode(batchMode); + relativeBorderSize->showEditedCB(); + redAdj->showEditedCB(); + greenAdj->showEditedCB(); + blueAdj->showEditedCB(); } void Framing::enabledChanged() @@ -670,22 +701,43 @@ void Framing::enabledChanged() } } -void Framing::update(bool isCropped, int croppedWidth, int croppedHeight, - int originalWidth, int originalHeight) +void Framing::update(int originalWidth, int originalHeight) { + // This is how it is checked in resize.cc if (originalWidth && originalHeight) { imgWidth = originalWidth; imgHeight = originalHeight; } +} - setDimensions(); +void Framing::setAdjusterBehavior(bool addRelativeBorderSize, bool addRed, bool addGreen, + bool addBlue) +{ + relativeBorderSize->setAddMode(addRelativeBorderSize); + redAdj->setAddMode(addRed); + greenAdj->setAddMode(addGreen); + blueAdj->setAddMode(addBlue); } void Framing::setDimensions() { idleRegister.add([this]() -> bool { + std::array blockers = { + ConnectionBlocker(width.connection), + ConnectionBlocker(height.connection), + ConnectionBlocker(minWidth.connection), + ConnectionBlocker(minHeight.connection), + ConnectionBlocker(absWidth.connection), + ConnectionBlocker(absHeight.connection) + }; + + // 16x the full image size is probably a reasonable max width.value->set_range(Resize::MIN_SIZE, Resize::MAX_SCALE * imgWidth); height.value->set_range(Resize::MIN_SIZE, Resize::MAX_SCALE * imgHeight); + minWidth.value->set_range(0, Resize::MAX_SCALE * imgWidth); + minHeight.value->set_range(0, Resize::MAX_SCALE * imgHeight); + absWidth.value->set_range(0, Resize::MAX_SCALE * imgWidth); + absHeight.value->set_range(0, Resize::MAX_SCALE * imgHeight); return false; }); @@ -693,7 +745,16 @@ void Framing::setDimensions() void Framing::updateFramingMethodGui() { - if (batchMode) return; + if (batchMode) { + aspectRatioLabel->show(); + aspectRatio->show(); + orientationLabel->show(); + orientation->show(); + width.show(); + height.show(); + allowUpscaling->show(); + return; + } int activeRow = framingMethod->get_active_row_number(); if (activeRow == INDEX_STANDARD) { @@ -725,7 +786,20 @@ void Framing::updateFramingMethodGui() void Framing::updateBorderSizeGui() { - if (batchMode) return; + if (batchMode) { + basisLabel->show(); + basis->show(); + relativeBorderSize->show(); + minSizeFrame->show(); + absWidth.show(); + absHeight.show(); + + aspectRatio->set_sensitive(true); + orientation->set_sensitive(true); + + minSizeFrameContent->set_sensitive(true); + return; + } int activeRow = borderSizeMethod->get_active_row_number(); if (activeRow == INDEX_SIZE_RELATIVE) { @@ -821,6 +895,18 @@ void Framing::onHeightChanged() void Framing::onAllowUpscalingToggled() { + if (batchMode) { + if (allowUpscaling->get_inconsistent()) { + allowUpscaling->set_inconsistent(false); + ConnectionBlocker block(allowUpscalingConnection); + allowUpscaling->set_active(false); + } else if (lastAllowUpscaling) { + allowUpscaling->set_inconsistent(true); + } + + lastAllowUpscaling = allowUpscaling->get_active(); + } + if (listener && (getEnabled() || batchMode)) { if (allowUpscaling->get_inconsistent()) { listener->panelChanged(EvFramingAllowUpscaling, M("GENERAL_UNCHANGED")); @@ -850,6 +936,18 @@ void Framing::onBasisChanged() void Framing::onMinSizeToggled() { + if (batchMode) { + if (minSizeEnabled->get_inconsistent()) { + minSizeEnabled->set_inconsistent(false); + ConnectionBlocker block(minSizeEnabledConnection); + minSizeEnabled->set_active(false); + } else if (lastMinSizeEnabled) { + minSizeEnabled->set_inconsistent(true); + } + + lastMinSizeEnabled = minSizeEnabled->get_active(); + } + updateBorderSizeGui(); if (listener && (getEnabled() || batchMode)) { @@ -901,4 +999,4 @@ void Framing::onAbsHeightChanged() listener->panelChanged(EvFramingAbsHeight, Glib::ustring::format(absHeight.value->get_value_as_int())); } -} \ No newline at end of file +} diff --git a/rtgui/framing.h b/rtgui/framing.h index 9f2cdab79..93ca6b65f 100644 --- a/rtgui/framing.h +++ b/rtgui/framing.h @@ -45,11 +45,12 @@ public: ParamsEdited* pedited = nullptr) override; void setDefaults(const rtengine::procparams::ProcParams* defParams, const ParamsEdited* pedited = nullptr) override; + void trimValues(rtengine::procparams::ProcParams* pp) override; void setBatchMode(bool batchMode) override; void enabledChanged() override; - void update(bool isCropped, int croppedWidth, int croppedHeight, - int originalWidth = 0, int originalHeight = 0); + void update(int originalWidth = 0, int originalHeight = 0); + void setAdjusterBehavior(bool addRelativeBorderSize, bool addRed, bool addGreen, bool addBlue); // AdjusterListener void adjusterChanged(Adjuster* adj, double newVal) override; @@ -146,4 +147,6 @@ private: int imgWidth; int imgHeight; -}; \ No newline at end of file + bool lastAllowUpscaling; + bool lastMinSizeEnabled; +}; diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index a77c48d3a..ca6af8e08 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -81,7 +81,8 @@ void initFrom(FramingParamsEdited& edits, const ProcParams& params, const ProcPa edits.borderBlue &= curr.borderBlue == other.borderBlue; } -void combine(FramingParams& toEdit, const FramingParams& mod, const FramingParamsEdited& edits) +void combine(FramingParams& toEdit, const FramingParams& mod, const FramingParamsEdited& edits, + bool dontForceSet) { if (edits.enabled) { toEdit.enabled = mod.enabled; @@ -112,7 +113,10 @@ void combine(FramingParams& toEdit, const FramingParams& mod, const FramingParam toEdit.basis = mod.basis; } if (edits.relativeBorderSize) { - toEdit.relativeBorderSize = mod.relativeBorderSize; + toEdit.relativeBorderSize = + dontForceSet && options.baBehav[ADDSET_FRAMING_RELATIVE_SCALE] ? + toEdit.relativeBorderSize + mod.relativeBorderSize : + mod.relativeBorderSize; } if (edits.minSizeEnabled) { toEdit.minSizeEnabled = mod.minSizeEnabled; @@ -131,13 +135,19 @@ void combine(FramingParams& toEdit, const FramingParams& mod, const FramingParam } if (edits.borderRed) { - toEdit.borderRed = mod.borderRed; + toEdit.borderRed = dontForceSet && options.baBehav[ADDSET_FRAMING_BORDER_RED] ? + toEdit.borderRed + mod.borderRed : + mod.borderRed; } if (edits.borderGreen) { - toEdit.borderGreen = mod.borderGreen; + toEdit.borderGreen = dontForceSet && options.baBehav[ADDSET_FRAMING_BORDER_GREEN] ? + toEdit.borderGreen + mod.borderGreen : + mod.borderGreen; } if (edits.borderBlue) { - toEdit.borderBlue = mod.borderBlue; + toEdit.borderBlue = dontForceSet && options.baBehav[ADDSET_FRAMING_BORDER_BLUE] ? + toEdit.borderBlue + mod.borderBlue : + mod.borderBlue; } } @@ -6978,7 +6988,7 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.resize.allowUpscaling = mods.resize.allowUpscaling; } - ::combine(toEdit.framing, mods.framing, framing); + ::combine(toEdit.framing, mods.framing, framing, dontforceSet); if (icm.inputProfile) { toEdit.icm.inputProfile = mods.icm.inputProfile; diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index 6c9756051..d54d9fa27 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -364,6 +364,13 @@ Gtk::Widget* Preferences::getBatchProcPanel() mi->set_value(behavColumns.label, M("TP_RESIZE_LABEL")); appendBehavList(mi, M("TP_RESIZE_SCALE"), ADDSET_RESIZE_SCALE, true); + mi = behModel->append(); + mi->set_value(behavColumns.label, M("TP_FRAMING_LABEL")); + appendBehavList(mi, M("TP_FRAMING_BORDER_SIZE"), ADDSET_FRAMING_RELATIVE_SCALE, false); + appendBehavList(mi, M("TP_FRAMING_RED"), ADDSET_FRAMING_BORDER_RED, false); + appendBehavList(mi, M("TP_FRAMING_GREEN"), ADDSET_FRAMING_BORDER_GREEN, false); + appendBehavList(mi, M("TP_FRAMING_BLUE"), ADDSET_FRAMING_BORDER_BLUE, false); + mi = behModel->append(); mi->set_value(behavColumns.label, M("TP_LENSGEOM_SCALE")); appendBehavList(mi, M("TP_LENSGEOM_SCALE"), ADDSET_LENSGEOM_SCALE, true); diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index a02993ddc..90e4b00be 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -1123,12 +1123,12 @@ void ToolPanelCoordinator::panelChanged(const rtengine::ProcEvent& event, const crop->write(params); resize->update(params->crop.enabled, params->crop.w, params->crop.h, ipc->getFullWidth(), ipc->getFullHeight()); resize->write(params); - framing->update(params->crop.enabled, params->crop.w, params->crop.h, ipc->getFullWidth(), ipc->getFullHeight()); + framing->update(ipc->getFullWidth(), ipc->getFullHeight()); framing->write(params); } else if (event == rtengine::EvCrop) { resize->update(params->crop.enabled, params->crop.w, params->crop.h); resize->write(params); - framing->update(params->crop.enabled, params->crop.w, params->crop.h, ipc->getFullWidth(), ipc->getFullHeight()); + framing->update(ipc->getFullWidth(), ipc->getFullHeight()); framing->write(params); } From 4358c2d7a62bd96d061e4f3bbcaced6d3f5a8943 Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Thu, 14 Nov 2024 19:30:00 -0500 Subject: [PATCH 13/21] Fix computation errors in framing tool * Compute image aspect ratio if required instead of leaving at 0 * Fix divide by zero error in computing framed size * Compute resize scale based on original crop * Reword "Current" aspect ratio to "As Image" --- rtengine/ipresize.cc | 53 ++++++++++++++++++-------------------------- rtgui/framing.cc | 2 +- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/rtengine/ipresize.cc b/rtengine/ipresize.cc index 16adff2ca..60a776637 100644 --- a/rtengine/ipresize.cc +++ b/rtengine/ipresize.cc @@ -277,6 +277,10 @@ Dimensions upscaleToBBox(const Dimensions& img, const Dimensions& bbox) { double orientAspectRatio(const FramingParams& framing, const Dimensions& imgSize) { double aspectRatio = framing.aspectRatio; + if (aspectRatio == FramingParams::AS_IMAGE_ASPECT_RATIO) { + aspectRatio = imgSize.aspectRatio(); + } + Orientation borderOrient = orient(framing, imgSize); if ((borderOrient == Orientation::PORTRAIT && aspectRatio > 1.0) || (borderOrient == Orientation::LANDSCAPE && aspectRatio < 1.0)) { @@ -407,12 +411,13 @@ Dimensions Framing::computeRelativeImageBBoxInFrame(const Dimensions& imgSize, // = frame_len * scale / (1 + 2 * scale) double borderBasis = frameBasis * scale / imgFrameScale; - // Compute image and border lengths for the non-basis side. + // Compute image and border lengths for the non-basis side double imgBasisToOther = side == Side::WIDTH ? 1.0 / imgAspectRatio : imgAspectRatio; double borderBasisToOther = side == Side::WIDTH ? 1.0 / borderAspectRatio : borderAspectRatio; double imgOther = imgBasis * imgBasisToOther; double borderOther = borderBasis * borderBasisToOther; + // Find the maximum allowed image size considering min size limits double maxImageBasis = frameBasis; double maxImageOther = frameOther; if (framing.minSizeEnabled) { @@ -429,6 +434,12 @@ Dimensions Framing::computeRelativeImageBBoxInFrame(const Dimensions& imgSize, } } + // Image is too large to satisfy requirements: + // a. Min border size limit not satisfied + // b. Basis size is too small for the requested aspect ratio + // (i.e. original image clipped) + // + // Resize the image so that it fits in bounds if (imgOther > maxImageOther) { imgOther = maxImageOther; imgBasis = imgOther / imgBasisToOther; @@ -550,46 +561,24 @@ Dimensions Framing::computeFramedSize(const Dimensions& imgSize) const Dimensions Framing::computeSizeWithBorders(const Dimensions& imgSize) const { - auto length = [](double img, double border) { - return img + 2.0 * border; - }; - auto evalBorder = [](double side, double scale, double img) { - double otherSide = side * scale; - double totalBorder = otherSide - img; - return std::max(totalBorder, 0.0) * 0.5; - }; - if (framing.borderSizingMethod == BorderSizing::FIXED_SIZE) { - return Dimensions(length(imgSize.width, framing.absWidth), - length(imgSize.height, framing.absHeight)); + return Dimensions(imgSize.width + 2.0 * framing.absWidth, + imgSize.height + 2.0 * framing.absHeight); } Side side = pickReferenceSide(framing, imgSize); double aspectRatio = orientAspectRatio(framing, imgSize); - - // Compute the size with borders given the requested reference side length - // and aspect ratio - Dimensions borderSize; double scale = framing.relativeBorderSize; + + Dimensions framedSize; if (side == Side::WIDTH) { - borderSize.width = imgSize.width * scale; - double totalWidth = length(imgSize.width, borderSize.width); - borderSize.height = evalBorder(totalWidth, 1.0 / aspectRatio, imgSize.height); + framedSize.width = (1.0 + 2.0 * scale) * imgSize.width; + framedSize.height = framedSize.width / aspectRatio; } else { - borderSize.height = imgSize.height * scale; - double totalHeight = length(imgSize.height, borderSize.height); - borderSize.width = evalBorder(totalHeight, aspectRatio, imgSize.width); + framedSize.height = (1.0 + 2.0 * scale) * imgSize.height; + framedSize.width = framedSize.height * aspectRatio; } - if (framing.minSizeEnabled) { - Dimensions minSize(static_cast(framing.minWidth), - static_cast(framing.minHeight)); - borderSize = clampToBBox(borderSize, minSize, OUTSIDE_BBOX); - } - - Dimensions framedSize(length(imgSize.width, borderSize.width), - length(imgSize.height, borderSize.height)); - // Check if the computed frame size satsifies the requested aspect ratio // without cutting off the original image. If the image is cut off, use // the smallest frame that preserves the original image and still @@ -1059,7 +1048,7 @@ ImProcFunctions::FramingData ImProcFunctions::framing(const FramingArgs& args) c result.enabled = true; result.imgWidth = std::round(adjusted.size.width); result.imgHeight = std::round(adjusted.size.height); - result.scale = adjusted.scale; + result.scale = result.scale * adjusted.scale; result.framedWidth = std::round(framedSize.width); result.framedHeight = std::round(framedSize.height); diff --git a/rtgui/framing.cc b/rtgui/framing.cc index f21e8ed1d..d277f6eca 100644 --- a/rtgui/framing.cc +++ b/rtgui/framing.cc @@ -243,7 +243,7 @@ public: static constexpr int INDEX_CURRENT = 0; AspectRatios() : - ratios{{M("GENERAL_CURRENT")}} + ratios{{M("GENERAL_ASIMAGE")}} { fillAspectRatios(ratios); } From 2458ba4a294a50bf372c5d294484eba9c90a6801 Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Mon, 18 Nov 2024 10:33:32 -0500 Subject: [PATCH 14/21] Fix missing FramingParams equality check in ProcParams --- rtengine/procparams.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 4dad02ed8..88a94a830 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -11685,6 +11685,7 @@ bool ProcParams::operator ==(const ProcParams& other) const && chmixer == other.chmixer && blackwhite == other.blackwhite && resize == other.resize + && framing == other.framing && spot == other.spot && raw == other.raw && icm == other.icm From 57ef07f3f63fa458f3f8096739ca4b1f7911bdd7 Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Mon, 18 Nov 2024 12:14:19 -0500 Subject: [PATCH 15/21] Improve framing tool usability * Rename "Resize" to "Resize & Framing" from discoverability * Add ColorPreview class for displaying a solid color * Add border color preview --- rtdata/languages/default | 2 +- rtengine/ipresize.cc | 4 ++- rtgui/CMakeLists.txt | 1 + rtgui/colorpreview.cc | 69 ++++++++++++++++++++++++++++++++++++++++ rtgui/colorpreview.h | 53 ++++++++++++++++++++++++++++++ rtgui/framing.cc | 37 ++++++++++++++++++--- rtgui/framing.h | 6 +++- 7 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 rtgui/colorpreview.cc create mode 100644 rtgui/colorpreview.h diff --git a/rtdata/languages/default b/rtdata/languages/default index 525195a53..6adf5601f 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -3956,7 +3956,7 @@ TP_RESIZE_FITBOX;Bounding Box TP_RESIZE_FULLIMAGE;Full Image TP_RESIZE_H;Height: TP_RESIZE_HEIGHT;Height -TP_RESIZE_LABEL;Resize +TP_RESIZE_LABEL;Resize & Framing TP_RESIZE_LANCZOS;Lanczos TP_RESIZE_LE;Long Edge: TP_RESIZE_LONG;Long Edge diff --git a/rtengine/ipresize.cc b/rtengine/ipresize.cc index 60a776637..043c302ec 100644 --- a/rtengine/ipresize.cc +++ b/rtengine/ipresize.cc @@ -1069,7 +1069,9 @@ Imagefloat* ImProcFunctions::drawFrame(Imagefloat* rgb, const FramingParams& par Imagefloat* framed = new Imagefloat(dims.framedWidth, dims.framedHeight); - auto clip = [](int v) { return std::max(0, std::min(v, 65535)); }; + // Color::gamma2curve expects a 16-bit value, but the GUI sliders are + // using 8-bit values. Step up the user value to 16-bits. + auto clip = [](int v) { return std::max(0, std::min(v, 255)) * 256; }; float r = Color::gamma2curve[clip(params.borderRed)]; float g = Color::gamma2curve[clip(params.borderGreen)]; diff --git a/rtgui/CMakeLists.txt b/rtgui/CMakeLists.txt index 289992653..5d3276ccd 100644 --- a/rtgui/CMakeLists.txt +++ b/rtgui/CMakeLists.txt @@ -35,6 +35,7 @@ set(NONCLISOURCEFILES colorappearance.cc coloredbar.cc colortoning.cc + colorpreview.cc controllines.cc controlspotpanel.cc coordinateadjuster.cc diff --git a/rtgui/colorpreview.cc b/rtgui/colorpreview.cc new file mode 100644 index 000000000..97fd53252 --- /dev/null +++ b/rtgui/colorpreview.cc @@ -0,0 +1,69 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2004-2010 Gabor Horvath + * + * 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 . + * + * 2024-2024 Daniel Gao + */ + +#include "colorpreview.h" + +#include "rtscalable.h" + +ColorPreview::ColorPreview() : color_red(1.0), color_green(1.0), color_blue(1.0) +{ +} + +void ColorPreview::setRgb(double r, double g, double b) +{ + color_red = r; + color_green = g; + color_blue = b; + + queue_draw(); +} + +bool ColorPreview::on_draw(const Cairo::RefPtr& cr) +{ + cr->set_source_rgb(color_red, color_green, color_blue); + cr->paint(); + + return true; +} + +void ColorPreview::get_preferred_height_vfunc(int& minimum_height, int& natural_height) const +{ + minimum_height = RTScalable::scalePixelSize(10); + natural_height = RTScalable::scalePixelSize(100); +} + +void ColorPreview::get_preferred_width_vfunc(int& minimum_width, int& natural_width) const +{ + minimum_width = RTScalable::scalePixelSize(10); + natural_width = RTScalable::scalePixelSize(100); +} + +void ColorPreview::get_preferred_height_for_width_vfunc(int width, int& minimum_height, + int& natural_height) const +{ + get_preferred_height_vfunc(minimum_height, natural_height); +} + +void ColorPreview::get_preferred_width_for_height_vfunc(int height, int& minimum_width, + int& natural_width) const +{ + get_preferred_width_vfunc(minimum_width, natural_width); +} diff --git a/rtgui/colorpreview.h b/rtgui/colorpreview.h new file mode 100644 index 000000000..755d5fae1 --- /dev/null +++ b/rtgui/colorpreview.h @@ -0,0 +1,53 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2004-2010 Gabor Horvath + * + * 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 . + * + * 2024-2024 Daniel Gao + */ + +#pragma once + +#include + +/** + * This widget displays a singular color as its contents. + */ +class ColorPreview : public Gtk::DrawingArea +{ +public: + ColorPreview(); + + // Values between 0.0 and 1.0 as in + // Cairo::Context::set_source_rgb() + void setRgb(double r, double g, double b); + + // Gtk::DrawingArea + bool on_draw(const Cairo::RefPtr& cr) override; + + // Gtk::Widget + void get_preferred_height_vfunc(int& minimum_height, int& natural_height) const override; + void get_preferred_width_vfunc(int& minimum_width, int& natural_width) const override; + void get_preferred_height_for_width_vfunc(int width, int& minimum_height, + int& natural_height) const override; + void get_preferred_width_for_height_vfunc(int height, int & minimum_width, + int& natural_width) const override; + +private: + double color_red; + double color_green; + double color_blue; +}; diff --git a/rtgui/framing.cc b/rtgui/framing.cc index d277f6eca..62587a518 100644 --- a/rtgui/framing.cc +++ b/rtgui/framing.cc @@ -15,16 +15,18 @@ * * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . - * + * * 2024-2024 Daniel Gao */ #include "framing.h" #include "aspectratios.h" +#include "colorpreview.h" #include "paramsedited.h" #include "resize.h" +#include "../rtengine/color.h" #include "../rtengine/procparams.h" #include @@ -211,6 +213,7 @@ FramingParams::Basis mapBasis(int comboIndex) constexpr int INITIAL_IMG_WIDTH = 100000; constexpr int INITIAL_IMG_HEIGHT = 100000; +constexpr int MAX_COLOR_VAL = 255; constexpr int ROW_SPACING = 4; constexpr float FRAME_LABEL_ALIGN_X = 0.025; @@ -457,16 +460,23 @@ void Framing::setupBorderColorsGui() frame->set_label_widget(*label); Gtk::Box* const box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); - redAdj = Gtk::manage(new Adjuster(M("TP_FRAMING_RED"), 0, 65535, 1, 65535)); + redAdj = Gtk::manage(new Adjuster(M("TP_FRAMING_RED"), 0, MAX_COLOR_VAL, 1, MAX_COLOR_VAL)); box->add(*redAdj); - greenAdj = Gtk::manage(new Adjuster(M("TP_FRAMING_GREEN"), 0, 65535, 1, 65535)); + greenAdj = Gtk::manage(new Adjuster(M("TP_FRAMING_GREEN"), 0, MAX_COLOR_VAL, 1, MAX_COLOR_VAL)); box->add(*greenAdj); - blueAdj = Gtk::manage(new Adjuster(M("TP_FRAMING_BLUE"), 0, 65535, 1, 65535)); + blueAdj = Gtk::manage(new Adjuster(M("TP_FRAMING_BLUE"), 0, MAX_COLOR_VAL, 1, MAX_COLOR_VAL)); box->add(*blueAdj); + Gtk::Frame* const colorFrame = Gtk::manage(new Gtk::Frame()); + colorPreview = Gtk::manage(new ColorPreview()); + colorFrame->add(*colorPreview); + box->add(*colorFrame); + frame->add(*box); pack_start(*frame); + updateBorderColorGui(); + redAdj->setAdjusterListener(this); greenAdj->setAdjusterListener(this); blueAdj->setAdjusterListener(this); @@ -502,6 +512,7 @@ void Framing::read(const rtengine::procparams::ProcParams* pp, const ParamsEdite updateFramingMethodGui(); updateBorderSizeGui(); + updateBorderColorGui(); setDimensions(); } @@ -827,8 +838,26 @@ void Framing::updateBorderSizeGui() minSizeFrameContent->set_sensitive(minSizeEnabled->get_active()); } +void Framing::updateBorderColorGui() +{ + auto gamma = [](double val) { + // adjuster is [0.0, 255.0] + // gamma2curve expects [0, 65535] + // setRgb expects [0.0, 1.0] + return Color::gamma2curve[val * (MAX_COLOR_VAL + 1)] / 65535.0; + }; + double r = gamma(redAdj->getValue()); + double g = gamma(greenAdj->getValue()); + double b = gamma(blueAdj->getValue()); + colorPreview->setRgb(r, g, b); +} + void Framing::adjusterChanged(Adjuster* adj, double newVal) { + if (adj == redAdj || adj == greenAdj || adj == blueAdj) { + updateBorderColorGui(); + } + if (listener && (getEnabled() || batchMode)) { Glib::ustring costr; if (adj == relativeBorderSize) { diff --git a/rtgui/framing.h b/rtgui/framing.h index 93ca6b65f..0d709c446 100644 --- a/rtgui/framing.h +++ b/rtgui/framing.h @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . - * + * * 2024-2024 Daniel Gao */ @@ -27,6 +27,8 @@ #include +class ColorPreview; + class Framing final : public ToolParamBlock, public AdjusterListener, @@ -106,6 +108,7 @@ private: void setDimensions(); void updateFramingMethodGui(); void updateBorderSizeGui(); + void updateBorderColorGui(); // Framing method MyComboBoxText* framingMethod; @@ -141,6 +144,7 @@ private: Adjuster* redAdj; Adjuster* greenAdj; Adjuster* blueAdj; + ColorPreview* colorPreview; IdleRegister idleRegister; std::unique_ptr aspectRatioData; From bbb3efd81dd2338bd0791ccfcacaf6187ce1a6ab Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Mon, 18 Nov 2024 13:29:28 -0500 Subject: [PATCH 16/21] Fix resize scale adjustment with framing --- rtengine/ipresize.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtengine/ipresize.cc b/rtengine/ipresize.cc index 043c302ec..26d906ed7 100644 --- a/rtengine/ipresize.cc +++ b/rtengine/ipresize.cc @@ -368,7 +368,7 @@ Dimensions Framing::clampResize(const Dimensions& imgSize, const Dimensions& bou ResizeArgs Framing::adjustResize(const ResizeArgs& resize, const Dimensions& bbox) const { Dimensions newSize = clampResize(resize.size, bbox); - double newScale = newSize.width / resize.size.width; + double newScale = newSize.width / postCropImageSize.width; return ResizeArgs(newSize, newScale); } @@ -1048,7 +1048,7 @@ ImProcFunctions::FramingData ImProcFunctions::framing(const FramingArgs& args) c result.enabled = true; result.imgWidth = std::round(adjusted.size.width); result.imgHeight = std::round(adjusted.size.height); - result.scale = result.scale * adjusted.scale; + result.scale = adjusted.scale; result.framedWidth = std::round(framedSize.width); result.framedHeight = std::round(framedSize.height); From 45bde1c9996346d67cee1d37173b1d57fa833dce Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Mon, 18 Nov 2024 15:24:15 -0500 Subject: [PATCH 17/21] Add uniform relative sizing method to framing tool --- rtdata/languages/default | 1 + rtengine/ipresize.cc | 57 +++++++++++++++++++++++++++++++++++++++- rtengine/procparams.cc | 3 +++ rtengine/procparams.h | 5 ++-- rtgui/framing.cc | 48 ++++++++++++++++++++++++++++----- 5 files changed, 104 insertions(+), 10 deletions(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index 6adf5601f..03e08be06 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -2744,6 +2744,7 @@ TP_FRAMING_BORDER_SIZE;Size TP_FRAMING_BORDER_SIZE_ABSOLUTE;Absolute TP_FRAMING_BORDER_SIZE_METHOD;Sizing: TP_FRAMING_BORDER_SIZE_RELATIVE;Relative +TP_FRAMING_BORDER_SIZE_UNIFORM_RELATIVE;Uniform Relative TP_FRAMING_FRAMED_HEIGHT;Framed Height TP_FRAMING_FRAMED_WIDTH;Framed Width TP_FRAMING_GREEN;Green diff --git a/rtengine/ipresize.cc b/rtengine/ipresize.cc index 26d906ed7..37fa6f32a 100644 --- a/rtengine/ipresize.cc +++ b/rtengine/ipresize.cc @@ -108,6 +108,8 @@ private: ResizeArgs adjustResize(const ResizeArgs& resize, const Dimensions& newSize) const; Dimensions computeRelativeImageBBoxInFrame(const Dimensions& imgSize, const Dimensions& framedSize) const; + Dimensions computeUniformRelativeImageBBox(const Dimensions& imgSize, + const Dimensions& framedSize) const; ResizeArgs resizeForFixedFrame(const ResizeArgs& resize) const; ResizeArgs resizeForBBox(const ResizeArgs& resize) const; Dimensions computeSizeWithBorders(const Dimensions& imgSize) const; @@ -456,6 +458,37 @@ Dimensions Framing::computeRelativeImageBBoxInFrame(const Dimensions& imgSize, } } +Dimensions Framing::computeUniformRelativeImageBBox(const Dimensions& imgSize, + const Dimensions& framedSize) const +{ + auto length = [](double frame, double border) { + return std::max(0.0, frame - 2.0 * border); + }; + + Side side = pickReferenceSide(framing, imgSize); + double scale = framing.relativeBorderSize; + + double minBorderWidth = 0; + double minBorderHeight = 0; + if (side == Side::WIDTH) { + minBorderWidth = scale * framedSize.width; + if (framing.minSizeEnabled && minBorderWidth < framing.minWidth) { + minBorderWidth = framing.minWidth; + } + } else { + minBorderHeight = scale * framedSize.height; + if (framing.minSizeEnabled && minBorderHeight < framing.minHeight) { + minBorderHeight = framing.minHeight; + } + } + + Dimensions bbox = { + length(framedSize.width, minBorderWidth), + length(framedSize.height, minBorderHeight) + }; + return bbox; +} + ResizeArgs Framing::adjustResizeForFraming(const ResizeArgs& resize) const { if (!framing.enabled) return resize; @@ -488,6 +521,8 @@ ResizeArgs Framing::resizeForFixedFrame(const ResizeArgs& args) const length(framedWidth, framing.absWidth), length(framedHeight, framing.absHeight) }; + } else if (framing.borderSizingMethod == BorderSizing::UNIFORM_PERCENTAGE) { + bbox = computeUniformRelativeImageBBox(args.size, frameSize); } else { bbox = computeRelativeImageBBoxInFrame(args.size, frameSize); } @@ -509,6 +544,8 @@ ResizeArgs Framing::resizeForBBox(const ResizeArgs& args) const length(boundary.width, framing.absWidth), length(boundary.height, framing.absHeight) }; + } else if (framing.borderSizingMethod == BorderSizing::UNIFORM_PERCENTAGE) { + bbox = computeUniformRelativeImageBBox(args.size, boundary); } else { // For the requested aspect ratio, it must fit inside the requested // bounding box @@ -567,9 +604,27 @@ Dimensions Framing::computeSizeWithBorders(const Dimensions& imgSize) const } Side side = pickReferenceSide(framing, imgSize); - double aspectRatio = orientAspectRatio(framing, imgSize); double scale = framing.relativeBorderSize; + if (framing.borderSizingMethod == BorderSizing::UNIFORM_PERCENTAGE) { + double borderSize = 0; + if (side == Side::WIDTH) { + borderSize = scale * imgSize.width; + if (framing.minSizeEnabled && borderSize < framing.minWidth) { + borderSize = framing.minWidth; + } + } else { + borderSize = scale * imgSize.width; + if (framing.minSizeEnabled && borderSize < framing.minHeight) { + borderSize = framing.minHeight; + } + } + + return Dimensions(imgSize.width + 2.0 * borderSize, + imgSize.height + 2.0 * borderSize); + } + + double aspectRatio = orientAspectRatio(framing, imgSize); Dimensions framedSize; if (side == Side::WIDTH) { framedSize.width = (1.0 + 2.0 * scale) * imgSize.width; diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 88a94a830..677418816 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -467,6 +467,7 @@ namespace Framing DEFINE_KEY(ORIENT_LANDSCAPE, "Landscape"); DEFINE_KEY(ORIENT_PORTRAIT, "Portait"); DEFINE_KEY(BORDER_SIZING_PERCENTAGE, "Percentage"); + DEFINE_KEY(BORDER_SIZING_UNIFORM_PERCENTAGE, "UniformPercentage"); DEFINE_KEY(BORDER_SIZING_FIXED_SIZE, "FixedSize"); DEFINE_KEY(BASIS_AUTO, "Auto"); DEFINE_KEY(BASIS_WIDTH, "Width"); @@ -514,6 +515,7 @@ void loadFramingParams( using BorderSizing = FramingParams::BorderSizing; const std::map borderSizingMapping = { {BORDER_SIZING_PERCENTAGE, BorderSizing::PERCENTAGE}, + {BORDER_SIZING_UNIFORM_PERCENTAGE, BorderSizing::UNIFORM_PERCENTAGE}, {BORDER_SIZING_FIXED_SIZE, BorderSizing::FIXED_SIZE} }; assignFromKeyfile(keyFile, group, BORDER_SIZING_METHOD, borderSizingMapping, params.borderSizingMethod, edited.borderSizingMethod); @@ -576,6 +578,7 @@ void saveFramingParams( using BorderSizing = FramingParams::BorderSizing; const std::map borderSizingMapping = { {BorderSizing::PERCENTAGE, BORDER_SIZING_PERCENTAGE}, + {BorderSizing::UNIFORM_PERCENTAGE, BORDER_SIZING_UNIFORM_PERCENTAGE}, {BorderSizing::FIXED_SIZE, BORDER_SIZING_FIXED_SIZE} }; saveToKeyfile(!pedited || edited.borderSizingMethod, group, BORDER_SIZING_METHOD, borderSizingMapping, params.borderSizingMethod, keyFile); diff --git a/rtengine/procparams.h b/rtengine/procparams.h index f61ca6505..94108e5fd 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -2001,8 +2001,9 @@ struct FramingParams { // How to size border? enum class BorderSizing { - PERCENTAGE, // Percentage of image size - FIXED_SIZE // Fixed pixel dimensions + PERCENTAGE, // Percentage of image size + UNIFORM_PERCENTAGE, // Percentage of image size (ignore aspect ratio) + FIXED_SIZE // Fixed pixel dimensions }; // Which dimension to use for percentage based border sizing? diff --git a/rtgui/framing.cc b/rtgui/framing.cc index 62587a518..3d04b265e 100644 --- a/rtgui/framing.cc +++ b/rtgui/framing.cc @@ -125,10 +125,12 @@ FramingParams::Orientation mapOrientation(int comboIndex) // Border sizing method combo box data constexpr int INDEX_SIZE_RELATIVE = 0; -constexpr int INDEX_SIZE_ABSOLUTE = 1; -constexpr int INDEX_SIZE_UNCHANGED = 2; -constexpr std::array BORDER_SIZE_METHODS = { +constexpr int INDEX_SIZE_UNIFORM_RELATIVE = 1; +constexpr int INDEX_SIZE_ABSOLUTE = 2; +constexpr int INDEX_SIZE_UNCHANGED = 3; +constexpr std::array BORDER_SIZE_METHODS = { "TP_FRAMING_BORDER_SIZE_RELATIVE", + "TP_FRAMING_BORDER_SIZE_UNIFORM_RELATIVE", "TP_FRAMING_BORDER_SIZE_ABSOLUTE" }; @@ -138,6 +140,8 @@ int mapBorderSizeMethod(FramingParams::BorderSizing sizing) switch (sizing) { case BorderSizing::PERCENTAGE: return INDEX_SIZE_RELATIVE; + case BorderSizing::UNIFORM_PERCENTAGE: + return INDEX_SIZE_UNIFORM_RELATIVE; case BorderSizing::FIXED_SIZE: return INDEX_SIZE_ABSOLUTE; default: @@ -151,6 +155,8 @@ FramingParams::BorderSizing mapBorderSizeMethod(int comboIndex) switch (comboIndex) { case INDEX_SIZE_RELATIVE: return BorderSizing::PERCENTAGE; + case INDEX_SIZE_UNIFORM_RELATIVE: + return BorderSizing::UNIFORM_PERCENTAGE; case INDEX_SIZE_ABSOLUTE: return BorderSizing::FIXED_SIZE; default: @@ -823,6 +829,16 @@ void Framing::updateBorderSizeGui() aspectRatio->set_sensitive(true); orientation->set_sensitive(true); + } else if (activeRow == INDEX_SIZE_UNIFORM_RELATIVE) { + basisLabel->show(); + basis->show(); + relativeBorderSize->show(); + minSizeFrame->show(); + absWidth.hide(); + absHeight.hide(); + + aspectRatio->set_sensitive(false); + orientation->set_sensitive(false); } else if (activeRow == INDEX_SIZE_ABSOLUTE) { basisLabel->hide(); basis->hide(); @@ -949,6 +965,12 @@ void Framing::onAllowUpscalingToggled() void Framing::onBorderSizeMethodChanged() { + if (borderSizeMethod->get_active_row_number() == INDEX_SIZE_UNIFORM_RELATIVE) { + ConnectionBlocker block(minHeight.connection); + minHeight.isDirty = true; + minHeight.value->set_value(minWidth.value->get_value_as_int()); + } + updateBorderSizeGui(); if (listener && (getEnabled() || batchMode)) { @@ -993,20 +1015,32 @@ void Framing::onMinSizeToggled() void Framing::onMinWidthChanged() { minWidth.isDirty = true; + int value = minWidth.value->get_value_as_int(); + + if (borderSizeMethod->get_active_row_number() == INDEX_SIZE_UNIFORM_RELATIVE) { + ConnectionBlocker block(minHeight.connection); + minHeight.isDirty = true; + minHeight.value->set_value(value); + } if (listener && (getEnabled() || batchMode)) { - listener->panelChanged(EvFramingMinWidth, - Glib::ustring::format(minWidth.value->get_value_as_int())); + listener->panelChanged(EvFramingMinWidth, Glib::ustring::format(value)); } } void Framing::onMinHeightChanged() { minHeight.isDirty = true; + int value = minHeight.value->get_value_as_int(); + + if (borderSizeMethod->get_active_row_number() == INDEX_SIZE_UNIFORM_RELATIVE) { + ConnectionBlocker block(minWidth.connection); + minWidth.isDirty = true; + minWidth.value->set_value(value); + } if (listener && (getEnabled() || batchMode)) { - listener->panelChanged(EvFramingMinHeight, - Glib::ustring::format(minHeight.value->get_value_as_int())); + listener->panelChanged(EvFramingMinHeight, Glib::ustring::format(value)); } } From f983da5d168cb96669004308bc973afec6dc7cbd Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Sun, 24 Nov 2024 19:56:17 -0500 Subject: [PATCH 18/21] Fix bugs in framing tool sizing * Rework uniform relative size calculations * Fix minimum size requirements not being respected * Fix typo in framing tool keylib value * Drive-by code formatting changes --- rtengine/ipresize.cc | 130 +++++++++++++++++++++++++++++------------ rtengine/procparams.cc | 2 +- 2 files changed, 95 insertions(+), 37 deletions(-) diff --git a/rtengine/ipresize.cc b/rtengine/ipresize.cc index 37fa6f32a..a1e53131d 100644 --- a/rtengine/ipresize.cc +++ b/rtengine/ipresize.cc @@ -47,7 +47,8 @@ using FramingMethod = FramingParams::FramingMethod; enum class Orientation { LANDSCAPE, PORTRAIT }; enum class Side { WIDTH, HEIGHT }; -struct Dimensions { +struct Dimensions +{ double width; double height; @@ -89,14 +90,16 @@ struct Dimensions { } }; -struct ResizeArgs { +struct ResizeArgs +{ Dimensions size; double scale = 1.0; ResizeArgs(const Dimensions& aSize, double aScale) : size(aSize), scale(aScale) {} }; -class Framing { +class Framing +{ public: Framing(const ProcParams& params, int fullWidth, int fullHeight); @@ -130,7 +133,18 @@ int computeSize(int dim, double scale) return static_cast(static_cast(dim) * scale + 0.5); } -Orientation orient(const FramingParams& params, const Dimensions& imgSize) { +std::pair computeImgAndBorderSize(double frameSize, double scale) +{ + // frame_len = img_len + 2 * scale * img_len = (1 + 2 * scale) * img_len + double imgFrameScale = (1.0 + 2.0 * scale); + double imgSize = frameSize / imgFrameScale; + double borderSize = scale * imgSize; + + return {imgSize, borderSize}; +} + +Orientation orient(const FramingParams& params, const Dimensions& imgSize) +{ switch (params.orientation) { case FramingParams::Orientation::LANDSCAPE: return Orientation::LANDSCAPE; @@ -198,7 +212,8 @@ Side pickReferenceSide(const FramingParams& params, const Dimensions& imgSize) constexpr bool INSIDE_BBOX = true; constexpr bool OUTSIDE_BBOX = false; -Dimensions clampToBBox(const Dimensions& img, const Dimensions& bbox, bool clampInside) { +Dimensions clampToBBox(const Dimensions& img, const Dimensions& bbox, bool clampInside) +{ double widthScale = 1.0; double heightScale = 1.0; if (bbox.width > 0) { @@ -239,7 +254,8 @@ Dimensions clampToBBox(const Dimensions& img, const Dimensions& bbox, bool clamp return newSize; } -Dimensions downscaleToTouchBBox(const Dimensions& img, const Dimensions& bbox) { +Dimensions downscaleToTouchBBox(const Dimensions& img, const Dimensions& bbox) +{ if (bbox.isDegenerate()) return Dimensions(0, 0); if (!bbox.inside(img)) return img; @@ -257,7 +273,8 @@ Dimensions downscaleToTouchBBox(const Dimensions& img, const Dimensions& bbox) { return downscaled; } -Dimensions upscaleToBBox(const Dimensions& img, const Dimensions& bbox) { +Dimensions upscaleToBBox(const Dimensions& img, const Dimensions& bbox) +{ if (bbox.isDegenerate()) return Dimensions(0, 0); if (!img.inside(bbox)) return img; @@ -405,13 +422,10 @@ Dimensions Framing::computeRelativeImageBBoxInFrame(const Dimensions& imgSize, // Compute image and border lengths on basis side double frameBasis = side == Side::WIDTH ? framedSize.width : framedSize.height; double frameOther = side == Side::WIDTH ? framedSize.height : framedSize.width; - // frame_len = img_len + 2 * scale * img_len = (1 + 2 * scale) * img_len - double imgFrameScale = (1.0 + 2.0 * scale); - double imgBasis = frameBasis / imgFrameScale; - // border_len = (scale * img_len) - // = frame_len / (1 / scale + 2) - // = frame_len * scale / (1 + 2 * scale) - double borderBasis = frameBasis * scale / imgFrameScale; + + auto computedSizes = computeImgAndBorderSize(frameBasis, scale); + double imgBasis = computedSizes.first; + double borderBasis = computedSizes.second; // Compute image and border lengths for the non-basis side double imgBasisToOther = side == Side::WIDTH ? 1.0 / imgAspectRatio : imgAspectRatio; @@ -461,32 +475,75 @@ Dimensions Framing::computeRelativeImageBBoxInFrame(const Dimensions& imgSize, Dimensions Framing::computeUniformRelativeImageBBox(const Dimensions& imgSize, const Dimensions& framedSize) const { - auto length = [](double frame, double border) { - return std::max(0.0, frame - 2.0 * border); - }; + if (imgSize.isDegenerate() || framedSize.isDegenerate()) { + return Dimensions(0, 0); + } Side side = pickReferenceSide(framing, imgSize); double scale = framing.relativeBorderSize; - double minBorderWidth = 0; - double minBorderHeight = 0; - if (side == Side::WIDTH) { - minBorderWidth = scale * framedSize.width; - if (framing.minSizeEnabled && minBorderWidth < framing.minWidth) { - minBorderWidth = framing.minWidth; - } - } else { - minBorderHeight = scale * framedSize.height; - if (framing.minSizeEnabled && minBorderHeight < framing.minHeight) { - minBorderHeight = framing.minHeight; + // Compute image and border lengths on basis side + double frameBasis = side == Side::WIDTH ? framedSize.width : framedSize.height; + double frameOther = side == Side::WIDTH ? framedSize.height : framedSize.width; + + auto computedSizes = computeImgAndBorderSize(frameBasis, scale); + double imgBasis = computedSizes.first; + double border = computedSizes.second; + + // Compute image and border lengths for the non-basis side + double imgAspectRatio = imgSize.aspectRatio(); + double imgBasisToOther = side == Side::WIDTH ? 1.0 / imgAspectRatio : imgAspectRatio; + double imgOther = imgBasis * imgBasisToOther; + + // If the frame doesn't constrain the non-basis side length, we just need + // to check the border minimum size. However, if the non-basis side is + // constrained, we need to adjust the image size to fit while still + // maintaining the border scale w.r.t. the basis side. + double totalOther = imgOther + 2.0 * border; + if (totalOther > frameOther) { + // Let: + // imgOther = imgBasis * imgBasisToOther + // border = imgBasis * scale + // + // Want: + // frameOther = imgOther + 2 * border + // = imgBasis * imgBasisToOther + 2 * scale * imgBasis + // = imgBasis * (imgBasisToOther + 2 * scale) + // + // Rearrange: + // imgBasis = frameOther / (imgBasisToOther + 2 * scale) + imgBasis = frameOther / (imgBasisToOther + 2.0 * scale); + imgOther = imgBasis * imgBasisToOther; + border = imgBasis * scale; + } + + // Find the maximum allowed image size considering min size limits + double maxImageBasis = frameBasis; + double maxImageOther = frameOther; + if (framing.minSizeEnabled) { + double minBorder = static_cast( + side == Side::WIDTH ? framing.minWidth : framing.minHeight); + + if (border < minBorder) { + maxImageBasis = std::floor(frameBasis - 2.0 * minBorder); + maxImageOther = std::floor(frameOther - 2.0 * minBorder); } } - Dimensions bbox = { - length(framedSize.width, minBorderWidth), - length(framedSize.height, minBorderHeight) - }; - return bbox; + if (imgOther > maxImageOther) { + imgOther = maxImageOther; + imgBasis = imgOther / imgBasisToOther; + } + if (imgBasis > maxImageBasis) { + imgBasis = maxImageBasis; + imgOther = imgBasis * imgBasisToOther; + } + + if (side == Side::WIDTH) { + return Dimensions(imgBasis, imgOther); + } else { + return Dimensions(imgOther, imgBasis); + } } ResizeArgs Framing::adjustResizeForFraming(const ResizeArgs& resize) const @@ -614,7 +671,7 @@ Dimensions Framing::computeSizeWithBorders(const Dimensions& imgSize) const borderSize = framing.minWidth; } } else { - borderSize = scale * imgSize.width; + borderSize = scale * imgSize.height; if (framing.minSizeEnabled && borderSize < framing.minHeight) { borderSize = framing.minHeight; } @@ -625,6 +682,7 @@ Dimensions Framing::computeSizeWithBorders(const Dimensions& imgSize) const } double aspectRatio = orientAspectRatio(framing, imgSize); + Dimensions framedSize; if (side == Side::WIDTH) { framedSize.width = (1.0 + 2.0 * scale) * imgSize.width; @@ -639,12 +697,12 @@ Dimensions Framing::computeSizeWithBorders(const Dimensions& imgSize) const // the smallest frame that preserves the original image and still // satisfies the requested aspect ratio. Dimensions minFramedSize = fromAspectRatio(imgSize, aspectRatio); + Dimensions limit = imgSize; if (framing.minSizeEnabled) { - Dimensions limit = imgSize; limit.width += 2.0 * framing.minWidth; limit.height += 2.0 * framing.minHeight; - minFramedSize = clampToBBox(minFramedSize, limit, OUTSIDE_BBOX); } + minFramedSize = clampToBBox(minFramedSize, limit, OUTSIDE_BBOX); if (minFramedSize.inside(framedSize)) { return framedSize; diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 677418816..1a32b1cc1 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -465,7 +465,7 @@ namespace Framing DEFINE_KEY(FRAMING_METHOD_FIXED_SIZE, "FixedSize"); DEFINE_KEY(ORIENT_AS_IMAGE, "AsImage"); DEFINE_KEY(ORIENT_LANDSCAPE, "Landscape"); - DEFINE_KEY(ORIENT_PORTRAIT, "Portait"); + DEFINE_KEY(ORIENT_PORTRAIT, "Portrait"); DEFINE_KEY(BORDER_SIZING_PERCENTAGE, "Percentage"); DEFINE_KEY(BORDER_SIZING_UNIFORM_PERCENTAGE, "UniformPercentage"); DEFINE_KEY(BORDER_SIZING_FIXED_SIZE, "FixedSize"); From d3962c7e56533280a12e1df4a7257f79896f94ec Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Sat, 30 Nov 2024 14:31:18 -0500 Subject: [PATCH 19/21] Fix framing tool issues * Print messages only in verbose mode * Linearize 8-bit values to 16-bit values properly * Fix memory leak --- rtengine/ipresize.cc | 8 +++++++- rtengine/simpleprocess.cc | 18 ++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/rtengine/ipresize.cc b/rtengine/ipresize.cc index a1e53131d..a49bd91c7 100644 --- a/rtengine/ipresize.cc +++ b/rtengine/ipresize.cc @@ -1184,7 +1184,12 @@ Imagefloat* ImProcFunctions::drawFrame(Imagefloat* rgb, const FramingParams& par // Color::gamma2curve expects a 16-bit value, but the GUI sliders are // using 8-bit values. Step up the user value to 16-bits. - auto clip = [](int v) { return std::max(0, std::min(v, 255)) * 256; }; + auto clip = [](int v) -> int { + int sanitized = std::max(0, std::min(v, 255)); + + double normalized = static_cast(sanitized) / 255.0; + return normalized * 65535.0; + }; float r = Color::gamma2curve[clip(params.borderRed)]; float g = Color::gamma2curve[clip(params.borderGreen)]; @@ -1223,6 +1228,7 @@ Imagefloat* ImProcFunctions::drawFrame(Imagefloat* rgb, const FramingParams& par } } + delete rgb; return framed; } diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index d5a94ea98..8493dd5c8 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -1923,14 +1923,16 @@ private: // If framing is not enabled, resize values simply pass through to output ImProcFunctions::FramingData framingData = ipf.framing(framingArgs); - printf("Framing Parameters (enabled=%s)\n", framingData.enabled ? "yes" : "no"); - printf(" Crop: w=%d h=%d\n", cw, ch); - printf(" Original resize: w=%d h=%d s=%f\n", - framingArgs.resizeWidth, framingArgs.resizeHeight, framingArgs.resizeScale); - printf(" Framed image size: w=%d h=%d s=%f\n", - framingData.imgWidth, framingData.imgHeight, framingData.scale); - printf(" Total size: w=%d h=%d\n", - framingData.framedWidth, framingData.framedHeight); + if (settings->verbose) { + printf("Framing Parameters (enabled=%s)\n", framingData.enabled ? "yes" : "no"); + printf(" Crop: w=%d h=%d\n", cw, ch); + printf(" Original resize: w=%d h=%d s=%f\n", + framingArgs.resizeWidth, framingArgs.resizeHeight, framingArgs.resizeScale); + printf(" Framed image size: w=%d h=%d s=%f\n", + framingData.imgWidth, framingData.imgHeight, framingData.scale); + printf(" Total size: w=%d h=%d\n", + framingData.framedWidth, framingData.framedHeight); + } bool labResize = params.resize.enabled && params.resize.method != "Nearest" && (framingData.scale != 1.0 || params.prsharpening.enabled || framingData.enabled); From a54aacca3f8f2ecd5a80702cefb8698097eac1d0 Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Sat, 30 Nov 2024 15:03:14 -0500 Subject: [PATCH 20/21] Switch framing tool events to use event mapper * Removed hard coded events from ProcEventCode and RefreshMap * Change HISTORY_MSG_* to HISTORY_MSG_FRAMING_* --- rtdata/languages/default | 36 ++++++++++++++++++------------------ rtengine/procevents.h | 19 ------------------- rtengine/refreshmap.cc | 20 +------------------- rtgui/framing.cc | 28 ++++++++++++++++++++++++++++ rtgui/framing.h | 23 +++++++++++++++++++++++ 5 files changed, 70 insertions(+), 56 deletions(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index 03e08be06..d47179bd9 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1380,24 +1380,6 @@ HISTORY_MSG_1147;Local - Jz BlackEv WhiteEv HISTORY_MSG_1148;Local - Jz Sigmoid HISTORY_MSG_1149;Local - Q Sigmoid HISTORY_MSG_1150;Local - Log encoding Q instead Sigmoid Q -HISTORY_MSG_1151;Framing -HISTORY_MSG_1152;Framing - Method -HISTORY_MSG_1153;Framing - Aspect Ratio -HISTORY_MSG_1154;Framing - Orientation -HISTORY_MSG_1155;Framing - Framed Width -HISTORY_MSG_1156;Framing - Framed Height -HISTORY_MSG_1157;Framing - Upscaling -HISTORY_MSG_1158;Framing - Border Sizing Method -HISTORY_MSG_1159;Framing - Basis -HISTORY_MSG_1160;Framing - Relative Size -HISTORY_MSG_1161;Framing - Min Size -HISTORY_MSG_1162;Framing - Min Width -HISTORY_MSG_1163;Framing - Min Height -HISTORY_MSG_1164;Framing - Border Width -HISTORY_MSG_1165;Framing - Border Height -HISTORY_MSG_1166;Framing - Border (R) -HISTORY_MSG_1167;Framing - Border (G) -HISTORY_MSG_1168;Framing - Border (B) HISTORY_MSG_BLSHAPE;Blur by level HISTORY_MSG_BLURCWAV;Blur chroma HISTORY_MSG_BLURWAV;Blur luminance @@ -1451,6 +1433,24 @@ HISTORY_MSG_FILMNEGATIVE_COLORSPACE;Film negative color space HISTORY_MSG_FILMNEGATIVE_ENABLED;Film Negative HISTORY_MSG_FILMNEGATIVE_REF_SPOT;FN - Reference input HISTORY_MSG_FILMNEGATIVE_VALUES;Film negative values +HISTORY_MSG_FRAMING_ABSOLUTE_HEIGHT;Framing - Border Height +HISTORY_MSG_FRAMING_ABSOLUTE_WIDTH;Framing - Border Width +HISTORY_MSG_FRAMING_ALLOW_UPSCALING;Framing - Upscaling +HISTORY_MSG_FRAMING_ASPECT_RATIO;Framing - Aspect Ratio +HISTORY_MSG_FRAMING_BASIS;Framing - Basis +HISTORY_MSG_FRAMING_BORDER_BLUE;Framing - Border (B) +HISTORY_MSG_FRAMING_BORDER_GREEN;Framing - Border (G) +HISTORY_MSG_FRAMING_BORDER_RED;Framing - Border (R) +HISTORY_MSG_FRAMING_BORDER_SIZE;Framing - Relative Size +HISTORY_MSG_FRAMING_BORDER_SIZE_METHOD;Framing - Border Sizing Method +HISTORY_MSG_FRAMING_ENABLED;Framing +HISTORY_MSG_FRAMING_FRAMED_HEIGHT;Framing - Framed Height +HISTORY_MSG_FRAMING_FRAMED_WIDTH;Framing - Framed Width +HISTORY_MSG_FRAMING_METHOD;Framing - Method +HISTORY_MSG_FRAMING_MIN_HEIGHT;Framing - Min Height +HISTORY_MSG_FRAMING_MIN_SIZE_ENABLED;Framing - Min Size +HISTORY_MSG_FRAMING_MIN_WIDTH;Framing - Min Width +HISTORY_MSG_FRAMING_ORIENTATION;Framing - Orientation HISTORY_MSG_GAMUTMUNSEL;Gamut-Munsell HISTORY_MSG_HISTMATCHING;Auto-matched tone curve HISTORY_MSG_HLBL;Color propagation - blur diff --git a/rtengine/procevents.h b/rtengine/procevents.h index c573de8c0..44b3bf6c1 100644 --- a/rtengine/procevents.h +++ b/rtengine/procevents.h @@ -1177,25 +1177,6 @@ enum ProcEventCode { Evlocallabsigjz = 1147, Evlocallabsigq = 1148, Evlocallablogcie = 1149, - EvFramingEnabled = 1150, - EvFramingMethod = 1151, - EvFramingAspectRatio = 1152, - EvFramingOrientation = 1153, - EvFramingFramedWidth = 1154, - EvFramingFramedHeight = 1155, - EvFramingAllowUpscaling = 1156, - EvFramingBorderSizingMethod = 1157, - EvFramingBasis = 1158, - EvFramingRelativeBorderSize = 1159, - EvFramingMinSizeEnabled = 1160, - EvFramingMinWidth = 1161, - EvFramingMinHeight = 1162, - EvFramingAbsWidth = 1163, - EvFramingAbsHeight = 1164, - EvFramingBorderRed = 1165, - EvFramingBorderGreen = 1166, - EvFramingBorderBlue = 1167, - NUMOFEVENTS }; diff --git a/rtengine/refreshmap.cc b/rtengine/refreshmap.cc index da5cbe3e0..935001c93 100644 --- a/rtengine/refreshmap.cc +++ b/rtengine/refreshmap.cc @@ -1185,25 +1185,7 @@ int refreshmap[rtengine::NUMOFEVENTS] = { AUTOEXP, //Evlocallabforcebw AUTOEXP, //Evlocallabsigjz AUTOEXP, //Evlocallabsigq - AUTOEXP, //Evlocallablogcie - RESIZE, // EvFramingEnabled - RESIZE, // EvFramingFramingMethod - RESIZE, // EvFramingAspectRatio - RESIZE, // EvFramingOrientation - RESIZE, // EvFramingFramedWidth - RESIZE, // EvFramingFramedHeight - RESIZE, // EvFramingAllowUpscaling - RESIZE, // EvFramingBorderSizingMethod - RESIZE, // EvFramingBasis - RESIZE, // EvFramingRelativeBorderSize - RESIZE, // EvFramingMinSizeEnabled - RESIZE, // EvFramingMinWidth - RESIZE, // EvFramingMinHeight - RESIZE, // EvFramingAbsWidth - RESIZE, // EvFramingAbsHeight - RESIZE, // EvFramingBorderRed - RESIZE, // EvFramingBorderGreen - RESIZE // EvFramingBorderBlue + AUTOEXP //Evlocallablogcie }; diff --git a/rtgui/framing.cc b/rtgui/framing.cc index 3d04b265e..20cf194bc 100644 --- a/rtgui/framing.cc +++ b/rtgui/framing.cc @@ -23,6 +23,7 @@ #include "aspectratios.h" #include "colorpreview.h" +#include "eventmapper.h" #include "paramsedited.h" #include "resize.h" @@ -312,6 +313,7 @@ Framing::Framing() : lastAllowUpscaling(false), lastMinSizeEnabled(false) { + setupEvents(); setupFramingMethodGui(); pack_start(*Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL))); setupBorderSizeGui(); @@ -323,6 +325,32 @@ Framing::~Framing() { idleRegister.destroy(); } +void Framing::setupEvents() +{ + auto m = ProcEventMapper::getInstance(); + + // clang-format off + EvFramingEnabled = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_ENABLED"); + EvFramingMethod = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_METHOD"); + EvFramingAspectRatio = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_ASPECT_RATIO"); + EvFramingOrientation = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_ORIENTATION"); + EvFramingFramedWidth = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_FRAMED_WIDTH"); + EvFramingFramedHeight = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_FRAMED_HEIGHT"); + EvFramingAllowUpscaling = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_ALLOW_UPSCALING"); + EvFramingBorderSizingMethod = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_BORDER_SIZE_METHOD"); + EvFramingBasis = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_BASIS"); + EvFramingRelativeBorderSize = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_BORDER_SIZE"); + EvFramingMinSizeEnabled = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_MIN_SIZE_ENABLED"); + EvFramingMinWidth = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_MIN_WIDTH"); + EvFramingMinHeight = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_MIN_HEIGHT"); + EvFramingAbsWidth = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_ABSOLUTE_WIDTH"); + EvFramingAbsHeight = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_ABSOLUTE_HEIGHT"); + EvFramingBorderRed = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_BORDER_RED"); + EvFramingBorderGreen = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_BORDER_GREEN"); + EvFramingBorderBlue = m->newEvent(RESIZE, "HISTORY_MSG_FRAMING_BORDER_BLUE"); + // clang-format on +} + void Framing::setupFramingMethodGui() { Gtk::Grid* combos = Gtk::manage(new Gtk::Grid()); diff --git a/rtgui/framing.h b/rtgui/framing.h index 0d709c446..5b186a076 100644 --- a/rtgui/framing.h +++ b/rtgui/framing.h @@ -25,6 +25,8 @@ #include "guiutils.h" #include "toolpanel.h" +#include "../rtengine/procevents.h" + #include class ColorPreview; @@ -96,6 +98,7 @@ private: bool isDirty; }; + void setupEvents(); void setupFramingMethodGui(); void setupBorderSizeGui(); void setupBorderColorsGui(); @@ -146,6 +149,26 @@ private: Adjuster* blueAdj; ColorPreview* colorPreview; + // Events + rtengine::ProcEvent EvFramingEnabled; + rtengine::ProcEvent EvFramingMethod; + rtengine::ProcEvent EvFramingAspectRatio; + rtengine::ProcEvent EvFramingOrientation; + rtengine::ProcEvent EvFramingFramedWidth; + rtengine::ProcEvent EvFramingFramedHeight; + rtengine::ProcEvent EvFramingAllowUpscaling; + rtengine::ProcEvent EvFramingBorderSizingMethod; + rtengine::ProcEvent EvFramingBasis; + rtengine::ProcEvent EvFramingRelativeBorderSize; + rtengine::ProcEvent EvFramingMinSizeEnabled; + rtengine::ProcEvent EvFramingMinWidth; + rtengine::ProcEvent EvFramingMinHeight; + rtengine::ProcEvent EvFramingAbsWidth; + rtengine::ProcEvent EvFramingAbsHeight; + rtengine::ProcEvent EvFramingBorderRed; + rtengine::ProcEvent EvFramingBorderGreen; + rtengine::ProcEvent EvFramingBorderBlue; + IdleRegister idleRegister; std::unique_ptr aspectRatioData; From f8cde10662b840f6a4b6c1c3cf906dec37d5ac2d Mon Sep 17 00:00:00 2001 From: Daniel Gao Date: Fri, 6 Dec 2024 22:16:27 -0500 Subject: [PATCH 21/21] Fix missing min frame border * Previously only considered min border if the current border is smaller * Need to consider as long as min size is enabled --- rtengine/ipresize.cc | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/rtengine/ipresize.cc b/rtengine/ipresize.cc index a49bd91c7..f5d310b6a 100644 --- a/rtengine/ipresize.cc +++ b/rtengine/ipresize.cc @@ -442,12 +442,8 @@ Dimensions Framing::computeRelativeImageBBoxInFrame(const Dimensions& imgSize, double minBorderOther = static_cast( side == Side::WIDTH ? framing.minHeight : framing.minWidth); - if (borderOther < minBorderOther) { - maxImageOther = std::floor(frameOther - 2.0 * minBorderOther); - } - if (borderBasis < minBorderBasis) { - maxImageBasis = std::floor(frameBasis - 2.0 * minBorderBasis); - } + maxImageOther = std::floor(frameOther - 2.0 * minBorderOther); + maxImageBasis = std::floor(frameBasis - 2.0 * minBorderBasis); } // Image is too large to satisfy requirements: @@ -524,10 +520,8 @@ Dimensions Framing::computeUniformRelativeImageBBox(const Dimensions& imgSize, double minBorder = static_cast( side == Side::WIDTH ? framing.minWidth : framing.minHeight); - if (border < minBorder) { - maxImageBasis = std::floor(frameBasis - 2.0 * minBorder); - maxImageOther = std::floor(frameOther - 2.0 * minBorder); - } + maxImageBasis = std::floor(frameBasis - 2.0 * minBorder); + maxImageOther = std::floor(frameOther - 2.0 * minBorder); } if (imgOther > maxImageOther) {