mirror of
https://github.com/aseprite/aseprite.git
synced 2024-09-03 05:29:13 +00:00
Add possibility to rotate ellipses and rectangles (fix #868)
This commit is contained in:
parent
109fcddaf7
commit
184736760a
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Aseprite -->
|
<!-- Aseprite -->
|
||||||
<!-- Copyright (C) 2001-2017 by David Capello -->
|
<!-- Copyright (C) 2001-2018 by David Capello -->
|
||||||
<gui version="1.3-dev">
|
<gui version="1.3-dev">
|
||||||
<!-- Keyboard shortcuts -->
|
<!-- Keyboard shortcuts -->
|
||||||
<keyboard version="1">
|
<keyboard version="1">
|
||||||
@ -546,9 +546,9 @@
|
|||||||
<!-- Modifiers for two-points tool controller -->
|
<!-- Modifiers for two-points tool controller -->
|
||||||
<key action="SquareAspect" shortcut="Shift" />
|
<key action="SquareAspect" shortcut="Shift" />
|
||||||
<key action="DrawFromCenter" shortcut="Ctrl" />
|
<key action="DrawFromCenter" shortcut="Ctrl" />
|
||||||
|
|
||||||
<!-- Modifiers for two-or-more-points tools -->
|
<!-- Modifiers for two-or-more-points tools -->
|
||||||
<key action="MoveOrigin" shortcut="Space" />
|
<key action="MoveOrigin" shortcut="Space" />
|
||||||
|
<key action="RotateShape" shortcut="Alt" />
|
||||||
|
|
||||||
<!-- Without default shortcuts -->
|
<!-- Without default shortcuts -->
|
||||||
<key action="LeftMouseButton" />
|
<key action="LeftMouseButton" />
|
||||||
|
@ -29,6 +29,30 @@ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# [Bresenham algorithm implementations by Alois Zingl](http://members.chello.at/easyfilter/bresenham.html)
|
||||||
|
|
||||||
|
```
|
||||||
|
Copyright (C) 2012-2016 Alois Zingl
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
```
|
||||||
|
|
||||||
# [cmark](https://github.com/jgm/cmark)
|
# [cmark](https://github.com/jgm/cmark)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2001-2017 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -58,6 +58,10 @@ namespace app {
|
|||||||
// and then a TracePolicy::Accumulate for freehand (Pencil tool).
|
// and then a TracePolicy::Accumulate for freehand (Pencil tool).
|
||||||
virtual bool handleTracePolicy() const { return false; }
|
virtual bool handleTracePolicy() const { return false; }
|
||||||
virtual TracePolicy getTracePolicy() const { return TracePolicy::Accumulate; }
|
virtual TracePolicy getTracePolicy() const { return TracePolicy::Accumulate; }
|
||||||
|
|
||||||
|
// Returns the angle for a shape-like intertwiner (rectangles,
|
||||||
|
// ellipses, etc.).
|
||||||
|
virtual double getShapeAngle() const { return 0.0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace tools
|
} // namespace tools
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2001-2017 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -105,7 +105,8 @@ public:
|
|||||||
void pressButton(Stroke& stroke, const Point& point) override {
|
void pressButton(Stroke& stroke, const Point& point) override {
|
||||||
MoveOriginCapability::pressButton(stroke, point);
|
MoveOriginCapability::pressButton(stroke, point);
|
||||||
|
|
||||||
m_first = point;
|
m_first = m_center = point;
|
||||||
|
m_angle = 0.0;
|
||||||
|
|
||||||
stroke.addPoint(point);
|
stroke.addPoint(point);
|
||||||
stroke.addPoint(point);
|
stroke.addPoint(point);
|
||||||
@ -123,6 +124,21 @@ public:
|
|||||||
if (MoveOriginCapability::isMovingOrigin(loop, stroke, point))
|
if (MoveOriginCapability::isMovingOrigin(loop, stroke, point))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!loop->getIntertwine()->snapByAngle() &&
|
||||||
|
int(loop->getModifiers()) & int(ToolLoopModifiers::kRotateShape)) {
|
||||||
|
if ((int(loop->getModifiers()) & int(ToolLoopModifiers::kFromCenter))) {
|
||||||
|
m_center = m_first;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
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));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stroke[0] = m_first;
|
||||||
stroke[1] = point;
|
stroke[1] = point;
|
||||||
|
|
||||||
if ((int(loop->getModifiers()) & int(ToolLoopModifiers::kSquareAspect))) {
|
if ((int(loop->getModifiers()) & int(ToolLoopModifiers::kSquareAspect))) {
|
||||||
@ -168,9 +184,15 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stroke[0] = m_first;
|
if (hasAngle()) {
|
||||||
|
int rx = stroke[1].x - m_center.x;
|
||||||
if ((int(loop->getModifiers()) & int(ToolLoopModifiers::kFromCenter))) {
|
int ry = stroke[1].y - m_center.y;
|
||||||
|
stroke[0].x = m_center.x - rx;
|
||||||
|
stroke[0].y = m_center.y - ry;
|
||||||
|
stroke[1].x = m_center.x + rx;
|
||||||
|
stroke[1].y = m_center.y + ry;
|
||||||
|
}
|
||||||
|
else if ((int(loop->getModifiers()) & int(ToolLoopModifiers::kFromCenter))) {
|
||||||
int rx = stroke[1].x - m_first.x;
|
int rx = stroke[1].x - m_first.x;
|
||||||
int ry = stroke[1].y - m_first.y;
|
int ry = stroke[1].y - m_first.y;
|
||||||
stroke[0].x = m_first.x - rx;
|
stroke[0].x = m_first.x - rx;
|
||||||
@ -215,22 +237,42 @@ public:
|
|||||||
|
|
||||||
gfx::Point offset = loop->statusBarPositionOffset();
|
gfx::Point offset = loop->statusBarPositionOffset();
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
sprintf(buf, ":start: %3d %3d :end: %3d %3d :size: %3d %3d :distance: %.1f :angle: %.1f",
|
sprintf(buf, ":start: %3d %3d :end: %3d %3d :size: %3d %3d :distance: %.1f",
|
||||||
stroke[0].x+offset.x, stroke[0].y+offset.y,
|
stroke[0].x+offset.x, stroke[0].y+offset.y,
|
||||||
stroke[1].x+offset.x, stroke[1].y+offset.y,
|
stroke[1].x+offset.x, stroke[1].y+offset.y,
|
||||||
w, h,
|
w, h, std::sqrt(w*w + h*h));
|
||||||
std::sqrt(w*w + h*h),
|
|
||||||
180.0 * std::atan2(static_cast<double>(stroke[0].y-stroke[1].y),
|
if (hasAngle() ||
|
||||||
static_cast<double>(stroke[1].x-stroke[0].x)) / PI);
|
loop->getIntertwine()->snapByAngle()) {
|
||||||
|
double angle;
|
||||||
|
if (hasAngle())
|
||||||
|
angle = m_angle;
|
||||||
|
else
|
||||||
|
angle = std::atan2(static_cast<double>(stroke[0].y-stroke[1].y),
|
||||||
|
static_cast<double>(stroke[1].x-stroke[0].x));
|
||||||
|
sprintf(buf+strlen(buf), " :angle: %.1f", 180.0 * angle / PI);
|
||||||
|
}
|
||||||
|
|
||||||
text = buf;
|
text = buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double getShapeAngle() const override {
|
||||||
|
return m_angle;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool hasAngle() const {
|
||||||
|
return (ABS(m_angle) > 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
void onMoveOrigin(const Point& delta) override {
|
void onMoveOrigin(const Point& delta) override {
|
||||||
m_first += delta;
|
m_first += delta;
|
||||||
|
m_center += delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
Point m_first;
|
Point m_first;
|
||||||
|
Point m_center;
|
||||||
|
double m_angle;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Controls clicks for tools like polygon
|
// Controls clicks for tools like polygon
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2001-2015 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -22,6 +22,11 @@ namespace tools {
|
|||||||
using namespace gfx;
|
using namespace gfx;
|
||||||
using namespace doc;
|
using namespace doc;
|
||||||
|
|
||||||
|
gfx::Rect Intertwine::getStrokeBounds(ToolLoop* loop, const Stroke& stroke)
|
||||||
|
{
|
||||||
|
return stroke.bounds();
|
||||||
|
}
|
||||||
|
|
||||||
void Intertwine::doPointshapePoint(int x, int y, ToolLoop* loop)
|
void Intertwine::doPointshapePoint(int x, int y, ToolLoop* loop)
|
||||||
{
|
{
|
||||||
Symmetry* symmetry = loop->getSymmetry();
|
Symmetry* symmetry = loop->getSymmetry();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2001-2015 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -9,8 +9,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "gfx/point.h"
|
#include "gfx/point.h"
|
||||||
|
#include "gfx/rect.h"
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace app {
|
namespace app {
|
||||||
namespace tools {
|
namespace tools {
|
||||||
@ -23,8 +22,6 @@ namespace app {
|
|||||||
// user-defined points.
|
// user-defined points.
|
||||||
class Intertwine {
|
class Intertwine {
|
||||||
public:
|
public:
|
||||||
typedef std::vector<gfx::Point> Points;
|
|
||||||
|
|
||||||
virtual ~Intertwine() { }
|
virtual ~Intertwine() { }
|
||||||
virtual bool snapByAngle() { return false; }
|
virtual bool snapByAngle() { return false; }
|
||||||
virtual void prepareIntertwine() { }
|
virtual void prepareIntertwine() { }
|
||||||
@ -33,6 +30,8 @@ namespace app {
|
|||||||
virtual void joinStroke(ToolLoop* loop, const Stroke& stroke) = 0;
|
virtual void joinStroke(ToolLoop* loop, const Stroke& stroke) = 0;
|
||||||
virtual void fillStroke(ToolLoop* loop, const Stroke& stroke) = 0;
|
virtual void fillStroke(ToolLoop* loop, const Stroke& stroke) = 0;
|
||||||
|
|
||||||
|
virtual gfx::Rect getStrokeBounds(ToolLoop* loop, const Stroke& stroke);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// The given point must be relative to the cel origin.
|
// The given point must be relative to the cel origin.
|
||||||
static void doPointshapePoint(int x, int y, ToolLoop* loop);
|
static void doPointshapePoint(int x, int y, ToolLoop* loop);
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2001-2017 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
|
|
||||||
|
#include "base/pi.h"
|
||||||
|
|
||||||
namespace app {
|
namespace app {
|
||||||
namespace tools {
|
namespace tools {
|
||||||
|
|
||||||
@ -80,6 +82,7 @@ public:
|
|||||||
// Fill content
|
// Fill content
|
||||||
doc::algorithm::polygon(stroke.size(), (const int*)&stroke[0], loop, (AlgoHLine)doPointshapeHline);
|
doc::algorithm::polygon(stroke.size(), (const int*)&stroke[0], loop, (AlgoHLine)doPointshapeHline);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class IntertwineAsRectangles : public Intertwine {
|
class IntertwineAsRectangles : public Intertwine {
|
||||||
@ -104,19 +107,31 @@ public:
|
|||||||
if (x1 > x2) std::swap(x1, x2);
|
if (x1 > x2) std::swap(x1, x2);
|
||||||
if (y1 > y2) std::swap(y1, y2);
|
if (y1 > y2) std::swap(y1, y2);
|
||||||
|
|
||||||
doPointshapeLine(x1, y1, x2, y1, loop);
|
const double angle = loop->getController()->getShapeAngle();
|
||||||
doPointshapeLine(x1, y2, x2, y2, loop);
|
if (ABS(angle) < 0.001) {
|
||||||
|
doPointshapeLine(x1, y1, x2, y1, loop);
|
||||||
|
doPointshapeLine(x1, y2, x2, y2, loop);
|
||||||
|
|
||||||
for (y=y1; y<=y2; y++) {
|
for (y=y1; y<=y2; y++) {
|
||||||
doPointshapePoint(x1, y, loop);
|
doPointshapePoint(x1, y, loop);
|
||||||
doPointshapePoint(x2, y, loop);
|
doPointshapePoint(x2, y, loop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Stroke p = rotateRectangle(x1, y1, x2, y2, angle);
|
||||||
|
int n = p.size();
|
||||||
|
for (int i=0; i+1<n; ++i) {
|
||||||
|
doPointshapeLine(p[i].x, p[i].y,
|
||||||
|
p[i+1].x, p[i+1].y, loop);
|
||||||
|
}
|
||||||
|
doPointshapeLine(p[n-1].x, p[n-1].y,
|
||||||
|
p[0].x, p[0].y, loop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fillStroke(ToolLoop* loop, const Stroke& stroke) override
|
void fillStroke(ToolLoop* loop, const Stroke& stroke) override {
|
||||||
{
|
|
||||||
if (stroke.size() < 2) {
|
if (stroke.size() < 2) {
|
||||||
joinStroke(loop, stroke);
|
joinStroke(loop, stroke);
|
||||||
return;
|
return;
|
||||||
@ -132,17 +147,63 @@ public:
|
|||||||
if (x1 > x2) std::swap(x1, x2);
|
if (x1 > x2) std::swap(x1, x2);
|
||||||
if (y1 > y2) std::swap(y1, y2);
|
if (y1 > y2) std::swap(y1, y2);
|
||||||
|
|
||||||
for (y=y1; y<=y2; y++)
|
const double angle = loop->getController()->getShapeAngle();
|
||||||
doPointshapeLine(x1, y, x2, y, loop);
|
if (ABS(angle) < 0.001) {
|
||||||
|
for (y=y1; y<=y2; y++)
|
||||||
|
doPointshapeLine(x1, y, x2, y, loop);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Stroke p = rotateRectangle(x1, y1, x2, y2, angle);
|
||||||
|
doc::algorithm::polygon(
|
||||||
|
p.size(), (const int*)&p[0],
|
||||||
|
loop, (AlgoHLine)doPointshapeHline);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gfx::Rect getStrokeBounds(ToolLoop* loop, const Stroke& stroke) override {
|
||||||
|
gfx::Rect bounds = stroke.bounds();
|
||||||
|
const double angle = loop->getController()->getShapeAngle();
|
||||||
|
|
||||||
|
if (ABS(angle) > 0.001) {
|
||||||
|
bounds = gfx::Rect();
|
||||||
|
if (stroke.size() >= 2) {
|
||||||
|
for (int c=0; c+1<stroke.size(); ++c) {
|
||||||
|
int x1 = stroke[c].x;
|
||||||
|
int y1 = stroke[c].y;
|
||||||
|
int x2 = stroke[c+1].x;
|
||||||
|
int y2 = stroke[c+1].y;
|
||||||
|
bounds |= rotateRectangle(x1, y1, x2, y2, angle).bounds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static Stroke rotateRectangle(int x1, int y1, int x2, int y2, double angle) {
|
||||||
|
int cx = (x1+x2)/2;
|
||||||
|
int cy = (y1+y2)/2;
|
||||||
|
int a = (x2-x1)/2;
|
||||||
|
int b = (y2-y1)/2;
|
||||||
|
double s = -std::sin(angle);
|
||||||
|
double c = std::cos(angle);
|
||||||
|
|
||||||
|
Stroke stroke;
|
||||||
|
stroke.addPoint(Point(cx-a*c-b*s, cy+a*s-b*c));
|
||||||
|
stroke.addPoint(Point(cx+a*c-b*s, cy-a*s-b*c));
|
||||||
|
stroke.addPoint(Point(cx+a*c+b*s, cy-a*s+b*c));
|
||||||
|
stroke.addPoint(Point(cx-a*c+b*s, cy+a*s+b*c));
|
||||||
|
return stroke;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class IntertwineAsEllipses : public Intertwine {
|
class IntertwineAsEllipses : public Intertwine {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
void joinStroke(ToolLoop* loop, const Stroke& stroke) override
|
void joinStroke(ToolLoop* loop, const Stroke& stroke) override {
|
||||||
{
|
|
||||||
if (stroke.size() == 0)
|
if (stroke.size() == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -159,13 +220,22 @@ public:
|
|||||||
if (x1 > x2) std::swap(x1, x2);
|
if (x1 > x2) std::swap(x1, x2);
|
||||||
if (y1 > y2) std::swap(y1, y2);
|
if (y1 > y2) std::swap(y1, y2);
|
||||||
|
|
||||||
algo_ellipse(x1, y1, x2, y2, loop, (AlgoPixel)doPointshapePoint);
|
const double angle = loop->getController()->getShapeAngle();
|
||||||
|
if (ABS(angle) < 0.001) {
|
||||||
|
algo_ellipse(x1, y1, x2, y2, loop, (AlgoPixel)doPointshapePoint);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
draw_rotated_ellipse((x1+x2)/2, (y1+y2)/2,
|
||||||
|
ABS(x2-x1)/2,
|
||||||
|
ABS(y2-y1)/2,
|
||||||
|
angle,
|
||||||
|
loop, (AlgoPixel)doPointshapePoint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fillStroke(ToolLoop* loop, const Stroke& stroke) override
|
void fillStroke(ToolLoop* loop, const Stroke& stroke) override {
|
||||||
{
|
|
||||||
if (stroke.size() < 2) {
|
if (stroke.size() < 2) {
|
||||||
joinStroke(loop, stroke);
|
joinStroke(loop, stroke);
|
||||||
return;
|
return;
|
||||||
@ -180,9 +250,45 @@ public:
|
|||||||
if (x1 > x2) std::swap(x1, x2);
|
if (x1 > x2) std::swap(x1, x2);
|
||||||
if (y1 > y2) std::swap(y1, y2);
|
if (y1 > y2) std::swap(y1, y2);
|
||||||
|
|
||||||
algo_ellipsefill(x1, y1, x2, y2, loop, (AlgoHLine)doPointshapeHline);
|
const double angle = loop->getController()->getShapeAngle();
|
||||||
|
if (ABS(angle) < 0.001) {
|
||||||
|
algo_ellipsefill(x1, y1, x2, y2, loop, (AlgoHLine)doPointshapeHline);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fill_rotated_ellipse((x1+x2)/2, (y1+y2)/2,
|
||||||
|
ABS(x2-x1)/2,
|
||||||
|
ABS(y2-y1)/2,
|
||||||
|
angle,
|
||||||
|
loop, (AlgoHLine)doPointshapeHline);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gfx::Rect getStrokeBounds(ToolLoop* loop, const Stroke& stroke) override {
|
||||||
|
gfx::Rect bounds = stroke.bounds();
|
||||||
|
const double angle = loop->getController()->getShapeAngle();
|
||||||
|
|
||||||
|
if (ABS(angle) > 0.001) {
|
||||||
|
Point center = bounds.center();
|
||||||
|
int a = bounds.w/2.0 + 0.5;
|
||||||
|
int b = bounds.h/2.0 + 0.5;
|
||||||
|
double xd = a*a;
|
||||||
|
double yd = b*b;
|
||||||
|
double s = std::sin(angle);
|
||||||
|
double zd = (xd-yd)*s;
|
||||||
|
|
||||||
|
a = std::sqrt(xd-zd*s) + 0.5;
|
||||||
|
b = std::sqrt(yd+zd*s) + 0.5;
|
||||||
|
|
||||||
|
bounds.x = center.x-a-1;
|
||||||
|
bounds.y = center.y-b-1;
|
||||||
|
bounds.w = 2*a+3;
|
||||||
|
bounds.h = 2*b+3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class IntertwineAsBezier : public Intertwine {
|
class IntertwineAsBezier : public Intertwine {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2001-2017 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -233,7 +233,9 @@ void ToolLoopManager::calculateDirtyArea(const Strokes& strokes)
|
|||||||
m_dirtyArea.clear();
|
m_dirtyArea.clear();
|
||||||
|
|
||||||
for (auto& stroke : strokes) {
|
for (auto& stroke : strokes) {
|
||||||
gfx::Rect strokeBounds = stroke.bounds();
|
gfx::Rect strokeBounds =
|
||||||
|
m_toolLoop->getIntertwine()->getStrokeBounds(m_toolLoop, stroke);
|
||||||
|
|
||||||
if (strokeBounds.isEmpty())
|
if (strokeBounds.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2016 David Capello
|
// Copyright (C) 2016-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -19,6 +19,7 @@ namespace tools {
|
|||||||
kMoveOrigin = 0x00000008,
|
kMoveOrigin = 0x00000008,
|
||||||
kSquareAspect = 0x00000010,
|
kSquareAspect = 0x00000010,
|
||||||
kFromCenter = 0x00000020,
|
kFromCenter = 0x00000020,
|
||||||
|
kRotateShape = 0x00000040,
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace tools
|
} // namespace tools
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2001-2017 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -1488,6 +1488,8 @@ void Editor::updateToolLoopModifiersIndicators()
|
|||||||
modifiers |= int(tools::ToolLoopModifiers::kSquareAspect);
|
modifiers |= int(tools::ToolLoopModifiers::kSquareAspect);
|
||||||
if (int(action & KeyAction::DrawFromCenter))
|
if (int(action & KeyAction::DrawFromCenter))
|
||||||
modifiers |= int(tools::ToolLoopModifiers::kFromCenter);
|
modifiers |= int(tools::ToolLoopModifiers::kFromCenter);
|
||||||
|
if (int(action & KeyAction::RotateShape))
|
||||||
|
modifiers |= int(tools::ToolLoopModifiers::kRotateShape);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Freehand modifiers
|
// Freehand modifiers
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2001-2017 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -38,16 +38,17 @@ namespace {
|
|||||||
{ "SnapToGrid" , "Snap To Grid" , app::KeyAction::SnapToGrid },
|
{ "SnapToGrid" , "Snap To Grid" , app::KeyAction::SnapToGrid },
|
||||||
{ "AngleSnap" , "Angle Snap" , app::KeyAction::AngleSnap },
|
{ "AngleSnap" , "Angle Snap" , app::KeyAction::AngleSnap },
|
||||||
{ "MaintainAspectRatio" , "Maintain Aspect Ratio", app::KeyAction::MaintainAspectRatio },
|
{ "MaintainAspectRatio" , "Maintain Aspect Ratio", app::KeyAction::MaintainAspectRatio },
|
||||||
{ "ScaleFromCenter" , "Scale From Center" , app::KeyAction::ScaleFromCenter },
|
{ "ScaleFromCenter" , "Scale From Center" , app::KeyAction::ScaleFromCenter },
|
||||||
{ "LockAxis" , "Lock Axis" , app::KeyAction::LockAxis },
|
{ "LockAxis" , "Lock Axis" , app::KeyAction::LockAxis },
|
||||||
{ "AddSelection" , "Add Selection" , app::KeyAction::AddSelection },
|
{ "AddSelection" , "Add Selection" , app::KeyAction::AddSelection },
|
||||||
{ "SubtractSelection" , "Subtract Selection" , app::KeyAction::SubtractSelection },
|
{ "SubtractSelection" , "Subtract Selection" , app::KeyAction::SubtractSelection },
|
||||||
{ "AutoSelectLayer" , "Auto Select Layer" , app::KeyAction::AutoSelectLayer },
|
{ "AutoSelectLayer" , "Auto Select Layer" , app::KeyAction::AutoSelectLayer },
|
||||||
{ "StraightLineFromLastPoint", "Straight Line from Last Point", app::KeyAction::StraightLineFromLastPoint },
|
{ "StraightLineFromLastPoint", "Straight Line from Last Point", app::KeyAction::StraightLineFromLastPoint },
|
||||||
{ "AngleSnapFromLastPoint", "Angle Snap from Last Point", app::KeyAction::AngleSnapFromLastPoint },
|
{ "AngleSnapFromLastPoint", "Angle Snap from Last Point", app::KeyAction::AngleSnapFromLastPoint },
|
||||||
{ "MoveOrigin" , "Move Origin" , app::KeyAction::MoveOrigin },
|
{ "MoveOrigin" , "Move Origin" , app::KeyAction::MoveOrigin },
|
||||||
{ "SquareAspect" , "Square Aspect" , app::KeyAction::SquareAspect },
|
{ "SquareAspect" , "Square Aspect" , app::KeyAction::SquareAspect },
|
||||||
{ "DrawFromCenter" , "Draw From Center" , app::KeyAction::DrawFromCenter },
|
{ "DrawFromCenter" , "Draw From Center" , app::KeyAction::DrawFromCenter },
|
||||||
|
{ "RotateShape" , "Rotate Shape" , app::KeyAction::RotateShape },
|
||||||
{ "LeftMouseButton" , "Trigger Left Mouse Button" , app::KeyAction::LeftMouseButton },
|
{ "LeftMouseButton" , "Trigger Left Mouse Button" , app::KeyAction::LeftMouseButton },
|
||||||
{ "RightMouseButton" , "Trigger Right Mouse Button" , app::KeyAction::RightMouseButton },
|
{ "RightMouseButton" , "Trigger Right Mouse Button" , app::KeyAction::RightMouseButton },
|
||||||
{ NULL , NULL , app::KeyAction::None }
|
{ NULL , NULL , app::KeyAction::None }
|
||||||
@ -165,6 +166,7 @@ Key::Key(KeyAction action)
|
|||||||
case KeyAction::MoveOrigin:
|
case KeyAction::MoveOrigin:
|
||||||
case KeyAction::SquareAspect:
|
case KeyAction::SquareAspect:
|
||||||
case KeyAction::DrawFromCenter:
|
case KeyAction::DrawFromCenter:
|
||||||
|
case KeyAction::RotateShape:
|
||||||
m_keycontext = KeyContext::ShapeTool;
|
m_keycontext = KeyContext::ShapeTool;
|
||||||
break;
|
break;
|
||||||
case KeyAction::LeftMouseButton:
|
case KeyAction::LeftMouseButton:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2001-2017 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -63,6 +63,7 @@ namespace app {
|
|||||||
DrawFromCenter = 0x00002000,
|
DrawFromCenter = 0x00002000,
|
||||||
ScaleFromCenter = 0x00004000,
|
ScaleFromCenter = 0x00004000,
|
||||||
AngleSnapFromLastPoint = 0x00008000,
|
AngleSnapFromLastPoint = 0x00008000,
|
||||||
|
RotateShape = 0x00010000,
|
||||||
};
|
};
|
||||||
|
|
||||||
inline KeyAction operator&(KeyAction a, KeyAction b) {
|
inline KeyAction operator&(KeyAction a, KeyAction b) {
|
||||||
|
186
src/doc/algo.cpp
186
src/doc/algo.cpp
@ -8,11 +8,15 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "base/base.h"
|
|
||||||
#include "doc/algo.h"
|
#include "doc/algo.h"
|
||||||
|
|
||||||
|
#include "base/base.h"
|
||||||
|
#include "base/debug.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace doc {
|
namespace doc {
|
||||||
|
|
||||||
@ -225,6 +229,186 @@ void algo_ellipsefill(int x1, int y1, int x2, int y2, void *data, AlgoHLine proc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rotated ellipse code based on Alois Zingl work released under the
|
||||||
|
// MIT license http://members.chello.at/easyfilter/bresenham.html
|
||||||
|
//
|
||||||
|
// Adapted for Aseprite by David Capello
|
||||||
|
|
||||||
|
static void draw_quad_rational_bezier_seg(int x0, int y0,
|
||||||
|
int x1, int y1,
|
||||||
|
int x2, int y2,
|
||||||
|
double w,
|
||||||
|
void* data,
|
||||||
|
AlgoPixel proc)
|
||||||
|
{ // plot a limited rational Bezier segment, squared weight
|
||||||
|
int sx = x2-x1; // relative values for checks
|
||||||
|
int sy = y2-y1;
|
||||||
|
int dx = x0-x2;
|
||||||
|
int dy = y0-y2;
|
||||||
|
int xx = x0-x1;
|
||||||
|
int yy = y0-y1;
|
||||||
|
double xy = xx*sy + yy*sx;
|
||||||
|
double cur = xx*sy - yy*sx; // curvature
|
||||||
|
double err;
|
||||||
|
|
||||||
|
ASSERT(xx*sx <= 0.0 && yy*sy <= 0.0); // sign of gradient must not change
|
||||||
|
|
||||||
|
if (cur != 0.0 && w > 0.0) { // no straight line
|
||||||
|
if (sx*sx+sy*sy > xx*xx+yy*yy) { // begin with shorter part
|
||||||
|
// swap P0 P2
|
||||||
|
x2 = x0;
|
||||||
|
x0 -= dx;
|
||||||
|
y2 = y0;
|
||||||
|
y0 -= dy;
|
||||||
|
cur = -cur;
|
||||||
|
}
|
||||||
|
xx = 2.0*(4.0*w*sx*xx+dx*dx); // differences 2nd degree
|
||||||
|
yy = 2.0*(4.0*w*sy*yy+dy*dy);
|
||||||
|
sx = x0 < x2 ? 1 : -1; // x step direction
|
||||||
|
sy = y0 < y2 ? 1 : -1; // y step direction
|
||||||
|
xy = -2.0*sx*sy*(2.0*w*xy+dx*dy);
|
||||||
|
|
||||||
|
if (cur*sx*sy < 0.0) { // negated curvature?
|
||||||
|
xx = -xx; yy = -yy; xy = -xy; cur = -cur;
|
||||||
|
}
|
||||||
|
dx = 4.0*w*(x1-x0)*sy*cur+xx/2.0+xy; // differences 1st degree
|
||||||
|
dy = 4.0*w*(y0-y1)*sx*cur+yy/2.0+xy;
|
||||||
|
|
||||||
|
if (w < 0.5 && (dy > xy || dx < xy)) { // flat ellipse, algorithm fails
|
||||||
|
cur = (w+1.0)/2.0;
|
||||||
|
w = std::sqrt(w);
|
||||||
|
xy = 1.0/(w+1.0);
|
||||||
|
|
||||||
|
sx = std::floor((x0+2.0*w*x1+x2)*xy/2.0+0.5); // subdivide curve in half
|
||||||
|
sy = std::floor((y0+2.0*w*y1+y2)*xy/2.0+0.5);
|
||||||
|
|
||||||
|
dx = std::floor((w*x1+x0)*xy+0.5);
|
||||||
|
dy = std::floor((y1*w+y0)*xy+0.5);
|
||||||
|
draw_quad_rational_bezier_seg(x0, y0, dx, dy, sx, sy, cur, data, proc); // plot separately
|
||||||
|
|
||||||
|
dx = std::floor((w*x1+x2)*xy+0.5);
|
||||||
|
dy = std::floor((y1*w+y2)*xy+0.5);
|
||||||
|
draw_quad_rational_bezier_seg(sx, sy, dx, dy, x2, y2, cur, data, proc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
err = dx+dy-xy; // error 1.step
|
||||||
|
do {
|
||||||
|
// plot curve
|
||||||
|
proc(x0, y0, data);
|
||||||
|
|
||||||
|
if (x0 == x2 && y0 == y2)
|
||||||
|
return; // last pixel -> curve finished
|
||||||
|
|
||||||
|
x1 = 2*err > dy;
|
||||||
|
y1 = 2*(err+yy) < -dy; // save value for test of x step
|
||||||
|
|
||||||
|
if (2*err < dx || y1) {
|
||||||
|
// y step
|
||||||
|
y0 += sy;
|
||||||
|
dy += xy;
|
||||||
|
err += dx += xx;
|
||||||
|
}
|
||||||
|
if (2*err > dx || x1) {
|
||||||
|
// x step
|
||||||
|
x0 += sx;
|
||||||
|
dx += xy;
|
||||||
|
err += dy += yy;
|
||||||
|
}
|
||||||
|
} while (dy <= xy && dx >= xy); // gradient negates -> algorithm fails
|
||||||
|
}
|
||||||
|
algo_line(x0, y0, x2, y2, data, proc); // plot remaining needle to end
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_rotated_ellipse_rect(int x0, int y0, int x1, int y1, double zd, void* data, AlgoPixel proc)
|
||||||
|
{
|
||||||
|
int xd = x1-x0;
|
||||||
|
int yd = y1-y0;
|
||||||
|
double w = xd*yd;
|
||||||
|
|
||||||
|
if (zd == 0)
|
||||||
|
return algo_ellipse(x0, y0, x1, y1, data, proc);
|
||||||
|
|
||||||
|
if (w != 0.0)
|
||||||
|
w = (w-zd) / (w+w); // squared weight of P1
|
||||||
|
|
||||||
|
w = MID(0.0, w, 1.0);
|
||||||
|
|
||||||
|
xd = std::floor(w*xd + 0.5);
|
||||||
|
yd = std::floor(w*yd + 0.5);
|
||||||
|
|
||||||
|
draw_quad_rational_bezier_seg(x0, y0+yd, x0, y0, x0+xd, y0, 1.0-w, data, proc);
|
||||||
|
draw_quad_rational_bezier_seg(x0, y0+yd, x0, y1, x1-xd, y1, w, data, proc);
|
||||||
|
draw_quad_rational_bezier_seg(x1, y1-yd, x1, y1, x1-xd, y1, 1.0-w, data, proc);
|
||||||
|
draw_quad_rational_bezier_seg(x1, y1-yd, x1, y0, x0+xd, y0, w, data, proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_rotated_ellipse(int cx, int cy, int a, int b, double angle, void* data, AlgoPixel proc)
|
||||||
|
{
|
||||||
|
double xd = a*a;
|
||||||
|
double yd = b*b;
|
||||||
|
double s = std::sin(angle);
|
||||||
|
double zd = (xd-yd)*s; // ellipse rotation
|
||||||
|
xd = std::sqrt(xd-zd*s); // surrounding rectangle
|
||||||
|
yd = std::sqrt(yd+zd*s);
|
||||||
|
|
||||||
|
a = std::floor(xd+0.5);
|
||||||
|
b = std::floor(yd+0.5);
|
||||||
|
zd = zd*a*b / (xd*yd);
|
||||||
|
zd = 4*zd*std::cos(angle);
|
||||||
|
|
||||||
|
draw_rotated_ellipse_rect(cx-a, cy-b, cx+a, cy+b, zd, data, proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fill_rotated_ellipse(int cx, int cy, int a, int b, double angle, void* data, AlgoHLine proc)
|
||||||
|
{
|
||||||
|
struct Rows {
|
||||||
|
int y0;
|
||||||
|
std::vector<std::pair<int, int> > row;
|
||||||
|
Rows(int y0, int nrows)
|
||||||
|
: y0(y0), row(nrows, std::make_pair(1, -1)) { }
|
||||||
|
void update(int x, int y) {
|
||||||
|
int i = MID(0, y-y0, row.size()-1);
|
||||||
|
auto& r = row[i];
|
||||||
|
if (r.first > r.second) {
|
||||||
|
r.first = r.second = x;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
r.first = MIN(r.first, x);
|
||||||
|
r.second = MAX(r.second, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
double xd = a*a;
|
||||||
|
double yd = b*b;
|
||||||
|
double s = std::sin(angle);
|
||||||
|
double zd = (xd-yd)*s;
|
||||||
|
xd = std::sqrt(xd-zd*s);
|
||||||
|
yd = std::sqrt(yd+zd*s);
|
||||||
|
|
||||||
|
a = std::floor(xd+0.5);
|
||||||
|
b = std::floor(yd+0.5);
|
||||||
|
zd = zd*a*b / (xd*yd);
|
||||||
|
zd = 4*zd*std::cos(angle);
|
||||||
|
|
||||||
|
Rows rows(cy-b, 2*b+1);
|
||||||
|
|
||||||
|
draw_rotated_ellipse_rect(
|
||||||
|
cx-a, cy-b, cx+a, cy+b, zd,
|
||||||
|
&rows,
|
||||||
|
(AlgoPixel)[](int x, int y, void* data){
|
||||||
|
Rows* rows = (Rows*)data;
|
||||||
|
rows->update(x, y);
|
||||||
|
});
|
||||||
|
|
||||||
|
int y = rows.y0;
|
||||||
|
for (const auto& r : rows.row) {
|
||||||
|
if (r.first <= r.second)
|
||||||
|
proc(r.first, y, r.second, data);
|
||||||
|
++y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Algorightm from Allegro (allegro/src/spline.c)
|
// Algorightm from Allegro (allegro/src/spline.c)
|
||||||
// Adapted for Aseprite by David Capello.
|
// Adapted for Aseprite by David Capello.
|
||||||
void algo_spline(double x0, double y0, double x1, double y1,
|
void algo_spline(double x0, double y0, double x1, double y1,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite Document Library
|
// Aseprite Document Library
|
||||||
// Copyright (c) 2001-2014 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.
|
||||||
// Read LICENSE.txt for more information.
|
// Read LICENSE.txt for more information.
|
||||||
@ -22,6 +22,9 @@ namespace doc {
|
|||||||
void algo_ellipse(int x1, int y1, int x2, int y2, void *data, AlgoPixel proc);
|
void algo_ellipse(int x1, int y1, int x2, int y2, void *data, AlgoPixel proc);
|
||||||
void algo_ellipsefill(int x1, int y1, int x2, int y2, void *data, AlgoHLine proc);
|
void algo_ellipsefill(int x1, int y1, int x2, int y2, void *data, AlgoHLine proc);
|
||||||
|
|
||||||
|
void draw_rotated_ellipse(int cx, int cy, int a, int b, double angle, void* data, AlgoPixel proc);
|
||||||
|
void fill_rotated_ellipse(int cx, int cy, int a, int b, double angle, void* data, AlgoHLine proc);
|
||||||
|
|
||||||
void algo_spline(double x0, double y0, double x1, double y1,
|
void algo_spline(double x0, double y0, double x1, double y1,
|
||||||
double x2, double y2, double x3, double y3,
|
double x2, double y2, double x3, double y3,
|
||||||
void *data, AlgoLine proc);
|
void *data, AlgoLine proc);
|
||||||
|
Loading…
Reference in New Issue
Block a user