mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-15 20:42:40 +00:00
Update IntertwineAsPixelPerfect to avoid redrawing all the strokes on each tool loop.
This commit is contained in:
parent
216a5af0f5
commit
8cee671b71
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
// Copyright (C) 2018-2021 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -451,6 +451,8 @@ public:
|
|||||||
m_pts.reset();
|
m_pts.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int thirdFromLastPt = 0, nextPt = 0;
|
||||||
|
|
||||||
if (stroke.size() == 0)
|
if (stroke.size() == 0)
|
||||||
return;
|
return;
|
||||||
else if (stroke.size() == 1) {
|
else if (stroke.size() == 1) {
|
||||||
@ -460,6 +462,13 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (m_pts[m_pts.size() - 1] == stroke.lastPoint() &&
|
||||||
|
stroke.firstPoint() == stroke.lastPoint())
|
||||||
|
return;
|
||||||
|
|
||||||
|
nextPt = m_pts.size();
|
||||||
|
thirdFromLastPt = (m_pts.size() > 2 ? m_pts.size() - 3 : m_pts.size() - 1);
|
||||||
|
|
||||||
for (int c=0; c+1<stroke.size(); ++c) {
|
for (int c=0; c+1<stroke.size(); ++c) {
|
||||||
auto lineAlgo = getLineAlgo(loop, stroke[c], stroke[c+1]);
|
auto lineAlgo = getLineAlgo(loop, stroke[c], stroke[c+1]);
|
||||||
LineData2 lineData(loop, stroke[c], stroke[c+1], m_pts);
|
LineData2 lineData(loop, stroke[c], stroke[c+1], m_pts);
|
||||||
@ -480,7 +489,7 @@ public:
|
|||||||
(loop->getBrush()->angle() == 0.0f ||
|
(loop->getBrush()->angle() == 0.0f ||
|
||||||
loop->getBrush()->angle() == 90.0f ||
|
loop->getBrush()->angle() == 90.0f ||
|
||||||
loop->getBrush()->angle() == 180.0f))) {
|
loop->getBrush()->angle() == 180.0f))) {
|
||||||
for (int c=0; c<m_pts.size(); ++c) {
|
for (int c=thirdFromLastPt; c<m_pts.size(); ++c) {
|
||||||
// We ignore a pixel that is between other two pixels in the
|
// We ignore a pixel that is between other two pixels in the
|
||||||
// corner of a L-like shape.
|
// corner of a L-like shape.
|
||||||
if (c > 0 && c+1 < m_pts.size()
|
if (c > 0 && c+1 < m_pts.size()
|
||||||
@ -488,18 +497,26 @@ public:
|
|||||||
&& (m_pts[c+1].x == m_pts[c].x || m_pts[c+1].y == m_pts[c].y)
|
&& (m_pts[c+1].x == m_pts[c].x || m_pts[c+1].y == m_pts[c].y)
|
||||||
&& m_pts[c-1].x != m_pts[c+1].x
|
&& m_pts[c-1].x != m_pts[c+1].x
|
||||||
&& m_pts[c-1].y != m_pts[c+1].y) {
|
&& m_pts[c-1].y != m_pts[c+1].y) {
|
||||||
|
loop->restoreLastPts(c, m_pts[c]);
|
||||||
|
if (c == nextPt-1)
|
||||||
|
nextPt--;
|
||||||
m_pts.erase(c);
|
m_pts.erase(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int c=0; c<m_pts.size(); ++c) {
|
for (int c=nextPt; c<m_pts.size(); ++c) {
|
||||||
// We must ignore to print the first point of the line after
|
// We must ignore to print the first point of the line after
|
||||||
// a joinStroke pass with a retained "Last" trace policy
|
// a joinStroke pass with a retained "Last" trace policy
|
||||||
// (i.e. the user confirms draw a line while he is holding
|
// (i.e. the user confirms draw a line while he is holding
|
||||||
// the SHIFT key))
|
// the SHIFT key))
|
||||||
if (c == 0 && m_retainedTracePolicyLast)
|
if (c == 0 && m_retainedTracePolicyLast)
|
||||||
continue;
|
continue;
|
||||||
|
// For the last point we store the source image content at that point so we
|
||||||
|
// can restore it when erasing a point because of pixel-perfect.
|
||||||
|
if (c == m_pts.size() - 1) {
|
||||||
|
loop->savePointshapeStrokePtArea(c, m_pts[c]);
|
||||||
|
}
|
||||||
doPointshapeStrokePt(m_pts[c], loop);
|
doPointshapeStrokePt(m_pts[c], loop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
#include "app/shade.h"
|
#include "app/shade.h"
|
||||||
#include "app/tools/dynamics.h"
|
#include "app/tools/dynamics.h"
|
||||||
|
#include "app/tools/stroke.h"
|
||||||
#include "app/tools/tool_loop_modifiers.h"
|
#include "app/tools/tool_loop_modifiers.h"
|
||||||
#include "app/tools/trace_policy.h"
|
#include "app/tools/trace_policy.h"
|
||||||
#include "doc/brush.h"
|
#include "doc/brush.h"
|
||||||
@ -251,6 +252,10 @@ namespace app {
|
|||||||
|
|
||||||
// Called when the user release the mouse on SliceInk
|
// Called when the user release the mouse on SliceInk
|
||||||
virtual void onSliceRect(const gfx::Rect& bounds) = 0;
|
virtual void onSliceRect(const gfx::Rect& bounds) = 0;
|
||||||
|
|
||||||
|
virtual void savePointshapeStrokePtArea(const int pti, const Stroke::Pt& pt) = 0;
|
||||||
|
|
||||||
|
virtual void restoreLastPts(const int pti, const tools::Stroke::Pt& pt) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace tools
|
} // namespace tools
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
// Copyright (C) 2019-2021 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -243,21 +243,6 @@ void ToolLoopManager::doLoopStep(bool lastStep)
|
|||||||
// (the final result is filled).
|
// (the final result is filled).
|
||||||
m_toolLoop->invalidateDstImage();
|
m_toolLoop->invalidateDstImage();
|
||||||
}
|
}
|
||||||
else if (m_toolLoop->getTracePolicy() == TracePolicy::AccumulateUpdateLast) {
|
|
||||||
// Revalidate only this last dirty area (e.g. pixel-perfect
|
|
||||||
// freehand algorithm needs this trace policy to redraw only the
|
|
||||||
// last dirty area, which can vary in one pixel from the previous
|
|
||||||
// tool loop cycle).
|
|
||||||
if (m_toolLoop->getBrush()->type() != kImageBrushType) {
|
|
||||||
m_toolLoop->invalidateDstImage(m_dirtyArea);
|
|
||||||
}
|
|
||||||
// For custom brush we revalidate the whole destination area so
|
|
||||||
// the whole trace is redrawn from scratch.
|
|
||||||
else {
|
|
||||||
m_toolLoop->invalidateDstImage();
|
|
||||||
m_toolLoop->validateDstImage(gfx::Region(m_toolLoop->getDstImage()->bounds()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_toolLoop->validateDstImage(m_dirtyArea);
|
m_toolLoop->validateDstImage(m_dirtyArea);
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2021 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2016 David Capello
|
// Copyright (C) 2001-2016 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -23,12 +24,6 @@ namespace app {
|
|||||||
// freehand like tools.
|
// freehand like tools.
|
||||||
Accumulate,
|
Accumulate,
|
||||||
|
|
||||||
// It's like accumulate, but the last modified area in the
|
|
||||||
// destination is invalidated and redraw from the source image +
|
|
||||||
// tool trace. It's used by pixel-perfect freehand algorithm
|
|
||||||
// (because last modified pixels can differ).
|
|
||||||
AccumulateUpdateLast,
|
|
||||||
|
|
||||||
// Only the last trace is used. It means that on each ToolLoop
|
// Only the last trace is used. It means that on each ToolLoop
|
||||||
// step, the destination image is completely invalidated and
|
// step, the destination image is completely invalidated and
|
||||||
// restored from the source image. Used by
|
// restored from the source image. Used by
|
||||||
|
@ -133,6 +133,21 @@ protected:
|
|||||||
// given document.
|
// given document.
|
||||||
gfx::Region m_allVisibleRgn;
|
gfx::Region m_allVisibleRgn;
|
||||||
|
|
||||||
|
// Helper struct to store an image's area that will be affected by the stroke
|
||||||
|
// point at the specified position of the original image.
|
||||||
|
struct SavedArea {
|
||||||
|
doc::ImageRef img;
|
||||||
|
// Original stroke point position.
|
||||||
|
tools::Stroke::Pt pos;
|
||||||
|
// Area of the original image that was saved into img.
|
||||||
|
gfx::Rect r;
|
||||||
|
};
|
||||||
|
// Holds the areas saved by savePointshapeStrokePtArea method and restored by
|
||||||
|
// restoreLastPts method.
|
||||||
|
std::vector<SavedArea> m_savedAreas;
|
||||||
|
// Last point index.
|
||||||
|
int m_lastPti;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ToolLoopBase(Editor* editor,
|
ToolLoopBase(Editor* editor,
|
||||||
Site& site, const doc::Grid& grid,
|
Site& site, const doc::Grid& grid,
|
||||||
@ -210,22 +225,18 @@ public:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (m_tracePolicy == tools::TracePolicy::Accumulate ||
|
if (m_tracePolicy == tools::TracePolicy::Accumulate) {
|
||||||
m_tracePolicy == tools::TracePolicy::AccumulateUpdateLast) {
|
|
||||||
tools::ToolBox* toolbox = App::instance()->toolBox();
|
tools::ToolBox* toolbox = App::instance()->toolBox();
|
||||||
|
|
||||||
switch (params.freehandAlgorithm) {
|
switch (params.freehandAlgorithm) {
|
||||||
case tools::FreehandAlgorithm::DEFAULT:
|
case tools::FreehandAlgorithm::DEFAULT:
|
||||||
m_intertwine = toolbox->getIntertwinerById(tools::WellKnownIntertwiners::AsLines);
|
m_intertwine = toolbox->getIntertwinerById(tools::WellKnownIntertwiners::AsLines);
|
||||||
m_tracePolicy = tools::TracePolicy::Accumulate;
|
|
||||||
break;
|
break;
|
||||||
case tools::FreehandAlgorithm::PIXEL_PERFECT:
|
case tools::FreehandAlgorithm::PIXEL_PERFECT:
|
||||||
m_intertwine = toolbox->getIntertwinerById(tools::WellKnownIntertwiners::AsPixelPerfect);
|
m_intertwine = toolbox->getIntertwinerById(tools::WellKnownIntertwiners::AsPixelPerfect);
|
||||||
m_tracePolicy = tools::TracePolicy::AccumulateUpdateLast;
|
|
||||||
break;
|
break;
|
||||||
case tools::FreehandAlgorithm::DOTS:
|
case tools::FreehandAlgorithm::DOTS:
|
||||||
m_intertwine = toolbox->getIntertwinerById(tools::WellKnownIntertwiners::None);
|
m_intertwine = toolbox->getIntertwinerById(tools::WellKnownIntertwiners::None);
|
||||||
m_tracePolicy = tools::TracePolicy::Accumulate;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,6 +432,10 @@ public:
|
|||||||
|
|
||||||
void onSliceRect(const gfx::Rect& bounds) override { }
|
void onSliceRect(const gfx::Rect& bounds) override { }
|
||||||
|
|
||||||
|
void savePointshapeStrokePtArea(const int pti, const tools::Stroke::Pt& pt) override { }
|
||||||
|
|
||||||
|
void restoreLastPts(const int pti, const tools::Stroke::Pt& pt) override { }
|
||||||
|
|
||||||
#ifdef ENABLE_UI
|
#ifdef ENABLE_UI
|
||||||
protected:
|
protected:
|
||||||
void updateAllVisibleRegion() {
|
void updateAllVisibleRegion() {
|
||||||
@ -726,8 +741,77 @@ public:
|
|||||||
m_internalCancel = true;
|
m_internalCancel = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_UI
|
// Saves the combined source image's areas and destination image's areas
|
||||||
|
// that will be updated by the last point of each stroke. The idea is to have
|
||||||
|
// the state of the image (only the portion modified by the stroke's point
|
||||||
|
// shape) before drawing the last point of the stroke, then if that point has
|
||||||
|
// to be deleted by the pixel-perfect algorithm, we can use this image to
|
||||||
|
// restore the image to the state previous to the deletion. This method is
|
||||||
|
// used by IntertwineAsPixelPerfect.joinStroke() method.
|
||||||
|
void savePointshapeStrokePtArea(const int pti, const tools::Stroke::Pt& pt) override {
|
||||||
|
if (m_savedAreas.size() > 0 && m_savedAreas[0].pos == pt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_savedAreas.clear();
|
||||||
|
m_lastPti = pti;
|
||||||
|
|
||||||
|
auto saveArea = [this](const tools::Stroke::Pt& pt) {
|
||||||
|
tools::Stroke::Pt pos = pt;
|
||||||
|
// By wrapping the stroke point position when tiled mode is active, the
|
||||||
|
// user can draw outside the canvas and still get the pixel-perfect
|
||||||
|
// effect.
|
||||||
|
wrapPositionOnTiledMode(pt, pos);
|
||||||
|
|
||||||
|
gfx::Rect r;
|
||||||
|
getPointShape()->getModifiedArea(this, pos.x, pos.y, r);
|
||||||
|
|
||||||
|
gfx::Region rgn(r);
|
||||||
|
m_editor->collapseRegionByTiledMode(rgn);
|
||||||
|
|
||||||
|
for (auto a : rgn) {
|
||||||
|
ImageRef i(Image::create(getSrcImage()->pixelFormat(), a.w, a.h));
|
||||||
|
i->copy(getSrcImage(), gfx::Clip(0, 0, a));
|
||||||
|
i->copy(getDstImage(), gfx::Clip(0, 0, a));
|
||||||
|
m_savedAreas.push_back(SavedArea{ i, pt, a});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tools::Symmetry* symmetry = getSymmetry();
|
||||||
|
if (symmetry) {
|
||||||
|
// Convert the point to the sprite position so we can apply the
|
||||||
|
// symmetry transformation.
|
||||||
|
tools::Stroke main_stroke;
|
||||||
|
main_stroke.addPoint(pt);
|
||||||
|
|
||||||
|
tools::Strokes strokes;
|
||||||
|
symmetry->generateStrokes(main_stroke, strokes, this);
|
||||||
|
for (const auto& stroke : strokes)
|
||||||
|
saveArea(stroke[0]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
saveArea(pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes the images saved by savePointshapeStrokePtArea and copies them to
|
||||||
|
// the destination image. It restores the destination image because the
|
||||||
|
// images in m_savedAreas are from previous states of the destination
|
||||||
|
// image. This method is used by IntertwineAsPixelPerfect.joinStroke()
|
||||||
|
// method.
|
||||||
|
void restoreLastPts(const int pti, const tools::Stroke::Pt& pt) override {
|
||||||
|
if (m_savedAreas.empty() || pti != m_lastPti || m_savedAreas[0].pos != pt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
tools::Stroke::Pt pos;
|
||||||
|
for (int i=0; i<m_savedAreas.size(); ++i) {
|
||||||
|
getDstImage()->copy(m_savedAreas[i].img.get(),
|
||||||
|
gfx::Clip(m_savedAreas[i].r.origin(),
|
||||||
|
m_savedAreas[i].img->bounds()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
#ifdef ENABLE_UI
|
||||||
// EditorObserver impl
|
// EditorObserver impl
|
||||||
void onScrollChanged(Editor* editor) override { updateAllVisibleRegion(); }
|
void onScrollChanged(Editor* editor) override { updateAllVisibleRegion(); }
|
||||||
void onZoomChanged(Editor* editor) override { updateAllVisibleRegion(); }
|
void onZoomChanged(Editor* editor) override { updateAllVisibleRegion(); }
|
||||||
@ -744,6 +828,19 @@ private:
|
|||||||
}
|
}
|
||||||
#endif // ENABLE_UI
|
#endif // ENABLE_UI
|
||||||
|
|
||||||
|
void wrapPositionOnTiledMode(const tools::Stroke::Pt& pt, tools::Stroke::Pt& result) {
|
||||||
|
result = pt;
|
||||||
|
if (int(getTiledMode()) & int(TiledMode::X_AXIS)) {
|
||||||
|
result.x %= m_editor->canvasSize().w;
|
||||||
|
if (result.x < 0)
|
||||||
|
result.x += m_editor->canvasSize().w;
|
||||||
|
}
|
||||||
|
if (int(getTiledMode()) & int(TiledMode::Y_AXIS)) {
|
||||||
|
result.y %= m_editor->canvasSize().h;
|
||||||
|
if (result.y < 0)
|
||||||
|
result.y += m_editor->canvasSize().h;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
Loading…
x
Reference in New Issue
Block a user