/* * 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 . */ #include #include "crop.h" #include "options.h" #include "rtimage.h" #include "../rtengine/procparams.h" #include "../rtengine/utils.h" using namespace rtengine; using namespace rtengine::procparams; const Glib::ustring Crop::TOOL_NAME = "crop"; namespace { inline void get_custom_ratio(int w, int h, double &rw, double &rh) { if (w < h) { double r = double(h) / double(w); int rr = r * 100 + 0.5; rw = 1.0; rh = rr / 100.0; } else { double r = double(w) / double(h); int rr = r * 100 + 0.5; rw = rr / 100.0; rh = 1.0; } } } // namespace class Crop::CropRatios final { 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... } { } std::vector getLabels() const { std::vector res; res.reserve(ratios.size()); for (const auto& ratio : ratios) { res.push_back(ratio.label); } return res; } double getValue(std::size_t index) const { return index < ratios.size() ? ratios[index].value : ratios[0].value; } void updateCurrentRatio(double value) { ratios[1].value = value; } private: struct CropRatio { Glib::ustring label; double value; }; std::vector ratios; }; Crop::Crop(): FoldableToolPanel(this, TOOL_NAME, M("TP_CROP_LABEL"), false, true), crop_ratios(new CropRatios), opt(0), wDirty(true), hDirty(true), xDirty(true), yDirty(true), lastFixRatio(true) { clistener = nullptr; maxw = 3000; maxh = 2000; methodgrid = Gtk::manage(new Gtk::Grid()); methodgrid->get_style_context()->add_class("grid-spacing"); setExpandAlignProperties(methodgrid, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); Gtk::Label* xlab = Gtk::manage (new Gtk::Label (M("TP_CROP_X") + ":")); setExpandAlignProperties(xlab, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); x = Gtk::manage (new MySpinButton ()); setExpandAlignProperties(x, true, false, Gtk::ALIGN_END, Gtk::ALIGN_CENTER); x->set_width_chars(6); Gtk::Label* ylab = Gtk::manage (new Gtk::Label (M("TP_CROP_Y") + ":")); setExpandAlignProperties(ylab, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); y = Gtk::manage (new MySpinButton ()); setExpandAlignProperties(y, true, false, Gtk::ALIGN_END, Gtk::ALIGN_CENTER); y->set_width_chars(6); Gtk::Label* wlab = Gtk::manage (new Gtk::Label (M("TP_CROP_W") + ":")); setExpandAlignProperties(wlab, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); w = Gtk::manage (new MySpinButton ()); setExpandAlignProperties(w, true, false, Gtk::ALIGN_END, Gtk::ALIGN_CENTER); w->set_width_chars(6); Gtk::Label* hlab = Gtk::manage (new Gtk::Label (M("TP_CROP_H") + ":")); setExpandAlignProperties(hlab, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); h = Gtk::manage (new MySpinButton ()); setExpandAlignProperties(h, true, false, Gtk::ALIGN_END, Gtk::ALIGN_CENTER); h->set_width_chars(6); selectCrop = Gtk::manage (new Gtk::Button (M("TP_CROP_SELECTCROP"))); setExpandAlignProperties(selectCrop, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); selectCrop->get_style_context()->add_class("independent"); selectCrop->set_image (*Gtk::manage (new RTImage ("crop-small.png"))); resetCrop = Gtk::manage (new Gtk::Button (M("TP_CROP_RESETCROP"))); setExpandAlignProperties(resetCrop, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); resetCrop->get_style_context()->add_class("independent"); resetCrop->set_image (*Gtk::manage (new RTImage ("undo-small.png"))); methodgrid->attach (*xlab, 0, 0, 1, 1); methodgrid->attach (*x, 1, 0, 1, 1); methodgrid->attach (*ylab, 2, 0, 1, 1); methodgrid->attach (*y, 3, 0, 1, 1); methodgrid->attach (*wlab, 0, 1, 1, 1); methodgrid->attach (*w, 1, 1, 1, 1); methodgrid->attach (*hlab, 2, 1, 1, 1); methodgrid->attach (*h, 3, 1, 1, 1); methodgrid->attach (*selectCrop, 0, 2, 2, 1); methodgrid->attach (*resetCrop, 2, 2, 2, 1); pack_start (*methodgrid, Gtk::PACK_EXPAND_WIDGET, 0 ); Gtk::Separator* methodseparator = Gtk::manage (new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL)); methodseparator->get_style_context()->add_class("grid-row-separator"); pack_start (*methodseparator, Gtk::PACK_SHRINK, 0); Gtk::Grid* settingsgrid = Gtk::manage(new Gtk::Grid()); settingsgrid->get_style_context()->add_class("grid-spacing"); setExpandAlignProperties(settingsgrid, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); fixr = Gtk::manage (new Gtk::CheckButton (M("TP_CROP_FIXRATIO"))); setExpandAlignProperties(fixr, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); fixr->set_active (1); Gtk::Grid* ratiogrid = Gtk::manage(new Gtk::Grid()); ratiogrid->get_style_context()->add_class("grid-spacing"); setExpandAlignProperties(ratiogrid, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); ratio = Gtk::manage (new MyComboBoxText ()); setExpandAlignProperties(ratio, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); orientation = Gtk::manage (new MyComboBoxText ()); setExpandAlignProperties(orientation, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); customRatioLabel = Gtk::manage(new Gtk::Label("")); customRatioLabel->hide(); setExpandAlignProperties(customRatioLabel, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER); ratiogrid->set_column_homogeneous (true); ratiogrid->attach (*ratio, 0, 0, 1, 1); ratiogrid->attach (*customRatioLabel, 1, 0, 1, 1); ratiogrid->attach (*orientation, 1, 0, 1, 1); Gtk::Label* guidelab = Gtk::manage (new Gtk::Label (M("TP_CROP_GUIDETYPE"))); setExpandAlignProperties(guidelab, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); guide = Gtk::manage (new MyComboBoxText ()); setExpandAlignProperties(guide, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); settingsgrid->attach (*fixr, 0, 0, 1, 1); settingsgrid->attach (*ratiogrid, 1, 0, 1, 1); settingsgrid->attach (*guidelab, 0, 1, 1, 1); settingsgrid->attach (*guide, 1, 1, 1, 1); pack_start (*settingsgrid, Gtk::PACK_SHRINK, 0 ); // ppigrid START ppigrid = Gtk::manage(new Gtk::Grid()); ppigrid->get_style_context()->add_class("grid-spacing"); ppigrid->set_column_homogeneous (true); setExpandAlignProperties(ppigrid, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); Gtk::Separator* ppiseparator = Gtk::manage (new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL)); ppiseparator->get_style_context()->add_class("grid-row-separator"); Gtk::Grid* ppisubgrid = Gtk::manage(new Gtk::Grid()); ppisubgrid->get_style_context()->add_class("grid-spacing"); setExpandAlignProperties(ppisubgrid, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); Gtk::Label* ppilab = Gtk::manage (new Gtk::Label (M("TP_CROP_PPI") + ":")); setExpandAlignProperties(ppilab, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); ppi = Gtk::manage (new MySpinButton ()); setExpandAlignProperties(ppi, true, false, Gtk::ALIGN_END, Gtk::ALIGN_CENTER); ppi->set_width_chars(6); ppisubgrid->attach (*ppilab, 0, 0, 1, 1); ppisubgrid->attach (*ppi, 1, 0, 1, 1); sizecm = Gtk::manage (new Gtk::Label (M("GENERAL_NA") + " cm x " + M("GENERAL_NA") + " cm")); setExpandAlignProperties(sizecm, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER); sizein = Gtk::manage (new Gtk::Label (M("GENERAL_NA") + " in x " + M("GENERAL_NA") + " in")); setExpandAlignProperties(sizein, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER); ppigrid->attach (*ppiseparator, 0, 0, 2, 1); ppigrid->attach (*sizecm, 1, 1, 1, 1); ppigrid->attach (*sizein, 1, 2, 1, 1); ppigrid->attach (*ppisubgrid, 0, 1, 1, 2); pack_start (*ppigrid, Gtk::PACK_SHRINK, 0 ); ppi->set_value (300); // ppigrid END // Populate the combobox for (const auto& label : crop_ratios->getLabels()) { ratio->append (label); } ratio->set_active (0); orientation->append (M("GENERAL_LANDSCAPE")); orientation->append (M("GENERAL_PORTRAIT")); orientation->append (M("GENERAL_ASIMAGE")); orientation->set_active (2); guide->append (M("TP_CROP_GTNONE")); guide->append (M("TP_CROP_GTFRAME")); guide->append (M("TP_CROP_GTRULETHIRDS")); guide->append (M("TP_CROP_GTDIAGONALS")); guide->append (M("TP_CROP_GTHARMMEANS")); guide->append (M("TP_CROP_GTGRID")); guide->append (M("TP_CROP_GTTRIANGLE1")); guide->append (M("TP_CROP_GTTRIANGLE2")); guide->append (M("TP_CROP_GTEPASSPORT")); guide->append (M("TP_CROP_GTCENTEREDSQUARE")); guide->set_active (0); w->set_range (1, maxw); h->set_range (1, maxh); x->set_range (0, maxw - 1); y->set_range (0, maxh - 1); x->set_digits (0); x->set_increments (1, 100); x->set_value (0); y->set_digits (0); y->set_increments (1, 100); y->set_value (0); w->set_digits (0); w->set_increments (1, 100); w->set_value (200); h->set_digits (0); h->set_increments (1, 100); h->set_value (200); ppi->set_digits (0); ppi->set_increments (1, 100); ppi->set_range (50, 12000); ppi->set_value (300); xconn = x->signal_value_changed().connect ( sigc::mem_fun(*this, &Crop::positionChanged), true); yconn = y->signal_value_changed().connect ( sigc::mem_fun(*this, &Crop::positionChanged), true); wconn = w->signal_value_changed().connect ( sigc::mem_fun(*this, &Crop::widthChanged), true); hconn = h->signal_value_changed().connect ( sigc::mem_fun(*this, &Crop::heightChanged), true); fconn = fixr->signal_toggled().connect( sigc::mem_fun(*this, &Crop::ratioFixedChanged) ); rconn = ratio->signal_changed().connect( sigc::mem_fun(*this, &Crop::ratioChanged) ); oconn = orientation->signal_changed().connect( sigc::mem_fun(*this, &Crop::ratioChanged) ); gconn = guide->signal_changed().connect( sigc::mem_fun(*this, &Crop::notifyListener) ); selectCrop->signal_pressed().connect( sigc::mem_fun(*this, &Crop::selectPressed) ); resetCrop->signal_pressed().connect( sigc::mem_fun(*this, &Crop::doresetCrop) ); ppi->signal_value_changed().connect( sigc::mem_fun(*this, &Crop::refreshSize) ); nx = ny = nw = nh = 0; lastRotationDeg = 0; //GTK318 #if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION < 20 methodgrid->set_row_spacing(4); methodgrid->set_column_spacing(4); settingsgrid->set_row_spacing(4); settingsgrid->set_column_spacing(4); ppigrid->set_row_spacing(4); ppigrid->set_column_spacing(4); ppisubgrid->set_row_spacing(4); ppisubgrid->set_column_spacing(4); #endif //GTK318 show_all (); } Crop::~Crop() { idle_register.destroy(); } void Crop::writeOptions () { options.cropPPI = (int)ppi->get_value (); } void Crop::readOptions () { disableListener (); ppi->set_value (options.cropPPI); enableListener (); } void Crop::read (const ProcParams* pp, const ParamsEdited* pedited) { disableListener (); xconn.block (true); yconn.block (true); wconn.block (true); hconn.block (true); rconn.block (true); fconn.block (true); oconn.block (true); gconn.block (true); setEnabled(pp->crop.enabled); // check if the new values are larger than the maximum double tmp, maxw, maxh; w->get_range (tmp, maxw); h->get_range (tmp, maxh); if (pp->crop.x + pp->crop.w > (int)maxw || pp->crop.y + pp->crop.h > (int)maxh) { setDimensions (pp->crop.x + pp->crop.w, pp->crop.y + pp->crop.h); } const bool flip_orientation = pp->crop.fixratio && crop_ratios->getValue(ratio->get_active_row_number()) > 0 && crop_ratios->getValue(ratio->get_active_row_number()) < 1.0; if (pp->crop.orientation == "Landscape") { orientation->set_active (flip_orientation ? 1 : 0); } else if (pp->crop.orientation == "Portrait") { orientation->set_active (flip_orientation ? 0 : 1); } else { orientation->set_active (2); } switch (pp->crop.guide) { case procparams::CropParams::Guide::NONE: case procparams::CropParams::Guide::FRAME: case procparams::CropParams::Guide::RULE_OF_THIRDS: case procparams::CropParams::Guide::RULE_OF_DIAGONALS: case procparams::CropParams::Guide::HARMONIC_MEANS: case procparams::CropParams::Guide::GRID: case procparams::CropParams::Guide::GOLDEN_TRIANGLE_1: case procparams::CropParams::Guide::GOLDEN_TRIANGLE_2: case procparams::CropParams::Guide::EPASSPORT: case procparams::CropParams::Guide::CENTERED_SQUARE: { guide->set_active(toUnderlying(pp->crop.guide)); } } x->set_value(pp->crop.x); y->set_value(pp->crop.y); w->set_value(std::max(pp->crop.w, 1)); h->set_value(std::max(pp->crop.h, 1)); nx = pp->crop.x; ny = pp->crop.y; nw = pp->crop.w; nh = pp->crop.h; customRatioLabel->hide(); orientation->show(); if (pp->crop.ratio == "As Image") { ratio->set_active(0); } else if (pp->crop.ratio == "Current") { ratio->set_active(1); updateCurrentRatio(); customRatioLabel->show(); orientation->hide(); } else { ratio->set_active_text (pp->crop.ratio); } fixr->set_active (pp->crop.fixratio); lastRotationDeg = pp->coarse.rotate; wDirty = false; hDirty = false; xDirty = false; yDirty = false; if (pedited) { wDirty = pedited->crop.w; hDirty = pedited->crop.h; xDirty = pedited->crop.x; yDirty = pedited->crop.y; if (!pedited->crop.ratio) { ratio->set_active_text (M("GENERAL_UNCHANGED")); } if (!pedited->crop.orientation) { orientation->set_active_text (M("GENERAL_UNCHANGED")); } if (!pedited->crop.guide) { guide->set_active_text (M("GENERAL_UNCHANGED")); } set_inconsistent (multiImage && !pedited->crop.enabled); fixr->set_inconsistent (!pedited->crop.fixratio); } lastFixRatio = pp->crop.fixratio; xconn.block (false); yconn.block (false); wconn.block (false); hconn.block (false); rconn.block (false); fconn.block (false); oconn.block (false); gconn.block (false); enableListener (); } void Crop::write (ProcParams* pp, ParamsEdited* pedited) { pp->crop.enabled = getEnabled (); pp->crop.x = nx; pp->crop.y = ny; pp->crop.w = nw; pp->crop.h = nh; pp->crop.fixratio = fixr->get_active (); if (ratio->get_active_row_number() == 0) { pp->crop.ratio = "As Image"; } else if (ratio->get_active_row_number() == 1) { pp->crop.ratio = "Current"; } else { pp->crop.ratio = ratio->get_active_text (); } // for historical reasons we store orientation different if ratio is written as 2:3 instead of 3:2, but in GUI 'landscape' is always long side horizontal regardless of the ratio is written short or long side first. const bool flip_orientation = fixr->get_active() && crop_ratios->getValue(ratio->get_active_row_number()) > 0 && crop_ratios->getValue(ratio->get_active_row_number()) < 1.0; if (orientation->get_active_row_number() == 0) { pp->crop.orientation = flip_orientation ? "Portrait" : "Landscape"; } else if (orientation->get_active_row_number() == 1) { pp->crop.orientation = flip_orientation ? "Landscape" : "Portrait"; } else { pp->crop.orientation = "As Image"; } pp->crop.guide = procparams::CropParams::Guide(guide->get_active_row_number()); if (pedited) { pedited->crop.enabled = !get_inconsistent(); pedited->crop.ratio = ratio->get_active_text() != M("GENERAL_UNCHANGED"); pedited->crop.orientation = orientation->get_active_text() != M("GENERAL_UNCHANGED"); pedited->crop.guide = guide->get_active_text() != M("GENERAL_UNCHANGED"); pedited->crop.fixratio = !fixr->get_inconsistent(); pedited->crop.w = wDirty; pedited->crop.h = hDirty; pedited->crop.x = xDirty; pedited->crop.y = yDirty; } } void Crop::trim (ProcParams* pp, int ow, int oh) { int xmin = pp->crop.x; int ymin = pp->crop.y; if (xmin > ow || ymin > oh) { // the crop is completely out of the image, so we disable the crop pp->crop.enabled = false; // and we set the values to the defaults pp->crop.x = 0; pp->crop.y = 0; pp->crop.w = ow; pp->crop.h = oh; // the ratio is now not guaranteed, so we set it off pp->crop.fixratio = false; } else { if ((xmin + pp->crop.w) > ow) { // crop overflow in the width dimension ; we trim it pp->crop.w = ow - xmin; } if ((ymin + pp->crop.h) > oh) { // crop overflow in the height dimension ; we trim it pp->crop.h = oh - ymin; } } } bool Crop::inImageArea (int x, int y) { return x >= 0 && x < maxw && y >= 0 && y < maxh; } void Crop::selectPressed () { if (clistener) { clistener->cropSelectRequested (); } } void Crop::doresetCrop () { xDirty = true; yDirty = true; wDirty = true; hDirty = true; // Reset ratio, ratio lock and orientation as well ratio->set_active(0); orientation->set_active(2); fixr->set_active(true); int X = 0; int Y = 0; int W = maxw; int H = maxh; cropResized (X, Y, W, H); idle_register.add( [this]() -> bool { notifyListener(); return false; } ); refreshSpins(); } void Crop::notifyListener () { if (listener && getEnabled ()) { if (nw == 1 && nh == 1) { setEnabled(false); nx = (int)x->get_value (); ny = (int)y->get_value (); nw = (int)w->get_value (); nh = (int)h->get_value (); listener->panelChanged (EvCrop, M("GENERAL_DISABLED")); } else { listener->panelChanged (EvCrop, Glib::ustring::compose ("%1=%2, %3=%4\n%5=%6, %7=%8", M("TP_CROP_X"), nx, M("TP_CROP_Y"), ny, M("TP_CROP_W"), nw, M("TP_CROP_H"), nh)); } } } void Crop::enabledChanged () { if (listener) { if (get_inconsistent()) { listener->panelChanged (EvCrop, M("GENERAL_UNCHANGED")); } else if (getEnabled()) { listener->panelChanged (EvCrop, M("GENERAL_ENABLED")); } else { listener->panelChanged (EvCrop, M("GENERAL_DISABLED")); } } } void Crop::hFlipCrop () { nx = maxw - nx - nw; idle_register.add( [this]() -> bool { refreshSpins(false); return false; } ); } void Crop::vFlipCrop () { ny = maxh - ny - nh; idle_register.add( [this]() -> bool { refreshSpins(false); return false; } ); } void Crop::rotateCrop (int deg, bool hflip, bool vflip) { int rotation = (360 + deg - lastRotationDeg) % 360; if((hflip != vflip) && ((rotation % 180) == 90)) { rotation = (rotation + 180) % 360; } int tmp; switch (rotation) { case 90: tmp = nx; nx = maxh - ny - nh; ny = tmp; tmp = nw; nw = nh; nh = tmp; break; case 270: tmp = ny; ny = maxw - nx - nw; nx = tmp; tmp = nw; nw = nh; nh = tmp; break; case 180: nx = maxw - nx - nw; ny = maxh - ny - nh; break; } lastRotationDeg = deg; idle_register.add( [this]() -> bool { refreshSpins(false); return false; } ); } void Crop::positionChanged () { xDirty = true; yDirty = true; int X = (int)x->get_value (); int Y = (int)y->get_value (); int W = nw; int H = nh; cropMoved (X, Y, W, H); idle_register.add( [this]() -> bool { notifyListener(); return false; } ); } void Crop::widthChanged () { wDirty = true; int X = nx; int Y = ny; int W = (int)w->get_value (); int H = nh; cropWidth2Resized (X, Y, W, H); idle_register.add( [this]() -> bool { notifyListener(); return false; } ); } void Crop::heightChanged () { hDirty = true; int X = nx; int Y = ny; int W = nw; int H = (int)h->get_value (); cropHeight2Resized (X, Y, W, H); idle_register.add( [this]() -> bool { notifyListener(); return false; } ); } // Fixed ratio toggle button void Crop::ratioFixedChanged () { // Batch mode handling when enabling/disabling fixed crop if (batchMode && lastFixRatio != fixr->get_active ()) { if (fixr->get_inconsistent()) { fixr->set_inconsistent (false); fconn.block (true); fixr->set_active (false); fconn.block (false); } else if (lastFixRatio) { fixr->set_inconsistent (true); } } lastFixRatio = fixr->get_active (); adjustCropToRatio(); } // change to orientation or ration void Crop::ratioChanged () { if (ratio->get_active_row_number() == 1) { orientation->hide(); updateCurrentRatio(); customRatioLabel->show(); } else { orientation->show(); customRatioLabel->hide(); } if (!fixr->get_active ()) { fixr->set_active(true); // will adjust ratio anyway } else { adjustCropToRatio(); } } // Correct current crop if it doesn't fit void Crop::adjustCropToRatio() { if (fixr->get_active() && !fixr->get_inconsistent()) { int W1 = nw, W2 = nw; int H1 = nh, H2 = nh; int X1 = nx, X2 = nx; int Y1 = ny, Y2 = ny; float r = getRatio(); H1 = round(W1 / r); Y1 = ny + (nh - H1)/2.0; if (Y1 < 0) { Y1 = 0; } if (H1 > maxh) { H1 = maxh; W1 = round(H1 * r); X1 = nx + (nw - W1)/2.0; } if (Y1+H1 > maxh) { Y1 = maxh - H1; } W2 = round(H2 * r); X2 = nx + (nw - W2)/2.0; if (X2 < 0) { X2 = 0; } if (W2 > maxw) { W2 = maxw; H2 = round(W2 / r); Y2 = ny + (nh - H2)/2.0; } if (X2+W2 > maxw) { X2 = maxw - W2; } if (W1 * H1 >= W2 * H2) { nx = X1; ny = Y1; nw = W1; nh = H1; } else { nx = X2; ny = Y2; nw = W2; nh = H2; } } // This will save the options idle_register.add( [this]() -> bool { refreshSpins(true); return false; } ); } void Crop::refreshSize () { if (!batchMode) { std::ostringstream ostrin; ostrin.precision (3); // ostrin << h->get_value()/ppi->get_value() << " in x " << w->get_value()/ppi->get_value() << " in";; ostrin << nh / ppi->get_value() << " in x " << nw / ppi->get_value() << " in";; sizein->set_text (ostrin.str ()); std::ostringstream ostrcm; ostrcm.precision (3); // ostrcm << h->get_value()/ppi->get_value()*2.54 << " cm x " << w->get_value()/ppi->get_value()*2.54 << " cm";; ostrcm << nh / ppi->get_value() * 2.54 << " cm x " << nw / ppi->get_value() * 2.54 << " cm";; sizecm->set_text (ostrcm.str ()); } } /* * Set the maximum dimensions of the image. This method can be called with wrong values, then * called with the good ones !? */ void Crop::setDimensions (int mw, int mh) { maxw = mw; maxh = mh; bool xconnWasBlocked = xconn.block (true); bool yconnWasBlocked = yconn.block (true); bool wconnWasBlocked = wconn.block (true); bool hconnWasBlocked = hconn.block (true); w->set_range (1, maxw); h->set_range (1, maxh); x->set_range (0, maxw - 1); y->set_range (0, maxh - 1); if (!xconnWasBlocked) { xconn.block (false); } if (!yconnWasBlocked) { yconn.block (false); } if (!wconnWasBlocked) { wconn.block (false); } if (!hconnWasBlocked) { hconn.block (false); } if (!getEnabled()) { nx = 0; ny = 0; nw = mw; nh = mh; refreshSpins (); } refreshSize (); } void Crop::sizeChanged(int x, int y, int ow, int oh) { idle_register.add( [this, x, y]() -> bool { setDimensions(x, y); return false; } ); } bool Crop::refreshSpins (bool notify) { xconn.block (true); yconn.block (true); wconn.block (true); hconn.block (true); x->set_value (nx); y->set_value (ny); w->set_value (nw); h->set_value (nh); xDirty = true; yDirty = true; wDirty = true; hDirty = true; xconn.block (false); yconn.block (false); wconn.block (false); hconn.block (false); if (ratio->get_active_row_number() == 1 && !fixr->get_active()) { updateCurrentRatio(); } refreshSize (); if (notify) { notifyListener (); } return false; } void Crop::cropMoved (int &X, int &Y, int &W, int &H) { // W = w->get_value (); // H = h->get_value (); W = nw; H = nh; if (X + W > maxw) { X = maxw - W; } if (Y + H > maxh) { Y = maxh - H; } if (X < 0) { X = 0; } if (Y < 0) { Y = 0; } nx = X; ny = Y; nw = W; nh = H; idle_register.add( [this]() -> bool { refreshSpins(false); return false; } ); // Glib::signal_idle().connect (sigc::mem_fun(*this, &Crop::refreshSpins)); } void Crop::cropWidth1Resized (int &X, int &Y, int &W, int &H, float custom_ratio) { int oldXR = nx + nw; if (W < 0) { W = 0; } if (W > oldXR) { W = oldXR; } if (fixr->get_active() || custom_ratio > 0) { double r = custom_ratio > 0.f ? custom_ratio : static_cast(getRatio()); H = (int)round(W / r); int Hmax = min(ny + nh, maxh - ny); if (H > Hmax) { H = Hmax; W = H * r; } ny = ny - (H - nh) / 2.0; if (ny < 0) { ny = 0; } if (ny + H > maxh) { ny = maxh - H; } } X = oldXR - W; Y = ny; nx = X; nw = W; nh = H; idle_register.add( [this]() -> bool { refreshSpins(false); return false; } ); } void Crop::cropWidth2Resized (int &X, int &Y, int &W, int &H, float custom_ratio) { if (W < 0) { W = 0; } if (W > maxw - nx) { W = maxw - nx; } if (fixr->get_active() || custom_ratio > 0) { double r = custom_ratio > 0 ? custom_ratio : static_cast(getRatio()); H = (int)round(W / r); int Hmax = min(ny + nh, maxh - ny); if (H > Hmax) { H = Hmax; W = H * r; } ny = ny - (H - nh) / 2.0; if (ny < 0) { ny = 0; } if (ny + H > maxh) { ny = maxh - H; } } X = nx; Y = ny; nw = W; nh = H; idle_register.add( [this]() -> bool { refreshSpins(false); return false; } ); } void Crop::cropHeight1Resized (int &X, int &Y, int &W, int &H, float custom_ratio) { int oldYB = ny + nh; if (H < 0) { H = 0; } if (H > oldYB) { H = oldYB; } if (fixr->get_active() || custom_ratio > 0) { double r = custom_ratio > 0 ? custom_ratio : static_cast(getRatio()); W = (int)round(H * r); int Wmax = min(nx + nw, maxw - nx); if (W > Wmax) { W = Wmax; H = W / r; } nx = nx - (W - nw) / 2.0; if (nx < 0) { nx = 0; } if (nx + W > maxw) { nx = maxw - W; } } X = nx; Y = oldYB - H; ny = Y; nw = W; nh = H; idle_register.add( [this]() -> bool { refreshSpins(false); return false; } ); } void Crop::cropHeight2Resized (int &X, int &Y, int &W, int &H, float custom_ratio) { if (H < 0) { H = 0; } if (H > maxh - ny) { H = maxh - ny; } if (fixr->get_active() || custom_ratio > 0) { double r = custom_ratio > 0 ? custom_ratio : static_cast(getRatio()); W = (int)round(H * r); int Wmax = min(nx + nw, maxw - nx); if (W > Wmax) { W = Wmax; H = W / r; } nx = nx - (W - nw) / 2.0; // nx must be floating point to avoid drifting if (nx < 0) { nx = 0; } if (nx + W > maxw) { nx = maxw - W; } } X = nx; Y = ny; nw = W; nh = H; idle_register.add( [this]() -> bool { refreshSpins(false); return false; } ); } void Crop::cropTopLeftResized (int &X, int &Y, int &W, int &H, float custom_ratio) { int oldXR = nx + nw; // right side int oldYB = ny + nh; // bottom side if (W < 0) { W = 0; } if (H < 0) { H = 0; } if (W > oldXR) { W = oldXR; } if (H > oldYB) { H = oldYB; } if (fixr->get_active() || custom_ratio > 0) { double r = custom_ratio > 0 ? custom_ratio : static_cast(getRatio()); W = (int)round(H * r); if (W > oldXR) { W = oldXR; H = (int)round(W / r); } } X = oldXR - W; Y = oldYB - H; nx = X; ny = Y; nw = W; nh = H; idle_register.add( [this]() -> bool { refreshSpins(false); return false; } ); } void Crop::cropTopRightResized (int &X, int &Y, int &W, int &H, float custom_ratio) { int oldYB = ny + nh; if (W < 0) { W = 0; } if (H < 0) { H = 0; } if (W > maxw - nx) { W = maxw - nx; } if (H > oldYB) { H = oldYB; } if (fixr->get_active() || custom_ratio > 0) { double r = custom_ratio > 0 ? custom_ratio : static_cast(getRatio()); W = (int)round(H * r); if (W > maxw - nx) { W = maxw - nx; H = (int)round(W / r); } } X = nx; Y = oldYB - H; ny = Y; nw = W; nh = H; idle_register.add( [this]() -> bool { refreshSpins(false); return false; } ); } void Crop::cropBottomLeftResized (int &X, int &Y, int &W, int &H, float custom_ratio) { int oldXR = nx + nw; if (W < 0) { W = 0; } if (H < 0) { H = 0; } if (W > oldXR) { W = oldXR; } if (H > maxh - ny) { H = maxh - ny; } if (fixr->get_active() || custom_ratio > 0) { double r = custom_ratio > 0 ? custom_ratio : static_cast(getRatio()); W = (int)round(H * r); if (W > oldXR) { W = oldXR; H = (int)round(W / r); } } X = oldXR - W; Y = ny; nx = X; nw = W; nh = H; idle_register.add( [this]() -> bool { refreshSpins(false); return false; } ); } void Crop::cropBottomRightResized (int &X, int &Y, int &W, int &H, float custom_ratio) { if (W < 0) { W = 0; } if (H < 0) { H = 0; } if (W > maxw - nx) { W = maxw - nx; } if (H > maxh - ny) { H = maxh - ny; } if (fixr->get_active() || custom_ratio > 0) { double r = custom_ratio > 0 ? custom_ratio : static_cast(getRatio()); W = (int)round(H * r); if (W > maxw - nx) { W = maxw - nx; H = (int)round(W / r); } } X = nx; Y = ny; nw = W; nh = H; idle_register.add( [this]() -> bool { refreshSpins(false); return false; } ); } void Crop::cropInit (int &x, int &y, int &w, int &h) { nx = x; ny = y; nw = 1; nh = 1; w = 1; h = 1; setEnabled(true); } void Crop::cropResized (int &x, int &y, int& x2, int& y2) { if (x2 < 0) { x2 = 0; } if (y2 < 0) { y2 = 0; } if (x2 >= maxw) { x2 = maxw - 1; } if (y2 >= maxh) { y2 = maxh - 1; } int W; if (x < x2) { W = x2 - x + 1; } else { W = x - x2 + 1; } int Y; if (y < y2) { Y = y; } else { Y = y2; } if (W > maxw) { W = maxw; } int H; if (fixr->get_active()) { double r = getRatio (); if (y <= y2) { int W2max = (int)round ((maxh - Y) * r); if (W > W2max) { W = W2max; } } else { int W2max = (int)round (y * r); if (W > W2max) { W = W2max; } } H = (int)round(W / r); if (x < x2) { x2 = x + W - 1; } else { x2 = x - W + 1; } if (y < y2) { y2 = y + H - 1; } else { y2 = y - H + 1; } } int X; if (x < x2) { W = x2 - x + 1; X = x; } else { W = x - x2 + 1; X = x2; } if (y < y2) { H = y2 - y + 1; Y = y; } else { H = y - y2 + 1; Y = y2; } nx = X; ny = Y; nw = W; nh = H; idle_register.add( [this]() -> bool { refreshSpins(false); return false; } ); } void Crop::cropManipReady () { idle_register.add( [this]() -> bool { notifyListener(); return false; } ); } double Crop::getRatio () const { double r = -1.0; if (!fixr->get_active()) { return r; } r = crop_ratios->getValue(ratio->get_active_row_number()); if (!r) { r = maxh <= maxw ? float(maxh)/float(maxw) : float(maxw)/float(maxh); } if (r < 1.0) { r = 1.0 / r; // convert to long side first (eg 4:5 becomes 5:4) } if (orientation->get_active_row_number() == 0) { return r; } else if(orientation->get_active_row_number() == 1) { return 1.0 / r; } else { return maxh <= maxw ? r : 1.0 / r; } } void Crop::setBatchMode (bool batchMode) { ToolPanel::setBatchMode (batchMode); ratio->append (M("GENERAL_UNCHANGED")); orientation->append (M("GENERAL_UNCHANGED")); guide->append (M("GENERAL_UNCHANGED")); removeIfThere (this, ppigrid); removeIfThere (methodgrid, selectCrop); removeIfThere (methodgrid, resetCrop); } void Crop::updateCurrentRatio() { double rw, rh; get_custom_ratio(w->get_value(), h->get_value(), rw, rh); customRatioLabel->set_text(Glib::ustring::compose("%1:%2", rw, rh)); crop_ratios->updateCurrentRatio(static_cast(w->get_value()) / static_cast(h->get_value())); }