diff --git a/src/app/tools/ink.h b/src/app/tools/ink.h index c2325d3b0..91b46d4fb 100644 --- a/src/app/tools/ink.h +++ b/src/app/tools/ink.h @@ -66,6 +66,11 @@ namespace app { // Returns true if this ink is used to mark slices virtual bool isSlice() const { return false; } + // Returns true if inkHline() needs source cel coordinates + // instead of sprite coordinates (i.e. relative to + // ToolLoop::getCelOrigin()). + virtual bool needsCelCoordinates() const { return true; } + // Returns true if this ink needs a special source area. For // example, blur tool needs one extra pixel to all sides of the // modified area, so it can use a 3x3 convolution matrix. diff --git a/src/app/tools/inks.h b/src/app/tools/inks.h index 4d1d61d43..8c0a1e983 100644 --- a/src/app/tools/inks.h +++ b/src/app/tools/inks.h @@ -173,6 +173,8 @@ public: Ink* clone() override { return new SliceInk(*this); } bool isSlice() const override { return true; } + bool needsCelCoordinates() const override { return false; } + void prepareInk(ToolLoop* loop) override { } void inkHline(int x1, int y, int x2, ToolLoop* loop) override { // TODO show the selection-preview with a XOR color or something like that @@ -319,21 +321,23 @@ public: Ink* clone() override { return new SelectionInk(*this); } bool isSelection() const override { return true; } + bool needsCelCoordinates() const override { + return (m_modify_selection ? false: true); + } void inkHline(int x1, int y, int x2, ToolLoop* loop) override { if (m_modify_selection) { int modifiers = int(loop->getModifiers()); - Point origin = loop->getCelOrigin(); if ((modifiers & (int(ToolLoopModifiers::kReplaceSelection) | int(ToolLoopModifiers::kAddSelection))) != 0) { - m_mask.add(gfx::Rect(x1+origin.x, y+origin.y, x2-x1+1, 1)); + m_mask.add(gfx::Rect(x1, y, x2-x1+1, 1)); } else if ((modifiers & int(ToolLoopModifiers::kSubtractSelection)) != 0) { - m_mask.subtract(gfx::Rect(x1+origin.x, y+origin.y, x2-x1+1, 1)); + m_mask.subtract(gfx::Rect(x1, y, x2-x1+1, 1)); } - m_maxBounds |= gfx::Rect(x1+origin.x, y+origin.y, x2-x1+1, 1); + m_maxBounds |= gfx::Rect(x1, 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 ce918b370..75bec16c4 100644 --- a/src/app/tools/intertwine.cpp +++ b/src/app/tools/intertwine.cpp @@ -27,12 +27,10 @@ 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(Point(x, y) + origin); + main_stroke.addPoint(Point(x, y)); Strokes strokes; symmetry->generateStrokes(main_stroke, strokes, loop); @@ -40,9 +38,7 @@ void Intertwine::doPointshapePoint(int x, int y, ToolLoop* loop) // We call transformPoint() moving back each point to the cel // origin. loop->getPointShape()->transformPoint( - loop, - stroke[0].x - origin.x, - stroke[0].y - origin.y); + loop, stroke[0].x, stroke[0].y); } } else { diff --git a/src/app/tools/point_shape.cpp b/src/app/tools/point_shape.cpp index a7326c057..ab49291ad 100644 --- a/src/app/tools/point_shape.cpp +++ b/src/app/tools/point_shape.cpp @@ -27,13 +27,22 @@ void PointShape::doInkHline(int x1, int y, int x2, ToolLoop* loop) TiledMode tiledMode = loop->getTiledMode(); int x, w, size; // width or height + // In case the ink needs original cel coordinates, we have to + // translate the x1/y/x2 coordinate. + if (loop->getInk()->needsCelCoordinates()) { + gfx::Point origin = loop->getCelOrigin(); + x1 -= origin.x; + x2 -= origin.x; + y -= origin.y; + } + // Tiled in Y axis if (int(tiledMode) & int(TiledMode::Y_AXIS)) { size = loop->getDstImage()->height(); // size = image height y = wrap_value(y, size); } else if (y < 0 || y >= loop->getDstImage()->height()) - return; + return; // Tiled in X axis if (int(tiledMode) & int(TiledMode::X_AXIS)) { diff --git a/src/app/tools/point_shapes.h b/src/app/tools/point_shapes.h index 61bf87467..6398cec8d 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->getCelOrigin()); + m_brush->setPatternOrigin(gfx::Point(x, y)); } } } else { if (m_brush->type() == kImageBrushType && m_brush->pattern() == BrushPattern::PAINT_BRUSH) { - m_brush->setPatternOrigin(gfx::Point(x, y)+loop->getCelOrigin()); + m_brush->setPatternOrigin(gfx::Point(x, y)); } } @@ -98,13 +98,8 @@ public: private: 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->getFloodFillSrcImage()->bounds()); + gfx::Rect bounds = loop->sprite()->bounds(); + bounds &= loop->getFloodFillSrcImage()->bounds(); // Limit the flood-fill to the current tile if the grid is visible. if (loop->getStopAtGrid()) { @@ -112,8 +107,8 @@ private: if (!grid.isEmpty()) { div_t d, dx, dy; - dx = div(grid.x-origin.x, grid.w); - dy = div(grid.y-origin.y, grid.h); + dx = div(grid.x, grid.w); + dy = div(grid.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/tool_loop_manager.cpp b/src/app/tools/tool_loop_manager.cpp index 06f2da7cb..f05ed4dd2 100644 --- a/src/app/tools/tool_loop_manager.cpp +++ b/src/app/tools/tool_loop_manager.cpp @@ -188,9 +188,6 @@ 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); @@ -229,8 +226,6 @@ 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()) @@ -241,20 +236,17 @@ void ToolLoopManager::calculateDirtyArea(const Strokes& strokes) m_toolLoop->getPointShape()->getModifiedArea( m_toolLoop, - strokeBounds.x - celOrigin.x, - strokeBounds.y - celOrigin.y, r1); + strokeBounds.x, + strokeBounds.y, r1); m_toolLoop->getPointShape()->getModifiedArea( m_toolLoop, - strokeBounds.x+strokeBounds.w-1 - celOrigin.x, - strokeBounds.y+strokeBounds.h-1 - celOrigin.y, r2); + strokeBounds.x+strokeBounds.w-1, + strokeBounds.y+strokeBounds.h-1, r2); m_dirtyArea.createUnion(m_dirtyArea, Region(r1.createUnion(r2))); } - // 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 // the new one) diff --git a/src/app/ui/editor/brush_preview.cpp b/src/app/ui/editor/brush_preview.cpp index 92f966372..02b695550 100644 --- a/src/app/ui/editor/brush_preview.cpp +++ b/src/app/ui/editor/brush_preview.cpp @@ -209,7 +209,9 @@ void BrushPreview::show(const gfx::Point& screenPos) loop->getIntertwine()->prepareIntertwine(); loop->getPointShape()->preparePointShape(loop); loop->getPointShape()->transformPoint( - loop, -origBrushBounds.x, -origBrushBounds.y); + loop, + brushBounds.x-origBrushBounds.x, + brushBounds.y-origBrushBounds.y); } } diff --git a/src/app/ui/editor/tool_loop_impl.cpp b/src/app/ui/editor/tool_loop_impl.cpp index 9ebd43fb8..084314924 100644 --- a/src/app/ui/editor/tool_loop_impl.cpp +++ b/src/app/ui/editor/tool_loop_impl.cpp @@ -269,7 +269,7 @@ class ToolLoopImpl : public ToolLoopBase { gfx::Point m_maskOrigin; bool m_canceled; Transaction m_transaction; - ExpandCelCanvas m_expandCelCanvas; + ExpandCelCanvas* m_expandCelCanvas; Image* m_floodfillSrcImage; public: @@ -294,23 +294,58 @@ public: getInk()->isSlice() || getInk()->isZoom()) ? DoesntModifyDocument: ModifyDocument)) - , m_expandCelCanvas( - editor->getSite(), - layer, - m_docPref.tiled.mode(), - m_transaction, - ExpandCelCanvas::Flags( - ExpandCelCanvas::NeedsSource | - // If the tool is freehand-like, we can use the modified - // region directly as undo information to save the modified - // pixels (it's faster than creating a Dirty object). - // See ExpandCelCanvas::commit() for details about this flag. - (getController()->isFreehand() ? - ExpandCelCanvas::UseModifiedRegionAsUndoInfo: - ExpandCelCanvas::None))) + , m_expandCelCanvas(nullptr) + , m_floodfillSrcImage(nullptr) { ASSERT(m_context->activeDocument() == m_editor->document()); + if (m_pointShape->isFloodFill()) { + // Prepare a special image for floodfill when it's configured to + // stop using all visible layers. + if (m_toolPref.floodfill.referTo() == gen::FillReferTo::ALL_LAYERS) { + m_floodfillSrcImage = Image::create(m_sprite->pixelFormat(), + m_sprite->width(), + m_sprite->height()); + + m_floodfillSrcImage->clear(m_sprite->transparentColor()); + + render::Render().renderSprite( + m_floodfillSrcImage, + m_sprite, + m_frame, + gfx::Clip(m_sprite->bounds()), + render::Zoom(1, 1)); + } + else { + Cel* cel = m_layer->cel(m_frame); + if (cel && (cel->x() != 0 || cel->y() != 0)) { + m_floodfillSrcImage = Image::create(m_sprite->pixelFormat(), + m_sprite->width(), + m_sprite->height()); + m_floodfillSrcImage->clear(m_sprite->transparentColor()); + copy_image(m_floodfillSrcImage, cel->image(), cel->x(), cel->y()); + } + } + } + + m_expandCelCanvas = new ExpandCelCanvas( + editor->getSite(), + layer, + m_docPref.tiled.mode(), + m_transaction, + ExpandCelCanvas::Flags( + ExpandCelCanvas::NeedsSource | + // If the tool is freehand-like, we can use the modified + // region directly as undo information to save the modified + // pixels (it's faster than creating a Dirty object). + // See ExpandCelCanvas::commit() for details about this flag. + (getController()->isFreehand() ? + ExpandCelCanvas::UseModifiedRegionAsUndoInfo: + ExpandCelCanvas::None))); + + if (!m_floodfillSrcImage) + m_floodfillSrcImage = const_cast(getSrcImage()); + // Settings switch (tool->getFill(m_button)) { case tools::FillNone: @@ -341,37 +376,17 @@ public: m_transaction.execute(new cmd::SetMask(m_document, &emptyMask)); } - m_celOrigin = m_expandCelCanvas.getCel()->position(); + m_celOrigin = m_expandCelCanvas->getCel()->position(); m_mask = m_document->mask(); 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)); - - // Prepare a special image for floodfill when it's configured to - // stop using all visible layers. - if (m_pointShape->isFloodFill() && - m_toolPref.floodfill.referTo() == gen::FillReferTo::ALL_LAYERS) { - m_floodfillSrcImage = Image::create(m_sprite->pixelFormat(), - m_sprite->width(), - m_sprite->height()); - - m_floodfillSrcImage->clear(m_sprite->transparentColor()); - - render::Render().renderSprite( - m_floodfillSrcImage, - m_sprite, - m_frame, - gfx::Clip(m_sprite->bounds()), - render::Zoom(1, 1)); - } - else { - m_floodfillSrcImage = const_cast(getSrcImage()); - } } ~ToolLoopImpl() { if (m_floodfillSrcImage != getSrcImage()) delete m_floodfillSrcImage; + delete m_expandCelCanvas; } // IToolLoop interface @@ -385,7 +400,7 @@ public: try { ContextReader reader(m_context, 500); ContextWriter writer(reader, 500); - m_expandCelCanvas.commit(); + m_expandCelCanvas->commit(); } catch (const LockedDocumentException& ex) { Console::showException(ex); @@ -410,7 +425,7 @@ public: try { ContextReader reader(m_context, 500); ContextWriter writer(reader, 500); - m_expandCelCanvas.rollback(); + m_expandCelCanvas->rollback(); } catch (const LockedDocumentException& ex) { Console::showException(ex); @@ -421,23 +436,23 @@ public: update_screen_for_document(m_document); } - const Image* getSrcImage() override { return m_expandCelCanvas.getSourceCanvas(); } + const Image* getSrcImage() override { return m_expandCelCanvas->getSourceCanvas(); } const Image* getFloodFillSrcImage() override { return m_floodfillSrcImage; } - Image* getDstImage() override { return m_expandCelCanvas.getDestCanvas(); } + Image* getDstImage() override { return m_expandCelCanvas->getDestCanvas(); } void validateSrcImage(const gfx::Region& rgn) override { - m_expandCelCanvas.validateSourceCanvas(rgn); + m_expandCelCanvas->validateSourceCanvas(rgn); } void validateDstImage(const gfx::Region& rgn) override { - m_expandCelCanvas.validateDestCanvas(rgn); + m_expandCelCanvas->validateDestCanvas(rgn); } void invalidateDstImage() override { - m_expandCelCanvas.invalidateDestCanvas(); + m_expandCelCanvas->invalidateDestCanvas(); } void invalidateDstImage(const gfx::Region& rgn) override { - m_expandCelCanvas.invalidateDestCanvas(rgn); + m_expandCelCanvas->invalidateDestCanvas(rgn); } void copyValidDstToSrcImage(const gfx::Region& rgn) override { - m_expandCelCanvas.copyValidDestToSourceCanvas(rgn); + m_expandCelCanvas->copyValidDestToSourceCanvas(rgn); } bool useMask() override { return m_useMask; }