mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-17 13:20:45 +00:00
Fix symmetry mode when cel origin != sprite origin (0,0)
Change ToolLoop::getOffset() to getCelOrigin()
This commit is contained in:
parent
c12cb26875
commit
08a04fcb64
@ -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;
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<Image*>(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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -38,6 +38,7 @@ namespace app {
|
||||
// is called.
|
||||
// 5. When the user release the mouse:
|
||||
// - ToolLoopManager::releaseButton
|
||||
//
|
||||
class ToolLoopManager {
|
||||
public:
|
||||
|
||||
|
@ -184,8 +184,7 @@ void BrushPreview::show(const gfx::Point& screenPos)
|
||||
base::UniquePtr<tools::ToolLoop> 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();
|
||||
|
@ -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<tools::Ink> 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;
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user