Draw mask boundaries with a gfx::Path

The path is also cached so on each re-paint we can re-use it while
it's still valid.
This commit is contained in:
David Capello 2020-05-18 17:58:22 -03:00
parent 9801010a2b
commit 3d2013b33c
13 changed files with 148 additions and 52 deletions

2
laf

@ -1 +1 @@
Subproject commit 207de6662911e9471c01681fb16912b756646f07 Subproject commit 8032d186a751326d0fc6436d69570ad4a3c4aaf1

View File

@ -304,9 +304,9 @@ void Doc::generateMaskBoundaries(const Mask* mask)
ASSERT(mask); ASSERT(mask);
if (!mask->isEmpty()) { if (!mask->isEmpty()) {
m_maskBoundaries.reset(new MaskBoundaries(mask->bitmap())); m_maskBoundaries.regen(mask->bitmap());
m_maskBoundaries->offset(mask->bounds().x, m_maskBoundaries.offset(mask->bounds().x,
mask->bounds().y); mask->bounds().y);
} }
notifySelectionBoundariesChanged(); notifySelectionBoundariesChanged();

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A. // Copyright (C) 2018-2020 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
@ -20,6 +20,7 @@
#include "doc/color.h" #include "doc/color.h"
#include "doc/document.h" #include "doc/document.h"
#include "doc/frame.h" #include "doc/frame.h"
#include "doc/mask_boundaries.h"
#include "doc/pixel_format.h" #include "doc/pixel_format.h"
#include "gfx/rect.h" #include "gfx/rect.h"
#include "obs/observable.h" #include "obs/observable.h"
@ -32,7 +33,6 @@ namespace doc {
class Cel; class Cel;
class Layer; class Layer;
class Mask; class Mask;
class MaskBoundaries;
class Sprite; class Sprite;
} }
@ -143,8 +143,16 @@ namespace app {
void destroyMaskBoundaries(); void destroyMaskBoundaries();
void generateMaskBoundaries(const Mask* mask = nullptr); void generateMaskBoundaries(const Mask* mask = nullptr);
const MaskBoundaries* getMaskBoundaries() const { const MaskBoundaries& maskBoundaries() const {
return m_maskBoundaries.get(); return m_maskBoundaries;
}
MaskBoundaries& maskBoundaries() {
return m_maskBoundaries;
}
bool hasMaskBoundaries() const {
return !m_maskBoundaries.isEmpty();
} }
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -217,7 +225,7 @@ namespace app {
Transaction* m_transaction; Transaction* m_transaction;
// Selected mask region boundaries // Selected mask region boundaries
std::unique_ptr<doc::MaskBoundaries> m_maskBoundaries; doc::MaskBoundaries m_maskBoundaries;
// Data to save the file in the same format that it was loaded // Data to save the file in the same format that it was loaded
FormatOptionsPtr m_format_options; FormatOptionsPtr m_format_options;

View File

@ -371,7 +371,7 @@ void BrushPreview::generateBoundaries()
{ {
BrushRef brush = getCurrentBrush(); BrushRef brush = getCurrentBrush();
if (m_brushBoundaries && if (!m_brushBoundaries.isEmpty() &&
m_brushGen == brush->gen()) m_brushGen == brush->gen())
return; return;
@ -398,12 +398,10 @@ void BrushPreview::generateBoundaries()
mask = brush->maskBitmap(); mask = brush->maskBitmap();
} }
m_brushBoundaries.reset( m_brushBoundaries.regen(mask ? mask: brushImage);
new MaskBoundaries(mask ? mask: brushImage));
if (!isOnePixel) if (!isOnePixel)
m_brushBoundaries->offset(-brush->center().x, m_brushBoundaries.offset(-brush->center().x,
-brush->center().y); -brush->center().y);
if (deleteMask) if (deleteMask)
delete mask; delete mask;
@ -512,7 +510,7 @@ void BrushPreview::traceBrushBoundaries(ui::Graphics* g,
gfx::Color color, gfx::Color color,
PixelDelegate pixelDelegate) PixelDelegate pixelDelegate)
{ {
for (const auto& seg : *m_brushBoundaries) { for (const auto& seg : m_brushBoundaries) {
gfx::Rect bounds = seg.bounds(); gfx::Rect bounds = seg.bounds();
bounds.offset(pos); bounds.offset(pos);
bounds = m_editor->editorToScreen(bounds); bounds = m_editor->editorToScreen(bounds);

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2020 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
@ -93,7 +93,7 @@ namespace app {
gfx::Point m_editorPosition; // Position in the editor (model) gfx::Point m_editorPosition; // Position in the editor (model)
// Information about current brush // Information about current brush
std::shared_ptr<doc::MaskBoundaries> m_brushBoundaries; doc::MaskBoundaries m_brushBoundaries;
int m_brushGen; int m_brushGen;
int m_brushWidth; int m_brushWidth;
int m_brushHeight; int m_brushHeight;

View File

@ -894,7 +894,7 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
} }
// Draw the mask // Draw the mask
if (m_document->getMaskBoundaries()) if (m_document->hasMaskBoundaries())
drawMask(g); drawMask(g);
// Post-render decorator. // Post-render decorator.
@ -934,34 +934,26 @@ void Editor::drawMask(Graphics* g)
!m_docPref.show.selectionEdges()) !m_docPref.show.selectionEdges())
return; return;
ASSERT(m_document->getMaskBoundaries()); ASSERT(m_document->hasMaskBoundaries());
gfx::Point pt = mainTilePosition(); gfx::Point pt = mainTilePosition();
pt.x = m_padding.x + m_proj.applyX(pt.x); pt.x = m_padding.x + m_proj.applyX(pt.x);
pt.y = m_padding.y + m_proj.applyY(pt.y); pt.y = m_padding.y + m_proj.applyY(pt.y);
for (const auto& seg : *m_document->getMaskBoundaries()) { // Create the mask boundaries path
CheckedDrawMode checked(g, m_antsOffset, auto& segs = m_document->maskBoundaries();
gfx::rgba(0, 0, 0, 255), segs.createPathIfNeeeded();
gfx::rgba(255, 255, 255, 255));
gfx::Rect bounds = m_proj.apply(seg.bounds());
if (m_proj.scaleX() >= 1.0) { CheckedDrawMode checked(g, m_antsOffset,
if (!seg.open() && seg.vertical()) gfx::rgba(0, 0, 0, 255),
--bounds.x; gfx::rgba(255, 255, 255, 255));
} os::Paint paint;
paint.style(os::Paint::Stroke);
if (m_proj.scaleY() >= 1.0) { paint.color(gfx::rgba(0, 0, 0));
if (!seg.open() && !seg.vertical()) g->setMatrix(Matrix::MakeTrans(pt.x, pt.y));
--bounds.y; g->concat(m_proj.scaleMatrix());
} g->drawPath(segs.path(), paint);
g->resetMatrix();
// The color doesn't matter, we are using CheckedDrawMode
if (seg.vertical())
g->drawVLine(gfx::rgba(0, 0, 0), pt.x+bounds.x, pt.y+bounds.y, bounds.h);
else
g->drawHLine(gfx::rgba(0, 0, 0), pt.x+bounds.x, pt.y+bounds.y, bounds.w);
}
} }
void Editor::drawMaskSafe() void Editor::drawMaskSafe()
@ -971,7 +963,7 @@ void Editor::drawMaskSafe()
if (isVisible() && if (isVisible() &&
m_document && m_document &&
m_document->getMaskBoundaries()) { m_document->hasMaskBoundaries()) {
Region region; Region region;
getDrawableRegion(region, kCutTopWindows); getDrawableRegion(region, kCutTopWindows);
region.offset(-bounds().origin()); region.offset(-bounds().origin());
@ -2047,7 +2039,7 @@ void Editor::onPaint(ui::PaintEvent& ev)
#endif // ENABLE_DEVMODE #endif // ENABLE_DEVMODE
// Draw the mask boundaries // Draw the mask boundaries
if (m_document->getMaskBoundaries()) { if (m_document->hasMaskBoundaries()) {
drawMask(g); drawMask(g);
m_antsTimer.start(); m_antsTimer.start();
} }

View File

@ -105,11 +105,11 @@ bool MovingSelectionState::onMouseMove(Editor* editor, MouseMessage* msg)
editor->document()->mask()->setOrigin(newMaskOrigin.x, editor->document()->mask()->setOrigin(newMaskOrigin.x,
newMaskOrigin.y); newMaskOrigin.y);
if (MaskBoundaries* boundaries = if (editor->document()->hasMaskBoundaries()) {
const_cast<MaskBoundaries*>(editor->document()->getMaskBoundaries())) { MaskBoundaries& boundaries = editor->document()->maskBoundaries();
const gfx::Point boundariesDelta = newMaskOrigin - oldMaskOrigin; const gfx::Point boundariesDelta = newMaskOrigin - oldMaskOrigin;
boundaries->offset(boundariesDelta.x, boundaries.offset(boundariesDelta.x,
boundariesDelta.y); boundariesDelta.y);
} }
else { else {
ASSERT(false); ASSERT(false);

View File

@ -832,13 +832,13 @@ bool StandbyState::overSelectionEdges(Editor* editor,
if (Preferences::instance().selection.moveEdges() && if (Preferences::instance().selection.moveEdges() &&
editor->isActive() && editor->isActive() &&
editor->document()->isMaskVisible() && editor->document()->isMaskVisible() &&
editor->document()->getMaskBoundaries() && editor->document()->hasMaskBoundaries() &&
// TODO improve this check, how we can know that we aren't in the MovingPixelsState // TODO improve this check, how we can know that we aren't in the MovingPixelsState
!dynamic_cast<MovingPixelsState*>(editor->getState().get())) { !dynamic_cast<MovingPixelsState*>(editor->getState().get())) {
gfx::Point mainOffset(editor->mainTilePosition()); gfx::Point mainOffset(editor->mainTilePosition());
// For each selection edge // For each selection edge
for (const auto& seg : *editor->document()->getMaskBoundaries()) { for (const auto& seg : editor->document()->maskBoundaries()) {
gfx::Rect segBounds = seg.bounds(); gfx::Rect segBounds = seg.bounds();
segBounds.offset(mainOffset); segBounds.offset(mainOffset);
segBounds = editor->editorToScreen(segBounds); segBounds = editor->editorToScreen(segBounds);

View File

@ -14,8 +14,17 @@
namespace doc { namespace doc {
MaskBoundaries::MaskBoundaries(const Image* bitmap) void MaskBoundaries::reset()
{ {
m_segs.clear();
if (!m_path.isEmpty())
m_path.rewind();
}
void MaskBoundaries::regen(const Image* bitmap)
{
reset();
int x, y, w = bitmap->width(), h = bitmap->height(); int x, y, w = bitmap->width(), h = bitmap->height();
const LockImageBits<BitmapTraits> bits(bitmap); const LockImageBits<BitmapTraits> bits(bitmap);
@ -334,6 +343,24 @@ void MaskBoundaries::offset(int x, int y)
{ {
for (Segment& seg : m_segs) for (Segment& seg : m_segs)
seg.offset(x, y); seg.offset(x, y);
m_path.offset(x, y);
}
void MaskBoundaries::createPathIfNeeeded()
{
if (!m_path.isEmpty())
return;
for (const auto& seg : m_segs) {
gfx::Rect rc = seg.bounds();
m_path.moveTo(rc.x, rc.y);
if (seg.vertical())
m_path.lineTo(rc.x, rc.y2());
else
m_path.lineTo(rc.x2(), rc.y);
}
} }
} // namespace doc } // namespace doc

View File

@ -1,4 +1,5 @@
// Aseprite Document Library // Aseprite Document Library
// Copyright (c) 2020 Igara Studio S.A.
// Copyright (c) 2001-2015 David Capello // Copyright (c) 2001-2015 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -8,6 +9,7 @@
#define DOC_MASK_BOUNDARIES_H_INCLUDED #define DOC_MASK_BOUNDARIES_H_INCLUDED
#pragma once #pragma once
#include "gfx/path.h"
#include "gfx/rect.h" #include "gfx/rect.h"
#include <vector> #include <vector>
@ -41,7 +43,9 @@ namespace doc {
typedef list_type::iterator iterator; typedef list_type::iterator iterator;
typedef list_type::const_iterator const_iterator; typedef list_type::const_iterator const_iterator;
MaskBoundaries(const Image* bitmap); bool isEmpty() const { return m_segs.empty(); }
void reset();
void regen(const Image* bitmap);
const_iterator begin() const { return m_segs.begin(); } const_iterator begin() const { return m_segs.begin(); }
const_iterator end() const { return m_segs.end(); } const_iterator end() const { return m_segs.end(); }
@ -49,9 +53,13 @@ namespace doc {
iterator end() { return m_segs.end(); } iterator end() { return m_segs.end(); }
void offset(int x, int y); void offset(int x, int y);
gfx::Path& path() { return m_path; }
void createPathIfNeeeded();
private: private:
list_type m_segs; list_type m_segs;
gfx::Path m_path;
}; };
} // namespace doc } // namespace doc

View File

@ -1,4 +1,5 @@
// Aseprite Render Library // Aseprite Render Library
// Copyright (c) 2020 Igara Studio S.A.
// Copyright (c) 2016 David Capello // Copyright (c) 2016 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -9,6 +10,7 @@
#pragma once #pragma once
#include "doc/pixel_ratio.h" #include "doc/pixel_ratio.h"
#include "gfx/matrix.h"
#include "render/zoom.h" #include "render/zoom.h"
namespace render { namespace render {
@ -79,6 +81,10 @@ namespace render {
removeY(r.y+r.h) - v); removeY(r.y+r.h) - v);
} }
gfx::Matrix scaleMatrix() const {
return gfx::Matrix::MakeScale(scaleX(), scaleY());
}
private: private:
doc::PixelRatio m_pixelRatio; doc::PixelRatio m_pixelRatio;
Zoom m_zoom; Zoom m_zoom;

View File

@ -13,6 +13,8 @@
#include "base/string.h" #include "base/string.h"
#include "gfx/clip.h" #include "gfx/clip.h"
#include "gfx/matrix.h"
#include "gfx/path.h"
#include "gfx/point.h" #include "gfx/point.h"
#include "gfx/rect.h" #include "gfx/rect.h"
#include "gfx/region.h" #include "gfx/region.h"
@ -81,6 +83,36 @@ bool Graphics::clipRect(const gfx::Rect& rc)
return m_surface->clipRect(gfx::Rect(rc).offset(m_dx, m_dy)); return m_surface->clipRect(gfx::Rect(rc).offset(m_dx, m_dy));
} }
void Graphics::save()
{
m_surface->save();
}
void Graphics::concat(const gfx::Matrix& matrix)
{
m_surface->concat(matrix);
}
void Graphics::setMatrix(const gfx::Matrix& matrix)
{
m_surface->setMatrix(matrix);
}
void Graphics::resetMatrix()
{
m_surface->resetMatrix();
}
void Graphics::restore()
{
m_surface->restore();
}
gfx::Matrix Graphics::matrix() const
{
return m_surface->matrix();
}
void Graphics::setDrawMode(DrawMode mode, int param, void Graphics::setDrawMode(DrawMode mode, int param,
const gfx::Color a, const gfx::Color a,
const gfx::Color b) const gfx::Color b)
@ -144,6 +176,21 @@ void Graphics::drawLine(gfx::Color color, const gfx::Point& _a, const gfx::Point
m_surface->drawLine(a, b, paint); m_surface->drawLine(a, b, paint);
} }
void Graphics::drawPath(gfx::Path& path, const Paint& paint)
{
os::SurfaceLock lock(m_surface);
auto m = matrix();
save();
setMatrix(gfx::Matrix::MakeTrans(m_dx, m_dy));
concat(m);
m_surface->drawPath(path, paint);
dirty(matrix().mapRect(path.bounds()).inflate(1, 1));
restore();
}
void Graphics::drawRect(gfx::Color color, const gfx::Rect& rcOrig) void Graphics::drawRect(gfx::Color color, const gfx::Rect& rcOrig)
{ {
gfx::Rect rc(rcOrig); gfx::Rect rc(rcOrig);

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -21,6 +21,8 @@
#include <string> #include <string>
namespace gfx { namespace gfx {
class Matrix;
class Path;
class Region; class Region;
} }
@ -58,6 +60,13 @@ namespace ui {
void restoreClip(); void restoreClip();
bool clipRect(const gfx::Rect& rc); bool clipRect(const gfx::Rect& rc);
void save();
void concat(const gfx::Matrix& matrix);
void setMatrix(const gfx::Matrix& matrix);
void resetMatrix();
void restore();
gfx::Matrix matrix() const;
void setDrawMode(DrawMode mode, int param = 0, void setDrawMode(DrawMode mode, int param = 0,
const gfx::Color a = gfx::ColorNone, const gfx::Color a = gfx::ColorNone,
const gfx::Color b = gfx::ColorNone); const gfx::Color b = gfx::ColorNone);
@ -68,6 +77,7 @@ namespace ui {
void drawHLine(gfx::Color color, int x, int y, int w); void drawHLine(gfx::Color color, int x, int y, int w);
void drawVLine(gfx::Color color, int x, int y, int h); void drawVLine(gfx::Color color, int x, int y, int h);
void drawLine(gfx::Color color, const gfx::Point& a, const gfx::Point& b); void drawLine(gfx::Color color, const gfx::Point& a, const gfx::Point& b);
void drawPath(gfx::Path& path, const Paint& paint);
void drawRect(gfx::Color color, const gfx::Rect& rc); void drawRect(gfx::Color color, const gfx::Rect& rc);
void fillRect(gfx::Color color, const gfx::Rect& rc); void fillRect(gfx::Color color, const gfx::Rect& rc);