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"?>
|
||||
<!-- Aseprite -->
|
||||
<!-- Copyright (C) 2001-2017 by David Capello -->
|
||||
<!-- Copyright (C) 2001-2018 by David Capello -->
|
||||
<gui version="1.3-dev">
|
||||
<!-- Keyboard shortcuts -->
|
||||
<keyboard version="1">
|
||||
@ -546,9 +546,9 @@
|
||||
<!-- Modifiers for two-points tool controller -->
|
||||
<key action="SquareAspect" shortcut="Shift" />
|
||||
<key action="DrawFromCenter" shortcut="Ctrl" />
|
||||
|
||||
<!-- Modifiers for two-or-more-points tools -->
|
||||
<key action="MoveOrigin" shortcut="Space" />
|
||||
<key action="RotateShape" shortcut="Alt" />
|
||||
|
||||
<!-- Without default shortcuts -->
|
||||
<key action="LeftMouseButton" />
|
||||
|
@ -29,6 +29,30 @@ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
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)
|
||||
|
||||
```
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -58,6 +58,10 @@ namespace app {
|
||||
// and then a TracePolicy::Accumulate for freehand (Pencil tool).
|
||||
virtual bool handleTracePolicy() const { return false; }
|
||||
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
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -105,7 +105,8 @@ public:
|
||||
void pressButton(Stroke& stroke, const Point& point) override {
|
||||
MoveOriginCapability::pressButton(stroke, point);
|
||||
|
||||
m_first = point;
|
||||
m_first = m_center = point;
|
||||
m_angle = 0.0;
|
||||
|
||||
stroke.addPoint(point);
|
||||
stroke.addPoint(point);
|
||||
@ -123,6 +124,21 @@ public:
|
||||
if (MoveOriginCapability::isMovingOrigin(loop, stroke, point))
|
||||
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;
|
||||
|
||||
if ((int(loop->getModifiers()) & int(ToolLoopModifiers::kSquareAspect))) {
|
||||
@ -168,9 +184,15 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
stroke[0] = m_first;
|
||||
|
||||
if ((int(loop->getModifiers()) & int(ToolLoopModifiers::kFromCenter))) {
|
||||
if (hasAngle()) {
|
||||
int rx = stroke[1].x - m_center.x;
|
||||
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 ry = stroke[1].y - m_first.y;
|
||||
stroke[0].x = m_first.x - rx;
|
||||
@ -215,22 +237,42 @@ public:
|
||||
|
||||
gfx::Point offset = loop->statusBarPositionOffset();
|
||||
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[1].x+offset.x, stroke[1].y+offset.y,
|
||||
w, h,
|
||||
std::sqrt(w*w + h*h),
|
||||
180.0 * std::atan2(static_cast<double>(stroke[0].y-stroke[1].y),
|
||||
static_cast<double>(stroke[1].x-stroke[0].x)) / PI);
|
||||
w, h, std::sqrt(w*w + h*h));
|
||||
|
||||
if (hasAngle() ||
|
||||
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;
|
||||
}
|
||||
|
||||
double getShapeAngle() const override {
|
||||
return m_angle;
|
||||
}
|
||||
|
||||
private:
|
||||
bool hasAngle() const {
|
||||
return (ABS(m_angle) > 0.001);
|
||||
}
|
||||
|
||||
void onMoveOrigin(const Point& delta) override {
|
||||
m_first += delta;
|
||||
m_center += delta;
|
||||
}
|
||||
|
||||
Point m_first;
|
||||
Point m_center;
|
||||
double m_angle;
|
||||
};
|
||||
|
||||
// Controls clicks for tools like polygon
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -22,6 +22,11 @@ namespace tools {
|
||||
using namespace gfx;
|
||||
using namespace doc;
|
||||
|
||||
gfx::Rect Intertwine::getStrokeBounds(ToolLoop* loop, const Stroke& stroke)
|
||||
{
|
||||
return stroke.bounds();
|
||||
}
|
||||
|
||||
void Intertwine::doPointshapePoint(int x, int y, ToolLoop* loop)
|
||||
{
|
||||
Symmetry* symmetry = loop->getSymmetry();
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -9,8 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "gfx/point.h"
|
||||
|
||||
#include <vector>
|
||||
#include "gfx/rect.h"
|
||||
|
||||
namespace app {
|
||||
namespace tools {
|
||||
@ -23,8 +22,6 @@ namespace app {
|
||||
// user-defined points.
|
||||
class Intertwine {
|
||||
public:
|
||||
typedef std::vector<gfx::Point> Points;
|
||||
|
||||
virtual ~Intertwine() { }
|
||||
virtual bool snapByAngle() { return false; }
|
||||
virtual void prepareIntertwine() { }
|
||||
@ -33,6 +30,8 @@ namespace app {
|
||||
virtual void joinStroke(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:
|
||||
// The given point must be relative to the cel origin.
|
||||
static void doPointshapePoint(int x, int y, ToolLoop* loop);
|
||||
|
@ -1,9 +1,11 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#include "base/pi.h"
|
||||
|
||||
namespace app {
|
||||
namespace tools {
|
||||
|
||||
@ -80,6 +82,7 @@ public:
|
||||
// Fill content
|
||||
doc::algorithm::polygon(stroke.size(), (const int*)&stroke[0], loop, (AlgoHLine)doPointshapeHline);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class IntertwineAsRectangles : public Intertwine {
|
||||
@ -104,19 +107,31 @@ public:
|
||||
if (x1 > x2) std::swap(x1, x2);
|
||||
if (y1 > y2) std::swap(y1, y2);
|
||||
|
||||
doPointshapeLine(x1, y1, x2, y1, loop);
|
||||
doPointshapeLine(x1, y2, x2, y2, loop);
|
||||
const double angle = loop->getController()->getShapeAngle();
|
||||
if (ABS(angle) < 0.001) {
|
||||
doPointshapeLine(x1, y1, x2, y1, loop);
|
||||
doPointshapeLine(x1, y2, x2, y2, loop);
|
||||
|
||||
for (y=y1; y<=y2; y++) {
|
||||
doPointshapePoint(x1, y, loop);
|
||||
doPointshapePoint(x2, y, loop);
|
||||
for (y=y1; y<=y2; y++) {
|
||||
doPointshapePoint(x1, 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) {
|
||||
joinStroke(loop, stroke);
|
||||
return;
|
||||
@ -132,17 +147,63 @@ public:
|
||||
if (x1 > x2) std::swap(x1, x2);
|
||||
if (y1 > y2) std::swap(y1, y2);
|
||||
|
||||
for (y=y1; y<=y2; y++)
|
||||
doPointshapeLine(x1, y, x2, y, loop);
|
||||
const double angle = loop->getController()->getShapeAngle();
|
||||
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 {
|
||||
public:
|
||||
|
||||
void joinStroke(ToolLoop* loop, const Stroke& stroke) override
|
||||
{
|
||||
void joinStroke(ToolLoop* loop, const Stroke& stroke) override {
|
||||
if (stroke.size() == 0)
|
||||
return;
|
||||
|
||||
@ -159,13 +220,22 @@ public:
|
||||
if (x1 > x2) std::swap(x1, x2);
|
||||
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) {
|
||||
joinStroke(loop, stroke);
|
||||
return;
|
||||
@ -180,9 +250,45 @@ public:
|
||||
if (x1 > x2) std::swap(x1, x2);
|
||||
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 {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -233,7 +233,9 @@ void ToolLoopManager::calculateDirtyArea(const Strokes& strokes)
|
||||
m_dirtyArea.clear();
|
||||
|
||||
for (auto& stroke : strokes) {
|
||||
gfx::Rect strokeBounds = stroke.bounds();
|
||||
gfx::Rect strokeBounds =
|
||||
m_toolLoop->getIntertwine()->getStrokeBounds(m_toolLoop, stroke);
|
||||
|
||||
if (strokeBounds.isEmpty())
|
||||
continue;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2016 David Capello
|
||||
// Copyright (C) 2016-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -19,6 +19,7 @@ namespace tools {
|
||||
kMoveOrigin = 0x00000008,
|
||||
kSquareAspect = 0x00000010,
|
||||
kFromCenter = 0x00000020,
|
||||
kRotateShape = 0x00000040,
|
||||
};
|
||||
|
||||
} // namespace tools
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -1488,6 +1488,8 @@ void Editor::updateToolLoopModifiersIndicators()
|
||||
modifiers |= int(tools::ToolLoopModifiers::kSquareAspect);
|
||||
if (int(action & KeyAction::DrawFromCenter))
|
||||
modifiers |= int(tools::ToolLoopModifiers::kFromCenter);
|
||||
if (int(action & KeyAction::RotateShape))
|
||||
modifiers |= int(tools::ToolLoopModifiers::kRotateShape);
|
||||
}
|
||||
|
||||
// Freehand modifiers
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -38,16 +38,17 @@ namespace {
|
||||
{ "SnapToGrid" , "Snap To Grid" , app::KeyAction::SnapToGrid },
|
||||
{ "AngleSnap" , "Angle Snap" , app::KeyAction::AngleSnap },
|
||||
{ "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 },
|
||||
{ "AddSelection" , "Add Selection" , app::KeyAction::AddSelection },
|
||||
{ "SubtractSelection" , "Subtract Selection" , app::KeyAction::SubtractSelection },
|
||||
{ "AutoSelectLayer" , "Auto Select Layer" , app::KeyAction::AutoSelectLayer },
|
||||
{ "StraightLineFromLastPoint", "Straight Line from Last Point", app::KeyAction::StraightLineFromLastPoint },
|
||||
{ "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 },
|
||||
{ "DrawFromCenter" , "Draw From Center" , app::KeyAction::DrawFromCenter },
|
||||
{ "RotateShape" , "Rotate Shape" , app::KeyAction::RotateShape },
|
||||
{ "LeftMouseButton" , "Trigger Left Mouse Button" , app::KeyAction::LeftMouseButton },
|
||||
{ "RightMouseButton" , "Trigger Right Mouse Button" , app::KeyAction::RightMouseButton },
|
||||
{ NULL , NULL , app::KeyAction::None }
|
||||
@ -165,6 +166,7 @@ Key::Key(KeyAction action)
|
||||
case KeyAction::MoveOrigin:
|
||||
case KeyAction::SquareAspect:
|
||||
case KeyAction::DrawFromCenter:
|
||||
case KeyAction::RotateShape:
|
||||
m_keycontext = KeyContext::ShapeTool;
|
||||
break;
|
||||
case KeyAction::LeftMouseButton:
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -63,6 +63,7 @@ namespace app {
|
||||
DrawFromCenter = 0x00002000,
|
||||
ScaleFromCenter = 0x00004000,
|
||||
AngleSnapFromLastPoint = 0x00008000,
|
||||
RotateShape = 0x00010000,
|
||||
};
|
||||
|
||||
inline KeyAction operator&(KeyAction a, KeyAction b) {
|
||||
|
186
src/doc/algo.cpp
186
src/doc/algo.cpp
@ -8,11 +8,15 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "base/base.h"
|
||||
#include "doc/algo.h"
|
||||
|
||||
#include "base/base.h"
|
||||
#include "base/debug.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
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)
|
||||
// Adapted for Aseprite by David Capello.
|
||||
void algo_spline(double x0, double y0, double x1, double y1,
|
||||
|
@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
// 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_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,
|
||||
double x2, double y2, double x3, double y3,
|
||||
void *data, AlgoLine proc);
|
||||
|
Loading…
Reference in New Issue
Block a user