/* * 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 #ifdef _WIN32 #include #endif #include "cropwindow.h" #include "cursormanager.h" #include "guiutils.h" #include "imagearea.h" #include "lockablecolorpicker.h" #include "options.h" #include "rtimage.h" #include "threadutils.h" #include "editcallbacks.h" #include "editbuffer.h" #include "editwidgets.h" #include "pointermotionlistener.h" #include "rtsurface.h" #include "../rtengine/dcrop.h" #include "../rtengine/imagesource.h" #include "../rtengine/procparams.h" #include "../rtengine/rt_math.h" using namespace rtengine; namespace { inline double zoomLimitToFraction(Options::MaxZoom z) { switch (z) { case Options::MaxZoom::PERCENTS_100: return 1.; case Options::MaxZoom::PERCENTS_200: return 2.; case Options::MaxZoom::PERCENTS_300: return 3.; case Options::MaxZoom::PERCENTS_400: return 4.; case Options::MaxZoom::PERCENTS_500: return 5.; case Options::MaxZoom::PERCENTS_600: return 6.; case Options::MaxZoom::PERCENTS_700: return 7.; case Options::MaxZoom::PERCENTS_800: return 8.; case Options::MaxZoom::PERCENTS_1600: default: return 16.; } } } bool CropWindow::initialized = false; Glib::ustring CropWindow::zoomOuttt; Glib::ustring CropWindow::zoomIntt; Glib::ustring CropWindow::zoom100tt; Glib::ustring CropWindow::closett; CropWindow::CropWindow (ImageArea* parent, bool isLowUpdatePriority_, bool isDetailWindow) : ObjectMOBuffer(parent), state(SNormal), press_x(0), press_y(0), action_x(0), action_y(0), pickedObject(-1), pickModifierKey(0), rot_deg(0), onResizeArea(false), deleted(false), fitZoomEnabled(true), fitZoom(false), cursor_type(CSArrow), /*isLowUpdatePriority(isLowUpdatePriority_),*/ hoveredPicker(nullptr), cropLabel(Glib::ustring("100%")), backColor(options.bgcolor), decorated(true), isFlawnOver(false), titleHeight(30), sideBorderWidth(3), lowerBorderWidth(3), upperBorderWidth(1), sepWidth(2), xpos(30), ypos(30), width(0), height(0), imgAreaX(0), imgAreaY(0), imgAreaW(0), imgAreaH(0), imgX(-1), imgY(-1), imgW(1), imgH(1), iarea(parent), cropZoom(0), zoomVersion(0), exposeVersion(0), cropgl(nullptr), pmlistener(nullptr), pmhlistener(nullptr), scrollAccum(0.0), observedCropWin(nullptr), crop_custom_ratio(0.f) { initZoomSteps(); Glib::RefPtr context = parent->get_pango_context () ; Pango::FontDescription fontd = parent->get_style_context()->get_font(); fontd.set_weight (Pango::WEIGHT_BOLD); const int fontSize = 8; // pt // Non-absolute size is defined in "Pango units" and shall be multiplied by // Pango::SCALE from "pt": fontd.set_size (fontSize * Pango::SCALE); context->set_font_description (fontd); Glib::RefPtr cllayout = parent->create_pango_layout("1000%"); int iw, ih; cllayout->get_pixel_size (iw, ih); titleHeight = ih; if (!initialized) { zoomOuttt = "Zoom Out"; zoomIntt = "Zoom In"; zoom100tt = "Zoom 100/%"; closett = "Close"; initialized = true; } bZoomOut = new LWButton(std::shared_ptr(new RTSurface("magnifier-minus-small", Gtk::ICON_SIZE_BUTTON)), 0, nullptr, LWButton::Left, LWButton::Center, &zoomOuttt); bZoomIn = new LWButton(std::shared_ptr(new RTSurface("magnifier-plus-small", Gtk::ICON_SIZE_BUTTON)), 1, nullptr, LWButton::Left, LWButton::Center, &zoomIntt); bZoom100 = new LWButton(std::shared_ptr(new RTSurface("magnifier-1to1-small", Gtk::ICON_SIZE_BUTTON)), 2, nullptr, LWButton::Left, LWButton::Center, &zoom100tt); //bZoomFit = new LWButton (std::shared_ptr(new RTSurface("magnifier-fit", Gtk::ICON_SIZE_BUTTON)), 3, NULL, LWButton::Left, LWButton::Center, "Zoom Fit"); bClose = new LWButton(std::shared_ptr(new RTSurface("cancel-small", Gtk::ICON_SIZE_BUTTON)), 4, nullptr, LWButton::Right, LWButton::Center, &closett); buttonSet.add (bZoomOut); buttonSet.add (bZoomIn); buttonSet.add (bZoom100); buttonSet.add (bClose); buttonSet.setColors (Gdk::RGBA("black"), Gdk::RGBA("white")); buttonSet.setButtonListener (this); int bsw, bsh; buttonSet.getMinimalDimensions (bsw, bsh); if (bsh > titleHeight) { titleHeight = bsh; } minWidth = bsw + iw + 2 * sideBorderWidth; cropHandler.setDisplayHandler(this); cropHandler.newImage (parent->getImProcCoordinator(), isDetailWindow); } CropWindow::~CropWindow () { for (auto colorPicker : colorPickers) { delete colorPicker; } } void CropWindow::initZoomSteps() { zoomSteps.push_back(ZoomStep(" 1%", 0.01, 999, true)); zoomSteps.push_back(ZoomStep(" 2%", 0.02, 500, true)); zoomSteps.push_back(ZoomStep(" 5%", 0.05, 200, true)); zoomSteps.push_back(ZoomStep(" 6%", 1.0/15.0, 150, true)); zoomSteps.push_back(ZoomStep(" 8%", 1.0/12.0, 120, true)); char lbl[64]; for (int s = 100; s >= 11; --s) { float z = 10.f / s; snprintf(lbl, sizeof(lbl), "% 2d%%", int(z * 100)); bool is_major = (s == s/10 * 10); zoomSteps.push_back(ZoomStep(lbl, z, s, is_major)); } zoom11index = zoomSteps.size(); for (int s = 1; s <= 8; ++s) { snprintf(lbl, sizeof(lbl), "%d00%%", s); zoomSteps.push_back(ZoomStep(lbl, s, s * 1000, true)); } zoomSteps.push_back(ZoomStep("1600%", 16, 16000, true)); } void CropWindow::enable() { cropHandler.setEnabled (true); } void CropWindow::setPosition (int x, int y) { if (y < 0) { y = 0; } xpos = x; ypos = y; if (decorated) { buttonSet.arrangeButtons (xpos + sideBorderWidth, ypos + upperBorderWidth, width - 2 * sideBorderWidth, titleHeight); } } void CropWindow::getPosition (int& x, int& y) { x = xpos; y = ypos; } void CropWindow::getCropPosition (int& x, int& y) { int cropX, cropY; cropHandler.getPosition (cropX, cropY); if (state != SCropImgMove) { x = cropX; y = cropY; } else { x = cropX + action_x; y = cropY + action_y; } } void CropWindow::getCropRectangle (int& x, int& y, int& w, int& h) { cropHandler.getPosition (x, y); cropHandler.getSize (w, h); } void CropWindow::setCropPosition (int x, int y, bool update) { cropHandler.setAnchorPosition (x, y, update); for (auto listener : listeners) { listener->cropPositionChanged (this); } } void CropWindow::centerCrop (bool update) { cropHandler.centerAnchor (update); for (auto listener : listeners) { listener->cropPositionChanged (this); } } void CropWindow::setSize (int w, int h, bool norefresh) { width = w; height = h; fitZoom = false; if (width < minWidth) { width = minWidth; } if (height < 64) { height = 64; } if (decorated) { imgAreaX = sideBorderWidth; imgAreaY = upperBorderWidth + titleHeight + sepWidth; imgAreaW = width - 2 * sideBorderWidth; imgAreaH = height - lowerBorderWidth - titleHeight - sepWidth - upperBorderWidth; buttonSet.arrangeButtons (xpos + sideBorderWidth, ypos + upperBorderWidth, width - 2 * sideBorderWidth, titleHeight); } else { imgAreaX = imgAreaY = 0; imgAreaW = width; imgAreaH = height; } if (!norefresh) { ObjectMOBuffer::resize(imgAreaW, imgAreaH); cropHandler.setWSize (imgAreaW, imgAreaH); } //iarea->redraw (); } void CropWindow::getSize (int& w, int& h) { w = width; h = height; } void CropWindow::getCropSize (int& w, int& h) { w = imgAreaW; h = imgAreaH; } void CropWindow::getCropAnchorPosition (int& x, int& y) { cropHandler.getAnchorPosition(x, y); } void CropWindow::setCropAnchorPosition (int x, int y) { cropHandler.setAnchorPosition(x, y); } bool CropWindow::isInside (int x, int y) { return x >= xpos && x < xpos + width && y >= ypos && y < ypos + height; } void CropWindow::leaveNotify (GdkEventCrossing* event) { EditSubscriber* subscriber = iarea->getCurrSubscriber(); if (state == SNormal && subscriber && subscriber->getEditingType() == ET_PIPETTE) { iarea->setPipetteVal1(-1.f); iarea->setPipetteVal2(-1.f); iarea->setPipetteVal3(-1.f); if (subscriber->mouseOver(0)) { iarea->redraw(); } } } void CropWindow::flawnOver (bool isFlawnOver) { this->isFlawnOver = isFlawnOver; } void CropWindow::scroll (int state, GdkScrollDirection direction, int x, int y, double deltaX, double deltaY) { double delta = 0.0; if (std::fabs(deltaX) > std::fabs(deltaY)) { delta = deltaX; } else { delta = deltaY; } if (direction == GDK_SCROLL_SMOOTH) { scrollAccum += delta; //Only change zoom level if we've accumulated +/- 1.0 of deltas. This conditional handles the previous delta=0.0 case if (std::fabs(scrollAccum) < 1.0) { return; } } bool isUp = direction == GDK_SCROLL_UP || (direction == GDK_SCROLL_SMOOTH && scrollAccum < 0.0); scrollAccum = 0.0; if ((state & GDK_CONTROL_MASK) && onArea(ColorPicker, x, y)) { // resizing a color picker if (isUp) { hoveredPicker->incSize(); updateHoveredPicker(); iarea->redraw (); } else { hoveredPicker->decSize(); updateHoveredPicker(); iarea->redraw (); } } else { // not over a color picker, we zoom in/out int newCenterX = x; int newCenterY = y; screenCoordToImage(newCenterX, newCenterY, newCenterX, newCenterY); if (isUp && !isMaxZoom()) { zoomIn (true, newCenterX, newCenterY); } else if (!isUp && !isMinZoom()) { zoomOut (true, newCenterX, newCenterY); } } } void CropWindow::buttonPress (int button, int type, int bstate, int x, int y) { bool needRedraw = true; // most common case ; not redrawing are exceptions const auto editSubscriber = iarea->getCurrSubscriber(); iarea->grabFocus (this); if (button == 1) { if (type == GDK_2BUTTON_PRESS && onArea (CropImage, x, y) && iarea->getToolMode () != TMColorPicker && (state == SNormal || state == SCropImgMove)) { if (fitZoomEnabled) { if (fitZoom) { state = SNormal; zoomVersion = exposeVersion; screenCoordToImage (x, y, action_x, action_y); changeZoom (zoom11index, true, action_x, action_y); fitZoom = false; } else if (options.cropAutoFit) { zoomFitCrop(); } else { zoomFit(); } } else { zoom11 (); } state = SNormal; } else if (type == GDK_BUTTON_PRESS && state == SNormal) { if (onArea (CropToolBar, x, y)) { if (!decorated || !buttonSet.pressNotify (x, y)) { state = SCropWinMove; action_x = x; action_y = y; press_x = xpos; press_y = ypos; } } else if (onArea (CropResize, x, y)) { state = SCropWinResize; action_x = x; action_y = y; press_x = width; press_y = height; } else { if (onArea (CropImage, x, y)) { // events inside of the image domain crop_custom_ratio = 0.f; if ((bstate & GDK_SHIFT_MASK) && cropHandler.cropParams->w > 0 && cropHandler.cropParams->h > 0) { crop_custom_ratio = float(cropHandler.cropParams->w) / float(cropHandler.cropParams->h); } if (iarea->getToolMode () == TMColorPicker) { if (hoveredPicker) { if ((bstate & GDK_CONTROL_MASK) && !(bstate & GDK_SHIFT_MASK)) { hoveredPicker->decSize(); updateHoveredPicker(); needRedraw = true; } else if (!(bstate & GDK_CONTROL_MASK) && (bstate & GDK_SHIFT_MASK)) { hoveredPicker->rollDisplayedValues(); needRedraw = true; } else if (!(bstate & GDK_CONTROL_MASK) && !(bstate & GDK_SHIFT_MASK)) { // Color Picker drag starts state = SDragPicker; } } else { // Add a new Color Picker rtengine::Coord imgPos; screenCoordToImage(x, y, imgPos.x, imgPos.y); LockableColorPicker *newPicker = new LockableColorPicker(this, cropHandler.colorParams.get()); colorPickers.push_back(newPicker); hoveredPicker = newPicker; updateHoveredPicker(&imgPos); state = SDragPicker; press_x = x; press_y = y; action_x = 0; action_y = 0; needRedraw = true; } } else if ((iarea->getToolMode () == TMHand || iarea->getToolMode() == TMPerspective) && editSubscriber && cropgl && cropgl->inImageArea(iarea->posImage.x, iarea->posImage.y) && editSubscriber->getEditingType() == ET_OBJECTS && iarea->getObject() >= 0 ) { needRedraw = editSubscriber->button1Pressed(bstate); if (editSubscriber->isDragging()) { state = SEditDrag1; } else if (editSubscriber->isPicking()) { state = SEditPick1; pickedObject = iarea->getObject(); pickModifierKey = bstate; } else if (iarea->getToolMode() == TMPerspective) { state = SCropImgMove; } press_x = x; press_y = y; action_x = 0; action_y = 0; } else if (onArea (CropTopLeft, x, y)) { state = SResizeTL; press_x = x; action_x = cropHandler.cropParams->x; press_y = y; action_y = cropHandler.cropParams->y; } else if (onArea (CropTopRight, x, y)) { state = SResizeTR; press_x = x; action_x = cropHandler.cropParams->w; press_y = y; action_y = cropHandler.cropParams->y; } else if (onArea (CropBottomLeft, x, y)) { state = SResizeBL; press_x = x; action_x = cropHandler.cropParams->x; press_y = y; action_y = cropHandler.cropParams->h; } else if (onArea (CropBottomRight, x, y)) { state = SResizeBR; press_x = x; action_x = cropHandler.cropParams->w; press_y = y; action_y = cropHandler.cropParams->h; } else if (onArea (CropTop, x, y)) { state = SResizeH1; press_y = y; action_y = cropHandler.cropParams->y; } else if (onArea (CropBottom, x, y)) { state = SResizeH2; press_y = y; action_y = cropHandler.cropParams->h; } else if (onArea (CropLeft, x, y)) { state = SResizeW1; press_x = x; action_x = cropHandler.cropParams->x; } else if (onArea (CropRight, x, y)) { state = SResizeW2; press_x = x; action_x = cropHandler.cropParams->w; } else if ((bstate & GDK_SHIFT_MASK) && onArea (CropInside, x, y)) { state = SCropMove; press_x = x; press_y = y; action_x = cropHandler.cropParams->x; action_y = cropHandler.cropParams->y; } else if (onArea (CropObserved, x, y)) { state = SObservedMove; press_x = x; press_y = y; action_x = 0; action_y = 0; } else if (iarea->getToolMode () == TMStraighten) { state = SRotateSelecting; press_x = x; press_y = y; action_x = x; action_y = y; rot_deg = 0; } else if (iarea->getToolMode () == TMSpotWB) { int spotx, spoty; screenCoordToImage (x, y, spotx, spoty); iarea->spotWBSelected (spotx, spoty); } else if (iarea->getToolMode () == TMCropSelect && cropgl) { state = SCropSelecting; screenCoordToImage (x, y, press_x, press_y); cropHandler.cropParams->enabled = true; cropHandler.cropParams->x = press_x; cropHandler.cropParams->y = press_y; cropHandler.cropParams->w = cropHandler.cropParams->h = 1; cropgl->cropInit (cropHandler.cropParams->x, cropHandler.cropParams->y, cropHandler.cropParams->w, cropHandler.cropParams->h); } else if (iarea->getToolMode () == TMHand) { if (editSubscriber) { if ((cropgl && cropgl->inImageArea(iarea->posImage.x, iarea->posImage.y) && (editSubscriber->getEditingType() == ET_PIPETTE && (bstate & GDK_CONTROL_MASK))) || editSubscriber->getEditingType() == ET_OBJECTS) { needRedraw = editSubscriber->button1Pressed(bstate); if (editSubscriber->isDragging()) { state = SEditDrag1; } else if (editSubscriber->isPicking()) { state = SEditPick1; pickedObject = iarea->getObject(); pickModifierKey = bstate; } press_x = x; press_y = y; action_x = 0; action_y = 0; } } if (state != SEditDrag1 && state != SEditPick1) { state = SCropImgMove; press_x = x; press_y = y; action_x = 0; action_y = 0; } } else { // if(zoomSteps[cropZoom].zoom > cropHandler.getFitZoom()) { // only allow move when image is only partial visible state = SCropImgMove; press_x = x; press_y = y; action_x = 0; action_y = 0; } } else if (iarea->getToolMode () == TMHand || iarea->getToolMode() == TMPerspective) { // events outside of the image domain EditSubscriber *editSubscriber = iarea->getCurrSubscriber(); if (editSubscriber && editSubscriber->getEditingType() == ET_OBJECTS) { needRedraw = editSubscriber->button1Pressed(bstate); if (editSubscriber->isDragging()) { state = SEditDrag1; } else if (editSubscriber->isPicking()) { state = SEditPick1; pickedObject = iarea->getObject(); pickModifierKey = bstate; } press_x = x; press_y = y; action_x = 0; action_y = 0; } } else if (iarea->getToolMode () == TMColorPicker && hoveredPicker) { if ((bstate & GDK_CONTROL_MASK) && !(bstate & GDK_SHIFT_MASK)) { if (hoveredPicker->decSize()) { updateHoveredPicker(); needRedraw = true; } } else if (!(bstate & GDK_CONTROL_MASK) && (bstate & GDK_SHIFT_MASK)) { hoveredPicker->rollDisplayedValues(); } else if (!(bstate & GDK_CONTROL_MASK) && !(bstate & GDK_SHIFT_MASK)) { // Color Picker drag starts state = SDragPicker; } } } } } else if (button == 2) { if (iarea->getToolMode () == TMHand) { EditSubscriber *editSubscriber = iarea->getCurrSubscriber(); if (editSubscriber && editSubscriber->getEditingType() == ET_OBJECTS) { needRedraw = editSubscriber->button2Pressed(bstate); if (editSubscriber->isDragging()) { state = SEditDrag2; } else if (editSubscriber->isPicking()) { state = SEditPick2; pickedObject = iarea->getObject(); pickModifierKey = bstate; } press_x = x; press_y = y; action_x = 0; action_y = 0; } } } else if (button == 3) { if (iarea->getToolMode () == TMHand || iarea->getToolMode() == TMPerspective) { EditSubscriber *editSubscriber = iarea->getCurrSubscriber(); if (editSubscriber && editSubscriber->getEditingType() == ET_OBJECTS) { needRedraw = editSubscriber->button3Pressed(bstate); if (editSubscriber->isDragging()) { state = SEditDrag3; } else if (editSubscriber->isPicking()) { state = SEditPick3; pickedObject = iarea->getObject(); pickModifierKey = bstate; } press_x = x; press_y = y; action_x = 0; action_y = 0; } } else if (iarea->getToolMode () == TMColorPicker && type == GDK_BUTTON_PRESS && state == SNormal) { if (hoveredPicker) { if((bstate & GDK_CONTROL_MASK) && (bstate & GDK_SHIFT_MASK)) { // Deleting all pickers ! for (auto colorPicker : colorPickers) { delete colorPicker; } colorPickers.clear(); hoveredPicker = nullptr; state = SDeletePicker; needRedraw = true; } else if ((bstate & GDK_CONTROL_MASK) && !(bstate & GDK_SHIFT_MASK)) { if (hoveredPicker->incSize()) { updateHoveredPicker(); needRedraw = true; } } else if (!(bstate & GDK_CONTROL_MASK) && !(bstate & GDK_SHIFT_MASK)) { // Deleting the hovered picker for (std::vector::iterator i = colorPickers.begin(); i != colorPickers.end(); ++i) { if (*i == hoveredPicker) { colorPickers.erase(i); delete hoveredPicker; hoveredPicker = nullptr; state = SDeletePicker; needRedraw = true; break; } } } } } } if (needRedraw) { iarea->redraw (); } updateCursor (x, y); } void CropWindow::buttonRelease (int button, int num, int bstate, int x, int y) { EditSubscriber *editSubscriber = iarea->getCurrSubscriber(); bool needRedraw = false; if (state == SCropWinResize) { int newWidth = press_x + x - action_x; int newHeight = press_y + y - action_y; setSize(newWidth, newHeight); if (decorated) { options.detailWindowWidth = newWidth; options.detailWindowHeight = newHeight; } state = SNormal; for (auto listener : listeners) { listener->cropWindowSizeChanged (this); } needRedraw = true; if (fitZoom && options.cropAutoFit) { zoomFitCrop(); } } else if (state == SCropWinMove) { if (iarea->showColorPickers () && !colorPickers.empty()) { needRedraw = true; } } else if (state == SCropImgMove) { cropHandler.update (); state = SNormal; for (std::list::iterator i = listeners.begin(); i != listeners.end(); ++i) { (*i)->cropPositionChanged (this); } needRedraw = true; } else if (state == SRotateSelecting) { iarea->straightenReady (rot_deg); iarea->setToolHand (); needRedraw = true; } else if (state == SObservedMove) { observedCropWin->remoteMoveReady (); state = SNormal; needRedraw = true; } else if (state == SEditDrag1 || state == SEditDrag2 || state == SEditDrag3) { if (editSubscriber) { if (state == SEditDrag1) { needRedraw = editSubscriber->button1Released(); } else if (state == SEditDrag2) { needRedraw = editSubscriber->button2Released(); } else if (state == SEditDrag3) { needRedraw = editSubscriber->button3Released(); } rtengine::Crop* crop = static_cast(cropHandler.getCrop()); Coord imgPos; action_x = x; action_y = y; screenCoordToImage (x, y, imgPos.x, imgPos.y); iarea->posImage.set(imgPos.x, imgPos.y); iarea->posScreen.set(x, y); Coord cropPos; if (state == SEditDrag1 && editSubscriber->getEditingType() == ET_PIPETTE) { screenCoordToCropBuffer (x, y, cropPos.x, cropPos.y); iarea->setObject(onArea (CropImage, x, y) && !onArea (CropObserved, x, y) ? 1 : 0); //iarea->setObject(cropgl && cropgl->inImageArea(iarea->posImage.x, iarea->posImage.y) ? 1 : 0); if (iarea->getObject()) { crop->getPipetteData(cropPos.x, cropPos.y, iarea->getPipetteRectSize()); //printf("PipetteData: %.3f %.3f %.3f\n", iarea->pipetteVal[0], iarea->pipetteVal[1], iarea->pipetteVal[2]); } else { iarea->setPipetteVal1(-1.f); iarea->setPipetteVal2(-1.f); iarea->setPipetteVal3(-1.f); } } else if (editSubscriber->getEditingType() == ET_OBJECTS) { screenCoordToCropCanvas (x, y, cropPos.x, cropPos.y); iarea->setObject(ObjectMOBuffer::getObjectID(cropPos)); } needRedraw |= editSubscriber->mouseOver(bstate); } else { iarea->setObject(0); } iarea->deltaImage.set(0, 0); iarea->deltaScreen.set(0, 0); iarea->deltaPrevImage.set(0, 0); iarea->deltaPrevScreen.set(0, 0); } else if (state == SEditPick1 || state == SEditPick2 || state == SEditPick3) { if (editSubscriber) { Coord imgPos; action_x = x; action_y = y; screenCoordToImage (x, y, imgPos.x, imgPos.y); iarea->posImage.set (imgPos.x, imgPos.y); iarea->posScreen.set (x, y); Coord cropPos; screenCoordToCropCanvas (x, y, cropPos.x, cropPos.y); iarea->setObject(ObjectMOBuffer::getObjectID(cropPos)); int buttonMask = ((state == SEditPick1) ? GDK_BUTTON1_MASK : 0) | ((state == SEditPick2) ? GDK_BUTTON2_MASK : 0) | ((state == SEditPick3) ? GDK_BUTTON3_MASK : 0); bool elemPicked = iarea->getObject() == pickedObject && bstate == (pickModifierKey | buttonMask); if (state == SEditPick1) { needRedraw = editSubscriber->pick1 (elemPicked); } else if (state == SEditPick2) { needRedraw = editSubscriber->pick2 (elemPicked); } else if (state == SEditPick3) { needRedraw = editSubscriber->pick3 (elemPicked); } pickedObject = -1; iarea->setObject(-1); pickModifierKey = 0; needRedraw |= editSubscriber->mouseOver (bstate); } else { iarea->setObject(0); } } else if (state == SDeletePicker) { needRedraw = true; } else if (state == SNormal && iarea->getToolMode() == TMColorPicker && !hoveredPicker && button == 3) { iarea->setToolHand (); } if (state != SEditDrag1 && state != SEditDrag2 && state != SEditDrag3) { iarea->deltaImage.set(0, 0); iarea->deltaScreen.set(0, 0); iarea->deltaPrevImage.set(0, 0); iarea->deltaPrevScreen.set(0, 0); } if (cropgl && (state == SCropSelecting || state == SResizeH1 || state == SResizeH2 || state == SResizeW1 || state == SResizeW2 || state == SResizeTL || state == SResizeTR || state == SResizeBL || state == SResizeBR || state == SCropMove)) { cropgl->cropManipReady (); iarea->setToolHand (); needRedraw = true; if (fitZoom && options.cropAutoFit) { zoomFitCrop(); } } if (decorated) { buttonSet.releaseNotify (x, y); } if (deleted) { iarea->flawnOverWindow = nullptr; delete this; return; } if (state != SDeletePicker && state != SEditDrag3 && state != SEditPick3 && button == 3 && !(bstate & (GDK_SHIFT_MASK|GDK_CONTROL_MASK))) { iarea->setPipetteVal1(-1.f); iarea->setPipetteVal2(-1.f); iarea->setPipetteVal3(-1.f); needRedraw = iarea->getObject() == 1; if (editSubscriber && editSubscriber->getEditingType() == ET_PIPETTE) { editSubscriber->mouseOver(0); } iarea->setToolHand (); } state = SNormal; iarea->grabFocus (nullptr); if (needRedraw) { iarea->redraw (); } updateCursor (x, y); } void CropWindow::pointerMoved (int bstate, int x, int y) { EditSubscriber *editSubscriber = iarea->getCurrSubscriber(); if (state == SCropWinMove) { setPosition (press_x + x - action_x, press_y + y - action_y); iarea->redraw (); } else if (state == SCropWinResize) { setSize (press_x + x - action_x, press_y + y - action_y, true); for (auto listener : listeners) { listener->cropWindowSizeChanged (this); } iarea->redraw (); } else if (state == SCropImgMove) { // multiplier is the amplification factor ; disabled if the user selected "1" (no amplification) double factor = options.panAccelFactor == 1 ? 1.0 : options.panAccelFactor * zoomSteps[cropZoom].zoom; // never move the preview slower than the cursor if (factor < 1.0) { factor = 1.0; } int newAction_x = (press_x - x) / zoomSteps[cropZoom].zoom * factor; int newAction_y = (press_y - y) / zoomSteps[cropZoom].zoom * factor; int deltaX = newAction_x - action_x; int deltaY = newAction_y - action_y; action_x = newAction_x; action_y = newAction_y; cropHandler.moveAnchor(deltaX, deltaY, false); for (auto listener : listeners) { listener->cropPositionChanged (this); } iarea->redraw (); } else if (state == SRotateSelecting) { action_x = x; action_y = y; iarea->redraw (); } else if (state == SNormal && iarea->getToolMode () == TMSpotWB) { action_x = x; action_y = y; iarea->redraw (); } else if (state == SResizeH1 && cropgl) { int oy = cropHandler.cropParams->y; cropHandler.cropParams->y = action_y + (y - press_y) / zoomSteps[cropZoom].zoom; cropHandler.cropParams->h += oy - cropHandler.cropParams->y; cropgl->cropHeight1Resized (cropHandler.cropParams->x, cropHandler.cropParams->y, cropHandler.cropParams->w, cropHandler.cropParams->h, crop_custom_ratio); iarea->redraw (); } else if (state == SResizeH2 && cropgl) { cropHandler.cropParams->h = action_y + (y - press_y) / zoomSteps[cropZoom].zoom; cropgl->cropHeight2Resized (cropHandler.cropParams->x, cropHandler.cropParams->y, cropHandler.cropParams->w, cropHandler.cropParams->h, crop_custom_ratio); iarea->redraw (); } else if (state == SResizeW1 && cropgl) { int ox = cropHandler.cropParams->x; cropHandler.cropParams->x = action_x + (x - press_x) / zoomSteps[cropZoom].zoom; cropHandler.cropParams->w += ox - cropHandler.cropParams->x; cropgl->cropWidth1Resized (cropHandler.cropParams->x, cropHandler.cropParams->y, cropHandler.cropParams->w, cropHandler.cropParams->h, crop_custom_ratio); iarea->redraw (); } else if (state == SResizeW2 && cropgl) { cropHandler.cropParams->w = action_x + (x - press_x) / zoomSteps[cropZoom].zoom; cropgl->cropWidth2Resized (cropHandler.cropParams->x, cropHandler.cropParams->y, cropHandler.cropParams->w, cropHandler.cropParams->h, crop_custom_ratio); iarea->redraw (); } else if (state == SResizeTL && cropgl) { int ox = cropHandler.cropParams->x; cropHandler.cropParams->x = action_x + (x - press_x) / zoomSteps[cropZoom].zoom; cropHandler.cropParams->w += ox - cropHandler.cropParams->x; int oy = cropHandler.cropParams->y; cropHandler.cropParams->y = action_y + (y - press_y) / zoomSteps[cropZoom].zoom; cropHandler.cropParams->h += oy - cropHandler.cropParams->y; cropgl->cropTopLeftResized (cropHandler.cropParams->x, cropHandler.cropParams->y, cropHandler.cropParams->w, cropHandler.cropParams->h, crop_custom_ratio); iarea->redraw (); } else if (state == SResizeTR && cropgl) { cropHandler.cropParams->w = action_x + (x - press_x) / zoomSteps[cropZoom].zoom; int oy = cropHandler.cropParams->y; cropHandler.cropParams->y = action_y + (y - press_y) / zoomSteps[cropZoom].zoom; cropHandler.cropParams->h += oy - cropHandler.cropParams->y; cropgl->cropTopRightResized (cropHandler.cropParams->x, cropHandler.cropParams->y, cropHandler.cropParams->w, cropHandler.cropParams->h, crop_custom_ratio); iarea->redraw (); } else if (state == SResizeBL && cropgl) { int ox = cropHandler.cropParams->x; cropHandler.cropParams->x = action_x + (x - press_x) / zoomSteps[cropZoom].zoom; cropHandler.cropParams->w += ox - cropHandler.cropParams->x; cropHandler.cropParams->h = action_y + (y - press_y) / zoomSteps[cropZoom].zoom; cropgl->cropBottomLeftResized (cropHandler.cropParams->x, cropHandler.cropParams->y, cropHandler.cropParams->w, cropHandler.cropParams->h, crop_custom_ratio); iarea->redraw (); } else if (state == SResizeBR && cropgl) { cropHandler.cropParams->w = action_x + (x - press_x) / zoomSteps[cropZoom].zoom; cropHandler.cropParams->h = action_y + (y - press_y) / zoomSteps[cropZoom].zoom; cropgl->cropBottomRightResized (cropHandler.cropParams->x, cropHandler.cropParams->y, cropHandler.cropParams->w, cropHandler.cropParams->h, crop_custom_ratio); iarea->redraw (); } else if (state == SCropMove && cropgl) { cropHandler.cropParams->x = action_x + (x - press_x) / zoomSteps[cropZoom].zoom; cropHandler.cropParams->y = action_y + (y - press_y) / zoomSteps[cropZoom].zoom; cropgl->cropMoved (cropHandler.cropParams->x, cropHandler.cropParams->y, cropHandler.cropParams->w, cropHandler.cropParams->h); iarea->redraw (); } else if (state == SCropSelecting && cropgl) { screenCoordToImage (x, y, action_x, action_y); int cx1 = press_x, cy1 = press_y; int cx2 = action_x, cy2 = action_y; cropgl->cropResized (cx1, cy1, cx2, cy2); if (cx2 > cx1) { cropHandler.cropParams->x = cx1; cropHandler.cropParams->w = cx2 - cx1 + 1; } else { cropHandler.cropParams->x = cx2; cropHandler.cropParams->w = cx1 - cx2 + 1; } if (cy2 > cy1) { cropHandler.cropParams->y = cy1; cropHandler.cropParams->h = cy2 - cy1 + 1; } else { cropHandler.cropParams->y = cy2; cropHandler.cropParams->h = cy1 - cy2 + 1; } iarea->redraw (); } else if (state == SObservedMove) { int new_action_x = x - press_x; int new_action_y = y - press_y; observedCropWin->remoteMove ((new_action_x - action_x) / zoomSteps[cropZoom].zoom, (new_action_y - action_y) / zoomSteps[cropZoom].zoom); action_x = new_action_x; action_y = new_action_y; iarea->redraw (); } else if (state == SDragPicker) { Coord imgPos; action_x = x - press_x; action_y = y - press_y; screenCoordToImage (x, y, imgPos.x, imgPos.y); if (imgPos.x < 0) { imgPos.x = 0; }else if (imgPos.x >= iarea->getImProcCoordinator()->getFullWidth()) { imgPos.x = iarea->getImProcCoordinator()->getFullWidth()-1; } if (imgPos.y < 0) { imgPos.y = 0; }else if (imgPos.y >= iarea->getImProcCoordinator()->getFullHeight()) { imgPos.y = iarea->getImProcCoordinator()->getFullHeight()-1; } updateHoveredPicker (&imgPos); iarea->redraw (); } else if (state == SNormal && iarea->getToolMode () == TMColorPicker && onArea(ColorPicker, x, y)) { // TODO: we could set the hovered picker as Highlighted here // Keep this if statement, the onArea will find out the hoveredPicker and will be used to update the cursor } else if (editSubscriber) { rtengine::Crop* crop = static_cast(cropHandler.getCrop()); if (state == SNormal || state == SEditPick1 || state == SEditPick2 || state == SEditPick3) { Coord imgPos; action_x = x; action_y = y; screenCoordToImage (x, y, imgPos.x, imgPos.y); iarea->posImage.set(imgPos.x, imgPos.y); iarea->posScreen.set(x, y); Coord cropPos; if (editSubscriber->getEditingType() == ET_PIPETTE) { screenCoordToCropBuffer (x, y, cropPos.x, cropPos.y); iarea->setObject(onArea (CropImage, x, y) && !onArea (CropObserved, x, y) ? 1 : 0); //iarea->setObject(cropgl && cropgl->inImageArea(iarea->posImage.x, iarea->posImage.y) ? 1 : 0); if (iarea->getObject()) { crop->getPipetteData(cropPos.x, cropPos.y, iarea->getPipetteRectSize()); //printf("PipetteData: %.3f %.3f %.3f\n", iarea->pipetteVal[0], iarea->pipetteVal[1], iarea->pipetteVal[2]); } else { iarea->setPipetteVal1(-1.f); iarea->setPipetteVal2(-1.f); iarea->setPipetteVal3(-1.f); } } else if (editSubscriber->getEditingType() == ET_OBJECTS) { screenCoordToCropCanvas (x, y, cropPos.x, cropPos.y); iarea->setObject(ObjectMOBuffer::getObjectID(cropPos)); } if (editSubscriber->mouseOver(bstate)) { iarea->redraw (); } } else if (state == SEditDrag1 || state == SEditDrag2 || state == SEditDrag3) { Coord currPos; action_x = x; action_y = y; Coord oldPosImage = iarea->posImage + iarea->deltaImage; //printf(">>> IMG / ImgPrev(%d x %d) = (%d x %d) + (%d x %d)\n", oldPosImage.x, oldPosImage.y, iarea->posImage.x, iarea->posImage.y, iarea->deltaImage.x, iarea->deltaImage.y); screenCoordToImage (x, y, currPos.x, currPos.y); iarea->deltaImage = currPos - iarea->posImage; iarea->deltaPrevImage = currPos - oldPosImage; //printf(" action_ & xy (%d x %d) -> (%d x %d) = (%d x %d) + (%d x %d) / deltaPrev(%d x %d)\n", action_x, action_y, currPos.x, currPos.y, iarea->posImage.x, iarea->posImage.y, iarea->deltaImage.x, iarea->deltaImage.y, iarea->deltaPrevImage.x, iarea->deltaPrevImage.y); Coord oldPosScreen = iarea->posScreen + iarea->deltaScreen; //printf(">>> SCR / ScrPrev(%d x %d) = (%d x %d) + (%d x %d)\n", oldPosScreen.x, oldPosScreen.y, iarea->posScreen.x, iarea->posScreen.y, iarea->deltaScreen.x, iarea->deltaScreen.y); currPos.set(x, y); iarea->deltaScreen = currPos - iarea->posScreen; iarea->deltaPrevScreen = currPos - oldPosScreen; //printf(" action_ & xy (%d x %d) -> (%d x %d) = (%d x %d) + (%d x %d) / deltaPrev(%d x %d)\n", action_x, action_y, currPos.x, currPos.y, iarea->posScreen.x, iarea->posScreen.y, iarea->deltaScreen.x, iarea->deltaScreen.y, iarea->deltaPrevScreen.x, iarea->deltaPrevScreen.y); if (state == SEditDrag1) { if (editSubscriber->drag1(bstate)) { iarea->redraw (); } } else if (state == SEditDrag2) { if (editSubscriber->drag2(bstate)) { iarea->redraw (); } } else if (state == SEditDrag3) { if (editSubscriber->drag3(bstate)) { iarea->redraw (); } } } } updateCursor (x, y); bool oRA = onArea (CropResize, x, y); if (oRA != onResizeArea) { onResizeArea = oRA; iarea->redraw (); } if (decorated) { buttonSet.motionNotify (x, y); } if (pmlistener) { int mx, my; screenCoordToImage (x, y, mx, my); MyMutex::MyLock lock(cropHandler.cimg); if (!onArea (CropImage, x, y) || !cropHandler.cropPixbuftrue) { cropHandler.getFullImageSize(mx, my); // pmlistener->pointerMoved (false, cropHandler.colorParams->working, mx, my, -1, -1, -1); // if (pmhlistener) pmhlistener->pointerMoved (false, cropHandler.colorParams->working, mx, my, -1, -1, -1); /* Glib::ustring outputProfile; outputProfile =cropHandler.colorParams->output ; printf("Using \"%s\" output\n", outputProfile.c_str()); if(outputProfile==options.rtSettings.srgb) printf("OK SRGB2"); */ pmlistener->pointerMoved (false, *cropHandler.colorParams, mx, my, -1, -1, -1); if (pmhlistener) { pmhlistener->pointerMoved (false, *cropHandler.colorParams, mx, my, -1, -1, -1); } } else { /* int vx = x - xpos - imgX; int vy = y - ypos - imgY; guint8* pix = cropHandler.cropPixbuf->get_pixels() + vy*cropHandler.cropPixbuf->get_rowstride() + vx*3; if (vx < cropHandler.cropPixbuf->get_width() && vy < cropHandler.cropPixbuf->get_height()) pmlistener->pointerMoved (true, mx, my, pix[0], pix[1], pix[2]); */ int vx = x - xpos - imgX; int vy = y - ypos - imgY; if(decorated) { vx -= sideBorderWidth; vy -= (titleHeight + upperBorderWidth + sepWidth); } // guint8* pix = cropHandler.cropPixbuf->get_pixels() + vy*cropHandler.cropPixbuf->get_rowstride() + vx*3; // if (vx < cropHandler.cropPixbuf->get_width() && vy < cropHandler.cropPixbuf->get_height()) // pmlistener->pointerMoved (true, mx, my, pix[0], pix[1], pix[2]); int imwidth = cropHandler.cropPixbuftrue->get_width(); int imheight = cropHandler.cropPixbuftrue->get_height(); if (vx < imwidth && vy < imheight) { guint8* pix = cropHandler.cropPixbuftrue->get_pixels() + vy * cropHandler.cropPixbuftrue->get_rowstride() + vx * 3; int rval = pix[0]; int gval = pix[1]; int bval = pix[2]; bool isRaw = false; rtengine::StagedImageProcessor* ipc = iarea->getImProcCoordinator(); if(ipc) { procparams::ProcParams params; ipc->getParams(¶ms, true); isRaw = params.raw.bayersensor.method == RAWParams::BayerSensor::getMethodString(RAWParams::BayerSensor::Method::NONE) || params.raw.xtranssensor.method == RAWParams::XTransSensor::getMethodString(RAWParams::XTransSensor::Method::NONE); if(isRaw) { ImageSource *isrc = static_cast(ipc->getInitialImage()); isrc->getRawValues(mx, my, params.coarse.rotate, rval, gval, bval); } } // Updates the Navigator // TODO: possible double color conversion if rval, gval, bval come from cropHandler.cropPixbuftrue ? see issue #4583 pmlistener->pointerMoved (true, *cropHandler.colorParams, mx, my, rval, gval, bval, isRaw); if (pmhlistener) { // Updates the HistogramRGBArea pmhlistener->pointerMoved (true, *cropHandler.colorParams, mx, my, rval, gval, bval); } } } } } bool CropWindow::onArea (CursorArea a, int x, int y) { int CROPRESIZEBORDER = rtengine::max(9 / zoomSteps[cropZoom].zoom, 3); int x1, y1, w, h; switch (a) { case CropWinButtons: return decorated && buttonSet.inside (x, y); case CropToolBar: return x > xpos && y > ypos && x < xpos + width - 1 && y < ypos + imgAreaY; case CropImage: return x >= xpos + imgX + imgAreaX && y >= ypos + imgY + imgAreaY && x < xpos + imgX + imgAreaX + imgW && y < ypos + imgY + imgAreaY + imgH; case ColorPicker: for (auto colorPicker : colorPickers) { if (colorPicker->isOver(x, y)) { hoveredPicker = colorPicker; return true; } } hoveredPicker = nullptr; return false; case CropBorder: return (x >= xpos + imgAreaX && y >= ypos + imgAreaY && x < xpos + imgAreaX + imgAreaW && y < ypos + imgAreaY + imgAreaH) && !(x >= xpos + imgX && y >= ypos + imgY && x < xpos + imgX + imgW && y < ypos + imgY + imgH); case CropTopLeft: screenCoordToImage (x, y, x1, y1); return cropHandler.cropParams->enabled && y1 >= cropHandler.cropParams->y - CROPRESIZEBORDER && y1 <= cropHandler.cropParams->y + CROPRESIZEBORDER && y >= ypos + imgY && x1 >= cropHandler.cropParams->x - CROPRESIZEBORDER && x1 <= cropHandler.cropParams->x + CROPRESIZEBORDER && x >= xpos + imgX; case CropTopRight: screenCoordToImage (x, y, x1, y1); return cropHandler.cropParams->enabled && y1 >= cropHandler.cropParams->y - CROPRESIZEBORDER && y1 <= cropHandler.cropParams->y + CROPRESIZEBORDER && y >= ypos + imgY && x1 >= cropHandler.cropParams->x + cropHandler.cropParams->w - 1 - CROPRESIZEBORDER && x1 <= cropHandler.cropParams->x + cropHandler.cropParams->w - 1 + CROPRESIZEBORDER && x < xpos + imgX + imgW; case CropBottomLeft: screenCoordToImage (x, y, x1, y1); return cropHandler.cropParams->enabled && y1 >= cropHandler.cropParams->y + cropHandler.cropParams->h - 1 - CROPRESIZEBORDER && y1 <= cropHandler.cropParams->y + cropHandler.cropParams->h - 1 + CROPRESIZEBORDER && y < ypos + imgY + imgH && x1 >= cropHandler.cropParams->x - CROPRESIZEBORDER && x1 <= cropHandler.cropParams->x + CROPRESIZEBORDER && x >= xpos + imgX; case CropBottomRight: screenCoordToImage (x, y, x1, y1); return cropHandler.cropParams->enabled && y1 >= cropHandler.cropParams->y + cropHandler.cropParams->h - 1 - CROPRESIZEBORDER && y1 <= cropHandler.cropParams->y + cropHandler.cropParams->h - 1 + CROPRESIZEBORDER && y < ypos + imgY + imgH && x1 >= cropHandler.cropParams->x + cropHandler.cropParams->w - 1 - CROPRESIZEBORDER && x1 <= cropHandler.cropParams->x + cropHandler.cropParams->w - 1 + CROPRESIZEBORDER && x < xpos + imgX + imgW; case CropTop: screenCoordToImage (x, y, x1, y1); return cropHandler.cropParams->enabled && x1 > cropHandler.cropParams->x + CROPRESIZEBORDER && x1 < cropHandler.cropParams->x + cropHandler.cropParams->w - 1 - CROPRESIZEBORDER && y1 > cropHandler.cropParams->y - CROPRESIZEBORDER && y1 < cropHandler.cropParams->y + CROPRESIZEBORDER && y >= ypos + imgY; case CropBottom: screenCoordToImage (x, y, x1, y1); return cropHandler.cropParams->enabled && x1 > cropHandler.cropParams->x + CROPRESIZEBORDER && x1 < cropHandler.cropParams->x + cropHandler.cropParams->w - 1 - CROPRESIZEBORDER && y1 > cropHandler.cropParams->y + cropHandler.cropParams->h - 1 - CROPRESIZEBORDER && y1 < cropHandler.cropParams->y + cropHandler.cropParams->h - 1 + CROPRESIZEBORDER && y < ypos + imgY + imgH; case CropLeft: screenCoordToImage (x, y, x1, y1); return cropHandler.cropParams->enabled && y1 > cropHandler.cropParams->y + CROPRESIZEBORDER && y1 < cropHandler.cropParams->y + cropHandler.cropParams->h - 1 - CROPRESIZEBORDER && x1 > cropHandler.cropParams->x - CROPRESIZEBORDER && x1 < cropHandler.cropParams->x + CROPRESIZEBORDER && x >= xpos + imgX; case CropRight: screenCoordToImage (x, y, x1, y1); return cropHandler.cropParams->enabled && y1 > cropHandler.cropParams->y + CROPRESIZEBORDER && y1 < cropHandler.cropParams->y + cropHandler.cropParams->h - 1 - CROPRESIZEBORDER && x1 > cropHandler.cropParams->x + cropHandler.cropParams->w - 1 - CROPRESIZEBORDER && x1 < cropHandler.cropParams->x + cropHandler.cropParams->w - 1 + CROPRESIZEBORDER && x < xpos + imgX + imgW; case CropInside: screenCoordToImage (x, y, x1, y1); return cropHandler.cropParams->enabled && y1 > cropHandler.cropParams->y && y1 < cropHandler.cropParams->y + cropHandler.cropParams->h - 1 && x1 > cropHandler.cropParams->x && x1 < cropHandler.cropParams->x + cropHandler.cropParams->w - 1; case CropResize: return decorated && x >= xpos + width - 16 && y >= ypos + height - 16 && x < xpos + width && y < ypos + height; case CropObserved: if (!observedCropWin) { return false; } getObservedFrameArea (x1, y1, w, h); return x >= x1 && x <= x1 + w && y >= y1 && y <= y1 + h; } return false; } void CropWindow::updateCursor (int x, int y) { EditSubscriber *editSubscriber = iarea->getCurrSubscriber(); ToolMode tm = iarea->getToolMode (); CursorShape newType = cursor_type; if (state == SNormal) { if (onArea (CropWinButtons, x, y)) { newType = CSArrow; } else if (onArea (CropToolBar, x, y)) { newType = CSMove; } else if (iarea->getObject() > -1 && editSubscriber && editSubscriber->getEditingType() == ET_OBJECTS) { int cursorX; int cursorY; screenCoordToImage (x, y, cursorX, cursorY); newType = editSubscriber->getCursor(iarea->getObject(), cursorX, cursorY); } else if (onArea (CropResize, x, y)) { newType = CSResizeDiagonal; } else if (tm == TMColorPicker && hoveredPicker) { newType = CSMove; } else if (tm == TMHand && (onArea (CropTopLeft, x, y))) { newType = CSResizeTopLeft; } else if (tm == TMHand && (onArea (CropTopRight, x, y))) { newType = CSResizeTopRight; } else if (tm == TMHand && (onArea (CropBottomLeft, x, y))) { newType = CSResizeBottomLeft; } else if (tm == TMHand && (onArea (CropBottomRight, x, y))) { newType = CSResizeBottomRight; } else if (tm == TMHand && (onArea (CropTop, x, y) || onArea (CropBottom, x, y))) { newType = CSResizeHeight; } else if (tm == TMHand && (onArea (CropLeft, x, y) || onArea (CropRight, x, y))) { newType = CSResizeWidth; } else if (onArea (CropImage, x, y)) { int objectID = -1; if (editSubscriber && editSubscriber->getEditingType() == ET_OBJECTS) { Coord cropPos; screenCoordToCropCanvas (iarea->posScreen.x, iarea->posScreen.y, cropPos.x, cropPos.y); objectID = ObjectMOBuffer::getObjectID(cropPos); } if (objectID > -1) { int cursorX; int cursorY; screenCoordToImage (x, y, cursorX, cursorY); newType = editSubscriber->getCursor(objectID, cursorX, cursorY); } else if (tm == TMHand) { if (onArea (CropObserved, x, y)) { newType = CSMove; } else { newType = CSCrosshair; } } else if (tm == TMSpotWB) { newType = CSSpotWB; } else if (tm == TMCropSelect) { newType = CSCropSelect; } else if (tm == TMStraighten) { newType = CSStraighten; } else if (tm == TMColorPicker) { newType = CSAddColPicker; } } else { int objectID = -1; if (editSubscriber && editSubscriber->getEditingType() == ET_OBJECTS) { Coord cropPos; screenCoordToCropCanvas (iarea->posScreen.x, iarea->posScreen.y, cropPos.x, cropPos.y); objectID = ObjectMOBuffer::getObjectID(cropPos); } if (objectID > -1) { int cursorX; int cursorY; screenCoordToImage (x, y, cursorX, cursorY); newType = editSubscriber->getCursor(objectID, cursorX, cursorY); } else { newType = CSArrow; } } } else if (state == SCropSelecting) { newType = CSCropSelect; } else if (state == SRotateSelecting) { newType = CSStraighten; } else if (state == SCropMove || state == SCropWinMove || state == SObservedMove) { newType = CSMove; } else if (state == SHandMove || state == SCropImgMove) { newType = CSHandClosed; } else if (state == SResizeW1 || state == SResizeW2) { newType = CSResizeWidth; } else if (state == SResizeH1 || state == SResizeH2) { newType = CSResizeHeight; } else if (state == SResizeTL) { newType = CSResizeTopLeft; } else if (state == SResizeTR) { newType = CSResizeTopRight; } else if (state == SResizeBL) { newType = CSResizeBottomLeft; } else if (state == SResizeBR) { newType = CSResizeBottomRight; } else if (state == SCropWinResize) { newType = CSResizeDiagonal; } else if (state == SDragPicker) { newType = CSMove2D; } else if (editSubscriber && editSubscriber->getEditingType() == ET_OBJECTS) { int objectID = iarea->getObject(); if (objectID > -1) { int cursorX; int cursorY; screenCoordToImage (x, y, cursorX, cursorY); newType = editSubscriber->getCursor(objectID, cursorX, cursorY); } else { newType = CSArrow; } } if (newType != cursor_type) { cursor_type = newType; CursorManager::setWidgetCursor(iarea->get_window(), cursor_type); } } void CropWindow::expose (Cairo::RefPtr cr) { MyMutex::MyLock lock(cropHandler.cimg); bool isPreviewImg = false; if (decorated) { drawDecoration (cr); } int x = xpos, y = ypos; // draw the background backColor = iarea->previewModePanel->GetbackColor(); Glib::RefPtr style = iarea->get_style_context(); options.bgcolor = backColor; if (backColor == 0) { style->render_background(cr, x + imgAreaX, y + imgAreaY, imgAreaW, imgAreaH); } else { if (backColor == 1) { cr->set_source_rgb (0, 0, 0); } else if (backColor == 2) { cr->set_source_rgb (1, 1, 1); } else if (backColor == 3) { cr->set_source_rgb (0.467, 0.467, 0.467); } cr->set_line_width (0.); cr->rectangle (x + imgAreaX, y + imgAreaY, imgAreaW, imgAreaH); cr->stroke_preserve (); cr->fill (); } // draw image if (state == SCropImgMove || state == SCropWinResize) { // draw a rough image int cropX, cropY; cropHandler.getPosition (cropX, cropY); Glib::RefPtr rough = iarea->getPreviewHandler()->getRoughImage (cropX, cropY, imgAreaW, imgAreaH, zoomSteps[cropZoom].zoom); if (rough) { int posX = x + imgAreaX + imgX; int posY = y + imgAreaY + imgY; Gdk::Cairo::set_source_pixbuf(cr, rough, posX, posY); cr->rectangle(posX, posY, rtengine::min (rough->get_width (), imgAreaW-imgX), rtengine::min (rough->get_height (), imgAreaH-imgY)); cr->fill(); // if (cropHandler.cropParams->enabled) // drawCrop (cr, x+imgX, y+imgY, imgW, imgH, cropX, cropY, zoomSteps[cropZoom].zoom, cropHandler.cropParams); } if (observedCropWin) { drawObservedFrame (cr); } } else { CropParams cropParams = *cropHandler.cropParams; if (state == SNormal) { switch (options.cropGuides) { case Options::CROP_GUIDE_NONE: cropParams.guide = procparams::CropParams::Guide::NONE; break; case Options::CROP_GUIDE_FRAME: cropParams.guide = procparams::CropParams::Guide::FRAME; break; default: break; } } bool useBgColor = (state == SNormal || state == SDragPicker || state == SDeletePicker || state == SEditDrag1); if (cropHandler.cropPixbuf) { imgW = cropHandler.cropPixbuf->get_width (); imgH = cropHandler.cropPixbuf->get_height (); exposeVersion++; const bool showR = iarea->previewModePanel->showR(); // will show clipping if R channel is clipped const bool showG = iarea->previewModePanel->showG(); // will show clipping if G channel is clipped const bool showB = iarea->previewModePanel->showB(); // will show clipping if B channel is clipped const bool showL = iarea->previewModePanel->showL(); // will show clipping if L value is clipped const bool showFocusMask = iarea->indClippedPanel->showFocusMask(); bool showcs = iarea->indClippedPanel->showClippedShadows(); bool showch = iarea->indClippedPanel->showClippedHighlights(); // While the Right-side ALT is pressed, auto-enable highlight and shadow clipping indicators // TODO: Add linux/MacOS specific functions for alternative #ifdef _WIN32 if (GetKeyState(VK_RMENU) < 0) { showcs = true; showch = true; } #endif if (showcs || showch || showR || showG || showB || showL || showFocusMask) { Glib::RefPtr tmp = cropHandler.cropPixbuf->copy (); guint8* pix = tmp->get_pixels(); guint8* pixWrkSpace = cropHandler.cropPixbuftrue->get_pixels(); const int pixRowStride = tmp->get_rowstride (); const int pixWSRowStride = cropHandler.cropPixbuftrue->get_rowstride (); const int bHeight = tmp->get_height(); const int bWidth = tmp->get_width(); if (showFocusMask) { // modulate preview to display focus mask const int blur_radius2 = 1; // radius of small kernel. 1 => 3x3 kernel const int blur_dim2 = 2 * blur_radius2 + 1; // dimension of small kernel const int blur_radius = (blur_dim2 * blur_dim2) / 2; // radius of big kernel const float kernel_size = SQR(2.f * blur_radius + 1.f); // count of pixels in the big blur kernel const float rkernel_size = 1.0f / kernel_size; // reciprocal of kernel_size to avoid divisions const float kernel_size2 = SQR(2.f * blur_radius2 + 1.f); // count of pixels in the small blur kernel const float rkernel_size2 = 1.0f / kernel_size2; // reciprocal of kernel_size to avoid divisions // allocate buffer for precalculated Luminance float* tmpL = (float*)malloc(bHeight * bWidth * sizeof(float) ); // allocate buffers for sums and sums of squares of small kernel float* tmpLsum = (float*)malloc((bHeight) * (bWidth) * sizeof(float) ); float* tmpLsumSq = (float*)malloc((bHeight) * (bWidth) * sizeof(float) ); float* tmpstdDev2 = (float*)malloc((bHeight) * (bWidth) * sizeof(float) ); float maxstdDev_L2 = 0.f; #ifdef _OPENMP #pragma omp parallel #endif { #ifdef _OPENMP #pragma omp for #endif // precalculate Luminance for(int i = 0; i < bHeight; i++) { guint8* currWS = pixWrkSpace + i * pixWSRowStride; float* currL = tmpL + i * bWidth; for(int j = 0; j < bWidth; j++) { *currL = 0.299f * (currWS)[0] + 0.587f * (currWS)[1] + 0.114f * (currWS)[2]; currL++; currWS += 3; } } float maxthrstdDev_L2 = 0.f; #ifdef _OPENMP #pragma omp for nowait #endif // precalculate sum and sum of squares of small kernel for(int i = blur_radius2; i < bHeight - blur_radius2; i++) { for(int j = blur_radius2; j < bWidth - blur_radius2; j++) { float sumL = 0.f; float sumLSqu = 0.f; for(int kh = -blur_radius2; kh <= blur_radius2; kh++) { for(int kw = -blur_radius2; kw <= blur_radius2; kw++) { float curL = tmpL[(i + kh) * bWidth + j + kw]; sumL += curL; sumLSqu += SQR(curL); } } tmpLsum[i * bWidth + j] = sumL; tmpLsumSq[i * bWidth + j] = sumLSqu; float stdDev_L2 = rkernel_size2 * sqrtf(sumLSqu * kernel_size2 - sumL * sumL); if(stdDev_L2 > maxthrstdDev_L2) { maxthrstdDev_L2 = stdDev_L2; } tmpstdDev2[i * bWidth + j] = stdDev_L2; } } #ifdef _OPENMP #pragma omp critical #endif { if(maxthrstdDev_L2 > maxstdDev_L2) { maxstdDev_L2 = maxthrstdDev_L2; } } } const float focus_thresh = 80.f; maxstdDev_L2 = std::min(maxstdDev_L2, focus_thresh); const float focus_threshby10 = focus_thresh / 10.f; #ifdef _OPENMP #pragma omp parallel for schedule(dynamic,16) #endif for (int i = blur_radius + 1; i < bHeight - blur_radius; i++) { guint8* curr = pix + i * pixRowStride + 3 * (blur_radius + 1); guint8* currWs = pixWrkSpace + i * pixWSRowStride + 3 * (blur_radius + 1); for (int j = blur_radius + 1; j < bWidth - blur_radius; j++) { //************* // Copyright (c) 2011 Michael Ezra michael@michaelezra.com // determine if pixel is in the sharp area of the image using // standard deviation analysis on two different scales //float focus_thresh2; //float opacity = 0.9;//TODO: implement opacity //TODO: evaluate effects of altering sampling frequency //TODO: dynamically determine appropriate values based on image analysis // calculate average in +-blur_radius pixels area around the current pixel // speed up: calculate sum of squares in the same loops float sum_L = 0.f; float sumsq_L = 0.f; // use precalculated values of small kernel to reduce number of iterations for (int kh = -blur_radius + blur_radius2; kh <= blur_radius - blur_radius2; kh += blur_dim2) { float* currLsum = &tmpLsum[(i + kh) * bWidth + j - blur_radius + 1]; float* currLsumSqu = &tmpLsumSq[(i + kh) * bWidth + j - blur_radius + 1]; for (int k = -blur_radius + blur_radius2; k <= blur_radius - blur_radius2; k += blur_dim2, currLsum += blur_dim2, currLsumSqu += blur_dim2) { sum_L += *currLsum; sumsq_L += *currLsumSqu; } } //float sum_L2 = tmpLsum[i * bWidth + j]; //float sumsq_L2 = tmpLsumSq[i * bWidth + j]; //************* // averages // Optimized formulas to avoid divisions float stdDev_L = rkernel_size * sqrtf(sumsq_L * kernel_size - sum_L * sum_L); float stdDev_L2 = tmpstdDev2[i * bWidth + j]; // float stdDev_L2 = rkernel_size2 * sqrtf(sumsq_L2 * kernel_size2 - sum_L2 * sum_L2); //TODO: try to normalize by average L of the entire (preview) image //detection method 1: detect focus in features //there is no strict condition between stdDev_L and stdDev_L2 themselves /* if (stdDev_L2>focus_thresh2 && (stdDev_L = stdDev_L2 //TODO: could vary this to bypass noise better && stdDev_L2 > stdDev_L //this is the key to select fine detail within lower contrast on larger scale && stdDev_L > focus_threshby10 //options.highlightThreshold ) { // transparency depends on sdtDev_L2 and maxstdDev_L2 float transparency = 1.f - std::min(stdDev_L2 / maxstdDev_L2, 1.0f) ; // first row of circle guint8* currtmp = &curr[0] + (-3 * pixRowStride); guint8* currtmpWS = &currWs[0] + (-3 * pixWSRowStride); for(int jj = -3; jj <= 3; jj += 3) { guint8* currtmpl = currtmp + jj; guint8* currtmpWSl = currtmpWS + jj; //transparent green currtmpl[0] = transparency * currtmpWSl[0]; currtmpl[1] = transparency * currtmpWSl[1] + (1.f - transparency) * 255.f; currtmpl[2] = transparency * currtmpWSl[2]; } // second row of circle currtmp = &curr[0] + (-2 * pixRowStride); currtmpWS = &currWs[0] + (-2 * pixWSRowStride); for(int jj = -6; jj <= 6; jj += 3) { guint8* currtmpl = currtmp + jj; guint8* currtmpWSl = currtmpWS + jj; //transparent green currtmpl[0] = transparency * currtmpWSl[0]; currtmpl[1] = transparency * currtmpWSl[1] + (1.f - transparency) * 255.f; currtmpl[2] = transparency * currtmpWSl[2]; } // three middle row of circle for(int ii = -1; ii <= 1; ii++) { currtmp = &curr[0] + (ii * pixRowStride); currtmpWS = &currWs[0] + (ii * pixWSRowStride); for(int jj = -9; jj <= 9; jj += 3) { guint8* currtmpl = currtmp + jj; guint8* currtmpWSl = currtmpWS + jj; //transparent green currtmpl[0] = transparency * currtmpWSl[0]; currtmpl[1] = transparency * currtmpWSl[1] + (1.f - transparency) * 255.f; currtmpl[2] = transparency * currtmpWSl[2]; } } // second last row of circle currtmp = &curr[0] + (2 * pixRowStride); currtmpWS = &currWs[0] + (2 * pixWSRowStride); for(int jj = -6; jj <= 6; jj += 3) { guint8* currtmpl = currtmp + jj; guint8* currtmpWSl = currtmpWS + jj; //transparent green currtmpl[0] = transparency * currtmpWSl[0]; currtmpl[1] = transparency * currtmpWSl[1] + (1.f - transparency) * 255.f; currtmpl[2] = transparency * currtmpWSl[2]; } // last row of circle currtmp = &curr[0] + (3 * pixRowStride); currtmpWS = &currWs[0] + (3 * pixWSRowStride); for(int jj = -3; jj <= 3; jj += 3) { guint8* currtmpl = currtmp + jj; guint8* currtmpWSl = currtmpWS + jj; //transparent green currtmpl[0] = transparency * currtmpWSl[0]; currtmpl[1] = transparency * currtmpWSl[1] + (1.f - transparency) * 255.f; currtmpl[2] = transparency * currtmpWSl[2]; } } curr += 3; currWs += 3; } } free(tmpL); free(tmpLsum); free(tmpLsumSq); free(tmpstdDev2); } else { // !showFocusMask const int hlThreshold = options.highlightThreshold; const int shThreshold = options.shadowThreshold; const float ShawdowFac = 64.f / (options.shadowThreshold + 1); const float HighlightFac = 64.f / (256 - options.highlightThreshold); const bool showclippedAny = (!showR && !showG && !showB && !showL); // will show clipping if any (all) of RGB channels is (shadow) clipped #ifdef _OPENMP #pragma omp parallel for schedule(dynamic,16) #endif for (int i = 0; i < bHeight; i++) { guint8* curr = pix + i * pixRowStride; guint8* currWS = pixWrkSpace + i * pixWSRowStride; for (int j = 0; j < bWidth; j++) { // we must compare clippings in working space, since the cropPixbuf is in sRGB, with mon profile bool changedHL = false; bool changedSH = false; int delta = 0; // for efficiency, pre-calculate currWS_L as it may be needed in both // if (showch) and if (showcs) branches int currWS_L = 0; if (showL && (showch || showcs)) { currWS_L = (int)(0.299f * currWS[0] + 0.587f * currWS[1] + 0.114f * currWS[2]); } if (showch) { if ((showclippedAny || showR) && currWS[0] >= hlThreshold ) { delta += 255 - currWS[0]; changedHL = true; } if ((showclippedAny || showG) && currWS[1] >= hlThreshold ) { delta += 255 - currWS[1]; changedHL = true; } if ((showclippedAny || showB) && currWS[2] >= hlThreshold ) { delta += 255 - currWS[2]; changedHL = true; } if (showL && currWS_L >= hlThreshold ) { delta += 255 - currWS_L ; changedHL = true; } if (changedHL) { delta *= HighlightFac; if (showclippedAny) { curr[0] = curr[1] = curr[2] = delta; // indicate clipped highlights in gray } else { curr[0] = 255; // indicate clipped highlights in red curr[1] = curr[2] = delta; } } } if (showcs) { bool scR = currWS[0] <= shThreshold; bool scG = currWS[1] <= shThreshold; bool scB = currWS[2] <= shThreshold; if (((showclippedAny && (scG && scB)) || showR) && scR ) { delta += currWS[0]; changedSH = true; } if (((showclippedAny && (scR && scB)) || showG) && scG ) { delta += currWS[1]; changedSH = true; } if (((showclippedAny && (scR && scG)) || showB) && scB ) { delta += currWS[2]; changedSH = true; } if (showL && currWS_L <= shThreshold ) { delta += currWS_L ; changedSH = true; } if (changedSH) { if (showclippedAny) { delta = 255 - (delta * ShawdowFac); curr[0] = curr[1] = curr[2] = delta; // indicate clipped shadows in gray } else { delta *= ShawdowFac; curr[2] = 255; curr[0] = curr[1] = delta; // indicate clipped shadows in blue } } } //if (showcs) // modulate the preview of channels & L; if (!changedHL && !changedSH && !showclippedAny) { //This condition allows clipping indicators for RGB channels to remain in color if (showR) { curr[1] = curr[2] = curr[0]; //Red channel in grayscale } if (showG) { curr[0] = curr[2] = curr[1]; //Green channel in grayscale } if (showB) { curr[0] = curr[1] = curr[2]; //Blue channel in grayscale } if (showL) { //Luminosity // see http://en.wikipedia.org/wiki/HSL_and_HSV#Lightness for more info //int L = (int)(0.212671*curr[0]+0.715160*curr[1]+0.072169*curr[2]); int L = (int)(0.299 * curr[0] + 0.587 * curr[1] + 0.114 * curr[2]); //Lightness - this matches Luminosity mode in Photoshop CS5 curr[0] = curr[1] = curr[2] = L; } } /* if (showch && (currWS[0]>=options.highlightThreshold || currWS[1]>=options.highlightThreshold || currWS[2]>=options.highlightThreshold)) curr[0] = curr[1] = curr[2] = 0; else if (showcs && (currWS[0]<=options.shadowThreshold || currWS[1]<=options.shadowThreshold || currWS[2]<=options.shadowThreshold)) curr[0] = curr[1] = curr[2] = 255; //if (showch && ((0.299*curr[0]+0.587*curr[1]+0.114*curr[2])>=options.highlightThreshold)) // curr[0] = curr[1] = curr[2] = 0; //else if (showcs && ((0.299*curr[0]+0.587*curr[1]+0.114*curr[2])<=options.shadowThreshold)) // curr[0] = curr[1] = curr[2] = 255; */ curr += 3; currWS += 3; } } } int posX = x + imgAreaX + imgX; int posY = y + imgAreaY + imgY; Gdk::Cairo::set_source_pixbuf(cr, tmp, posX, posY); cr->rectangle(posX, posY, rtengine::min (tmp->get_width (), imgAreaW-imgX), rtengine::min (tmp->get_height (), imgAreaH-imgY)); cr->fill(); } else { int posX = x + imgAreaX + imgX; int posY = y + imgAreaY + imgY; Gdk::Cairo::set_source_pixbuf(cr, cropHandler.cropPixbuf, posX, posY); cr->rectangle(posX, posY, rtengine::min (cropHandler.cropPixbuf->get_width (), imgAreaW-imgX), rtengine::min (cropHandler.cropPixbuf->get_height (), imgAreaH-imgY)); cr->fill(); } if (cropHandler.cropParams->enabled) { int cropX, cropY; cropHandler.getPosition (cropX, cropY); drawCrop (cr, x + imgAreaX + imgX, y + imgAreaY + imgY, imgW, imgH, cropX, cropY, zoomSteps[cropZoom].zoom, cropParams, (this == iarea->mainCropWindow), useBgColor, cropHandler.isFullDisplay ()); } if (observedCropWin) { drawObservedFrame (cr); } EditSubscriber *editSubscriber = iarea->getCurrSubscriber(); if (editSubscriber && editSubscriber->getEditingType() == ET_OBJECTS && bufferCreated()) { cr->set_line_width (0.); cr->rectangle (x + imgAreaX, y + imgAreaY, imgAreaW, imgAreaH); cr->clip(); // drawing Subscriber's visible geometry const std::vector visibleGeom = editSubscriber->getVisibleGeometry(); cr->set_antialias(Cairo::ANTIALIAS_DEFAULT); // ANTIALIAS_SUBPIXEL ? cr->set_line_cap(Cairo::LINE_CAP_SQUARE); cr->set_line_join(Cairo::LINE_JOIN_ROUND); // drawing outer lines for (auto geom : visibleGeom) { geom->drawOuterGeometry(cr, this, *this); } // drawing inner lines for (auto geom : visibleGeom) { geom->drawInnerGeometry(cr, this, *this); } // drawing to the "mouse over" channel const auto mouseOverGeom = editSubscriber->getMouseOverGeometry(); if (mouseOverGeom.size()) { if (mouseOverGeom.size() > 255) { // Once it has been switched to OM_65535, it won't return back to OM_255 // to avoid constant memory allocations in some particular situation. // It will return to OM_255 on a new editing session setObjectMode(OM_65535); } Cairo::RefPtr crMO = Cairo::Context::create(ObjectMOBuffer::getObjectMap()); crMO->set_antialias(Cairo::ANTIALIAS_NONE); crMO->set_line_cap(Cairo::LINE_CAP_SQUARE); crMO->set_line_join(Cairo::LINE_JOIN_ROUND); crMO->set_operator(Cairo::OPERATOR_SOURCE); // clear the bitmap crMO->set_source_rgba(0., 0., 0., 0.); crMO->rectangle(0., 0., ObjectMOBuffer::getObjectMap()->get_width(), ObjectMOBuffer::getObjectMap()->get_height()); crMO->set_line_width(0.); crMO->fill(); int a=0; for (auto moGeom : mouseOverGeom) { moGeom->drawToMOChannel(crMO, a, this, *this); ++a; } } if (this != iarea->mainCropWindow) { cr->reset_clip(); } } isPreviewImg = true; } else { // cropHandler.cropPixbuf is null int cropX, cropY; cropHandler.getPosition (cropX, cropY); Glib::RefPtr rough = iarea->getPreviewHandler()->getRoughImage (cropX, cropY, imgAreaW, imgAreaH, zoomSteps[cropZoom].zoom); if (rough) { int posX = x + imgAreaX + imgX; int posY = y + imgAreaY + imgY; Gdk::Cairo::set_source_pixbuf(cr, rough, posX, posY); cr->rectangle(posX, posY, rtengine::min (rough->get_width (), imgAreaW-imgX), rtengine::min (rough->get_height (), imgAreaH-imgY)); cr->fill(); if (cropHandler.cropParams->enabled) { drawCrop (cr, x + imgAreaX + imgX, y + imgAreaY + imgY, rough->get_width(), rough->get_height(), cropX, cropY, zoomSteps[cropZoom].zoom, cropParams, (this == iarea->mainCropWindow), useBgColor, cropHandler.isFullDisplay ()); } if (observedCropWin) { drawObservedFrame (cr); } } } } if (state == SRotateSelecting) { drawStraightenGuide (cr); } if (state == SNormal && isFlawnOver) { EditSubscriber *editSubscriber = iarea->getCurrSubscriber(); if (iarea->getToolMode () == TMHand && editSubscriber && editSubscriber->getEditingType() == ET_PIPETTE && iarea->getObject()) { drawUnscaledSpotRectangle (cr, iarea->getPipetteRectSize ()); } else if (iarea->getToolMode () == TMSpotWB) { drawScaledSpotRectangle (cr, iarea->getSpotWBRectSize ()); } } style->render_frame (cr, x + imgAreaX, y + imgAreaY, imgAreaW, imgAreaH); if ((state == SNormal || state == SDragPicker) && isPreviewImg && iarea->showColorPickers()) { for (auto colorPicker : colorPickers) { colorPicker->draw(cr); } } //t2.set (); // printf ("etime --> %d, %d\n", t2.etime (t1), t4.etime (t3)); } void CropWindow::setEditSubscriber (EditSubscriber* newSubscriber) { // Delete, create, update all buffers based upon newSubscriber's type if (newSubscriber) { ObjectMOBuffer::resize (imgAreaW, imgAreaH); } else { ObjectMOBuffer::flush (); } cropHandler.setEditSubscriber(newSubscriber); } // zoom* is called from the zoomPanel or the scroll wheel in the preview area void CropWindow::zoomIn (bool toCursor, int cursorX, int cursorY) { int x = -1; int y = -1; if (toCursor) { x = cursorX; y = cursorY; } else { if (zoomSteps[cropZoom].zoom <= cropHandler.getFitZoom()) { if (cropHandler.cropParams->enabled) { x = cropHandler.cropParams->x + cropHandler.cropParams->w / 2; y = cropHandler.cropParams->y + cropHandler.cropParams->h / 2; } else { int fw, fh; cropHandler.getFullImageSize(fw, fh); x = fw / 2; y = fh / 2; } zoomVersion = exposeVersion; } else if (zoomVersion != exposeVersion) { screenCoordToImage(xpos + imgX + imgW / 2, ypos + imgY + imgH / 2, x, y); if (cropHandler.cropParams->enabled) { // add some gravity towards crop center int x1 = cropHandler.cropParams->x + cropHandler.cropParams->w / 2; int y1 = cropHandler.cropParams->y + cropHandler.cropParams->h / 2; double cropd = sqrt(cropHandler.cropParams->h * cropHandler.cropParams->h + cropHandler.cropParams->w * cropHandler.cropParams->w) * zoomSteps[cropZoom].zoom; double imd = sqrt(imgW * imgW + imgH * imgH); double d; // the more we can see of the crop, the more gravity towards crop center if (cropd > imd) { d = 0.8; } else if (cropd < imd * 0.5) { d = 0.0; } else { d = 1.6 * (cropd - imd * 0.5) / imd; } x = d * x + (1.0 - d) * x1; y = d * y + (1.0 - d) * y1; } zoomVersion = exposeVersion; } } int z = cropZoom + 1; while (z < int(zoomSteps.size()) && !zoomSteps[z].is_major) { ++z; } changeZoom (z, true, x, y); fitZoom = false; } void CropWindow::zoomOut (bool toCursor, int cursorX, int cursorY) { int x = -1; int y = -1; if (toCursor) { x = cursorX; y = cursorY; } else { screenCoordToImage(xpos + imgX + imgW / 2, ypos + imgY + imgH / 2, x, y); } zoomVersion = exposeVersion; int z = cropZoom - 1; while (z >= 0 && !zoomSteps[z].is_major) { --z; } changeZoom (z, true, x, y); fitZoom = false; } void CropWindow::zoom11 (bool notify) { int x = -1; int y = -1; if (zoomSteps[cropZoom].zoom <= cropHandler.getFitZoom()) { if (cropHandler.cropParams->enabled) { x = cropHandler.cropParams->x + cropHandler.cropParams->w / 2; y = cropHandler.cropParams->y + cropHandler.cropParams->h / 2; } else { int fw, fh; cropHandler.getFullImageSize(fw, fh); x = fw / 2; y = fh / 2; } zoomVersion = exposeVersion; } else { screenCoordToImage(xpos + imgX + imgW / 2, ypos + imgY + imgH / 2, x, y); } changeZoom (zoom11index, notify, x, y, notify); fitZoom = false; } double CropWindow::getZoom () { return zoomSteps[cropZoom].zoom; } bool CropWindow::isMinZoom () { return cropZoom <= 0; } bool CropWindow::isMaxZoom () { return cropZoom >= int(zoomSteps.size())-1; } void CropWindow::setZoom (double zoom) { int cz = int(zoomSteps.size())-1; if (zoom < zoomSteps[0].zoom) { cz = 0; } else for (int i = 0; i < int(zoomSteps.size())-1; i++) if (zoomSteps[i].zoom <= zoom && zoomSteps[i + 1].zoom > zoom) { cz = i; break; } changeZoom (cz, false); } double CropWindow::getZoomFitVal () { double z = cropHandler.getFitZoom (); int cz = int(zoomSteps.size())-1; if (z < zoomSteps[0].zoom) { cz = 0; } else for (int i = 0; i < int(zoomSteps.size())-1; i++) if (zoomSteps[i].zoom <= z && zoomSteps[i + 1].zoom > z) { cz = i; break; } return zoomSteps[cz].zoom; } void CropWindow::zoomFit () { double z = cropHandler.getFitZoom (); int cz = int(zoomSteps.size())-1; if (z < zoomSteps[0].zoom) { cz = 0; } else for (int i = 0; i < int(zoomSteps.size())-1; i++) if (zoomSteps[i].zoom <= z && zoomSteps[i + 1].zoom > z) { cz = i; break; } zoomVersion = exposeVersion; changeZoom (cz, true, -1, -1); fitZoom = true; } void CropWindow::zoomFitCrop () { if(cropHandler.cropParams->enabled) { double z = cropHandler.getFitCropZoom (); int cz = int(zoomSteps.size())-1; if (z < zoomSteps[0].zoom) { cz = 0; } else for (int i = 0; i < int(zoomSteps.size())-1; i++) if (zoomSteps[i].zoom <= z && zoomSteps[i + 1].zoom > z) { cz = i; break; } zoomVersion = exposeVersion; int centerX, centerY; centerX = cropHandler.cropParams->x + cropHandler.cropParams->w / 2; centerY = cropHandler.cropParams->y + cropHandler.cropParams->h / 2; setCropAnchorPosition(centerX, centerY); changeZoom (cz, true, centerX, centerY); fitZoom = options.cropAutoFit; } else { zoomFit(); } } void CropWindow::buttonPressed (LWButton* button, int actionCode, void* actionData) { if (button == bZoomIn) { // zoom in zoomIn (); } else if (button == bZoomOut) { // zoom out zoomOut (); } else if (button == bZoom100) { // zoom 100 zoom11 (); } else if (button == bClose) { // close if(iarea->getImProcCoordinator()->updateTryLock()) { deleted = true; iarea->cropWindowClosed (this); iarea->getImProcCoordinator()->updateUnLock(); } } } void CropWindow::redrawNeeded (LWButton* button) { iarea->redraw (); } void CropWindow::updateHoveredPicker (rtengine::Coord *imgPos) { if (!hoveredPicker) { return; } rtengine::Coord cropPos; if (imgPos) { imageCoordToCropImage(imgPos->x, imgPos->y, cropPos.x, cropPos.y); hoveredPicker->setPosition (*imgPos); } else { rtengine::Coord imgPos2; hoveredPicker->getImagePosition(imgPos2); imageCoordToCropImage(imgPos2.x, imgPos2.y, cropPos.x, cropPos.y); } LockableColorPicker::Validity validity = checkValidity (hoveredPicker, cropPos); hoveredPicker->setValidity (validity); { MyMutex::MyLock lock(cropHandler.cimg); if (validity == LockableColorPicker::Validity::INSIDE) { float r=0.f, g=0.f, b=0.f; float rpreview=0.f, gpreview=0.f, bpreview=0.f; cropHandler.colorPick(cropPos, r, g, b, rpreview, gpreview, bpreview, hoveredPicker->getSize()); hoveredPicker->setRGB (r, g, b, rpreview, gpreview, bpreview); } } } void CropWindow::changeZoom (int zoom, bool notify, int centerx, int centery, bool needsRedraw) { if (zoom < 0) { zoom = 0; } else if (zoom > int(zoomSteps.size())-1) { zoom = int(zoomSteps.size())-1; } // Limit zoom according to user preferences double zoomLimit = zoomLimitToFraction(options.maxZoomLimit); while(zoomSteps[zoom].zoom > zoomLimit && zoom != 0) { --zoom; } cropZoom = zoom; cropLabel = zoomSteps[cropZoom].label; cropHandler.setZoom (zoomSteps[cropZoom].czoom, centerx, centery); if (notify) for (auto listener : listeners) { listener->cropZoomChanged (this); } if (needsRedraw) iarea->redraw (); } LockableColorPicker::Validity CropWindow::checkValidity (LockableColorPicker* picker, const rtengine::Coord &pos) { if (!cropHandler.cropPixbuftrue) { return LockableColorPicker::Validity::OUTSIDE; } rtengine::Coord cropTopLeft, cropBottomRight, cropSize; int skip; cropHandler.getWindow(cropTopLeft.x, cropTopLeft.y, cropSize.x, cropSize.y, skip); cropBottomRight = cropTopLeft + cropSize; rtengine::Coord pickerPos, cropPickerPos; picker->getImagePosition(pickerPos); rtengine::Coord minPos(0, 0); rtengine::Coord maxPos(cropHandler.cropPixbuftrue->get_width(), cropHandler.cropPixbuftrue->get_height()); rtengine::Coord halfPickerSize((int)picker->getSize()/2, (int)picker->getSize()/2); imageCoordToCropImage (pickerPos.x, pickerPos.y, cropPickerPos.x, cropPickerPos.y); imageCoordToCropImage (cropTopLeft.x, cropTopLeft.y, minPos.x, minPos.y); imageCoordToCropImage (cropBottomRight.x, cropBottomRight.y, maxPos.x, maxPos.y); rtengine::Coord pickerMinPos = cropPickerPos - halfPickerSize; rtengine::Coord pickerMaxPos = cropPickerPos + halfPickerSize; if (pickerMaxPos.x < minPos.x || pickerMaxPos.y < minPos.y || pickerMinPos.x > maxPos.x || pickerMinPos.y > maxPos.y) { return LockableColorPicker::Validity::OUTSIDE; } else if (pickerMinPos >= minPos && pickerMaxPos < maxPos) { return LockableColorPicker::Validity::INSIDE; } else { return LockableColorPicker::Validity::CROSSING; } } void CropWindow::deleteColorPickers () { for (auto colorPicker : colorPickers) { delete colorPicker; } colorPickers.clear(); } void CropWindow::screenCoordToCropBuffer (int phyx, int phyy, int& cropx, int& cropy) { rtengine::Crop* crop = static_cast(cropHandler.getCrop()); cropx = phyx - xpos - imgX - imgAreaX; cropy = phyy - ypos - imgY - imgAreaY; if (zoomSteps[cropZoom].zoom > 1.) { cropx = int(double(cropx) / zoomSteps[cropZoom].zoom); cropy = int(double(cropy) / zoomSteps[cropZoom].zoom); } else { float czoom = float((zoomSteps[cropZoom].czoom/10) * 10) / float(zoomSteps[cropZoom].czoom); cropx = cropx / czoom; cropy = cropy / czoom; } cropx += crop->getLeftBorder(); cropy += crop->getUpperBorder(); } void CropWindow::screenCoordToImage (int phyx, int phyy, int& imgx, int& imgy) { int cropX, cropY; cropHandler.getPosition (cropX, cropY); imgx = cropX + (phyx - xpos - imgX - imgAreaX) / zoomSteps[cropZoom].zoom; imgy = cropY + (phyy - ypos - imgY - imgAreaY) / zoomSteps[cropZoom].zoom; } void CropWindow::screenCoordToCropCanvas (int phyx, int phyy, int& prevx, int& prevy) { prevx = phyx - xpos - imgAreaX; prevy = phyy - ypos - imgAreaY; } void CropWindow::imageCoordToScreen (int imgx, int imgy, int& phyx, int& phyy) { int cropX, cropY; cropHandler.getPosition (cropX, cropY); phyx = (imgx - cropX) * zoomSteps[cropZoom].zoom + xpos + imgX + imgAreaX; phyy = (imgy - cropY) * zoomSteps[cropZoom].zoom + ypos + imgY + imgAreaY; } void CropWindow::imageCoordToCropCanvas (int imgx, int imgy, int& phyx, int& phyy) { int cropX, cropY; cropHandler.getPosition (cropX, cropY); phyx = (imgx - cropX) * zoomSteps[cropZoom].zoom + imgX; phyy = (imgy - cropY) * zoomSteps[cropZoom].zoom + imgY; } void CropWindow::imageCoordToCropBuffer (int imgx, int imgy, int& phyx, int& phyy) { int cropX, cropY; rtengine::Crop* crop = static_cast(cropHandler.getCrop()); cropHandler.getPosition (cropX, cropY); phyx = (imgx - cropX) * zoomSteps[cropZoom].zoom + /*xpos + imgX +*/ crop->getLeftBorder(); phyy = (imgy - cropY) * zoomSteps[cropZoom].zoom + /*ypos + imgY +*/ crop->getUpperBorder(); } void CropWindow::imageCoordToCropImage (int imgx, int imgy, int& phyx, int& phyy) { int cropX, cropY; cropHandler.getPosition (cropX, cropY); phyx = (imgx - cropX) * zoomSteps[cropZoom].zoom; phyy = (imgy - cropY) * zoomSteps[cropZoom].zoom; } int CropWindow::scaleValueToImage (int value) { return int(double(value) / zoomSteps[cropZoom].zoom); } float CropWindow::scaleValueToImage (float value) { return float(double(value) / zoomSteps[cropZoom].zoom); } double CropWindow::scaleValueToImage (double value) { return value / zoomSteps[cropZoom].zoom; } int CropWindow::scaleValueToCanvas (int value) { return int(double(value) * zoomSteps[cropZoom].zoom); } float CropWindow::scaleValueToCanvas (float value) { return float(double(value) * zoomSteps[cropZoom].zoom); } double CropWindow::scaleValueToCanvas (double value) { return value * zoomSteps[cropZoom].zoom; } void CropWindow::drawDecoration (Cairo::RefPtr cr) { int x = xpos, y = ypos; // prepare label Glib::RefPtr context = iarea->get_pango_context () ; Pango::FontDescription fontd = iarea->get_style_context()->get_font(); fontd.set_weight (Pango::WEIGHT_BOLD); const int fontSize = 8; // pt // Non-absolute size is defined in "Pango units" and shall be multiplied by // Pango::SCALE from "pt": fontd.set_size (fontSize * Pango::SCALE); context->set_font_description (fontd); Glib::RefPtr cllayout = iarea->create_pango_layout(cropLabel); int iw, ih; cllayout->get_pixel_size (iw, ih); // draw decoration (border) int h = height, w = width; cr->set_source_rgb (0.1, 0.1, 0.1); cr->set_line_width (1.0); cr->move_to (x + 2.5, y + titleHeight + 2.5 ); cr->line_to (x + 2.5, y + h - 2.5); cr->line_to (x + w - 2.5, y + h - 2.5); cr->line_to (x + w - 2.5, y + titleHeight + 2.5 ); cr->set_source_rgba (0.0, 0.0, 0.0, 0.5); cr->rectangle (x + 2.5, y + 0.5, w - 5, titleHeight + 2); cr->stroke_preserve (); cr->fill (); // draw label cr->set_source_rgba (1, 1, 1, 0.5); cr->move_to (x + 10 + sideBorderWidth + bZoomIn->getIcon()->getWidth() + bZoomOut->getIcon()->getWidth() + bZoom100->getIcon()->getWidth(), y + 1 + upperBorderWidth + (titleHeight - ih) / 2); cllayout->add_to_cairo_context (cr); cr->fill (); buttonSet.redraw (cr); } void CropWindow::drawStraightenGuide (Cairo::RefPtr cr) { if (action_x != press_x || action_y != press_y) { double arg = (press_x - action_x) / sqrt(double((press_x - action_x) * (press_x - action_x) + (press_y - action_y) * (press_y - action_y))); double sol1, sol2; double pi = rtengine::RT_PI; if (press_y > action_y) { sol1 = acos(arg) * 180 / pi; sol2 = -acos(-arg) * 180 / pi; } else { sol1 = acos(-arg) * 180 / pi; sol2 = -acos(arg) * 180 / pi; } if (fabs(sol1) < fabs(sol2)) { rot_deg = sol1; } else { rot_deg = sol2; } if (rot_deg < -45) { rot_deg = 90.0 + rot_deg; } else if (rot_deg > 45) { rot_deg = - 90.0 + rot_deg; } } else { rot_deg = 0; } Glib::RefPtr context = iarea->get_pango_context () ; Pango::FontDescription fontd = iarea->get_style_context()->get_font(); fontd.set_weight (Pango::WEIGHT_BOLD); const int fontSize = 8; // pt // Non-absolute size is defined in "Pango units" and shall be multiplied by // Pango::SCALE from "pt": fontd.set_size (fontSize * Pango::SCALE); context->set_font_description (fontd); Glib::RefPtr deglayout = iarea->create_pango_layout(Glib::ustring::compose ("%1 deg", Glib::ustring::format(std::setprecision(2), rot_deg))); int x1 = press_x; int y1 = press_y; int y2 = action_y; int x2 = action_x; /* if (x1<0) x1 = 0; if (y1<0) y1 = 0; if (x2<0) x2 = 0; if (y2<0) y2 = 0; if (x2>=image->getWidth()) x2 = image->getWidth()-1; if (y2>=image->getHeight()) y2 = image->getHeight()-1; if (x1>=image->getWidth()) x1 = image->getWidth()-1; if (y1>=image->getHeight()) y1 = image->getHeight()-1; */ cr->set_line_width (1); cr->set_source_rgba (1.0, 1.0, 1.0, 0.618); cr->move_to (x1 + 0.5, y1 + 0.5); cr->line_to (x2 + 0.5, y2 + 0.5); cr->stroke (); cr->set_source_rgba (0.0, 0.0, 0.0, 0.618); std::valarray ds (1); ds[0] = 4; cr->set_dash (ds, 0); cr->move_to (x1 + 0.5, y1 + 0.5); cr->line_to (x2 + 0.5, y2 + 0.5); cr->stroke (); if (press_x != action_x && press_y != action_y) { cr->set_source_rgb (0.0, 0.0, 0.0); cr->move_to ((x1 + x2) / 2 + 1, (y1 + y2) / 2 + 1); deglayout->add_to_cairo_context (cr); cr->move_to ((x1 + x2) / 2 + 1, (y1 + y2) / 2 - 1); deglayout->add_to_cairo_context (cr); cr->move_to ((x1 + x2) / 2 - 1, (y1 + y2) / 2 + 1); deglayout->add_to_cairo_context (cr); cr->move_to ((x1 + x2) / 2 + 1, (y1 + y2) / 2 + 1); deglayout->add_to_cairo_context (cr); cr->fill (); cr->set_source_rgb (1.0, 1.0, 1.0); cr->move_to ((x1 + x2) / 2, (y1 + y2) / 2); deglayout->add_to_cairo_context (cr); cr->fill (); } } void CropWindow::drawScaledSpotRectangle (Cairo::RefPtr cr, int rectSize) { int x1 = action_x / zoomSteps[cropZoom].zoom - rectSize; int y1 = action_y / zoomSteps[cropZoom].zoom - rectSize; int y2 = action_y / zoomSteps[cropZoom].zoom + rectSize; int x2 = action_x / zoomSteps[cropZoom].zoom + rectSize; cr->set_line_width (1.0); cr->rectangle (xpos + imgX + imgAreaX - 0.5, ypos + imgY + imgAreaY - 0.5, imgAreaW, imgAreaH); cr->clip (); cr->set_source_rgb (1.0, 1.0, 1.0); cr->rectangle (x1 * zoomSteps[cropZoom].zoom - 1.5, y1 * zoomSteps[cropZoom].zoom - 1.5, x2 * zoomSteps[cropZoom].zoom - x1 * zoomSteps[cropZoom].zoom + 2, y2 * zoomSteps[cropZoom].zoom - y1 * zoomSteps[cropZoom].zoom + 2); cr->stroke (); cr->set_source_rgb (0.0, 0.0, 0.0); cr->rectangle (x1 * zoomSteps[cropZoom].zoom - 0.5, y1 * zoomSteps[cropZoom].zoom - 0.5, x2 * zoomSteps[cropZoom].zoom - x1 * zoomSteps[cropZoom].zoom, y2 * zoomSteps[cropZoom].zoom - y1 * zoomSteps[cropZoom].zoom); cr->stroke (); cr->reset_clip (); } void CropWindow::drawUnscaledSpotRectangle (Cairo::RefPtr cr, int rectSize) { int x1 = action_x - rectSize; int y1 = action_y - rectSize; int y2 = action_y + rectSize; int x2 = action_x + rectSize; cr->set_line_width (1.0); cr->rectangle (xpos + imgX + imgAreaX - 0.5, ypos + imgY + imgAreaY - 0.5, imgAreaW, imgAreaH); cr->clip (); cr->set_source_rgb (1.0, 1.0, 1.0); cr->rectangle (x1 - 1.5, y1 - 1.5, x2 - x1 + 2, y2 - y1 + 2); cr->stroke (); cr->set_source_rgb (0.0, 0.0, 0.0); cr->rectangle (x1 - 0.5, y1 - 0.5, x2 - x1, y2 - y1); cr->stroke (); cr->reset_clip (); } void CropWindow::getObservedFrameArea (int& x, int& y, int& w, int& h, int rw, int rh) { int observedCropX, observedCropY, observedCropW, observedCropH; observedCropWin->getCropRectangle (observedCropX, observedCropY, observedCropW, observedCropH); int mainCropX, mainCropY, mainCropW, mainCropH; getCropRectangle (mainCropX, mainCropY, mainCropW, mainCropH); // translate it to screen coordinates if (rw) { // rw and rh are the rough image's dimension x = xpos + imgAreaX + (imgAreaW - rw) / 2 + (observedCropX - mainCropX) * zoomSteps[cropZoom].zoom; y = ypos + imgAreaY + (imgAreaH - rh) / 2 + (observedCropY - mainCropY) * zoomSteps[cropZoom].zoom; } else { x = xpos + imgX + (observedCropX - mainCropX) * zoomSteps[cropZoom].zoom; y = ypos + imgY + (observedCropY - mainCropY) * zoomSteps[cropZoom].zoom; } w = observedCropW * zoomSteps[cropZoom].zoom; h = observedCropH * zoomSteps[cropZoom].zoom; } void CropWindow::drawObservedFrame (Cairo::RefPtr cr, int rw, int rh) { int x, y, w, h; getObservedFrameArea (x, y, w, h, rw, rh); // draw a black "shadow" line cr->set_source_rgba( 0, 0, 0, 0.65); cr->set_line_width (1); cr->rectangle (x - 0.5, y - 0.5, w + 4, h + 4); cr->stroke (); // draw a "frame" line. Color of frame line can be set in preferences cr->set_source_rgba(options.navGuideBrush[0], options.navGuideBrush[1], options.navGuideBrush[2], options.navGuideBrush[3]); //( 1, 1, 1, 1.0); cr->rectangle (x - 1.5, y - 1.5, w + 4, h + 4); cr->stroke (); } void CropWindow::cropImageUpdated () { MyMutex::MyLock lock(cropHandler.cimg); for (auto colorPicker : colorPickers) { Coord imgPos, cropPos; colorPicker->getImagePosition(imgPos); imageCoordToCropImage(imgPos.x, imgPos.y, cropPos.x, cropPos.y); float r=0.f, g=0.f, b=0.f; float rpreview=0.f, gpreview=0.f, bpreview=0.f; colorPicker->setValidity (checkValidity (colorPicker, cropPos)); cropHandler.colorPick(cropPos, r, g, b, rpreview, gpreview, bpreview, colorPicker->getSize()); colorPicker->setRGB (r, g, b, rpreview, gpreview, bpreview); } iarea->redraw (); } void CropWindow::cropWindowChanged () { if (!decorated) { iarea->syncBeforeAfterViews (); } iarea->redraw (); } void CropWindow::initialImageArrived () { for (auto listener : listeners) { listener->initialImageArrived(); } } void CropWindow::setDisplayPosition (int x, int y) { imgX = x; imgY = y; } void CropWindow::remoteMove (int deltaX, int deltaY) { state = SCropImgMove; cropHandler.moveAnchor(deltaX, deltaY, false); for (auto listener : listeners) { listener->cropPositionChanged (this); } } void CropWindow::remoteMoveReady () { cropHandler.update (); state = SNormal; for (auto listener : listeners) { listener->cropPositionChanged (this); } } void CropWindow::delCropWindowListener (CropWindowListener* l) { std::list::iterator i = listeners.begin(); while (i != listeners.end()) if (*i == l) { i = listeners.erase (i); } else { ++i; } } ImageArea* CropWindow::getImageArea() { return iarea; } void CropWindow::setCropGUIListener (CropGUIListener* cgl) { cropgl = cgl; } void CropWindow::setPointerMotionListener (PointerMotionListener* pml) { pmlistener = pml; if (pml) { pml->signal_cycle_rgb().connect( sigc::mem_fun(*this, &CropWindow::cycleRGB) ); pml->signal_cycle_hsv().connect( sigc::mem_fun(*this, &CropWindow::cycleHSV) ); } } PointerMotionListener* CropWindow::getPointerMotionListener () { return pmlistener; } void CropWindow::setPointerMotionHListener (PointerMotionListener* pml) { pmhlistener = pml; } // crop window listeners void CropWindow::addCropWindowListener (CropWindowListener* l) { listeners.push_back (l); } void CropWindow::cycleRGB () { bool redraw = false; for (auto colorPicker : colorPickers) { redraw |= colorPicker->cycleRGB (); } if (redraw) { iarea->redraw (); } } void CropWindow::cycleHSV () { bool redraw = false; for (auto colorPicker : colorPickers) { redraw |= colorPicker->cycleHSV (); } if (redraw) { iarea->redraw (); } }