/* * 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 . */ #ifndef _EDIT_H_ #define _EDIT_H_ #include #include "../rtengine/imagefloat.h" #include "editid.h" #include "cursormanager.h" #include "../rtengine/rt_math.h" class EditDataProvider; namespace rtengine { class EditBuffer; } /** @file * * The Edit mechanism is designed to let tools (subscribers) communicate with the preview area (provider). * Subscribers will be tools that need to create some graphics in the preview area, to let the user interact * with it in a more user friendly way. * * Do not confuse with a _local_ editing, which is another topic implemented in another class. The Edit feature * is also not supported in batch editing from the File Browser. * * Each Edit tool must have a unique ID, that will identify them, and which let the ImProcCoordinator or other * mechanism act as appropriated. They are all defined in rtgui/editid.h. * * ## How does it works? * * When a tool is being constructed, unique IDs are affected to the EditSubscribers. Then the EditorPanel class * will ask all ToolPanel to register the 'after' preview ImageArea object as data provider. The Subscribers * have now to provide a toggle button to click on to start the Edit listening. When toggling on, the Subscriber * register itself to the DataProvider, then an event is thrown through the standard ToolPanelListener::panelChanged * method to update the preview with new graphics to be displayed, and eventually buffers to create and populate. * * When the Edit process stops, the Subscriber is removed from the DataProvider, so buffer can be freed up. * A new ToolPanelListener::panelChanged event is also thrown to update the preview again, without the tool's * graphical objects. The Edit button is also toggled off (by the user or programmatically). * * It means that each Edit buttons toggled on will start an update of the preview which might or might not create * a new History entry, depending on the event. * */ class PolarCoord; // Do not confuse with rtengine::Coord2D, this one is for the GUI class Coord { public: int x; int y; Coord() : x(-1), y(-1) {} Coord(int x, int y) : x(x), y(y) {} void set (int x, int y) { this->x = x; this->y = y; } void setFromPolar(PolarCoord polar); /// @brief Clip the coord to stay in the width x height bounds /// @return true if the x or y coordinate has changed bool clip(int width, int height) { int trimmedX = rtengine::LIM(x, 0, width); int trimmedY = rtengine::LIM(y, 0, height); bool retval = trimmedX!=x || trimmedY!=y; x = trimmedX; y = trimmedY; return retval; } void operator+=(const Coord & rhs) { x += rhs.x; y += rhs.y; } void operator-=(const Coord & rhs) { x -= rhs.x; y -= rhs.y; } void operator*=(double scale) { x *= scale; y *= scale; } Coord operator+(Coord & rhs) { Coord result(x+rhs.x, y+rhs.y); return result; } Coord operator-(Coord & rhs) { Coord result(x-rhs.x, y-rhs.y); return result; } Coord operator*(double scale) { Coord result(x*scale, y*scale); return result; } }; class PolarCoord { public: double radius; double angle; // degree PolarCoord() : radius(1.), angle(0.) {} PolarCoord(double radius, double angle) : radius(radius), angle(angle) {} void set (double radius, double angle) { this->radius = radius; this->angle = angle; } void setFromCartesian(Coord start, Coord end) { Coord delta(end.x-start.x, end.y-start.y); setFromCartesian(delta); } void setFromCartesian(Coord delta) { if (!delta.x && !delta.y) { // null vector, we set to a default value radius = 1.; angle = 0.; return; } double x_ = double(delta.x); double y_ = double(delta.y); radius = sqrt(x_*x_+y_*y_); if (delta.x>0.) { if (delta.y>=0.) angle = atan(y_/x_)/(2*M_PI)*360.; else if (delta.y<0.) angle = (atan(y_/x_)+2*M_PI)/(2*M_PI)*360.; } else if (delta.x<0.) angle = (atan(y_/x_)+M_PI)/(2*M_PI)*360.; else if (delta.x==0.) { if (delta.y>0.) angle = 90.; else angle = 270.; } } void operator+=(const PolarCoord & rhs) { Coord thisCoord, rhsCoord; thisCoord.setFromPolar(*this); rhsCoord.setFromPolar(rhs); thisCoord += rhsCoord; setFromCartesian(thisCoord); } void operator-=(const PolarCoord & rhs) { Coord thisCoord, rhsCoord; thisCoord.setFromPolar(*this); rhsCoord.setFromPolar(rhs); thisCoord -= rhsCoord; setFromCartesian(thisCoord); } void operator*=(double scale) { radius *= scale; } PolarCoord operator+(PolarCoord & rhs) { Coord thisCoord, rhsCoord; thisCoord.setFromPolar(*this); rhsCoord.setFromPolar(rhs); thisCoord += rhsCoord; PolarCoord result; result.setFromCartesian(thisCoord); return result; } PolarCoord operator-(PolarCoord & rhs) { Coord thisCoord, rhsCoord; thisCoord.setFromPolar(*this); rhsCoord.setFromPolar(rhs); thisCoord -= rhsCoord; PolarCoord result; result.setFromCartesian(thisCoord); return result; } Coord operator*(double scale) { Coord result(radius*scale, angle); return result; } }; /** @brief Coordinate system where the widgets will be drawn * * The EditCoordSystem is used to define a screen and an image coordinate system. */ class EditCoordSystem { public: virtual ~EditCoordSystem() {} /// Convert the widget's DrawingArea (i.e. preview area) coords to the edit buffer coords virtual void screenCoordToCropBuffer (int phyx, int phyy, int& cropx, int& cropy) =0; /// Convert the widget's DrawingArea (i.e. preview area) coords to the full image coords virtual void screenCoordToImage (int phyx, int phyy, int& imgx, int& imgy) =0; /// Convert the image coords to the widget's DrawingArea (i.e. preview area) coords virtual void imageCoordToScreen (int imgx, int imgy, int& phyx, int& phyy) =0; /// Convert the image coords to the edit buffer coords virtual void imageCoordToCropBuffer (int imgx, int imgy, int& phyx, int& phyy) =0; /// Convert a size value from the preview's scale to the image's scale virtual int scaleValueToImage (int value) =0; /// Convert a size value from the preview's scale to the image's scale virtual float scaleValueToImage (float value) =0; /// Convert a size value from the preview's scale to the image's scale virtual double scaleValueToImage (double value) =0; /// Convert a size value from the image's scale to the preview's scale virtual int scaleValueToScreen (int value) =0; /// Convert a size value from the image's scale to the preview's scale virtual float scaleValueToScreen (float value) =0; /// Convert a size value from the image's scale to the preview's scale virtual double scaleValueToScreen (double value) =0; }; class RGBColor { double r; double g; double b; public: RGBColor () : r(0.), g(0.), b(0.) {} explicit RGBColor (double r, double g, double b) : r(r), g(g), b(b) {} explicit RGBColor (char r, char g, char b) : r(double(r)/255.), g(double(g)/255.), b(double(b)/255.) {} void setColor(double r, double g, double b) { this->r = r; this->g = g; this->b = b; } void setColor(char r, char g, char b) { this->r = double(r)/255.; this->g = double(g)/255.; this->b = double(b)/255.; } double getR() { return r; } double getG() { return g; } double getB() { return b; } }; class Geometry { public: enum State { NORMAL, PRELIGHT, DRAGGED }; enum Datum { IMAGE, CLICKED_POINT, CURSOR }; enum Flags { ACTIVE = 1<<0, // true if the geometry is active and have to be drawn AUTO_COLOR = 1<<1, // true if the color depend on the state value, not the color field above }; protected: RGBColor innerLineColor; RGBColor outerLineColor; short flags; public: float innerLineWidth; // ...outerLineWidth = innerLineWidth+2 Datum datum; State state; // set by the Subscriber Geometry () : innerLineColor(char(255), char(255), char(255)), outerLineColor(char(0), char(0), char(0)), flags(ACTIVE|AUTO_COLOR), innerLineWidth(1.f), datum(IMAGE), state(NORMAL) {} virtual ~Geometry() {} void setInnerLineColor (double r, double g, double b) { innerLineColor.setColor(r, g, b); flags &= ~AUTO_COLOR; } void setInnerLineColor (char r, char g, char b) { innerLineColor.setColor(r, g, b); flags &= ~AUTO_COLOR; } RGBColor getInnerLineColor (); void setOuterLineColor (double r, double g, double b) { outerLineColor.setColor(r, g, b); flags &= ~AUTO_COLOR; } void setOuterLineColor (char r, char g, char b) { outerLineColor.setColor(r, g, b); flags &= ~AUTO_COLOR; } RGBColor getOuterLineColor (); double getOuterLineWidth () { return double(innerLineWidth)+2.; } double getMouseOverLineWidth () { return getOuterLineWidth()+2.; } void setAutoColor (bool aColor) { if (aColor) flags |= AUTO_COLOR; else flags &= ~AUTO_COLOR; } void setActive (bool active) { if (active) flags |= ACTIVE; else flags &= ~ACTIVE; } virtual void drawOuterGeometry (Cairo::RefPtr &cr, rtengine::EditBuffer *parent, EditCoordSystem &coordSystem) =0; virtual void drawInnerGeometry (Cairo::RefPtr &cr, rtengine::EditBuffer *parent, EditCoordSystem &coordSystem) =0; virtual void drawToMOChannel (Cairo::RefPtr &cr, Cairo::RefPtr &cr2, unsigned short id, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem) =0; }; class Circle : public Geometry { public: Coord center; int radius; bool filled; bool radiusInImageSpace; /// If true, the radius depend on the image scale; if false, it is a fixed 'screen' size Circle () : center(100,100), radius(10), filled(false), radiusInImageSpace(false) {} Circle (Coord ¢er, int radius, bool filled=false, bool radiusInImageSpace=false) : center(center), radius(radius), filled(filled), radiusInImageSpace(radiusInImageSpace) {} Circle (int centerX, int centerY, int radius, bool filled=false, bool radiusInImageSpace=false) : center(centerX, centerY), radius(radius), filled(filled), radiusInImageSpace(radiusInImageSpace) {} void drawOuterGeometry (Cairo::RefPtr &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem); void drawInnerGeometry (Cairo::RefPtr &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem); void drawToMOChannel (Cairo::RefPtr &cr, Cairo::RefPtr &cr2, unsigned short id, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem); }; class Line : public Geometry { public: Coord begin; Coord end; Line () : begin(10,10), end(100,100) {} Line (Coord &begin, Coord &end) : begin(begin), end(end) {} Line (int beginX, int beginY, int endX, int endY) : begin(beginX, beginY), end(endX, endY) {} void drawOuterGeometry (Cairo::RefPtr &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem); void drawInnerGeometry (Cairo::RefPtr &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem); void drawToMOChannel (Cairo::RefPtr &cr, Cairo::RefPtr &cr2, unsigned short id, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem); }; class Polyline : public Geometry { public: std::vector points; bool filled; Polyline() : filled(false) {} void drawOuterGeometry (Cairo::RefPtr &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem); void drawInnerGeometry (Cairo::RefPtr &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem); void drawToMOChannel (Cairo::RefPtr &cr, Cairo::RefPtr &cr2, unsigned short id, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem); }; class Rectangle : public Geometry { public: Coord topLeft; Coord bottomRight; bool filled; Rectangle() : topLeft(0,0), bottomRight(10,10), filled(false) {} void setXYWH(int left, int top, int width, int height); void setXYXY(int left, int top, int right, int bottom); void setXYWH(Coord topLeft, Coord widthHeight); void setXYXY(Coord topLeft, Coord bottomRight); void drawOuterGeometry (Cairo::RefPtr &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem); void drawInnerGeometry (Cairo::RefPtr &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem); void drawToMOChannel (Cairo::RefPtr &cr, Cairo::RefPtr &cr2, unsigned short id, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem); }; /// @brief Method for client tools needing Edit information class EditSubscriber { public: private: EditUniqueID ID; /// this will be used in improcfun to locate the data that has to be stored in the buffer; it must be unique in RT EditType editingType; BufferType bufferType; EditDataProvider *provider; protected: std::vector visibleGeometry; std::vector mouseOverGeometry; public: EditSubscriber (EditType editType); virtual ~EditSubscriber () {} void setEditProvider(EditDataProvider *provider); EditDataProvider* getEditProvider() { return provider; } void setEditID(EditUniqueID ID, BufferType buffType); bool isCurrentSubscriber(); virtual void subscribe(); virtual void unsubscribe(); virtual void switchOffEditMode (); /// Occurs when the user want to stop the editing mode EditUniqueID getEditID(); EditType getEditingType(); BufferType getEditBufferType(); /** @brief Get the cursor to be displayed when above handles @param objectID object currently "hovered" */ virtual CursorShape getCursor(int objectID) { return CSOpenHand; } /** @brief Triggered when the mouse is moving over an object This method is also triggered when the cursor is moving over the image in ET_PIPETTE mode @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) @param editBuffer buffer to get the pipette values and the from @return true if the preview has to be redrawn, false otherwise */ virtual bool mouseOver(int modifierKey) { return false; } /** @brief Triggered when mouse button 1 is pressed, together with the CTRL modifier key if the subscriber is of type ET_PIPETTE @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) @return true if the preview has to be redrawn, false otherwise */ virtual bool button1Pressed(int modifierKey) { return false; } /** @brief Triggered when mouse button 1 is released @return true if the preview has to be redrawn, false otherwise */ virtual bool button1Released() { return false; } /** @brief Triggered when the user is moving while holding down mouse button 1 @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) @return true if the preview has to be redrawn, false otherwise */ virtual bool drag(int modifierKey) { return false; } /** @brief Get the geometry to be shown to the user */ const std::vector & getVisibleGeometry() { return visibleGeometry; } /** @brief Get the geometry to be drawn in the "mouse over" channel, hidden from the user */ const std::vector & getMouseOverGeometry() { return mouseOverGeometry; } }; /** @brief Class to handle the furniture of data to the subscribers. * * It is admitted that only one Subscriber can ask data at a time. If the Subscriber is of type ET_PIPETTE, it will have to * trigger the usual event so that the image will be reprocessed to compute the buffer of the current subscriber. */ class EditDataProvider { private: EditSubscriber *currSubscriber; public: int object; /// ET_OBJECTS mode: Object detected under the cursor, 0 otherwise; ET_PIPETTE mode: 1 if above the image, 0 otherwise float pipetteVal[3]; /// Current pipette values; if bufferType==BT_SINGLEPLANE_FLOAT, #2 & #3 will be set to 0 Coord posScreen; /// Location of the mouse button press, in preview image space Coord posImage; /// Location of the mouse button press, in the full image space Coord deltaScreen; /// Delta relative to posScreen Coord deltaImage; /// Delta relative to posImage Coord deltaPrevScreen; /// Delta relative to the previous mouse location, in preview image space Coord deltaPrevImage; /// Delta relative to the previous mouse location, in the full image space EditDataProvider(); virtual ~EditDataProvider() {} virtual void subscribe(EditSubscriber *subscriber); virtual void unsubscribe(); /// Occurs when the subscriber has been switched off first virtual void switchOffEditMode (); /// Occurs when the user want to stop the editing mode virtual CursorShape getCursor(int objectID); int getPipetteRectSize() { return 8; } // TODO: make a GUI EditSubscriber* getCurrSubscriber(); virtual void getImageSize (int &w, int&h) =0; }; #endif