diff --git a/data/gui.xml b/data/gui.xml index afa0aef81..1ccbf3e02 100644 --- a/data/gui.xml +++ b/data/gui.xml @@ -1,6 +1,6 @@ - + @@ -546,9 +546,9 @@ - + diff --git a/docs/LICENSES.md b/docs/LICENSES.md index b5e9a9545..5f0e746d0 100644 --- a/docs/LICENSES.md +++ b/docs/LICENSES.md @@ -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) ``` diff --git a/src/app/tools/controller.h b/src/app/tools/controller.h index 7fecf3ffb..8bb67ac0e 100644 --- a/src/app/tools/controller.h +++ b/src/app/tools/controller.h @@ -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 diff --git a/src/app/tools/controllers.h b/src/app/tools/controllers.h index acc857142..5c9209457 100644 --- a/src/app/tools/controllers.h +++ b/src/app/tools/controllers.h @@ -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(point.y-m_center.y), + static_cast(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(stroke[0].y-stroke[1].y), - static_cast(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(stroke[0].y-stroke[1].y), + static_cast(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 diff --git a/src/app/tools/intertwine.cpp b/src/app/tools/intertwine.cpp index 4aee58e0e..73d065e03 100644 --- a/src/app/tools/intertwine.cpp +++ b/src/app/tools/intertwine.cpp @@ -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(); diff --git a/src/app/tools/intertwine.h b/src/app/tools/intertwine.h index 5571767d2..47b34cd7d 100644 --- a/src/app/tools/intertwine.h +++ b/src/app/tools/intertwine.h @@ -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 +#include "gfx/rect.h" namespace app { namespace tools { @@ -23,8 +22,6 @@ namespace app { // user-defined points. class Intertwine { public: - typedef std::vector 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); diff --git a/src/app/tools/intertwiners.h b/src/app/tools/intertwiners.h index c6a7289e9..636336374 100644 --- a/src/app/tools/intertwiners.h +++ b/src/app/tools/intertwiners.h @@ -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 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 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 { diff --git a/src/app/tools/tool_loop_manager.cpp b/src/app/tools/tool_loop_manager.cpp index 06fbc08b2..d018e80ca 100644 --- a/src/app/tools/tool_loop_manager.cpp +++ b/src/app/tools/tool_loop_manager.cpp @@ -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; diff --git a/src/app/tools/tool_loop_modifiers.h b/src/app/tools/tool_loop_modifiers.h index dcdeb528a..769b82d6d 100644 --- a/src/app/tools/tool_loop_modifiers.h +++ b/src/app/tools/tool_loop_modifiers.h @@ -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 diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index 04024ff87..f7e2b9844 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -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 diff --git a/src/app/ui/keyboard_shortcuts.cpp b/src/app/ui/keyboard_shortcuts.cpp index 1731f321d..f99a5ecb7 100644 --- a/src/app/ui/keyboard_shortcuts.cpp +++ b/src/app/ui/keyboard_shortcuts.cpp @@ -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: diff --git a/src/app/ui/keyboard_shortcuts.h b/src/app/ui/keyboard_shortcuts.h index e73027f89..f550e160b 100644 --- a/src/app/ui/keyboard_shortcuts.h +++ b/src/app/ui/keyboard_shortcuts.h @@ -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) { diff --git a/src/doc/algo.cpp b/src/doc/algo.cpp index 1a0d7e801..9791bc328 100644 --- a/src/doc/algo.cpp +++ b/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 #include +#include +#include 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 > 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, diff --git a/src/doc/algo.h b/src/doc/algo.h index 6474c879c..f66f21641 100644 --- a/src/doc/algo.h +++ b/src/doc/algo.h @@ -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);