Add possibility to rotate ellipses and rectangles (fix #868)

This commit is contained in:
David Capello 2018-03-06 19:21:09 -03:00
parent 109fcddaf7
commit 184736760a
14 changed files with 420 additions and 45 deletions

View File

@ -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" />

View File

@ -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)
```

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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);

View File

@ -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 {

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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) {

View File

@ -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,

View File

@ -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);