diff --git a/src/app/tools/controller.h b/src/app/tools/controller.h index e0305e2b0..4206edf3b 100644 --- a/src/app/tools/controller.h +++ b/src/app/tools/controller.h @@ -39,7 +39,8 @@ namespace app { // Called when the user starts drawing and each time a new button is // pressed. The controller could be sure that this method is called - // at least one time. + // at least one time. The point is a position relative to sprite + // bounds. virtual void pressButton(Stroke& stroke, const gfx::Point& point) = 0; // Called each time a mouse button is released. @@ -48,6 +49,7 @@ namespace app { // Called when the mouse is moved. virtual void movement(ToolLoop* loop, Stroke& stroke, const gfx::Point& point) = 0; + // The input and output strokes are relative to sprite coordinates. virtual void getStrokeToInterwine(const Stroke& input, Stroke& output) = 0; virtual void getStatusBarText(const Stroke& stroke, std::string& text) = 0; }; diff --git a/src/app/tools/ink_processing.h b/src/app/tools/ink_processing.h index 7ec1df9ea..3add6b047 100644 --- a/src/app/tools/ink_processing.h +++ b/src/app/tools/ink_processing.h @@ -906,8 +906,8 @@ public: m_opacity = loop->getOpacity(); m_width = m_brush->bounds().w; m_height = m_brush->bounds().h; - m_u = (loop->getOffset().x + m_brush->patternOrigin().x) % m_width; - m_v = (loop->getOffset().y + m_brush->patternOrigin().y) % m_height; + m_u = (m_brush->patternOrigin().x - loop->getCelOrigin().x) % m_width; + m_v = (m_brush->patternOrigin().y - loop->getCelOrigin().y) % m_height; } void processPixel(int x, int y) { diff --git a/src/app/tools/inks.h b/src/app/tools/inks.h index 95aa4f4f4..da49656a4 100644 --- a/src/app/tools/inks.h +++ b/src/app/tools/inks.h @@ -321,19 +321,19 @@ public: void inkHline(int x1, int y, int x2, ToolLoop* loop) override { if (m_modify_selection) { - Point offset = loop->getOffset(); + Point origin = loop->getCelOrigin(); switch (loop->getSelectionMode()) { case SelectionMode::DEFAULT: case SelectionMode::ADD: - m_mask.add(gfx::Rect(x1-offset.x, y-offset.y, x2-x1+1, 1)); + m_mask.add(gfx::Rect(x1+origin.x, y+origin.y, x2-x1+1, 1)); break; case SelectionMode::SUBTRACT: - m_mask.subtract(gfx::Rect(x1-offset.x, y-offset.y, x2-x1+1, 1)); + m_mask.subtract(gfx::Rect(x1+origin.x, y+origin.y, x2-x1+1, 1)); break; } - m_maxBounds |= gfx::Rect(x1-offset.x, y-offset.y, x2-x1+1, 1); + m_maxBounds |= gfx::Rect(x1+origin.x, y+origin.y, x2-x1+1, 1); } // TODO show the selection-preview with a XOR color or something like that else { diff --git a/src/app/tools/intertwine.cpp b/src/app/tools/intertwine.cpp index 3cb2c97ab..02e992e53 100644 --- a/src/app/tools/intertwine.cpp +++ b/src/app/tools/intertwine.cpp @@ -27,14 +27,23 @@ void Intertwine::doPointshapePoint(int x, int y, ToolLoop* loop) { Symmetry* symmetry = loop->getSymmetry(); if (symmetry) { + Point origin(loop->getCelOrigin()); + + // Convert the point to the sprite position so we can apply the + // symmetry transformation. Stroke main_stroke; - main_stroke.addPoint(gfx::Point(x, y)); + main_stroke.addPoint(Point(x, y) + origin); Strokes strokes; symmetry->generateStrokes(main_stroke, strokes); - for (const auto& stroke : strokes) + for (const auto& stroke : strokes) { + // We call transformPoint() moving back each point to the cel + // origin. loop->getPointShape()->transformPoint( - loop, stroke[0].x, stroke[0].y); + loop, + stroke[0].x - origin.x, + stroke[0].y - origin.y); + } } else { loop->getPointShape()->transformPoint(loop, x, y); diff --git a/src/app/tools/intertwine.h b/src/app/tools/intertwine.h index f73d8a2ab..cf1b174df 100644 --- a/src/app/tools/intertwine.h +++ b/src/app/tools/intertwine.h @@ -29,10 +29,13 @@ namespace app { virtual ~Intertwine() { } virtual bool snapByAngle() { return false; } virtual void prepareIntertwine() { } + + // The given stroke must be relative to the cel origin. virtual void joinStroke(ToolLoop* loop, const Stroke& stroke) = 0; virtual void fillStroke(ToolLoop* loop, const Stroke& stroke) = 0; protected: + // The given point must be relative to the cel origin. static void doPointshapePoint(int x, int y, ToolLoop* loop); static void doPointshapeHline(int x1, int y, int x2, ToolLoop* loop); static void doPointshapeLine(int x1, int y1, int x2, int y2, ToolLoop* loop); diff --git a/src/app/tools/point_shape.h b/src/app/tools/point_shape.h index 137c0dcd3..6f2034cc4 100644 --- a/src/app/tools/point_shape.h +++ b/src/app/tools/point_shape.h @@ -22,6 +22,8 @@ namespace app { virtual bool isFloodFill() { return false; } virtual bool isSpray() { return false; } virtual void preparePointShape(ToolLoop* loop) { } + + // The x, y position must be relative to the cel/src/dst image origin. virtual void transformPoint(ToolLoop* loop, int x, int y) = 0; virtual void getModifiedArea(ToolLoop* loop, int x, int y, gfx::Rect& area) = 0; diff --git a/src/app/tools/point_shapes.h b/src/app/tools/point_shapes.h index a3fb49000..d5247684f 100644 --- a/src/app/tools/point_shapes.h +++ b/src/app/tools/point_shapes.h @@ -52,14 +52,14 @@ public: if (m_brush->type() == kImageBrushType) { if (m_brush->pattern() == BrushPattern::ALIGNED_TO_DST || m_brush->pattern() == BrushPattern::PAINT_BRUSH) { - m_brush->setPatternOrigin(gfx::Point(x, y)-loop->getOffset()); + m_brush->setPatternOrigin(gfx::Point(x, y)+loop->getCelOrigin()); } } } else { if (m_brush->type() == kImageBrushType && m_brush->pattern() == BrushPattern::PAINT_BRUSH) { - m_brush->setPatternOrigin(gfx::Point(x, y)-loop->getOffset()); + m_brush->setPatternOrigin(gfx::Point(x, y)+loop->getCelOrigin()); } } @@ -84,24 +84,25 @@ public: void transformPoint(ToolLoop* loop, int x, int y) override { doc::algorithm::floodfill( const_cast(loop->getSrcImage()), x, y, - paintBounds(loop, x, y), + floodfillBounds(loop, x, y), loop->getTolerance(), loop->getContiguous(), loop, (AlgoHLine)doInkHline); } void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override { - area = paintBounds(loop, x, y); + area = floodfillBounds(loop, x, y); } private: - gfx::Rect paintBounds(ToolLoop* loop, int x, int y) { - gfx::Point offset = loop->getOffset(); - gfx::Rect bounds( - offset.x, offset.y, - loop->sprite()->width(), loop->sprite()->height()); + gfx::Rect floodfillBounds(ToolLoop* loop, int x, int y) const { + gfx::Point origin = loop->getCelOrigin(); + gfx::Rect bounds(-origin.x, -origin.y, + loop->sprite()->width(), + loop->sprite()->height()); - bounds = bounds.createIntersection(loop->getSrcImage()->bounds()); + bounds = bounds.createIntersection( + loop->getSrcImage()->bounds()); // Limit the flood-fill to the current tile if the grid is visible. if (loop->getStopAtGrid()) { @@ -109,8 +110,8 @@ private: if (!grid.isEmpty()) { div_t d, dx, dy; - dx = div(grid.x+loop->getOffset().x, grid.w); - dy = div(grid.y+loop->getOffset().y, grid.h); + dx = div(grid.x-origin.x, grid.w); + dy = div(grid.y-origin.y, grid.h); if (dx.rem > 0) dx.rem -= grid.w; if (dy.rem > 0) dy.rem -= grid.h; diff --git a/src/app/tools/symmetry.h b/src/app/tools/symmetry.h index d1368dd02..d70c63728 100644 --- a/src/app/tools/symmetry.h +++ b/src/app/tools/symmetry.h @@ -20,6 +20,8 @@ namespace app { class Symmetry { public: virtual ~Symmetry() { } + + // The "stroke" must be relative to the sprite origin. virtual void generateStrokes(const Stroke& stroke, Strokes& strokes) = 0; }; diff --git a/src/app/tools/tool_loop.h b/src/app/tools/tool_loop.h index 9e8306f8c..1a5d8f8bf 100644 --- a/src/app/tools/tool_loop.h +++ b/src/app/tools/tool_loop.h @@ -187,8 +187,8 @@ namespace app { virtual int getSprayWidth() = 0; virtual int getSpraySpeed() = 0; - // Offset for each point - virtual gfx::Point getOffset() = 0; + // X,Y origin of the cel where we are drawing + virtual gfx::Point getCelOrigin() = 0; // Velocity vector of the mouse virtual void setSpeed(const gfx::Point& speed) = 0; diff --git a/src/app/tools/tool_loop_manager.cpp b/src/app/tools/tool_loop_manager.cpp index ce858b4c8..8e7565fbe 100644 --- a/src/app/tools/tool_loop_manager.cpp +++ b/src/app/tools/tool_loop_manager.cpp @@ -164,15 +164,14 @@ void ToolLoopManager::movement(const Pointer& pointer) void ToolLoopManager::doLoopStep(bool last_step) { - // Original set of points to interwine (original user stroke). + // Original set of points to interwine (original user stroke, + // relative to sprite origin). Stroke main_stroke; if (!last_step) m_toolLoop->getController()->getStrokeToInterwine(m_stroke, main_stroke); else main_stroke = m_stroke; - main_stroke.offset(m_toolLoop->getOffset()); - // Calculate the area to be updated in all document observers. Symmetry* symmetry = m_toolLoop->getSymmetry(); Strokes strokes; @@ -209,6 +208,9 @@ void ToolLoopManager::doLoopStep(bool last_step) m_toolLoop->validateDstImage(m_dirtyArea); + // Move the stroke to be relative to the cel origin. + main_stroke.offset(-m_toolLoop->getCelOrigin()); + // Join or fill user points if (!m_toolLoop->getFilled() || (!last_step && !m_toolLoop->getPreviewFilled())) m_toolLoop->getIntertwine()->joinStroke(m_toolLoop, main_stroke); @@ -235,6 +237,7 @@ void ToolLoopManager::snapToGrid(Point& point) point = snap_to_grid(m_toolLoop->getGridBounds(), point); } +// Strokes are relative to sprite origin. void ToolLoopManager::calculateDirtyArea(const Strokes& strokes) { // Save the current dirty area if it's needed @@ -245,6 +248,8 @@ void ToolLoopManager::calculateDirtyArea(const Strokes& strokes) // Start with a fresh dirty area m_dirtyArea.clear(); + const Point celOrigin = m_toolLoop->getCelOrigin(); + for (auto& stroke : strokes) { gfx::Rect strokeBounds = stroke.bounds(); if (strokeBounds.isEmpty()) @@ -255,20 +260,19 @@ void ToolLoopManager::calculateDirtyArea(const Strokes& strokes) m_toolLoop->getPointShape()->getModifiedArea( m_toolLoop, - strokeBounds.x, - strokeBounds.y, r1); + strokeBounds.x - celOrigin.x, + strokeBounds.y - celOrigin.y, r1); m_toolLoop->getPointShape()->getModifiedArea( m_toolLoop, - strokeBounds.x+strokeBounds.w-1, - strokeBounds.y+strokeBounds.h-1, r2); + strokeBounds.x+strokeBounds.w-1 - celOrigin.x, + strokeBounds.y+strokeBounds.h-1 - celOrigin.y, r2); m_dirtyArea.createUnion(m_dirtyArea, Region(r1.createUnion(r2))); } - // Apply offset mode - Point offset(m_toolLoop->getOffset()); - m_dirtyArea.offset(-offset); + // Make the dirty area relative to the sprite. + m_dirtyArea.offset(celOrigin); // Merge new dirty area with the previous one (for tools like line // or rectangle it's needed to redraw the previous position and diff --git a/src/app/tools/tool_loop_manager.h b/src/app/tools/tool_loop_manager.h index 500f77734..063d19172 100644 --- a/src/app/tools/tool_loop_manager.h +++ b/src/app/tools/tool_loop_manager.h @@ -38,6 +38,7 @@ namespace app { // is called. // 5. When the user release the mouse: // - ToolLoopManager::releaseButton + // class ToolLoopManager { public: diff --git a/src/app/ui/editor/brush_preview.cpp b/src/app/ui/editor/brush_preview.cpp index cdeae7b9f..d236399d4 100644 --- a/src/app/ui/editor/brush_preview.cpp +++ b/src/app/ui/editor/brush_preview.cpp @@ -184,8 +184,7 @@ void BrushPreview::show(const gfx::Point& screenPos) base::UniquePtr loop( create_tool_loop_preview( m_editor, extraImage, - -gfx::Point(brushBounds.x, - brushBounds.y))); + brushBounds.getOrigin())); if (loop) { loop->getInk()->prepareInk(loop); loop->getIntertwine()->prepareIntertwine(); diff --git a/src/app/ui/editor/tool_loop_impl.cpp b/src/app/ui/editor/tool_loop_impl.cpp index 218965caa..b4b3a424d 100644 --- a/src/app/ui/editor/tool_loop_impl.cpp +++ b/src/app/ui/editor/tool_loop_impl.cpp @@ -70,7 +70,7 @@ protected: int m_opacity; int m_tolerance; bool m_contiguous; - gfx::Point m_offset; + gfx::Point m_celOrigin; gfx::Point m_speed; tools::ToolLoop::Button m_button; base::UniquePtr m_ink; @@ -207,7 +207,7 @@ public: return false; } gfx::Rect getGridBounds() override { return m_docPref.grid.bounds(); } - gfx::Point getOffset() override { return m_offset; } + gfx::Point getCelOrigin() override { return m_celOrigin; } void setSpeed(const gfx::Point& speed) override { m_speed = speed; } gfx::Point getSpeed() override { return m_speed; } tools::Ink* getInk() override { return m_ink; } @@ -317,16 +317,11 @@ public: m_transaction.execute(new cmd::SetMask(m_document, &emptyMask)); } - int x1 = m_expandCelCanvas.getCel()->x(); - int y1 = m_expandCelCanvas.getCel()->y(); - + m_celOrigin = m_expandCelCanvas.getCel()->position(); m_mask = m_document->mask(); - m_maskOrigin = (!m_mask->isEmpty() ? gfx::Point(m_mask->bounds().x-x1, - m_mask->bounds().y-y1): + m_maskOrigin = (!m_mask->isEmpty() ? gfx::Point(m_mask->bounds().x-m_celOrigin.x, + m_mask->bounds().y-m_celOrigin.y): gfx::Point(0, 0)); - - m_offset.x = -x1; - m_offset.y = -y1; } // IToolLoop interface @@ -485,12 +480,12 @@ public: const app::Color& fgColor, const app::Color& bgColor, Image* image, - const gfx::Point& offset) + const gfx::Point& celOrigin) : ToolLoopBase(editor, tool, ink, document, tools::ToolLoop::Left, fgColor, bgColor) , m_image(image) { - m_offset = offset; + m_celOrigin = celOrigin; // Avoid preview for spray and flood fill like tools if (m_pointShape->isSpray()) { @@ -529,7 +524,7 @@ public: tools::ToolLoop* create_tool_loop_preview( Editor* editor, Image* image, - const gfx::Point& offset) + const gfx::Point& celOrigin) { tools::Tool* current_tool = editor->getCurrentEditorTool(); tools::Ink* current_ink = editor->getCurrentEditorInk(); @@ -557,7 +552,7 @@ tools::ToolLoop* create_tool_loop_preview( current_tool, current_ink, editor->document(), - fg, bg, image, offset); + fg, bg, image, celOrigin); } catch (const std::exception&) { return nullptr; diff --git a/src/app/ui/editor/tool_loop_impl.h b/src/app/ui/editor/tool_loop_impl.h index 0a83a3b56..e89ec41ee 100644 --- a/src/app/ui/editor/tool_loop_impl.h +++ b/src/app/ui/editor/tool_loop_impl.h @@ -29,7 +29,7 @@ namespace app { tools::ToolLoop* create_tool_loop_preview( Editor* editor, doc::Image* image, - const gfx::Point& offset); + const gfx::Point& celOrigin); } // namespace app