mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-14 04:19:12 +00:00
Add/process dynamic data in/from stroke points (related to #710)
* Converted Stroke points from gfx::Point to Stroke::Pt * Process gradient data to create shading betweens points or dithering with new doc::Brush patterns
This commit is contained in:
parent
79f9e28ce8
commit
3c1ea2f407
@ -9,6 +9,7 @@
|
||||
#define APP_TOOLS_CONTROLLER_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/tools/stroke.h"
|
||||
#include "app/tools/trace_policy.h"
|
||||
#include "gfx/point.h"
|
||||
|
||||
@ -18,7 +19,6 @@
|
||||
namespace app {
|
||||
namespace tools {
|
||||
|
||||
class Stroke;
|
||||
class ToolLoop;
|
||||
|
||||
// This class controls user input.
|
||||
@ -37,13 +37,13 @@ namespace app {
|
||||
// pressed. The controller could be sure that this method is called
|
||||
// at least one time. The point is a position relative to sprite
|
||||
// bounds.
|
||||
virtual void pressButton(ToolLoop* loop, Stroke& stroke, const gfx::Point& point) = 0;
|
||||
virtual void pressButton(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) = 0;
|
||||
|
||||
// Called each time a mouse button is released.
|
||||
virtual bool releaseButton(Stroke& stroke, const gfx::Point& point) = 0;
|
||||
virtual bool releaseButton(Stroke& stroke, const Stroke::Pt& pt) = 0;
|
||||
|
||||
// Called when the mouse is moved.
|
||||
virtual void movement(ToolLoop* loop, Stroke& stroke, const gfx::Point& point) = 0;
|
||||
virtual void movement(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) = 0;
|
||||
|
||||
// The input and output strokes are relative to sprite coordinates.
|
||||
virtual void getStrokeToInterwine(const Stroke& input, Stroke& output) = 0;
|
||||
@ -51,7 +51,7 @@ namespace app {
|
||||
|
||||
// Last point used by this controller, useful to save the last
|
||||
// point of a freehand tool.
|
||||
virtual gfx::Point getLastPoint() const { return gfx::Point(0, 0); }
|
||||
virtual Stroke::Pt getLastPoint() const { return gfx::Point(0, 0); }
|
||||
|
||||
// Special trace policy that can change in the middle of the
|
||||
// ToolLoop. This is for LineFreehandController which uses a
|
||||
|
@ -21,23 +21,24 @@ using namespace gfx;
|
||||
// using the space bar.
|
||||
class MoveOriginCapability : public Controller {
|
||||
public:
|
||||
void pressButton(ToolLoop* loop, Stroke& stroke, const Point& point) override {
|
||||
m_last = point;
|
||||
void pressButton(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
m_last = pt;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool isMovingOrigin(ToolLoop* loop, Stroke& stroke, const Point& point) {
|
||||
bool isMovingOrigin(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) {
|
||||
bool used = false;
|
||||
|
||||
if (int(loop->getModifiers()) & int(ToolLoopModifiers::kMoveOrigin)) {
|
||||
Point delta = (point - m_last);
|
||||
Point delta(pt.x - m_last.x,
|
||||
pt.y - m_last.y);
|
||||
stroke.offset(delta);
|
||||
|
||||
onMoveOrigin(delta);
|
||||
used = true;
|
||||
}
|
||||
|
||||
m_last = point;
|
||||
m_last = pt;
|
||||
return used;
|
||||
}
|
||||
|
||||
@ -48,7 +49,7 @@ protected:
|
||||
private:
|
||||
// Last known mouse position used to calculate delta values (dx, dy)
|
||||
// with the new mouse position to displace all points.
|
||||
Point m_last;
|
||||
Stroke::Pt m_last;
|
||||
};
|
||||
|
||||
// Controls clicks for tools like pencil
|
||||
@ -56,20 +57,20 @@ class FreehandController : public Controller {
|
||||
public:
|
||||
bool isFreehand() override { return true; }
|
||||
|
||||
gfx::Point getLastPoint() const override { return m_last; }
|
||||
Stroke::Pt getLastPoint() const override { return m_last; }
|
||||
|
||||
void pressButton(ToolLoop* loop, Stroke& stroke, const Point& point) override {
|
||||
m_last = point;
|
||||
stroke.addPoint(point);
|
||||
void pressButton(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
m_last = pt;
|
||||
stroke.addPoint(pt);
|
||||
}
|
||||
|
||||
bool releaseButton(Stroke& stroke, const Point& point) override {
|
||||
bool releaseButton(Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
void movement(ToolLoop* loop, Stroke& stroke, const Point& point) override {
|
||||
m_last = point;
|
||||
stroke.addPoint(point);
|
||||
void movement(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
m_last = pt;
|
||||
stroke.addPoint(pt);
|
||||
}
|
||||
|
||||
void getStrokeToInterwine(const Stroke& input, Stroke& output) override {
|
||||
@ -102,7 +103,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
Point m_last;
|
||||
Stroke::Pt m_last;
|
||||
};
|
||||
|
||||
// Controls clicks for tools like line
|
||||
@ -110,29 +111,29 @@ class TwoPointsController : public MoveOriginCapability {
|
||||
public:
|
||||
bool isTwoPoints() override { return true; }
|
||||
|
||||
void pressButton(ToolLoop* loop, Stroke& stroke, const Point& point) override {
|
||||
MoveOriginCapability::pressButton(loop, stroke, point);
|
||||
void pressButton(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
MoveOriginCapability::pressButton(loop, stroke, pt);
|
||||
|
||||
m_first = m_center = point;
|
||||
m_first = m_center = pt;
|
||||
m_angle = 0.0;
|
||||
|
||||
stroke.addPoint(point);
|
||||
stroke.addPoint(point);
|
||||
stroke.addPoint(pt);
|
||||
stroke.addPoint(pt);
|
||||
|
||||
if (loop->isSelectingTiles())
|
||||
snapPointsToGridTiles(loop, stroke);
|
||||
}
|
||||
|
||||
bool releaseButton(Stroke& stroke, const Point& point) override {
|
||||
bool releaseButton(Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
void movement(ToolLoop* loop, Stroke& stroke, const Point& point) override {
|
||||
void movement(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
ASSERT(stroke.size() >= 2);
|
||||
if (stroke.size() < 2)
|
||||
return;
|
||||
|
||||
if (MoveOriginCapability::isMovingOrigin(loop, stroke, point))
|
||||
if (MoveOriginCapability::isMovingOrigin(loop, stroke, pt))
|
||||
return;
|
||||
|
||||
if (!loop->getIntertwine()->snapByAngle() &&
|
||||
@ -144,13 +145,13 @@ public:
|
||||
m_center.x = (stroke[0].x+stroke[1].x)/2;
|
||||
m_center.y = (stroke[0].y+stroke[1].y)/2;
|
||||
}
|
||||
m_angle = std::atan2(static_cast<double>(point.y-m_center.y),
|
||||
static_cast<double>(point.x-m_center.x));
|
||||
m_angle = std::atan2(static_cast<double>(pt.y-m_center.y),
|
||||
static_cast<double>(pt.x-m_center.x));
|
||||
return;
|
||||
}
|
||||
|
||||
stroke[0] = m_first;
|
||||
stroke[1] = point;
|
||||
stroke[1] = pt;
|
||||
|
||||
if ((int(loop->getModifiers()) & int(ToolLoopModifiers::kSquareAspect))) {
|
||||
int dx = stroke[1].x - m_first.x;
|
||||
@ -285,10 +286,10 @@ private:
|
||||
void snapPointsToGridTiles(ToolLoop* loop, Stroke& stroke) {
|
||||
auto grid = loop->getGridBounds();
|
||||
|
||||
Rect a(snap_to_grid(grid, stroke[0], PreferSnapTo::BoxOrigin),
|
||||
snap_to_grid(grid, stroke[0], PreferSnapTo::BoxEnd));
|
||||
Rect b(snap_to_grid(grid, stroke[1], PreferSnapTo::BoxOrigin),
|
||||
snap_to_grid(grid, stroke[1], PreferSnapTo::BoxEnd));
|
||||
Rect a(snap_to_grid(grid, stroke[0].toPoint(), PreferSnapTo::BoxOrigin),
|
||||
snap_to_grid(grid, stroke[0].toPoint(), PreferSnapTo::BoxEnd));
|
||||
Rect b(snap_to_grid(grid, stroke[1].toPoint(), PreferSnapTo::BoxOrigin),
|
||||
snap_to_grid(grid, stroke[1].toPoint(), PreferSnapTo::BoxEnd));
|
||||
|
||||
a |= b;
|
||||
|
||||
@ -301,12 +302,14 @@ private:
|
||||
}
|
||||
|
||||
void onMoveOrigin(const Point& delta) override {
|
||||
m_first += delta;
|
||||
m_center += delta;
|
||||
m_first.x += delta.x;
|
||||
m_first.y += delta.y;
|
||||
m_center.x += delta.x;
|
||||
m_center.y += delta.y;
|
||||
}
|
||||
|
||||
Point m_first;
|
||||
Point m_center;
|
||||
Stroke::Pt m_first;
|
||||
Stroke::Pt m_center;
|
||||
double m_angle;
|
||||
};
|
||||
|
||||
@ -314,34 +317,34 @@ private:
|
||||
class PointByPointController : public MoveOriginCapability {
|
||||
public:
|
||||
|
||||
void pressButton(ToolLoop* loop, Stroke& stroke, const Point& point) override {
|
||||
MoveOriginCapability::pressButton(loop, stroke, point);
|
||||
void pressButton(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
MoveOriginCapability::pressButton(loop, stroke, pt);
|
||||
|
||||
stroke.addPoint(point);
|
||||
stroke.addPoint(point);
|
||||
stroke.addPoint(pt);
|
||||
stroke.addPoint(pt);
|
||||
}
|
||||
|
||||
bool releaseButton(Stroke& stroke, const Point& point) override {
|
||||
bool releaseButton(Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
ASSERT(!stroke.empty());
|
||||
if (stroke.empty())
|
||||
return false;
|
||||
|
||||
if (stroke[stroke.size()-2] == point &&
|
||||
stroke[stroke.size()-1] == point)
|
||||
if (stroke[stroke.size()-2] == pt &&
|
||||
stroke[stroke.size()-1] == pt)
|
||||
return false; // Click in the same point (no-drag), we are done
|
||||
else
|
||||
return true; // Continue adding points
|
||||
}
|
||||
|
||||
void movement(ToolLoop* loop, Stroke& stroke, const Point& point) override {
|
||||
void movement(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
ASSERT(!stroke.empty());
|
||||
if (stroke.empty())
|
||||
return;
|
||||
|
||||
if (MoveOriginCapability::isMovingOrigin(loop, stroke, point))
|
||||
if (MoveOriginCapability::isMovingOrigin(loop, stroke, pt))
|
||||
return;
|
||||
|
||||
stroke[stroke.size()-1] = point;
|
||||
stroke[stroke.size()-1] = pt;
|
||||
}
|
||||
|
||||
void getStrokeToInterwine(const Stroke& input, Stroke& output) override {
|
||||
@ -371,16 +374,16 @@ public:
|
||||
bool canSnapToGrid() override { return false; }
|
||||
bool isOnePoint() override { return true; }
|
||||
|
||||
void pressButton(ToolLoop* loop, Stroke& stroke, const Point& point) override {
|
||||
void pressButton(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
if (stroke.size() == 0)
|
||||
stroke.addPoint(point);
|
||||
stroke.addPoint(pt);
|
||||
}
|
||||
|
||||
bool releaseButton(Stroke& stroke, const Point& point) override {
|
||||
bool releaseButton(Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
void movement(ToolLoop* loop, Stroke& stroke, const Point& point) override {
|
||||
void movement(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@ -406,38 +409,38 @@ public:
|
||||
class FourPointsController : public MoveOriginCapability {
|
||||
public:
|
||||
|
||||
void pressButton(ToolLoop* loop, Stroke& stroke, const Point& point) override {
|
||||
MoveOriginCapability::pressButton(loop, stroke, point);
|
||||
void pressButton(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
MoveOriginCapability::pressButton(loop, stroke, pt);
|
||||
|
||||
if (stroke.size() == 0) {
|
||||
stroke.reset(4, point);
|
||||
stroke.reset(4, pt);
|
||||
m_clickCounter = 0;
|
||||
}
|
||||
else
|
||||
m_clickCounter++;
|
||||
}
|
||||
|
||||
bool releaseButton(Stroke& stroke, const Point& point) override {
|
||||
bool releaseButton(Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
m_clickCounter++;
|
||||
return m_clickCounter < 4;
|
||||
}
|
||||
|
||||
void movement(ToolLoop* loop, Stroke& stroke, const Point& point) override {
|
||||
if (MoveOriginCapability::isMovingOrigin(loop, stroke, point))
|
||||
void movement(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
if (MoveOriginCapability::isMovingOrigin(loop, stroke, pt))
|
||||
return;
|
||||
|
||||
switch (m_clickCounter) {
|
||||
case 0:
|
||||
for (int i=1; i<stroke.size(); ++i)
|
||||
stroke[i] = point;
|
||||
stroke[i] = pt;
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
stroke[1] = point;
|
||||
stroke[2] = point;
|
||||
stroke[1] = pt;
|
||||
stroke[2] = pt;
|
||||
break;
|
||||
case 3:
|
||||
stroke[2] = point;
|
||||
stroke[2] = pt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -472,14 +475,14 @@ class LineFreehandController : public Controller {
|
||||
public:
|
||||
bool isFreehand() override { return true; }
|
||||
|
||||
gfx::Point getLastPoint() const override { return m_last; }
|
||||
Stroke::Pt getLastPoint() const override { return m_last; }
|
||||
|
||||
void prepareController(ToolLoop* loop) override {
|
||||
m_controller = nullptr;
|
||||
}
|
||||
|
||||
void pressButton(ToolLoop* loop, Stroke& stroke, const Point& point) override {
|
||||
m_last = point;
|
||||
void pressButton(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
m_last = pt;
|
||||
|
||||
if (m_controller == nullptr)
|
||||
m_controller = &m_twoPoints;
|
||||
@ -488,18 +491,18 @@ public:
|
||||
return; // Don't send first pressButton() click to the freehand controller
|
||||
}
|
||||
|
||||
m_controller->pressButton(loop, stroke, point);
|
||||
m_controller->pressButton(loop, stroke, pt);
|
||||
}
|
||||
|
||||
bool releaseButton(Stroke& stroke, const Point& point) override {
|
||||
bool releaseButton(Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
if (!stroke.empty())
|
||||
m_last = stroke.lastPoint();
|
||||
return false;
|
||||
}
|
||||
|
||||
void movement(ToolLoop* loop, Stroke& stroke, const Point& point) override {
|
||||
m_last = point;
|
||||
m_controller->movement(loop, stroke, point);
|
||||
void movement(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) override {
|
||||
m_last = pt;
|
||||
m_controller->movement(loop, stroke, pt);
|
||||
}
|
||||
|
||||
void getStrokeToInterwine(const Stroke& input, Stroke& output) override {
|
||||
@ -519,7 +522,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
Point m_last;
|
||||
Stroke::Pt m_last;
|
||||
TwoPointsController m_twoPoints;
|
||||
FreehandController m_freehand;
|
||||
Controller* m_controller;
|
||||
|
@ -130,6 +130,9 @@ template<typename ImageTraits>
|
||||
class CopyInkProcessing : public SimpleInkProcessing<CopyInkProcessing<ImageTraits>, ImageTraits> {
|
||||
public:
|
||||
CopyInkProcessing(ToolLoop* loop) {
|
||||
}
|
||||
|
||||
void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
|
||||
m_color = loop->getPrimaryColor();
|
||||
|
||||
if (loop->getLayer()->isBackground()) {
|
||||
@ -156,8 +159,11 @@ template<typename ImageTraits>
|
||||
class LockAlphaInkProcessing : public DoubleInkProcessing<LockAlphaInkProcessing<ImageTraits>, ImageTraits> {
|
||||
public:
|
||||
LockAlphaInkProcessing(ToolLoop* loop)
|
||||
: m_color(loop->getPrimaryColor())
|
||||
, m_opacity(loop->getOpacity()) {
|
||||
: m_opacity(loop->getOpacity()) {
|
||||
}
|
||||
|
||||
void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
|
||||
m_color = loop->getPrimaryColor();
|
||||
}
|
||||
|
||||
void processPixel(int x, int y) {
|
||||
@ -165,7 +171,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
const color_t m_color;
|
||||
color_t m_color;
|
||||
const int m_opacity;
|
||||
};
|
||||
|
||||
@ -193,11 +199,14 @@ public:
|
||||
LockAlphaInkProcessing(ToolLoop* loop)
|
||||
: m_palette(get_current_palette())
|
||||
, m_rgbmap(loop->getRgbMap())
|
||||
, m_color(m_palette->getEntry(loop->getPrimaryColor()))
|
||||
, m_opacity(loop->getOpacity())
|
||||
, m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()) {
|
||||
}
|
||||
|
||||
void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
|
||||
m_color = m_palette->getEntry(loop->getPrimaryColor());
|
||||
}
|
||||
|
||||
void processPixel(int x, int y) {
|
||||
color_t c = *m_srcAddress;
|
||||
if (int(c) == m_maskIndex)
|
||||
@ -217,7 +226,7 @@ public:
|
||||
private:
|
||||
const Palette* m_palette;
|
||||
const RgbMap* m_rgbmap;
|
||||
const color_t m_color;
|
||||
color_t m_color;
|
||||
const int m_opacity;
|
||||
const int m_maskIndex;
|
||||
};
|
||||
@ -230,10 +239,13 @@ template<typename ImageTraits>
|
||||
class TransparentInkProcessing : public DoubleInkProcessing<TransparentInkProcessing<ImageTraits>, ImageTraits> {
|
||||
public:
|
||||
TransparentInkProcessing(ToolLoop* loop) {
|
||||
m_color = loop->getPrimaryColor();
|
||||
m_opacity = loop->getOpacity();
|
||||
}
|
||||
|
||||
void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
|
||||
m_color = loop->getPrimaryColor();
|
||||
}
|
||||
|
||||
void processPixel(int x, int y) {
|
||||
// Do nothing
|
||||
}
|
||||
@ -260,10 +272,13 @@ public:
|
||||
m_palette(get_current_palette()),
|
||||
m_rgbmap(loop->getRgbMap()),
|
||||
m_opacity(loop->getOpacity()),
|
||||
m_color(m_palette->getEntry(loop->getPrimaryColor())),
|
||||
m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()) {
|
||||
}
|
||||
|
||||
void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
|
||||
m_color = m_palette->getEntry(loop->getPrimaryColor());
|
||||
}
|
||||
|
||||
void processPixel(int x, int y) {
|
||||
color_t c = *m_srcAddress;
|
||||
if (int(c) == m_maskIndex)
|
||||
@ -282,7 +297,7 @@ private:
|
||||
const Palette* m_palette;
|
||||
const RgbMap* m_rgbmap;
|
||||
const int m_opacity;
|
||||
const color_t m_color;
|
||||
color_t m_color;
|
||||
const int m_maskIndex;
|
||||
};
|
||||
|
||||
@ -294,10 +309,13 @@ template<typename ImageTraits>
|
||||
class MergeInkProcessing : public DoubleInkProcessing<MergeInkProcessing<ImageTraits>, ImageTraits> {
|
||||
public:
|
||||
MergeInkProcessing(ToolLoop* loop) {
|
||||
m_color = loop->getPrimaryColor();
|
||||
m_opacity = loop->getOpacity();
|
||||
}
|
||||
|
||||
void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
|
||||
m_color = loop->getPrimaryColor();
|
||||
}
|
||||
|
||||
void processPixel(int x, int y) {
|
||||
// Do nothing
|
||||
}
|
||||
@ -324,10 +342,13 @@ public:
|
||||
m_palette(get_current_palette()),
|
||||
m_rgbmap(loop->getRgbMap()),
|
||||
m_opacity(loop->getOpacity()),
|
||||
m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()),
|
||||
m_color(int(loop->getPrimaryColor()) == m_maskIndex ?
|
||||
(m_palette->getEntry(loop->getPrimaryColor()) & rgba_rgb_mask):
|
||||
(m_palette->getEntry(loop->getPrimaryColor()))) {
|
||||
m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()) {
|
||||
}
|
||||
|
||||
void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
|
||||
m_color = (int(loop->getPrimaryColor()) == m_maskIndex ?
|
||||
(m_palette->getEntry(loop->getPrimaryColor()) & rgba_rgb_mask):
|
||||
(m_palette->getEntry(loop->getPrimaryColor())));
|
||||
}
|
||||
|
||||
void processPixel(int x, int y) {
|
||||
@ -349,7 +370,7 @@ private:
|
||||
const RgbMap* m_rgbmap;
|
||||
const int m_opacity;
|
||||
const int m_maskIndex;
|
||||
const color_t m_color;
|
||||
color_t m_color;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@ -911,8 +932,8 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
const gfx::Point u = strokes[0].firstPoint();
|
||||
const gfx::Point v = strokes[0].lastPoint();
|
||||
const gfx::Point u = strokes[0].firstPoint().toPoint();
|
||||
const gfx::Point v = strokes[0].lastPoint().toPoint();
|
||||
|
||||
// The image position for the gradient depends on the first user
|
||||
// click. The gradient depends on the first clicked tile.
|
||||
@ -952,7 +973,6 @@ public:
|
||||
, m_palette(get_current_palette())
|
||||
, m_rgbmap(loop->getRgbMap())
|
||||
, m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor())
|
||||
, m_matrix(loop->getDitheringMatrix())
|
||||
{
|
||||
}
|
||||
|
||||
@ -974,7 +994,6 @@ private:
|
||||
const Palette* m_palette;
|
||||
const RgbMap* m_rgbmap;
|
||||
const int m_maskIndex;
|
||||
const render::DitheringMatrix m_matrix;
|
||||
};
|
||||
|
||||
template<>
|
||||
@ -1115,8 +1134,10 @@ public:
|
||||
m_bgColor = loop->getSecondaryColor();
|
||||
m_palette = get_current_palette();
|
||||
m_brush = loop->getBrush();
|
||||
m_brushImage = m_brush->image();
|
||||
m_brushImage = (m_brush->patternImage() ? m_brush->patternImage():
|
||||
m_brush->image());
|
||||
m_brushMask = m_brush->maskBitmap();
|
||||
m_patternAlign = m_brush->pattern();
|
||||
m_opacity = loop->getOpacity();
|
||||
m_width = m_brush->bounds().w;
|
||||
m_height = m_brush->bounds().h;
|
||||
@ -1126,16 +1147,15 @@ public:
|
||||
}
|
||||
|
||||
void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
|
||||
if ((m_brush->pattern() == BrushPattern::ALIGNED_TO_DST && firstPoint) ||
|
||||
(m_brush->pattern() == BrushPattern::PAINT_BRUSH)) {
|
||||
if ((m_patternAlign == BrushPattern::ALIGNED_TO_DST && firstPoint) ||
|
||||
(m_patternAlign == BrushPattern::PAINT_BRUSH)) {
|
||||
m_u = ((m_brush->patternOrigin().x % loop->sprite()->width()) - loop->getCelOrigin().x) % m_width;
|
||||
m_v = ((m_brush->patternOrigin().y % loop->sprite()->height()) - loop->getCelOrigin().y) % m_height;
|
||||
}
|
||||
}
|
||||
|
||||
void prepareVForPointShape(ToolLoop* loop, int y) override {
|
||||
|
||||
if (m_brush->pattern() == doc::BrushPattern::ALIGNED_TO_SRC) {
|
||||
if (m_patternAlign == doc::BrushPattern::ALIGNED_TO_SRC) {
|
||||
m_v = (m_brush->patternOrigin().y - loop->getCelOrigin().y) % m_height;
|
||||
if (m_v < 0) m_v += m_height;
|
||||
}
|
||||
@ -1151,7 +1171,7 @@ public:
|
||||
}
|
||||
|
||||
void prepareUForPointShapeWholeScanline(ToolLoop* loop, int x1) override {
|
||||
if (m_brush->pattern() == doc::BrushPattern::ALIGNED_TO_SRC) {
|
||||
if (m_patternAlign == doc::BrushPattern::ALIGNED_TO_SRC) {
|
||||
m_u = (m_brush->patternOrigin().x - loop->getCelOrigin().x) % m_width;
|
||||
if (m_u < 0) m_u += m_height;
|
||||
}
|
||||
@ -1163,7 +1183,7 @@ public:
|
||||
}
|
||||
|
||||
void prepareUForPointShapeSlicedScanline(ToolLoop* loop, bool leftSlice, int x1) override {
|
||||
if (loop->getBrush()->pattern() == doc::BrushPattern::ALIGNED_TO_SRC) {
|
||||
if (m_patternAlign == doc::BrushPattern::ALIGNED_TO_SRC) {
|
||||
m_u = (m_brush->patternOrigin().x - loop->getCelOrigin().x) % m_width;
|
||||
if (m_u < 0) m_u += m_height;
|
||||
return;
|
||||
@ -1189,11 +1209,27 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
void alignPixelPoint(int& x, int& y) {
|
||||
x = (x - m_u) % m_width;
|
||||
y = (y - m_v) % m_height;
|
||||
bool alignPixelPoint(int& x0, int& y0) {
|
||||
int x = (x0 - m_u) % m_width;
|
||||
int y = (y0 - m_v) % m_height;
|
||||
if (x < 0) x = m_width - ((-x) % m_width);
|
||||
if (y < 0) y = m_height - ((-y) % m_height);
|
||||
|
||||
if (m_brushMask && !get_pixel_fast<BitmapTraits>(m_brushMask, x, y))
|
||||
return false;
|
||||
|
||||
if (m_brush->patternImage()) {
|
||||
const int w = m_brush->patternImage()->width();
|
||||
const int h = m_brush->patternImage()->height();
|
||||
x = x0 % w;
|
||||
y = y0 % h;
|
||||
if (x < 0) x = w - ((-x) % w);
|
||||
if (y < 0) y = h - ((-y) % h);
|
||||
}
|
||||
|
||||
x0 = x;
|
||||
y0 = y;
|
||||
return true;
|
||||
}
|
||||
|
||||
color_t m_fgColor;
|
||||
@ -1202,6 +1238,7 @@ protected:
|
||||
const Brush* m_brush;
|
||||
const Image* m_brushImage;
|
||||
const Image* m_brushMask;
|
||||
BrushPattern m_patternAlign;
|
||||
int m_opacity;
|
||||
int m_u, m_v, m_width, m_height;
|
||||
// When we have a image brush from an INDEXED sprite, we need to know
|
||||
@ -1212,8 +1249,7 @@ protected:
|
||||
|
||||
template<>
|
||||
bool BrushInkProcessingBase<RgbTraits>::preProcessPixel(int x, int y, color_t* result) {
|
||||
alignPixelPoint(x, y);
|
||||
if (m_brushMask && !get_pixel_fast<BitmapTraits>(m_brushMask, x, y))
|
||||
if (!alignPixelPoint(x, y))
|
||||
return false;
|
||||
|
||||
color_t c;
|
||||
@ -1264,8 +1300,7 @@ bool BrushInkProcessingBase<RgbTraits>::preProcessPixel(int x, int y, color_t* r
|
||||
|
||||
template<>
|
||||
bool BrushInkProcessingBase<GrayscaleTraits>::preProcessPixel(int x, int y, color_t* result) {
|
||||
alignPixelPoint(x, y);
|
||||
if (m_brushMask && !get_pixel_fast<BitmapTraits>(m_brushMask, x, y))
|
||||
if (!alignPixelPoint(x, y))
|
||||
return false;
|
||||
|
||||
color_t c;
|
||||
@ -1313,8 +1348,7 @@ bool BrushInkProcessingBase<GrayscaleTraits>::preProcessPixel(int x, int y, colo
|
||||
|
||||
template<>
|
||||
bool BrushInkProcessingBase<IndexedTraits>::preProcessPixel(int x, int y, color_t* result) {
|
||||
alignPixelPoint(x, y);
|
||||
if (m_brushMask && !get_pixel_fast<BitmapTraits>(m_brushMask, x, y))
|
||||
if (!alignPixelPoint(x, y))
|
||||
return false;
|
||||
|
||||
color_t c;
|
||||
@ -1444,8 +1478,7 @@ public:
|
||||
|
||||
template<>
|
||||
void BrushEraserInkProcessing<RgbTraits>::processPixel(int x, int y) {
|
||||
alignPixelPoint(x, y);
|
||||
if (m_brushMask && !get_pixel_fast<BitmapTraits>(m_brushMask, x, y))
|
||||
if (!alignPixelPoint(x, y))
|
||||
return;
|
||||
|
||||
color_t c;
|
||||
@ -1502,8 +1535,7 @@ void BrushEraserInkProcessing<RgbTraits>::processPixel(int x, int y) {
|
||||
|
||||
template<>
|
||||
void BrushEraserInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
|
||||
alignPixelPoint(x, y);
|
||||
if (m_brushMask && !get_pixel_fast<BitmapTraits>(m_brushMask, x, y))
|
||||
if (!alignPixelPoint(x, y))
|
||||
return;
|
||||
|
||||
color_t c;
|
||||
@ -1554,8 +1586,7 @@ void BrushEraserInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
|
||||
|
||||
template<>
|
||||
void BrushEraserInkProcessing<IndexedTraits>::processPixel(int x, int y) {
|
||||
alignPixelPoint(x, y);
|
||||
if (m_brushMask && !get_pixel_fast<BitmapTraits>(m_brushMask, x, y))
|
||||
if (!alignPixelPoint(x, y))
|
||||
return;
|
||||
|
||||
color_t c;
|
||||
@ -1632,8 +1663,7 @@ RgbTraits::pixel_t BrushShadingInkProcessing<RgbTraits>::shadingProcessPixel(int
|
||||
|
||||
template <>
|
||||
void BrushShadingInkProcessing<RgbTraits>::processPixel(int x, int y) {
|
||||
alignPixelPoint(x, y);
|
||||
if (m_brushMask && !get_pixel_fast<BitmapTraits>(m_brushMask, x, y))
|
||||
if (!alignPixelPoint(x, y))
|
||||
return;
|
||||
|
||||
RgbTraits::pixel_t c;
|
||||
@ -1689,8 +1719,7 @@ IndexedTraits::pixel_t BrushShadingInkProcessing<IndexedTraits>::shadingProcessP
|
||||
|
||||
template <>
|
||||
void BrushShadingInkProcessing<IndexedTraits>::processPixel(int x, int y) {
|
||||
alignPixelPoint(x, y);
|
||||
if (m_brushMask && !get_pixel_fast<BitmapTraits>(m_brushMask, x, y))
|
||||
if (!alignPixelPoint(x, y))
|
||||
return;
|
||||
|
||||
color_t c;
|
||||
@ -1750,8 +1779,7 @@ GrayscaleTraits::pixel_t BrushShadingInkProcessing<GrayscaleTraits>::shadingProc
|
||||
|
||||
template <>
|
||||
void BrushShadingInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
|
||||
alignPixelPoint(x, y);
|
||||
if (m_brushMask && !get_pixel_fast<BitmapTraits>(m_brushMask, x, y))
|
||||
if (!alignPixelPoint(x, y))
|
||||
return;
|
||||
|
||||
int iBrush = -1;
|
||||
@ -1819,8 +1847,7 @@ public:
|
||||
|
||||
template<>
|
||||
void BrushCopyInkProcessing<RgbTraits>::processPixel(int x, int y) {
|
||||
alignPixelPoint(x, y);
|
||||
if (m_brushMask && !get_pixel_fast<BitmapTraits>(m_brushMask, x, y))
|
||||
if (!alignPixelPoint(x, y))
|
||||
return;
|
||||
|
||||
color_t c;
|
||||
@ -1866,8 +1893,7 @@ void BrushCopyInkProcessing<RgbTraits>::processPixel(int x, int y) {
|
||||
|
||||
template<>
|
||||
void BrushCopyInkProcessing<IndexedTraits>::processPixel(int x, int y) {
|
||||
alignPixelPoint(x, y);
|
||||
if (m_brushMask && !get_pixel_fast<BitmapTraits>(m_brushMask, x, y))
|
||||
if (!alignPixelPoint(x, y))
|
||||
return;
|
||||
|
||||
color_t c;
|
||||
@ -1912,8 +1938,7 @@ void BrushCopyInkProcessing<IndexedTraits>::processPixel(int x, int y) {
|
||||
|
||||
template<>
|
||||
void BrushCopyInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
|
||||
alignPixelPoint(x, y);
|
||||
if (m_brushMask && !get_pixel_fast<BitmapTraits>(m_brushMask, x, y))
|
||||
if (!alignPixelPoint(x, y))
|
||||
return;
|
||||
|
||||
color_t c;
|
||||
|
@ -96,7 +96,7 @@ public:
|
||||
break;
|
||||
}
|
||||
|
||||
if (loop->getBrush()->type() == doc::kImageBrushType)
|
||||
if (loop->getBrush()->type() == doc::kImageBrushType) {
|
||||
switch (m_type) {
|
||||
case Simple:
|
||||
setProc(get_ink_proc<BrushSimpleInkProcessing>(loop));
|
||||
@ -111,12 +111,19 @@ public:
|
||||
setProc(get_ink_proc<BrushSimpleInkProcessing>(loop));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (m_type) {
|
||||
case Simple: {
|
||||
bool opaque = false;
|
||||
|
||||
if (loop->getOpacity() == 255) {
|
||||
if (loop->getOpacity() == 255 &&
|
||||
// The trace policy is "overlap" when the dynamics has
|
||||
// a gradient between FG <-> BG
|
||||
//
|
||||
// TODO this trace policy is configured in
|
||||
// ToolLoopBase() ctor, is there a better place?
|
||||
loop->getTracePolicy() != TracePolicy::Overlap) {
|
||||
color_t color = loop->getPrimaryColor();
|
||||
|
||||
switch (loop->sprite()->pixelFormat()) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -24,35 +24,76 @@ namespace tools {
|
||||
using namespace gfx;
|
||||
using namespace doc;
|
||||
|
||||
Intertwine::LineData::LineData(ToolLoop* loop,
|
||||
const Stroke::Pt& a,
|
||||
const Stroke::Pt& b)
|
||||
: loop(loop)
|
||||
, a(a)
|
||||
, b(b)
|
||||
, pt(a)
|
||||
{
|
||||
const int steps = std::max(std::abs(b.x - a.x),
|
||||
std::abs(b.y - a.y))+1;
|
||||
t = 0.0f;
|
||||
step = 1.0f / steps;
|
||||
}
|
||||
|
||||
void Intertwine::LineData::doStep(int x, int y)
|
||||
{
|
||||
t += step;
|
||||
const float ti = 1.0f-t;
|
||||
|
||||
pt.x = x;
|
||||
pt.y = y;
|
||||
pt.size = ti*a.size + t*b.size;
|
||||
pt.angle = ti*a.angle + t*b.angle;
|
||||
pt.gradient = ti*a.gradient + t*b.gradient;
|
||||
}
|
||||
|
||||
gfx::Rect Intertwine::getStrokeBounds(ToolLoop* loop, const Stroke& stroke)
|
||||
{
|
||||
return stroke.bounds();
|
||||
}
|
||||
|
||||
// static
|
||||
void Intertwine::doPointshapePoint(int x, int y, ToolLoop* loop)
|
||||
void Intertwine::doPointshapeStrokePt(const Stroke::Pt& pt, ToolLoop* loop)
|
||||
{
|
||||
Symmetry* symmetry = loop->getSymmetry();
|
||||
if (symmetry) {
|
||||
// Convert the point to the sprite position so we can apply the
|
||||
// symmetry transformation.
|
||||
Stroke main_stroke;
|
||||
main_stroke.addPoint(Point(x, y));
|
||||
main_stroke.addPoint(pt);
|
||||
|
||||
Strokes strokes;
|
||||
symmetry->generateStrokes(main_stroke, strokes, loop);
|
||||
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->getPointShape()->transformPoint(loop, stroke[0]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
loop->getPointShape()->transformPoint(loop, x, y);
|
||||
loop->getPointShape()->transformPoint(loop, pt);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void Intertwine::doPointshapePoint(int x, int y, ToolLoop* loop)
|
||||
{
|
||||
Stroke::Pt pt(x, y);
|
||||
pt.size = loop->getBrush()->size();
|
||||
pt.angle = loop->getBrush()->angle();
|
||||
doPointshapeStrokePt(pt, loop);
|
||||
}
|
||||
|
||||
// static
|
||||
void Intertwine::doPointshapePointDynamics(int x, int y, Intertwine::LineData* data)
|
||||
{
|
||||
data->doStep(x, y);
|
||||
doPointshapeStrokePt(data->pt, data->loop);
|
||||
}
|
||||
|
||||
// static
|
||||
void Intertwine::doPointshapeHline(int x1, int y, int x2, ToolLoop* loop)
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -9,13 +9,13 @@
|
||||
#define APP_TOOLS_INTERTWINE_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/tools/stroke.h"
|
||||
#include "doc/algo.h"
|
||||
#include "gfx/point.h"
|
||||
#include "gfx/rect.h"
|
||||
|
||||
namespace app {
|
||||
namespace tools {
|
||||
class Stroke;
|
||||
class ToolLoop;
|
||||
|
||||
// Converts a sequence of points in several call to
|
||||
@ -34,9 +34,19 @@ namespace app {
|
||||
|
||||
virtual gfx::Rect getStrokeBounds(ToolLoop* loop, const Stroke& stroke);
|
||||
|
||||
struct LineData {
|
||||
ToolLoop* loop;
|
||||
Stroke::Pt a, b, pt;
|
||||
float t, step;
|
||||
LineData(ToolLoop* loop, const Stroke::Pt& a, const Stroke::Pt& b);
|
||||
void doStep(int x, int y);
|
||||
};
|
||||
|
||||
protected:
|
||||
static void doPointshapeStrokePt(const Stroke::Pt& pt, ToolLoop* loop);
|
||||
// The given point must be relative to the cel origin.
|
||||
static void doPointshapePoint(int x, int y, ToolLoop* loop);
|
||||
static void doPointshapePointDynamics(int x, int y, LineData* data);
|
||||
static void doPointshapeHline(int x1, int y, int x2, ToolLoop* loop);
|
||||
static void doPointshapeLine(int x1, int y1, int x2, int y2, ToolLoop* loop);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,12 +10,22 @@
|
||||
namespace app {
|
||||
namespace tools {
|
||||
|
||||
static void addPointsWithoutDuplicatingLastOne(int x, int y, Stroke* stroke)
|
||||
struct LineData2 {
|
||||
Intertwine::LineData head;
|
||||
Stroke& output;
|
||||
LineData2(ToolLoop* loop, const Stroke::Pt& a, const Stroke::Pt& b,
|
||||
Stroke& output)
|
||||
: head(loop, a, b)
|
||||
, output(output) {
|
||||
}
|
||||
};
|
||||
|
||||
static void addPointsWithoutDuplicatingLastOne(int x, int y, LineData2* data)
|
||||
{
|
||||
const gfx::Point newPoint(x, y);
|
||||
if (stroke->empty() ||
|
||||
stroke->lastPoint() != newPoint) {
|
||||
stroke->addPoint(newPoint);
|
||||
data->head.doStep(x, y);
|
||||
if (data->output.empty() ||
|
||||
data->output.lastPoint() != data->head.pt) {
|
||||
data->output.addPoint(data->head.pt);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +34,7 @@ public:
|
||||
|
||||
void joinStroke(ToolLoop* loop, const Stroke& stroke) override {
|
||||
for (int c=0; c<stroke.size(); ++c)
|
||||
doPointshapePoint(stroke[c].x, stroke[c].y, loop);
|
||||
doPointshapeStrokePt(stroke[c], loop);
|
||||
}
|
||||
|
||||
void fillStroke(ToolLoop* loop, const Stroke& stroke) override {
|
||||
@ -56,7 +66,7 @@ public:
|
||||
mid.y /= n;
|
||||
}
|
||||
else {
|
||||
mid = stroke[0];
|
||||
mid = stroke[0].toPoint();
|
||||
}
|
||||
|
||||
doPointshapePoint(mid.x, mid.y, loop);
|
||||
@ -107,9 +117,10 @@ public:
|
||||
Stroke pts;
|
||||
doc::AlgoLineWithAlgoPixel lineAlgo = getLineAlgo(loop);
|
||||
for (int c=0; c+1<stroke.size(); ++c) {
|
||||
LineData2 lineData(loop, stroke[c], stroke[c+1], pts);
|
||||
lineAlgo(stroke[c].x, stroke[c].y,
|
||||
stroke[c+1].x, stroke[c+1].y,
|
||||
(void*)&pts,
|
||||
(void*)&lineData,
|
||||
(AlgoPixel)&addPointsWithoutDuplicatingLastOne);
|
||||
}
|
||||
|
||||
@ -123,7 +134,7 @@ public:
|
||||
(m_retainedTracePolicyLast || !m_firstStroke) ? 1: 0);
|
||||
|
||||
for (int c=start; c<pts.size(); ++c)
|
||||
doPointshapePoint(pts[c].x, pts[c].y, loop);
|
||||
doPointshapeStrokePt(pts[c], loop);
|
||||
|
||||
// Closed shape (polygon outline)
|
||||
// Note: Contour tool was getting into the condition with no need, so
|
||||
@ -160,7 +171,10 @@ public:
|
||||
#endif
|
||||
|
||||
// Fill content
|
||||
doc::algorithm::polygon(stroke.size(), (const int*)&stroke[0], loop, (AlgoHLine)doPointshapeHline);
|
||||
auto v = stroke.toXYInts();
|
||||
doc::algorithm::polygon(
|
||||
v.size()/2, &v[0],
|
||||
loop, (AlgoHLine)doPointshapeHline);
|
||||
}
|
||||
|
||||
};
|
||||
@ -233,8 +247,9 @@ public:
|
||||
}
|
||||
else {
|
||||
Stroke p = rotateRectangle(x1, y1, x2, y2, angle);
|
||||
auto v = p.toXYInts();
|
||||
doc::algorithm::polygon(
|
||||
p.size(), (const int*)&p[0],
|
||||
v.size()/2, &v[0],
|
||||
loop, (AlgoHLine)doPointshapeHline);
|
||||
}
|
||||
}
|
||||
@ -445,17 +460,18 @@ public:
|
||||
else if (stroke.size() == 1) {
|
||||
if (m_pts.empty())
|
||||
m_pts = stroke;
|
||||
doPointshapePoint(stroke[0].x, stroke[0].y, loop);
|
||||
doPointshapeStrokePt(stroke[0], loop);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
for (int c=0; c+1<stroke.size(); ++c) {
|
||||
LineData2 lineData(loop, stroke[c], stroke[c+1], m_pts);
|
||||
algo_line_continuous(
|
||||
stroke[c].x,
|
||||
stroke[c].y,
|
||||
stroke[c+1].x,
|
||||
stroke[c+1].y,
|
||||
(void*)&m_pts,
|
||||
(void*)&lineData,
|
||||
(AlgoPixel)&addPointsWithoutDuplicatingLastOne);
|
||||
}
|
||||
}
|
||||
@ -477,7 +493,7 @@ public:
|
||||
// the SHIFT key))
|
||||
if (c == 0 && m_retainedTracePolicyLast)
|
||||
continue;
|
||||
doPointshapePoint(m_pts[c].x, m_pts[c].y, loop);
|
||||
doPointshapeStrokePt(m_pts[c], loop);
|
||||
}
|
||||
}
|
||||
|
||||
@ -486,8 +502,12 @@ public:
|
||||
joinStroke(loop, stroke);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill content
|
||||
doc::algorithm::polygon(m_pts.size(), (const int*)&m_pts[0], loop, (AlgoHLine)doPointshapeHline);
|
||||
auto v = m_pts.toXYInts();
|
||||
doc::algorithm::polygon(
|
||||
v.size()/2, &v[0],
|
||||
loop, (AlgoHLine)doPointshapeHline);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -8,6 +9,7 @@
|
||||
#define APP_TOOLS_TOOL_POINT_SHAPE_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/tools/stroke.h"
|
||||
#include "gfx/rect.h"
|
||||
|
||||
namespace app {
|
||||
@ -24,7 +26,7 @@ namespace app {
|
||||
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 transformPoint(ToolLoop* loop, const Stroke::Pt& pt) = 0;
|
||||
virtual void getModifiedArea(ToolLoop* loop, int x, int y, gfx::Rect& area) = 0;
|
||||
|
||||
protected:
|
||||
|
@ -8,13 +8,14 @@
|
||||
#include "app/util/wrap_point.h"
|
||||
|
||||
#include "app/tools/ink.h"
|
||||
#include "render/gradient.h"
|
||||
|
||||
namespace app {
|
||||
namespace tools {
|
||||
|
||||
class NonePointShape : public PointShape {
|
||||
public:
|
||||
void transformPoint(ToolLoop* loop, int x, int y) override {
|
||||
void transformPoint(ToolLoop* loop, const Stroke::Pt& pt) override {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@ -27,8 +28,8 @@ class PixelPointShape : public PointShape {
|
||||
public:
|
||||
bool isPixel() override { return true; }
|
||||
|
||||
void transformPoint(ToolLoop* loop, int x, int y) override {
|
||||
doInkHline(x, y, x, loop);
|
||||
void transformPoint(ToolLoop* loop, const Stroke::Pt& pt) override {
|
||||
doInkHline(pt.x, pt.y, pt.x, loop);
|
||||
}
|
||||
|
||||
void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {
|
||||
@ -37,51 +38,162 @@ public:
|
||||
};
|
||||
|
||||
class BrushPointShape : public PointShape {
|
||||
Brush* m_lastBrush;
|
||||
std::shared_ptr<CompressedImage> m_compressedImage;
|
||||
bool m_firstPoint;
|
||||
Brush* m_lastBrush;
|
||||
BrushType m_origBrushType;
|
||||
std::shared_ptr<CompressedImage> m_compressedImage;
|
||||
// For dynamics
|
||||
DynamicsOptions m_dynamics;
|
||||
bool m_useDynamics;
|
||||
bool m_hasDynamicGradient;
|
||||
color_t m_primaryColor;
|
||||
color_t m_secondaryColor;
|
||||
float m_lastGradientValue;
|
||||
|
||||
public:
|
||||
|
||||
void preparePointShape(ToolLoop* loop) override {
|
||||
m_firstPoint = true;
|
||||
m_lastBrush = nullptr;
|
||||
m_origBrushType = loop->getBrush()->type();
|
||||
|
||||
m_dynamics = loop->getDynamics();
|
||||
m_useDynamics = (m_dynamics.isDynamic() &&
|
||||
// TODO support custom brushes in future versions
|
||||
m_origBrushType != kImageBrushType);
|
||||
|
||||
// For dynamic gradient
|
||||
m_hasDynamicGradient = (m_dynamics.gradient != DynamicSensor::Static);
|
||||
m_primaryColor = loop->getPrimaryColor();
|
||||
m_secondaryColor = loop->getSecondaryColor();
|
||||
m_lastGradientValue = -1;
|
||||
}
|
||||
|
||||
void transformPoint(ToolLoop* loop, int x, int y) override {
|
||||
Brush* m_brush = loop->getBrush();
|
||||
if (m_lastBrush != m_brush) {
|
||||
m_lastBrush = m_brush;
|
||||
m_compressedImage.reset(new CompressedImage(m_brush->image(),
|
||||
m_brush->maskBitmap(),
|
||||
void transformPoint(ToolLoop* loop, const Stroke::Pt& pt) override {
|
||||
int x = pt.x;
|
||||
int y = pt.y;
|
||||
|
||||
Brush* brush = loop->getBrush();
|
||||
|
||||
// Dynamics
|
||||
if (m_useDynamics) {
|
||||
// Dynamic gradient info
|
||||
if (m_hasDynamicGradient &&
|
||||
m_dynamics.ditheringMatrix.rows() == 1 &&
|
||||
m_dynamics.ditheringMatrix.cols() == 1) {
|
||||
color_t a = m_primaryColor;
|
||||
color_t b = m_secondaryColor;
|
||||
const float t = pt.gradient;
|
||||
const float ti = 1.0f - pt.gradient;
|
||||
switch (loop->sprite()->pixelFormat()) {
|
||||
case IMAGE_RGB:
|
||||
if (rgba_geta(a) == 0) a = b;
|
||||
else if (rgba_geta(b) == 0) b = a;
|
||||
a = doc::rgba(int(ti*rgba_getr(a) + t*rgba_getr(b)),
|
||||
int(ti*rgba_getg(a) + t*rgba_getg(b)),
|
||||
int(ti*rgba_getb(a) + t*rgba_getb(b)),
|
||||
int(ti*rgba_geta(a) + t*rgba_geta(b)));
|
||||
break;
|
||||
case IMAGE_GRAYSCALE:
|
||||
if (graya_geta(a) == 0) a = b;
|
||||
else if (graya_geta(b) == 0) b = a;
|
||||
a = doc::graya(int(ti*graya_getv(a) + t*graya_getv(b)),
|
||||
int(ti*graya_geta(a) + t*graya_geta(b)));
|
||||
break;
|
||||
case IMAGE_INDEXED: {
|
||||
int maskIndex = (loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor());
|
||||
// Convert index to RGBA
|
||||
if (a == maskIndex) a = 0;
|
||||
else a = get_current_palette()->getEntry(a);
|
||||
if (b == maskIndex) b = 0;
|
||||
else b = get_current_palette()->getEntry(b);
|
||||
// Same as in RGBA gradient
|
||||
if (rgba_geta(a) == 0) a = b;
|
||||
else if (rgba_geta(b) == 0) b = a;
|
||||
a = doc::rgba(int(ti*rgba_getr(a) + t*rgba_getr(b)),
|
||||
int(ti*rgba_getg(a) + t*rgba_getg(b)),
|
||||
int(ti*rgba_getb(a) + t*rgba_getb(b)),
|
||||
int(ti*rgba_geta(a) + t*rgba_geta(b)));
|
||||
// Convert RGBA to index
|
||||
a = loop->getRgbMap()->mapColor(rgba_getr(a),
|
||||
rgba_getg(a),
|
||||
rgba_getb(a),
|
||||
rgba_geta(a));
|
||||
break;
|
||||
}
|
||||
}
|
||||
loop->setPrimaryColor(a);
|
||||
}
|
||||
|
||||
// Dynamic size and angle
|
||||
int size = base::clamp(int(pt.size), int(Brush::kMinBrushSize), int(Brush::kMaxBrushSize));
|
||||
int angle = base::clamp(int(pt.angle), -180, 180);
|
||||
if ((brush->size() != size) ||
|
||||
(brush->angle() != angle && m_origBrushType != kCircleBrushType) ||
|
||||
(m_hasDynamicGradient && pt.gradient != m_lastGradientValue)) {
|
||||
// TODO cache brushes
|
||||
BrushRef newBrush = std::make_shared<Brush>(
|
||||
m_origBrushType, size, angle);
|
||||
|
||||
// Dynamic gradient with dithering
|
||||
bool prepareInk = false;
|
||||
if (m_hasDynamicGradient &&
|
||||
(m_dynamics.ditheringMatrix.rows() > 1 ||
|
||||
m_dynamics.ditheringMatrix.cols() > 1)) {
|
||||
convert_bitmap_brush_to_dithering_brush(
|
||||
newBrush.get(),
|
||||
loop->sprite()->pixelFormat(),
|
||||
m_dynamics.ditheringMatrix,
|
||||
pt.gradient,
|
||||
m_primaryColor,
|
||||
m_secondaryColor);
|
||||
prepareInk = true;
|
||||
}
|
||||
m_lastGradientValue = pt.gradient;
|
||||
|
||||
loop->setBrush(newBrush);
|
||||
brush = loop->getBrush();
|
||||
|
||||
if (prepareInk) {
|
||||
// Prepare ink for the new brush
|
||||
loop->getInk()->prepareInk(loop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO cache compressed images (or remove them completelly)
|
||||
if (m_lastBrush != brush) {
|
||||
m_lastBrush = brush;
|
||||
m_compressedImage.reset(new CompressedImage(brush->image(),
|
||||
brush->maskBitmap(),
|
||||
false));
|
||||
}
|
||||
|
||||
x += m_brush->bounds().x;
|
||||
y += m_brush->bounds().y;
|
||||
x += brush->bounds().x;
|
||||
y += brush->bounds().y;
|
||||
|
||||
if (m_firstPoint) {
|
||||
if ((m_brush->type() == kImageBrushType) &&
|
||||
(m_brush->pattern() == BrushPattern::ALIGNED_TO_DST ||
|
||||
m_brush->pattern() == BrushPattern::PAINT_BRUSH)) {
|
||||
m_brush->setPatternOrigin(gfx::Point(x, y));
|
||||
if ((brush->type() == kImageBrushType) &&
|
||||
(brush->pattern() == BrushPattern::ALIGNED_TO_DST ||
|
||||
brush->pattern() == BrushPattern::PAINT_BRUSH)) {
|
||||
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));
|
||||
if (brush->type() == kImageBrushType &&
|
||||
brush->pattern() == BrushPattern::PAINT_BRUSH) {
|
||||
brush->setPatternOrigin(gfx::Point(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
if (int(loop->getTiledMode()) & int(TiledMode::X_AXIS)) {
|
||||
int wrappedPatternOriginX = wrap_value(m_brush->patternOrigin().x, loop->sprite()->width()) % m_brush->bounds().w;
|
||||
m_brush->setPatternOrigin(gfx::Point(wrappedPatternOriginX, m_brush->patternOrigin().y));
|
||||
int wrappedPatternOriginX = wrap_value(brush->patternOrigin().x, loop->sprite()->width()) % brush->bounds().w;
|
||||
brush->setPatternOrigin(gfx::Point(wrappedPatternOriginX, brush->patternOrigin().y));
|
||||
x = wrap_value(x, loop->sprite()->width());
|
||||
}
|
||||
if (int(loop->getTiledMode()) & int(TiledMode::Y_AXIS)) {
|
||||
int wrappedPatternOriginY = wrap_value(m_brush->patternOrigin().y, loop->sprite()->height()) % m_brush->bounds().h;
|
||||
m_brush->setPatternOrigin(gfx::Point(m_brush->patternOrigin().x, wrappedPatternOriginY));
|
||||
int wrappedPatternOriginY = wrap_value(brush->patternOrigin().y, loop->sprite()->height()) % brush->bounds().h;
|
||||
brush->setPatternOrigin(gfx::Point(brush->patternOrigin().x, wrappedPatternOriginY));
|
||||
y = wrap_value(y, loop->sprite()->height());
|
||||
}
|
||||
|
||||
@ -107,19 +219,19 @@ class FloodFillPointShape : public PointShape {
|
||||
public:
|
||||
bool isFloodFill() override { return true; }
|
||||
|
||||
void transformPoint(ToolLoop* loop, int x, int y) override {
|
||||
void transformPoint(ToolLoop* loop, const Stroke::Pt& pt) override {
|
||||
const doc::Image* srcImage = loop->getFloodFillSrcImage();
|
||||
gfx::Point pt = wrap_point(loop->getTiledMode(),
|
||||
gfx::Size(srcImage->width(),
|
||||
srcImage->height()),
|
||||
gfx::Point(x, y), true);
|
||||
gfx::Point wpt = wrap_point(loop->getTiledMode(),
|
||||
gfx::Size(srcImage->width(),
|
||||
srcImage->height()),
|
||||
pt.toPoint(), true);
|
||||
|
||||
doc::algorithm::floodfill(
|
||||
srcImage,
|
||||
(loop->useMask() ? loop->getMask(): nullptr),
|
||||
pt.x, pt.y,
|
||||
floodfillBounds(loop, pt.x, pt.y),
|
||||
get_pixel(srcImage, pt.x, pt.y),
|
||||
wpt.x, wpt.y,
|
||||
floodfillBounds(loop, wpt.x, wpt.y),
|
||||
get_pixel(srcImage, wpt.x, wpt.y),
|
||||
loop->getTolerance(),
|
||||
loop->getContiguous(),
|
||||
loop->isPixelConnectivityEightConnected(),
|
||||
@ -173,7 +285,7 @@ public:
|
||||
m_subPointShape.preparePointShape(loop);
|
||||
}
|
||||
|
||||
void transformPoint(ToolLoop* loop, int x, int y) override {
|
||||
void transformPoint(ToolLoop* loop, const Stroke::Pt& pt) override {
|
||||
int spray_width = loop->getSprayWidth();
|
||||
int spray_speed = loop->getSpraySpeed();
|
||||
|
||||
@ -203,9 +315,10 @@ public:
|
||||
radius = rand() % fixmath::itofix(spray_width);
|
||||
#endif
|
||||
|
||||
int u = fixmath::fixtoi(fixmath::fixmul(radius, fixmath::fixcos(angle)));
|
||||
int v = fixmath::fixtoi(fixmath::fixmul(radius, fixmath::fixsin(angle)));
|
||||
m_subPointShape.transformPoint(loop, x+u, y+v);
|
||||
Stroke::Pt pt2(pt);
|
||||
pt2.x += fixmath::fixtoi(fixmath::fixmul(radius, fixmath::fixcos(angle)));
|
||||
pt2.y += fixmath::fixtoi(fixmath::fixmul(radius, fixmath::fixsin(angle)));
|
||||
m_subPointShape.transformPoint(loop, pt2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -16,44 +16,46 @@ namespace tools {
|
||||
|
||||
void Stroke::reset()
|
||||
{
|
||||
m_points.clear();
|
||||
m_pts.clear();
|
||||
}
|
||||
|
||||
void Stroke::reset(int n, const gfx::Point& point)
|
||||
void Stroke::reset(int n, const Pt& pt)
|
||||
{
|
||||
m_points.resize(n, point);
|
||||
m_pts.resize(n, pt);
|
||||
}
|
||||
|
||||
void Stroke::addPoint(const gfx::Point& point)
|
||||
void Stroke::addPoint(const Pt& pt)
|
||||
{
|
||||
m_points.push_back(point);
|
||||
m_pts.push_back(pt);
|
||||
}
|
||||
|
||||
void Stroke::offset(const gfx::Point& delta)
|
||||
{
|
||||
for (auto& p : m_points)
|
||||
p += delta;
|
||||
for (auto& p : m_pts) {
|
||||
p.x += delta.x;
|
||||
p.y += delta.y;
|
||||
}
|
||||
}
|
||||
|
||||
void Stroke::erase(int index)
|
||||
{
|
||||
ASSERT(0 <= index && index < m_points.size());
|
||||
if (0 <= index && index < m_points.size())
|
||||
m_points.erase(m_points.begin()+index);
|
||||
ASSERT(0 <= index && index < m_pts.size());
|
||||
if (0 <= index && index < m_pts.size())
|
||||
m_pts.erase(m_pts.begin()+index);
|
||||
}
|
||||
|
||||
gfx::Rect Stroke::bounds() const
|
||||
{
|
||||
if (m_points.empty())
|
||||
if (m_pts.empty())
|
||||
return gfx::Rect();
|
||||
|
||||
gfx::Point
|
||||
minpt(m_points[0]),
|
||||
maxpt(m_points[0]);
|
||||
minpt(m_pts[0].x, m_pts[0].y),
|
||||
maxpt(m_pts[0].x, m_pts[0].y);
|
||||
|
||||
for (std::size_t c=1; c<m_points.size(); ++c) {
|
||||
int x = m_points[c].x;
|
||||
int y = m_points[c].y;
|
||||
for (std::size_t c=1; c<m_pts.size(); ++c) {
|
||||
int x = m_pts[c].x;
|
||||
int y = m_pts[c].y;
|
||||
if (minpt.x > x) minpt.x = x;
|
||||
if (minpt.y > y) minpt.y = y;
|
||||
if (maxpt.x < x) maxpt.x = x;
|
||||
@ -65,5 +67,18 @@ gfx::Rect Stroke::bounds() const
|
||||
maxpt.y - minpt.y + 1);
|
||||
}
|
||||
|
||||
std::vector<int> Stroke::toXYInts() const
|
||||
{
|
||||
std::vector<int> output;
|
||||
if (!empty()) {
|
||||
output.reserve(2*size());
|
||||
for (auto pt : m_pts) {
|
||||
output.push_back(pt.x);
|
||||
output.push_back(pt.y);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
} // namespace app
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -19,36 +19,49 @@ namespace app {
|
||||
|
||||
class Stroke {
|
||||
public:
|
||||
typedef std::vector<gfx::Point> Points;
|
||||
typedef Points::const_iterator const_iterator;
|
||||
struct Pt {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
float size = 0.0f;
|
||||
float angle = 0.0f;
|
||||
float gradient = 0.0f;
|
||||
Pt() { }
|
||||
Pt(const gfx::Point& point) : x(point.x), y(point.y) { }
|
||||
Pt(int x, int y) : x(x), y(y) { }
|
||||
gfx::Point toPoint() const { return gfx::Point(x, y); }
|
||||
bool operator==(const Pt& that) const { return x == that.x && y == that.y; }
|
||||
bool operator!=(const Pt& that) const { return x != that.x || y != that.y; }
|
||||
};
|
||||
typedef std::vector<Pt> Pts;
|
||||
typedef Pts::const_iterator const_iterator;
|
||||
|
||||
const_iterator begin() const { return m_points.begin(); }
|
||||
const_iterator end() const { return m_points.end(); }
|
||||
const_iterator begin() const { return m_pts.begin(); }
|
||||
const_iterator end() const { return m_pts.end(); }
|
||||
|
||||
bool empty() const { return m_points.empty(); }
|
||||
int size() const { return (int)m_points.size(); }
|
||||
bool empty() const { return m_pts.empty(); }
|
||||
int size() const { return (int)m_pts.size(); }
|
||||
|
||||
const gfx::Point& operator[](int i) const { return m_points[i]; }
|
||||
gfx::Point& operator[](int i) { return m_points[i]; }
|
||||
const Pt& operator[](int i) const { return m_pts[i]; }
|
||||
Pt& operator[](int i) { return m_pts[i]; }
|
||||
|
||||
const gfx::Point& firstPoint() const {
|
||||
ASSERT(!m_points.empty());
|
||||
return m_points[0];
|
||||
const Pt& firstPoint() const {
|
||||
ASSERT(!m_pts.empty());
|
||||
return m_pts[0];
|
||||
}
|
||||
|
||||
const gfx::Point& lastPoint() const {
|
||||
ASSERT(!m_points.empty());
|
||||
return m_points[m_points.size()-1];
|
||||
const Pt& lastPoint() const {
|
||||
ASSERT(!m_pts.empty());
|
||||
return m_pts[m_pts.size()-1];
|
||||
}
|
||||
|
||||
// Clears the whole stroke.
|
||||
void reset();
|
||||
|
||||
// Reset the stroke as "n" points in the given "point" position.
|
||||
void reset(int n, const gfx::Point& point);
|
||||
// Reset the stroke as "n" points in the given point position.
|
||||
void reset(int n, const Pt& pt);
|
||||
|
||||
// Adds a new point to the stroke.
|
||||
void addPoint(const gfx::Point& point);
|
||||
void addPoint(const Pt& pt);
|
||||
|
||||
// Displaces all X,Y coordinates the given delta.
|
||||
void offset(const gfx::Point& delta);
|
||||
@ -59,8 +72,10 @@ namespace app {
|
||||
// Returns the bounds of the stroke (minimum/maximum position).
|
||||
gfx::Rect bounds() const;
|
||||
|
||||
std::vector<int> toXYInts() const;
|
||||
|
||||
public:
|
||||
Points m_points;
|
||||
Pts m_pts;
|
||||
};
|
||||
|
||||
typedef std::vector<Stroke> Strokes;
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2015-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -38,10 +39,9 @@ void HorizontalSymmetry::generateStrokes(const Stroke& mainStroke, Strokes& stro
|
||||
|
||||
Stroke stroke2;
|
||||
for (const auto& pt : mainStroke) {
|
||||
stroke2.addPoint(
|
||||
gfx::Point(
|
||||
m_x - ((pt.x-brushCenter) - m_x + 1) - (brushSize - brushCenter - 1),
|
||||
pt.y));
|
||||
Stroke::Pt pt2 = pt;
|
||||
pt2.x = m_x - ((pt.x-brushCenter) - m_x + 1) - (brushSize - brushCenter - 1);
|
||||
stroke2.addPoint(pt2);
|
||||
}
|
||||
strokes.push_back(stroke2);
|
||||
}
|
||||
@ -64,10 +64,9 @@ void VerticalSymmetry::generateStrokes(const Stroke& mainStroke, Strokes& stroke
|
||||
|
||||
Stroke stroke2;
|
||||
for (const auto& pt : mainStroke) {
|
||||
stroke2.addPoint(
|
||||
gfx::Point(
|
||||
pt.x,
|
||||
m_y - ((pt.y-brushCenter) - m_y + 1) - (brushSize - brushCenter - 1)));
|
||||
Stroke::Pt pt2 = pt;
|
||||
pt2.y = m_y - ((pt.y-brushCenter) - m_y + 1) - (brushSize - brushCenter - 1);
|
||||
stroke2.addPoint(pt2);
|
||||
}
|
||||
strokes.push_back(stroke2);
|
||||
}
|
||||
|
@ -103,12 +103,7 @@ void ToolLoopManager::pressButton(const Pointer& pointer)
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the screen point to a sprite point
|
||||
Point spritePoint = pointer.point();
|
||||
m_toolLoop->setSpeed(Point(0, 0));
|
||||
m_oldPoint = spritePoint;
|
||||
snapToGrid(spritePoint);
|
||||
|
||||
Stroke::Pt spritePoint = getSpriteStrokePt(pointer, true);
|
||||
m_toolLoop->getController()->pressButton(m_toolLoop, m_stroke, spritePoint);
|
||||
|
||||
std::string statusText;
|
||||
@ -138,9 +133,7 @@ bool ToolLoopManager::releaseButton(const Pointer& pointer)
|
||||
if (isCanceled())
|
||||
return false;
|
||||
|
||||
Point spritePoint = pointer.point();
|
||||
snapToGrid(spritePoint);
|
||||
|
||||
Stroke::Pt spritePoint = getSpriteStrokePt(pointer, false);
|
||||
bool res = m_toolLoop->getController()->releaseButton(m_stroke, spritePoint);
|
||||
|
||||
if (!res && (m_toolLoop->getTracePolicy() == TracePolicy::Last ||
|
||||
@ -162,18 +155,7 @@ void ToolLoopManager::movement(const Pointer& pointer)
|
||||
if (isCanceled())
|
||||
return;
|
||||
|
||||
// Convert the screen point to a sprite point
|
||||
Point spritePoint = pointer.point();
|
||||
// Calculate the velocity (new sprite point - old sprite point)
|
||||
Point velocity = (spritePoint - m_oldPoint);
|
||||
m_toolLoop->setSpeed(velocity);
|
||||
m_oldPoint = spritePoint;
|
||||
snapToGrid(spritePoint);
|
||||
|
||||
// Control dynamic parameters through sensors
|
||||
if (m_dynamics.isDynamic())
|
||||
adjustBrushWithDynamics(pointer, velocity);
|
||||
|
||||
Stroke::Pt spritePoint = getSpriteStrokePt(pointer, false);
|
||||
m_toolLoop->getController()->movement(m_toolLoop, m_stroke, spritePoint);
|
||||
|
||||
std::string statusText;
|
||||
@ -283,16 +265,19 @@ void ToolLoopManager::doLoopStep(bool lastStep)
|
||||
}
|
||||
|
||||
// Applies the grid settings to the specified sprite point.
|
||||
void ToolLoopManager::snapToGrid(Point& point)
|
||||
void ToolLoopManager::snapToGrid(Stroke::Pt& pt)
|
||||
{
|
||||
if (!m_toolLoop->getController()->canSnapToGrid() ||
|
||||
!m_toolLoop->getSnapToGrid() ||
|
||||
m_toolLoop->isSelectingTiles())
|
||||
return;
|
||||
|
||||
gfx::Point point(pt.x, pt.y);
|
||||
point = snap_to_grid(m_toolLoop->getGridBounds(), point,
|
||||
PreferSnapTo::ClosestGridVertex);
|
||||
point += m_toolLoop->getBrush()->center();
|
||||
pt.x = point.x;
|
||||
pt.y = point.y;
|
||||
}
|
||||
|
||||
// Strokes are relative to sprite origin.
|
||||
@ -379,11 +364,58 @@ void ToolLoopManager::calculateDirtyArea(const Strokes& strokes)
|
||||
}
|
||||
}
|
||||
|
||||
void ToolLoopManager::adjustBrushWithDynamics(const Pointer& pointer,
|
||||
const Point& velocity)
|
||||
Stroke::Pt ToolLoopManager::getSpriteStrokePt(const Pointer& pointer,
|
||||
const bool firstPoint)
|
||||
{
|
||||
int size = m_brush0.size();
|
||||
int angle = m_brush0.angle();
|
||||
const base::tick_t t = base::current_tick();
|
||||
const base::tick_t dt = t - m_lastPointerT;
|
||||
m_lastPointerT = t;
|
||||
|
||||
// Convert the screen point to a sprite point
|
||||
Stroke::Pt spritePoint = pointer.point();
|
||||
spritePoint.size = m_brush0.size();
|
||||
spritePoint.angle = m_brush0.angle();
|
||||
|
||||
// Calculate the velocity (new sprite point - old sprite point)
|
||||
gfx::Point newVelocity;
|
||||
if (firstPoint)
|
||||
m_velocity = newVelocity = gfx::Point(0, 0);
|
||||
else {
|
||||
newVelocity.x = (spritePoint.x - m_oldPoint.x);
|
||||
newVelocity.y = (spritePoint.y - m_oldPoint.y);
|
||||
float a = base::clamp(float(dt) / 50.0f, 0.0f, 1.0f);
|
||||
m_velocity.x = (1.0f-a)*m_velocity.x + a*newVelocity.x;
|
||||
m_velocity.y = (1.0f-a)*m_velocity.y + a*newVelocity.y;
|
||||
}
|
||||
m_oldPoint.x = spritePoint.x;
|
||||
m_oldPoint.y = spritePoint.y;
|
||||
|
||||
// Center the input to some grid point if needed
|
||||
snapToGrid(spritePoint);
|
||||
|
||||
// Control dynamic parameters through sensors
|
||||
if (useDynamics()) {
|
||||
adjustPointWithDynamics(pointer, spritePoint);
|
||||
}
|
||||
|
||||
// Inform the original velocity vector to the ToolLoop
|
||||
m_toolLoop->setSpeed(newVelocity);
|
||||
|
||||
return spritePoint;
|
||||
}
|
||||
|
||||
bool ToolLoopManager::useDynamics() const
|
||||
{
|
||||
return (m_dynamics.isDynamic() &&
|
||||
!m_toolLoop->getFilled() &&
|
||||
m_toolLoop->getController()->isFreehand());
|
||||
}
|
||||
|
||||
void ToolLoopManager::adjustPointWithDynamics(const Pointer& pointer,
|
||||
Stroke::Pt& pt)
|
||||
{
|
||||
int size = pt.size;
|
||||
int angle = pt.angle;
|
||||
|
||||
// Pressure
|
||||
bool hasP = (pointer.type() == Pointer::Type::Pen ||
|
||||
@ -392,8 +424,8 @@ void ToolLoopManager::adjustBrushWithDynamics(const Pointer& pointer,
|
||||
ASSERT(p >= 0.0f && p <= 1.0f);
|
||||
|
||||
// Velocity
|
||||
float v = float(std::sqrt(velocity.x*velocity.x +
|
||||
velocity.y*velocity.y)) / 32.0f; // TODO 16 should be configurable
|
||||
float v = float(std::sqrt(m_velocity.x*m_velocity.x +
|
||||
m_velocity.y*m_velocity.y)) / 16.0f; // TODO 16 should be configurable
|
||||
v = base::clamp(v, 0.0f, 1.0f);
|
||||
|
||||
switch (m_dynamics.size) {
|
||||
@ -414,17 +446,18 @@ void ToolLoopManager::adjustBrushWithDynamics(const Pointer& pointer,
|
||||
break;
|
||||
}
|
||||
|
||||
size = base::clamp(size, int(Brush::kMinBrushSize), int(Brush::kMaxBrushSize));
|
||||
angle = base::clamp(angle, -180, 180);
|
||||
|
||||
Brush* currrentBrush = m_toolLoop->getBrush();
|
||||
|
||||
if (currrentBrush->size() != size ||
|
||||
(currrentBrush->type() != kCircleBrushType &&
|
||||
currrentBrush->angle() != angle)) {
|
||||
m_toolLoop->setBrush(
|
||||
std::make_shared<Brush>(m_brush0.type(), size, angle));
|
||||
switch (m_dynamics.gradient) {
|
||||
case DynamicSensor::Pressure:
|
||||
if (hasP)
|
||||
pt.gradient = p;
|
||||
break;
|
||||
case DynamicSensor::Velocity:
|
||||
pt.gradient = v;
|
||||
break;
|
||||
}
|
||||
|
||||
pt.size = base::clamp(size, int(Brush::kMinBrushSize), int(Brush::kMaxBrushSize));
|
||||
pt.angle = base::clamp(angle, -180, 180);
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "app/tools/dynamics.h"
|
||||
#include "app/tools/pointer.h"
|
||||
#include "app/tools/stroke.h"
|
||||
#include "base/time.h"
|
||||
#include "doc/brush.h"
|
||||
#include "gfx/point.h"
|
||||
#include "gfx/region.h"
|
||||
@ -73,9 +74,11 @@ public:
|
||||
|
||||
private:
|
||||
void doLoopStep(bool lastStep);
|
||||
void snapToGrid(gfx::Point& point);
|
||||
void adjustBrushWithDynamics(const Pointer& pointer,
|
||||
const gfx::Point& velocity);
|
||||
void snapToGrid(Stroke::Pt& pt);
|
||||
Stroke::Pt getSpriteStrokePt(const Pointer& pointer,
|
||||
const bool firstPoint);
|
||||
bool useDynamics() const;
|
||||
void adjustPointWithDynamics(const Pointer& pointer, Stroke::Pt& pt);
|
||||
|
||||
void calculateDirtyArea(const Strokes& strokes);
|
||||
|
||||
@ -83,6 +86,8 @@ private:
|
||||
Stroke m_stroke;
|
||||
Pointer m_lastPointer;
|
||||
gfx::Point m_oldPoint;
|
||||
gfx::Point m_velocity;
|
||||
base::tick_t m_lastPointerT;
|
||||
gfx::Region m_dirtyArea;
|
||||
gfx::Region m_nextDirtyArea;
|
||||
doc::Brush m_brush0;
|
||||
|
@ -1878,6 +1878,10 @@ void ContextBar::updateForTool(tools::Tool* tool)
|
||||
(tool->getInk(0)->withDitheringOptions() ||
|
||||
tool->getInk(1)->withDitheringOptions());
|
||||
|
||||
// True if the brush supports dynamics
|
||||
// TODO add support for dynamics in custom brushes in the future
|
||||
const bool supportDynamics = (!hasImageBrush);
|
||||
|
||||
// Show/Hide fields
|
||||
m_zoomButtons->setVisible(needZoomButtons);
|
||||
m_brushBack->setVisible(supportOpacity && hasImageBrush && !withDithering);
|
||||
@ -1891,6 +1895,7 @@ void ContextBar::updateForTool(tools::Tool* tool)
|
||||
m_inkShades->setVisible(hasInkShades);
|
||||
m_eyedropperField->setVisible(isEyedropper);
|
||||
m_autoSelectLayer->setVisible(isMove);
|
||||
m_dynamics->setVisible(isFreehand && supportDynamics);
|
||||
m_freehandBox->setVisible(isFreehand && supportOpacity);
|
||||
m_toleranceLabel->setVisible(hasTolerance);
|
||||
m_tolerance->setVisible(hasTolerance);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -258,10 +258,12 @@ void BrushPreview::show(const gfx::Point& screenPos)
|
||||
loop->getController()->prepareController(loop.get());
|
||||
loop->getIntertwine()->prepareIntertwine();
|
||||
loop->getPointShape()->preparePointShape(loop.get());
|
||||
loop->getPointShape()->transformPoint(
|
||||
loop.get(),
|
||||
brushBounds.x-origBrushBounds.x,
|
||||
brushBounds.y-origBrushBounds.y);
|
||||
|
||||
tools::Stroke::Pt pt(brushBounds.x-origBrushBounds.x,
|
||||
brushBounds.y-origBrushBounds.y);
|
||||
pt.size = brush->size();
|
||||
pt.angle = brush->angle();
|
||||
loop->getPointShape()->transformPoint(loop.get(), pt);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,6 +162,18 @@ public:
|
||||
m_tracePolicy = tools::TracePolicy::Accumulate;
|
||||
break;
|
||||
}
|
||||
|
||||
// Use overlap trace policy for dynamic gradient
|
||||
auto dynamics = getDynamics();
|
||||
if (dynamics.isDynamic() &&
|
||||
dynamics.gradient != tools::DynamicSensor::Static &&
|
||||
m_controller->isFreehand()) {
|
||||
// Use overlap trace policy to accumulate changes of colors
|
||||
// between stroke points.
|
||||
//
|
||||
// TODO this is connected with a condition in tools::PaintInk::prepareInk()
|
||||
m_tracePolicy = tools::TracePolicy::Overlap;
|
||||
}
|
||||
}
|
||||
|
||||
// Symmetry mode
|
||||
@ -371,7 +383,6 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void onSliceRect(const gfx::Rect& bounds) override { }
|
||||
|
||||
};
|
||||
@ -521,7 +532,7 @@ public:
|
||||
if (m_saveLastPoint) {
|
||||
m_tx(new cmd::SetLastPoint(
|
||||
m_document,
|
||||
getController()->getLastPoint()));
|
||||
getController()->getLastPoint().toPoint()));
|
||||
}
|
||||
|
||||
// Paint ink
|
||||
@ -863,6 +874,11 @@ public:
|
||||
void cancel() override { }
|
||||
bool isCanceled() override { return true; }
|
||||
|
||||
tools::DynamicsOptions getDynamics() override {
|
||||
// Preview without dynamics
|
||||
return tools::DynamicsOptions();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
tools::ToolLoop* create_tool_loop_preview(
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -42,6 +42,7 @@ namespace doc {
|
||||
|
||||
BrushPattern pattern() const { return m_pattern; }
|
||||
gfx::Point patternOrigin() const { return m_patternOrigin; }
|
||||
Image* patternImage() const { return m_patternImage.get(); }
|
||||
|
||||
const gfx::Rect& bounds() const { return m_bounds; }
|
||||
const gfx::Point& center() const { return m_center; }
|
||||
@ -58,6 +59,9 @@ namespace doc {
|
||||
void setPatternOrigin(const gfx::Point& patternOrigin) {
|
||||
m_patternOrigin = patternOrigin;
|
||||
}
|
||||
void setPatternImage(ImageRef& patternImage) {
|
||||
m_patternImage = patternImage;
|
||||
}
|
||||
void setCenter(const gfx::Point& center);
|
||||
|
||||
private:
|
||||
@ -74,6 +78,7 @@ namespace doc {
|
||||
gfx::Point m_center;
|
||||
BrushPattern m_pattern; // How the image should be replicated
|
||||
gfx::Point m_patternOrigin; // From what position the brush was taken
|
||||
ImageRef m_patternImage;
|
||||
int m_gen;
|
||||
|
||||
// Extra data used for setImageColor()
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite Render Library
|
||||
// Copyright (c) 2020 Igara Studio S.A.
|
||||
// Copyright (c) 2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -8,6 +9,7 @@
|
||||
#define RENDER_DITHERING_MATRIX_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
namespace render {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Render Library
|
||||
// Copyright (c) 2019 Igara Studio S.A.
|
||||
// Copyright (c) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (c) 2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -224,4 +224,57 @@ void render_rgba_radial_gradient(
|
||||
}
|
||||
}
|
||||
|
||||
template<typename ImageTraits>
|
||||
static void create_dithering_pattern_templ(
|
||||
doc::Image* pattern,
|
||||
const render::DitheringMatrix& matrix,
|
||||
const float f,
|
||||
const doc::color_t c0,
|
||||
const doc::color_t c1)
|
||||
{
|
||||
const int w = pattern->width();
|
||||
const int h = pattern->height();
|
||||
|
||||
doc::LockImageBits<ImageTraits> dstBits(pattern);
|
||||
auto dst = dstBits.begin();
|
||||
for (int y=0; y<h; ++y) {
|
||||
for (int x=0; x<w; ++x, ++dst)
|
||||
*dst = (f*(matrix.maxValue()+2) < matrix(y, x)+1 ? c0: c1);
|
||||
}
|
||||
}
|
||||
|
||||
void convert_bitmap_brush_to_dithering_brush(
|
||||
doc::Brush* brush,
|
||||
const doc::PixelFormat pixelFormat,
|
||||
const render::DitheringMatrix& matrix,
|
||||
const float f,
|
||||
const doc::color_t c0,
|
||||
const doc::color_t c1)
|
||||
{
|
||||
// Create a pattern
|
||||
doc::ImageRef pattern(
|
||||
doc::Image::create(pixelFormat,
|
||||
matrix.cols(), matrix.rows()));
|
||||
|
||||
switch (pixelFormat) {
|
||||
case doc::IMAGE_RGB:
|
||||
create_dithering_pattern_templ<doc::RgbTraits>(
|
||||
pattern.get(), matrix, f, c0, c1);
|
||||
break;
|
||||
case doc::IMAGE_GRAYSCALE:
|
||||
create_dithering_pattern_templ<doc::GrayscaleTraits>(
|
||||
pattern.get(), matrix, f, c0, c1);
|
||||
break;
|
||||
case doc::IMAGE_INDEXED:
|
||||
create_dithering_pattern_templ<doc::IndexedTraits>(
|
||||
pattern.get(), matrix, f, c0, c1);
|
||||
break;
|
||||
}
|
||||
|
||||
doc::ImageRef copy(doc::Image::createCopy(brush->image()));
|
||||
brush->setImage(copy.get(), copy.get());
|
||||
brush->setPatternImage(pattern);
|
||||
brush->setPattern(doc::BrushPattern::PAINT_BRUSH);
|
||||
}
|
||||
|
||||
} // namespace render
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Render Library
|
||||
// Copyright (c) 2019 Igara Studio S.A.
|
||||
// Copyright (c) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (c) 2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -9,6 +9,7 @@
|
||||
#define RENDER_GRADIENT_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "doc/brush.h"
|
||||
#include "doc/color.h"
|
||||
#include "gfx/point.h"
|
||||
|
||||
@ -53,6 +54,14 @@ void render_rgba_radial_gradient(
|
||||
doc::color_t c1,
|
||||
const render::DitheringMatrix& matrix);
|
||||
|
||||
void convert_bitmap_brush_to_dithering_brush(
|
||||
doc::Brush* brush,
|
||||
const doc::PixelFormat pixelFormat,
|
||||
const render::DitheringMatrix& matrix,
|
||||
const float f,
|
||||
const doc::color_t c0,
|
||||
const doc::color_t c1);
|
||||
|
||||
} // namespace render
|
||||
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user