mirror of https://github.com/aseprite/aseprite.git
Merge 5d8a50d009
into 1b16cfbe71
This commit is contained in:
commit
14ded417cd
Binary file not shown.
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
@ -393,6 +393,7 @@
|
||||||
<part id="icon_slice" x="248" y="264" w="8" h="8"/>
|
<part id="icon_slice" x="248" y="264" w="8" h="8"/>
|
||||||
<part id="icon_aspect_ratio" x="256" y="264" w="10" h="8"/>
|
<part id="icon_aspect_ratio" x="256" y="264" w="10" h="8"/>
|
||||||
<part id="icon_delta" x="266" y="264" w="6" h="8"/>
|
<part id="icon_delta" x="266" y="264" w="6" h="8"/>
|
||||||
|
<part id="icon_corner_radius" x="272" y="264" w="8" h="8"/>
|
||||||
<part id="icon_add" x="184" y="200" w="5" h="5"/>
|
<part id="icon_add" x="184" y="200" w="5" h="5"/>
|
||||||
<part id="tool_rectangular_marquee" x="144" y="0" w="16" h="16"/>
|
<part id="tool_rectangular_marquee" x="144" y="0" w="16" h="16"/>
|
||||||
<part id="tool_elliptical_marquee" x="160" y="0" w="16" h="16"/>
|
<part id="tool_elliptical_marquee" x="160" y="0" w="16" h="16"/>
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
@ -389,6 +389,7 @@
|
||||||
<part id="icon_slice" x="248" y="264" w="8" h="8"/>
|
<part id="icon_slice" x="248" y="264" w="8" h="8"/>
|
||||||
<part id="icon_aspect_ratio" x="256" y="264" w="10" h="8"/>
|
<part id="icon_aspect_ratio" x="256" y="264" w="10" h="8"/>
|
||||||
<part id="icon_delta" x="266" y="264" w="6" h="8"/>
|
<part id="icon_delta" x="266" y="264" w="6" h="8"/>
|
||||||
|
<part id="icon_corner_radius" x="272" y="264" w="8" h="8"/>
|
||||||
<part id="icon_add" x="184" y="200" w="5" h="5"/>
|
<part id="icon_add" x="184" y="200" w="5" h="5"/>
|
||||||
<part id="tool_rectangular_marquee" x="144" y="0" w="16" h="16"/>
|
<part id="tool_rectangular_marquee" x="144" y="0" w="16" h="16"/>
|
||||||
<part id="tool_elliptical_marquee" x="160" y="0" w="16" h="16"/>
|
<part id="tool_elliptical_marquee" x="160" y="0" w="16" h="16"/>
|
||||||
|
|
|
@ -678,6 +678,8 @@
|
||||||
<!-- 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" />
|
||||||
|
<key action="CornerRadius" shortcut="C" />
|
||||||
|
|
||||||
<!-- 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" />
|
<key action="RotateShape" shortcut="Alt" />
|
||||||
|
|
|
@ -481,6 +481,7 @@
|
||||||
<option id="filled_preview" type="bool" default="false" />
|
<option id="filled_preview" type="bool" default="false" />
|
||||||
<option id="ink" type="app::tools::InkType" default="app::tools::InkType::DEFAULT" />
|
<option id="ink" type="app::tools::InkType" default="app::tools::InkType::DEFAULT" />
|
||||||
<option id="freehand_algorithm" type="app::tools::FreehandAlgorithm" default="app::tools::FreehandAlgorithm::DEFAULT" />
|
<option id="freehand_algorithm" type="app::tools::FreehandAlgorithm" default="app::tools::FreehandAlgorithm::DEFAULT" />
|
||||||
|
<option id="corner_radius" type="int" default="0" />
|
||||||
<!-- Update app::Preferences::resetToolPreferences() function if you add new sections here -->
|
<!-- Update app::Preferences::resetToolPreferences() function if you add new sections here -->
|
||||||
<section id="brush">
|
<section id="brush">
|
||||||
<option id="type" type="BrushType" default="BrushType::CIRCLE" />
|
<option id="type" type="BrushType" default="BrushType::CIRCLE" />
|
||||||
|
|
|
@ -601,6 +601,7 @@ discard_brush = Discard Brush (Esc)
|
||||||
brush_type = Brush Type
|
brush_type = Brush Type
|
||||||
brush_size = Brush Size (in pixels)
|
brush_size = Brush Size (in pixels)
|
||||||
brush_angle = Brush Angle (in degrees)
|
brush_angle = Brush Angle (in degrees)
|
||||||
|
corner_radius = Corner Radius (in pixels)
|
||||||
ink = Ink
|
ink = Ink
|
||||||
opacity = Opacity (paint intensity)
|
opacity = Opacity (paint intensity)
|
||||||
shades = Shades
|
shades = Shades
|
||||||
|
@ -1006,6 +1007,7 @@ move_origin = Move Origin
|
||||||
square_aspect = Square Aspect
|
square_aspect = Square Aspect
|
||||||
draw_from_center = Draw From Center
|
draw_from_center = Draw From Center
|
||||||
rotate_shape = Rotate Shape
|
rotate_shape = Rotate Shape
|
||||||
|
corner_radius = Corner Radius
|
||||||
trigger_left_mouse_button = Trigger Left Mouse Button
|
trigger_left_mouse_button = Trigger Left Mouse Button
|
||||||
trigger_right_mouse_button = Trigger Right Mouse Button
|
trigger_right_mouse_button = Trigger Right Mouse Button
|
||||||
ok = &OK
|
ok = &OK
|
||||||
|
|
|
@ -62,6 +62,10 @@ public:
|
||||||
// Returns the angle for a shape-like intertwiner (rectangles,
|
// Returns the angle for a shape-like intertwiner (rectangles,
|
||||||
// ellipses, etc.).
|
// ellipses, etc.).
|
||||||
virtual double getShapeAngle() const { return 0.0; }
|
virtual double getShapeAngle() const { return 0.0; }
|
||||||
|
|
||||||
|
// Returns the radius for each corner for a rectangle intertwiner when drawing
|
||||||
|
// rounded rectangles.
|
||||||
|
virtual int getCornerRadius() const { return 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
}} // namespace app::tools
|
}} // namespace app::tools
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 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 "app/app.h"
|
||||||
|
#include "app/pref/preferences.h"
|
||||||
#include "app/snap_to_grid.h"
|
#include "app/snap_to_grid.h"
|
||||||
|
#include "app/tools/controller.h"
|
||||||
|
#include "app/tools/intertwine.h"
|
||||||
|
#include "app/tools/tool.h"
|
||||||
|
#include "app/tools/tool_loop.h"
|
||||||
|
#include "app/tools/tool_loop_modifiers.h"
|
||||||
#include "base/gcd.h"
|
#include "base/gcd.h"
|
||||||
#include "base/pi.h"
|
#include "base/pi.h"
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
|
@ -105,6 +112,72 @@ private:
|
||||||
Stroke::Pt m_last;
|
Stroke::Pt m_last;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CornerRadius {
|
||||||
|
public:
|
||||||
|
ToolLoop* loop() const { return m_loop; }
|
||||||
|
void loop(ToolLoop* loop) { m_loop = loop; }
|
||||||
|
|
||||||
|
void load()
|
||||||
|
{
|
||||||
|
auto* tool = App::instance()->activeTool();
|
||||||
|
m_pref = &Preferences::instance().tool(tool);
|
||||||
|
m_radius = m_pref->cornerRadius();
|
||||||
|
}
|
||||||
|
|
||||||
|
void save() { m_pref->cornerRadius.setValue(m_radius); }
|
||||||
|
|
||||||
|
bool isModifying() const { return m_modifying; }
|
||||||
|
|
||||||
|
void modifyRadius(Stroke& stroke, const Stroke::Pt& pt)
|
||||||
|
{
|
||||||
|
if (!m_modifying) {
|
||||||
|
m_lastRadius = std::min(m_radius, maxRadius(stroke));
|
||||||
|
m_modifying = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dx = stroke[1].x - pt.x;
|
||||||
|
int dy = stroke[1].y - pt.y;
|
||||||
|
if (stroke[1].y < stroke[0].y)
|
||||||
|
dy = -dy;
|
||||||
|
if (stroke[1].x < stroke[0].x)
|
||||||
|
dx = -dx;
|
||||||
|
|
||||||
|
m_radius = std::max(0, m_lastRadius + dx + dy);
|
||||||
|
|
||||||
|
capRadius(stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopModifying()
|
||||||
|
{
|
||||||
|
m_modifying = false;
|
||||||
|
m_lastRadius = m_radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasRadius() const { return m_radius > 0; }
|
||||||
|
|
||||||
|
int radius() const { return m_radius; }
|
||||||
|
|
||||||
|
// Gets the corner radius limited by the maximum radius allowed by the stroke
|
||||||
|
// points.
|
||||||
|
int radius(const Stroke& stroke) const { return std::min(m_radius, maxRadius(stroke)); }
|
||||||
|
|
||||||
|
void radius(int r) { m_radius = r; }
|
||||||
|
|
||||||
|
void capRadius(Stroke& stroke) { m_radius = radius(stroke); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static int maxRadius(const Stroke& stroke)
|
||||||
|
{
|
||||||
|
return std::min(ABS(stroke[1].x - stroke[0].x + 1), ABS(stroke[1].y - stroke[0].y + 1)) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolLoop* m_loop;
|
||||||
|
ToolPreferences* m_pref;
|
||||||
|
bool m_modifying = false;
|
||||||
|
int m_lastRadius = 0;
|
||||||
|
int m_radius = 0;
|
||||||
|
};
|
||||||
|
|
||||||
// Controls clicks for tools like line
|
// Controls clicks for tools like line
|
||||||
class TwoPointsController : public MoveOriginCapability {
|
class TwoPointsController : public MoveOriginCapability {
|
||||||
public:
|
public:
|
||||||
|
@ -117,6 +190,11 @@ public:
|
||||||
m_first = m_center = pt;
|
m_first = m_center = pt;
|
||||||
m_angle = 0.0;
|
m_angle = 0.0;
|
||||||
|
|
||||||
|
m_cornerRadius.loop(loop);
|
||||||
|
if (loop->getIntertwine()->cornerRadiusSupport()) {
|
||||||
|
m_cornerRadius.load();
|
||||||
|
}
|
||||||
|
|
||||||
stroke.addPoint(pt);
|
stroke.addPoint(pt);
|
||||||
stroke.addPoint(pt);
|
stroke.addPoint(pt);
|
||||||
|
|
||||||
|
@ -124,7 +202,14 @@ public:
|
||||||
snapPointsToGridTiles(loop, stroke);
|
snapPointsToGridTiles(loop, stroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool releaseButton(Stroke& stroke, const Stroke::Pt& pt) override { return false; }
|
bool releaseButton(Stroke& stroke, const Stroke::Pt& pt) override
|
||||||
|
{
|
||||||
|
if (m_cornerRadius.loop() && m_cornerRadius.loop()->getIntertwine()->cornerRadiusSupport()) {
|
||||||
|
m_cornerRadius.capRadius(stroke);
|
||||||
|
m_cornerRadius.save();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void movement(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) override
|
void movement(ToolLoop* loop, Stroke& stroke, const Stroke::Pt& pt) override
|
||||||
{
|
{
|
||||||
|
@ -135,6 +220,18 @@ public:
|
||||||
if (MoveOriginCapability::isMovingOrigin(loop, stroke, pt))
|
if (MoveOriginCapability::isMovingOrigin(loop, stroke, pt))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (loop->getIntertwine()->cornerRadiusSupport() &&
|
||||||
|
(int(loop->getModifiers()) & int(ToolLoopModifiers::kCornerRadius))) {
|
||||||
|
m_cornerRadius.modifyRadius(stroke, pt);
|
||||||
|
m_cornerRadius.save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_cornerRadius.isModifying()) {
|
||||||
|
m_cornerRadius.stopModifying();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!loop->getIntertwine()->snapByAngle() &&
|
if (!loop->getIntertwine()->snapByAngle() &&
|
||||||
int(loop->getModifiers()) & int(ToolLoopModifiers::kRotateShape)) {
|
int(loop->getModifiers()) & int(ToolLoopModifiers::kRotateShape)) {
|
||||||
if ((int(loop->getModifiers()) & int(ToolLoopModifiers::kFromCenter))) {
|
if ((int(loop->getModifiers()) & int(ToolLoopModifiers::kFromCenter))) {
|
||||||
|
@ -277,11 +374,15 @@ public:
|
||||||
text += fmt::format(" :angle: {:.1f}", 180.0 * angle / PI);
|
text += fmt::format(" :angle: {:.1f}", 180.0 * angle / PI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_cornerRadius.hasRadius() && loop->getIntertwine()->cornerRadiusSupport())
|
||||||
|
text += fmt::format(" :corner_radius: {}", m_cornerRadius.radius(stroke));
|
||||||
|
|
||||||
// Aspect ratio at the end
|
// Aspect ratio at the end
|
||||||
text += fmt::format(" :aspect_ratio: {}:{}", w / gcd, h / gcd);
|
text += fmt::format(" :aspect_ratio: {}:{}", w / gcd, h / gcd);
|
||||||
}
|
}
|
||||||
|
|
||||||
double getShapeAngle() const override { return m_angle; }
|
double getShapeAngle() const override { return m_angle; }
|
||||||
|
int getCornerRadius() const override { return m_cornerRadius.radius(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void snapPointsToGridTiles(ToolLoop* loop, Stroke& stroke)
|
void snapPointsToGridTiles(ToolLoop* loop, Stroke& stroke)
|
||||||
|
@ -312,6 +413,7 @@ private:
|
||||||
Stroke::Pt m_first;
|
Stroke::Pt m_first;
|
||||||
Stroke::Pt m_center;
|
Stroke::Pt m_center;
|
||||||
double m_angle;
|
double m_angle;
|
||||||
|
CornerRadius m_cornerRadius;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Controls clicks for tools like polygon
|
// Controls clicks for tools like polygon
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include "app/tools/tool_loop.h"
|
#include "app/tools/tool_loop.h"
|
||||||
#include "base/pi.h"
|
#include "base/pi.h"
|
||||||
#include "doc/algo.h"
|
#include "doc/algo.h"
|
||||||
|
#include "doc/algorithm/hline.h"
|
||||||
#include "doc/layer.h"
|
#include "doc/layer.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019-2021 Igara Studio S.A.
|
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -26,6 +26,8 @@ public:
|
||||||
virtual ~Intertwine() {}
|
virtual ~Intertwine() {}
|
||||||
virtual bool snapByAngle() { return false; }
|
virtual bool snapByAngle() { return false; }
|
||||||
virtual void prepareIntertwine(ToolLoop* loop) {}
|
virtual void prepareIntertwine(ToolLoop* loop) {}
|
||||||
|
// Returns true if the implementation supports corner radius modification.
|
||||||
|
virtual bool cornerRadiusSupport() { return false; }
|
||||||
|
|
||||||
// The given stroke must be relative to the cel origin.
|
// The given stroke must be relative to the cel origin.
|
||||||
virtual void joinStroke(ToolLoop* loop, const Stroke& stroke) = 0;
|
virtual void joinStroke(ToolLoop* loop, const Stroke& stroke) = 0;
|
||||||
|
|
|
@ -5,8 +5,19 @@
|
||||||
// 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 "app/tools/controller.h"
|
||||||
|
#include "app/tools/intertwine.h"
|
||||||
|
#include "app/tools/point_shape.h"
|
||||||
|
#include "app/tools/tool_loop.h"
|
||||||
|
#include "app/tools/tool_loop_modifiers.h"
|
||||||
#include "base/pi.h"
|
#include "base/pi.h"
|
||||||
|
#include "doc/algo.h"
|
||||||
|
#include "doc/algorithm/polygon.h"
|
||||||
#include "doc/layer_tilemap.h"
|
#include "doc/layer_tilemap.h"
|
||||||
|
#include "gfx/point.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
using namespace gfx;
|
||||||
|
|
||||||
namespace app { namespace tools {
|
namespace app { namespace tools {
|
||||||
|
|
||||||
|
@ -160,6 +171,8 @@ public:
|
||||||
|
|
||||||
class IntertwineAsRectangles : public Intertwine {
|
class IntertwineAsRectangles : public Intertwine {
|
||||||
public:
|
public:
|
||||||
|
bool cornerRadiusSupport() override { return true; }
|
||||||
|
|
||||||
void joinStroke(ToolLoop* loop, const Stroke& stroke) override
|
void joinStroke(ToolLoop* loop, const Stroke& stroke) override
|
||||||
{
|
{
|
||||||
if (stroke.size() == 0)
|
if (stroke.size() == 0)
|
||||||
|
@ -183,22 +196,62 @@ public:
|
||||||
std::swap(y1, y2);
|
std::swap(y1, y2);
|
||||||
|
|
||||||
const double angle = loop->getController()->getShapeAngle();
|
const double angle = loop->getController()->getShapeAngle();
|
||||||
|
const int cornerRadius = loop->getController()->getCornerRadius();
|
||||||
if (ABS(angle) < 0.001) {
|
if (ABS(angle) < 0.001) {
|
||||||
doPointshapeLineWithoutDynamics(x1, y1, x2, y1, loop);
|
int r = 0;
|
||||||
doPointshapeLineWithoutDynamics(x1, y2, x2, y2, loop);
|
if (cornerRadius > 0) {
|
||||||
|
int w = x2 - x1 + 1;
|
||||||
|
int h = y2 - y1 + 1;
|
||||||
|
r = std::min(w, std::min(h, 2 * cornerRadius)) / 2;
|
||||||
|
algo_sliced_circle(x1, y1, x2, y2, r, loop, (AlgoPixel)doPointshapePoint);
|
||||||
|
}
|
||||||
|
|
||||||
for (y = y1; y <= y2; y++) {
|
doPointshapeLineWithoutDynamics(x1 + r, y1, x2 - r, y1, loop);
|
||||||
|
doPointshapeLineWithoutDynamics(x1 + r, y2, x2 - r, y2, loop);
|
||||||
|
|
||||||
|
for (y = y1 + r; y <= y2 - r; y++) {
|
||||||
doPointshapePoint(x1, y, loop);
|
doPointshapePoint(x1, y, loop);
|
||||||
doPointshapePoint(x2, y, loop);
|
doPointshapePoint(x2, y, loop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Stroke p = rotateRectangle(x1, y1, x2, y2, angle);
|
if (cornerRadius <= 0) {
|
||||||
int n = p.size();
|
Stroke p = rotateRectangle(x1, y1, x2, y2, angle);
|
||||||
for (int i = 0; i + 1 < n; ++i) {
|
int n = p.size();
|
||||||
doPointshapeLine(p[i], p[i + 1], loop);
|
for (int i = 0; i + 1 < n; ++i) {
|
||||||
|
doPointshapeLine(p[i], p[i + 1], loop);
|
||||||
|
}
|
||||||
|
doPointshapeLine(p[n - 1], p[0], loop);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int w = x2 - x1 + 1;
|
||||||
|
int h = y2 - y1 + 1;
|
||||||
|
int r = std::min(w, std::min(h, 2 * cornerRadius)) / 2;
|
||||||
|
Stroke p = rotateRectangle(x1, y1, x2, y2, angle, r);
|
||||||
|
int n = p.size();
|
||||||
|
for (int i = 0; i + 1 < n; i += 3) {
|
||||||
|
doPointshapeLine(p[i], p[i + 1], loop);
|
||||||
|
}
|
||||||
|
const double ang_minus_PI_2 = base::fmod_radians(angle - PI / 2);
|
||||||
|
const double ang_plus_PI_2 = base::fmod_radians(angle + PI / 2);
|
||||||
|
const double ang_plus_PI = base::fmod_radians(angle + PI);
|
||||||
|
algo_arc(p[2].x, p[2].y, ang_minus_PI_2, angle, r, loop, (AlgoPixel)doPointshapePoint);
|
||||||
|
algo_arc(p[5].x, p[5].y, angle, ang_plus_PI_2, r, loop, (AlgoPixel)doPointshapePoint);
|
||||||
|
algo_arc(p[8].x,
|
||||||
|
p[8].y,
|
||||||
|
ang_plus_PI_2,
|
||||||
|
ang_plus_PI,
|
||||||
|
r,
|
||||||
|
loop,
|
||||||
|
(AlgoPixel)doPointshapePoint);
|
||||||
|
algo_arc(p[11].x,
|
||||||
|
p[11].y,
|
||||||
|
ang_plus_PI,
|
||||||
|
ang_minus_PI_2,
|
||||||
|
r,
|
||||||
|
loop,
|
||||||
|
(AlgoPixel)doPointshapePoint);
|
||||||
}
|
}
|
||||||
doPointshapeLine(p[n - 1], p[0], loop);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,14 +277,66 @@ public:
|
||||||
std::swap(y1, y2);
|
std::swap(y1, y2);
|
||||||
|
|
||||||
const double angle = loop->getController()->getShapeAngle();
|
const double angle = loop->getController()->getShapeAngle();
|
||||||
|
const int cornerRadius = loop->getController()->getCornerRadius();
|
||||||
if (ABS(angle) < 0.001) {
|
if (ABS(angle) < 0.001) {
|
||||||
for (y = y1; y <= y2; y++)
|
int r = 0;
|
||||||
|
if (cornerRadius > 0) {
|
||||||
|
int w = x2 - x1 + 1;
|
||||||
|
int h = y2 - y1 + 1;
|
||||||
|
r = std::min(w, std::min(h, 2 * cornerRadius)) / 2;
|
||||||
|
algo_sliced_circlefill(x1, y1, x2, y2, r, loop, (AlgoHLine)doPointshapeHline);
|
||||||
|
|
||||||
|
for (y = y1; y < y1 + r; y++)
|
||||||
|
doPointshapeLineWithoutDynamics(x1 + r, y, x2 - r, y, loop);
|
||||||
|
for (y = y2 - r + 1; y <= y2; y++)
|
||||||
|
doPointshapeLineWithoutDynamics(x1 + r, y, x2 - r, y, loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (y = y1 + r; y <= y2 - r; y++)
|
||||||
doPointshapeLineWithoutDynamics(x1, y, x2, y, loop);
|
doPointshapeLineWithoutDynamics(x1, y, x2, y, loop);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Stroke p = rotateRectangle(x1, y1, x2, y2, angle);
|
if (cornerRadius <= 0) {
|
||||||
auto v = p.toXYInts();
|
Stroke p = rotateRectangle(x1, y1, x2, y2, angle);
|
||||||
doc::algorithm::polygon(v.size() / 2, &v[0], loop, (AlgoHLine)doPointshapeHline);
|
auto v = p.toXYInts();
|
||||||
|
doc::algorithm::polygon(v.size() / 2, &v[0], loop, (AlgoHLine)doPointshapeHline);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int w = x2 - x1 + 1;
|
||||||
|
int h = y2 - y1 + 1;
|
||||||
|
int r = std::min(w, std::min(h, 2 * cornerRadius)) / 2;
|
||||||
|
Stroke p = rotateRectangle(x1, y1, x2, y2, angle, cornerRadius);
|
||||||
|
auto v = p.toXYInts();
|
||||||
|
doc::algorithm::polygon(v.size() / 2, &v[0], loop, (AlgoHLine)doPointshapeHline);
|
||||||
|
algo_sliced_circlefill(p[2].x - r,
|
||||||
|
p[2].y - r,
|
||||||
|
p[2].x + r,
|
||||||
|
p[2].y + r,
|
||||||
|
r,
|
||||||
|
loop,
|
||||||
|
(AlgoHLine)doPointshapeHline);
|
||||||
|
algo_sliced_circlefill(p[5].x - r,
|
||||||
|
p[5].y - r,
|
||||||
|
p[5].x + r,
|
||||||
|
p[5].y + r,
|
||||||
|
r,
|
||||||
|
loop,
|
||||||
|
(AlgoHLine)doPointshapeHline);
|
||||||
|
algo_sliced_circlefill(p[8].x - r,
|
||||||
|
p[8].y - r,
|
||||||
|
p[8].x + r,
|
||||||
|
p[8].y + r,
|
||||||
|
r,
|
||||||
|
loop,
|
||||||
|
(AlgoHLine)doPointshapeHline);
|
||||||
|
algo_sliced_circlefill(p[11].x - r,
|
||||||
|
p[11].y - r,
|
||||||
|
p[11].x + r,
|
||||||
|
p[11].y + r,
|
||||||
|
r,
|
||||||
|
loop,
|
||||||
|
(AlgoHLine)doPointshapeHline);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -274,6 +379,54 @@ private:
|
||||||
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;
|
return stroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a stroke with the rotated points of a rectangle making room for a
|
||||||
|
// rounded corner of the specified radius, and with points where the center of
|
||||||
|
// each corner must be.
|
||||||
|
static Stroke rotateRectangle(int x1, int y1, int x2, int y2, double angle, int cornerRadius)
|
||||||
|
{
|
||||||
|
cornerRadius = std::max(cornerRadius, 0);
|
||||||
|
|
||||||
|
int cx = (x1 + x2) / 2;
|
||||||
|
int cy = (y1 + y2) / 2;
|
||||||
|
int a = ((x2 - x1) / 2);
|
||||||
|
int b = ((y2 - y1) / 2);
|
||||||
|
int ai = a - cornerRadius;
|
||||||
|
int bi = b - cornerRadius;
|
||||||
|
|
||||||
|
double s = -std::sin(angle);
|
||||||
|
double c = std::cos(angle);
|
||||||
|
|
||||||
|
Stroke stroke;
|
||||||
|
// Top segment
|
||||||
|
stroke.addPoint(Point(cx - ai * c - b * s, cy + ai * s - b * c));
|
||||||
|
stroke.addPoint(Point(cx + ai * c - b * s, cy - ai * s - b * c));
|
||||||
|
|
||||||
|
// Center for top-right corner
|
||||||
|
stroke.addPoint(Point(cx + ai * c - bi * s, cy - ai * s - bi * c));
|
||||||
|
|
||||||
|
// Right segment
|
||||||
|
stroke.addPoint(Point(cx + a * c - bi * s, cy - a * s - bi * c));
|
||||||
|
stroke.addPoint(Point(cx + a * c + bi * s, cy - a * s + bi * c));
|
||||||
|
|
||||||
|
// Center for bottom-right corner
|
||||||
|
stroke.addPoint(Point(cx + ai * c + bi * s, cy - ai * s + bi * c));
|
||||||
|
|
||||||
|
// Bottom segment
|
||||||
|
stroke.addPoint(Point(cx + ai * c + b * s, cy - ai * s + b * c));
|
||||||
|
stroke.addPoint(Point(cx - ai * c + b * s, cy + ai * s + b * c));
|
||||||
|
|
||||||
|
// Center for bottom-left corner
|
||||||
|
stroke.addPoint(Point(cx - ai * c + bi * s, cy + ai * s + bi * c));
|
||||||
|
|
||||||
|
// Left segment
|
||||||
|
stroke.addPoint(Point(cx - a * c + bi * s, cy + a * s + bi * c));
|
||||||
|
stroke.addPoint(Point(cx - a * c - bi * s, cy + a * s - bi * c));
|
||||||
|
|
||||||
|
// Center for top-left corner
|
||||||
|
stroke.addPoint(Point(cx - ai * c - bi * s, cy + ai * s - bi * c));
|
||||||
|
return stroke;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class IntertwineAsEllipses : public Intertwine {
|
class IntertwineAsEllipses : public Intertwine {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018 Igara Studio S.A.
|
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2016-2018 David Capello
|
// Copyright (C) 2016-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -21,6 +21,7 @@ enum class ToolLoopModifiers {
|
||||||
kSquareAspect = 0x00000020,
|
kSquareAspect = 0x00000020,
|
||||||
kFromCenter = 0x00000040,
|
kFromCenter = 0x00000040,
|
||||||
kRotateShape = 0x00000080,
|
kRotateShape = 0x00000080,
|
||||||
|
kCornerRadius = 0x00000100,
|
||||||
};
|
};
|
||||||
|
|
||||||
}} // namespace app::tools
|
}} // namespace app::tools
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include "app/tools/controller.h"
|
#include "app/tools/controller.h"
|
||||||
#include "app/tools/ink.h"
|
#include "app/tools/ink.h"
|
||||||
#include "app/tools/ink_type.h"
|
#include "app/tools/ink_type.h"
|
||||||
|
#include "app/tools/intertwine.h"
|
||||||
#include "app/tools/point_shape.h"
|
#include "app/tools/point_shape.h"
|
||||||
#include "app/tools/tool.h"
|
#include "app/tools/tool.h"
|
||||||
#include "app/tools/tool_box.h"
|
#include "app/tools/tool_box.h"
|
||||||
|
@ -349,6 +350,24 @@ protected:
|
||||||
bool m_lock;
|
bool m_lock;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ContextBar::CornerRadiusField : public IntEntry {
|
||||||
|
public:
|
||||||
|
CornerRadiusField() : IntEntry(0, 999) { setSuffix("px"); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onValueChange() override
|
||||||
|
{
|
||||||
|
if (g_updatingFromCode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IntEntry::onValueChange();
|
||||||
|
base::ScopedValue lockFlag(g_updatingFromCode, true);
|
||||||
|
|
||||||
|
Tool* tool = App::instance()->activeTool();
|
||||||
|
Preferences::instance().tool(tool).cornerRadius.setValue(getValue());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class ContextBar::ToleranceField : public IntEntry {
|
class ContextBar::ToleranceField : public IntEntry {
|
||||||
public:
|
public:
|
||||||
ToleranceField() : IntEntry(0, 255) {}
|
ToleranceField() : IntEntry(0, 255) {}
|
||||||
|
@ -1919,6 +1938,8 @@ ContextBar::ContextBar(TooltipManager* tooltipManager, ColorBar* colorBar)
|
||||||
addChild(m_brushBack = new BrushBackField);
|
addChild(m_brushBack = new BrushBackField);
|
||||||
addChild(m_brushType = new BrushTypeField(this));
|
addChild(m_brushType = new BrushTypeField(this));
|
||||||
addChild(m_brushSize = new BrushSizeField());
|
addChild(m_brushSize = new BrushSizeField());
|
||||||
|
addChild(m_cornerRadius = new CornerRadiusField());
|
||||||
|
m_cornerRadius->useSlider(false);
|
||||||
addChild(m_brushAngle = new BrushAngleField(m_brushType));
|
addChild(m_brushAngle = new BrushAngleField(m_brushType));
|
||||||
addChild(m_brushPatternField = new BrushPatternField());
|
addChild(m_brushPatternField = new BrushPatternField());
|
||||||
|
|
||||||
|
@ -2074,6 +2095,11 @@ void ContextBar::onBrushSizeChange()
|
||||||
updateForActiveTool();
|
updateForActiveTool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContextBar::onCornerRadiusChange(int value)
|
||||||
|
{
|
||||||
|
m_cornerRadius->setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
void ContextBar::onBrushAngleChange()
|
void ContextBar::onBrushAngleChange()
|
||||||
{
|
{
|
||||||
if (m_activeBrush->type() != kImageBrushType)
|
if (m_activeBrush->type() != kImageBrushType)
|
||||||
|
@ -2164,6 +2190,9 @@ void ContextBar::updateForTool(tools::Tool* tool)
|
||||||
m_freehandAlgoConn = toolPref->freehandAlgorithm.AfterChange.connect(
|
m_freehandAlgoConn = toolPref->freehandAlgorithm.AfterChange.connect(
|
||||||
[this] { onToolSetFreehandAlgorithm(); });
|
[this] { onToolSetFreehandAlgorithm(); });
|
||||||
m_contiguousConn = toolPref->contiguous.AfterChange.connect([this] { onToolSetContiguous(); });
|
m_contiguousConn = toolPref->contiguous.AfterChange.connect([this] { onToolSetContiguous(); });
|
||||||
|
m_cornerRadius->setValue(toolPref->cornerRadius());
|
||||||
|
m_cornerRadiusConn = toolPref->cornerRadius.AfterChange.connect(
|
||||||
|
[this](const int value) { onCornerRadiusChange(value); });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tool)
|
if (tool)
|
||||||
|
@ -2241,6 +2270,9 @@ void ContextBar::updateForTool(tools::Tool* tool)
|
||||||
const bool isFloodfill = tool && (tool->getPointShape(0)->isFloodFill() ||
|
const bool isFloodfill = tool && (tool->getPointShape(0)->isFloodFill() ||
|
||||||
tool->getPointShape(1)->isFloodFill());
|
tool->getPointShape(1)->isFloodFill());
|
||||||
|
|
||||||
|
const bool hasCornerRadius = tool && (tool->getIntertwine(0)->cornerRadiusSupport() ||
|
||||||
|
tool->getIntertwine(1)->cornerRadiusSupport());
|
||||||
|
|
||||||
// True if the current tool needs tolerance options
|
// True if the current tool needs tolerance options
|
||||||
const bool hasTolerance = tool && (tool->getPointShape(0)->isFloodFill() ||
|
const bool hasTolerance = tool && (tool->getPointShape(0)->isFloodFill() ||
|
||||||
tool->getPointShape(1)->isFloodFill());
|
tool->getPointShape(1)->isFloodFill());
|
||||||
|
@ -2276,6 +2308,7 @@ void ContextBar::updateForTool(tools::Tool* tool)
|
||||||
m_brushSize->setVisible(supportOpacity && !isFloodfill && !hasImageBrush);
|
m_brushSize->setVisible(supportOpacity && !isFloodfill && !hasImageBrush);
|
||||||
m_brushAngle->setVisible(supportOpacity && !isFloodfill && !hasImageBrush && hasBrushWithAngle);
|
m_brushAngle->setVisible(supportOpacity && !isFloodfill && !hasImageBrush && hasBrushWithAngle);
|
||||||
m_brushPatternField->setVisible(supportOpacity && hasImageBrush && !withDithering);
|
m_brushPatternField->setVisible(supportOpacity && hasImageBrush && !withDithering);
|
||||||
|
m_cornerRadius->setVisible(hasCornerRadius);
|
||||||
m_inkType->setVisible(hasInk);
|
m_inkType->setVisible(hasInk);
|
||||||
m_inkOpacityLabel->setVisible(showOpacity);
|
m_inkOpacityLabel->setVisible(showOpacity);
|
||||||
m_inkOpacity->setVisible(showOpacity);
|
m_inkOpacity->setVisible(showOpacity);
|
||||||
|
@ -2637,6 +2670,7 @@ void ContextBar::setupTooltips(TooltipManager* tooltipManager)
|
||||||
tooltipManager->addTooltipFor(m_brushType->at(0), Strings::context_bar_brush_type(), BOTTOM);
|
tooltipManager->addTooltipFor(m_brushType->at(0), Strings::context_bar_brush_type(), BOTTOM);
|
||||||
tooltipManager->addTooltipFor(m_brushSize, Strings::context_bar_brush_size(), BOTTOM);
|
tooltipManager->addTooltipFor(m_brushSize, Strings::context_bar_brush_size(), BOTTOM);
|
||||||
tooltipManager->addTooltipFor(m_brushAngle, Strings::context_bar_brush_angle(), BOTTOM);
|
tooltipManager->addTooltipFor(m_brushAngle, Strings::context_bar_brush_angle(), BOTTOM);
|
||||||
|
tooltipManager->addTooltipFor(m_cornerRadius, Strings::context_bar_corner_radius(), BOTTOM);
|
||||||
tooltipManager->addTooltipFor(m_inkType->at(0), Strings::context_bar_ink(), BOTTOM);
|
tooltipManager->addTooltipFor(m_inkType->at(0), Strings::context_bar_ink(), BOTTOM);
|
||||||
tooltipManager->addTooltipFor(m_inkOpacity, Strings::context_bar_opacity(), BOTTOM);
|
tooltipManager->addTooltipFor(m_inkOpacity, Strings::context_bar_opacity(), BOTTOM);
|
||||||
tooltipManager->addTooltipFor(m_inkShades->at(0), Strings::context_bar_shades(), BOTTOM);
|
tooltipManager->addTooltipFor(m_inkShades->at(0), Strings::context_bar_shades(), BOTTOM);
|
||||||
|
|
|
@ -131,6 +131,7 @@ protected:
|
||||||
private:
|
private:
|
||||||
void onBrushSizeChange();
|
void onBrushSizeChange();
|
||||||
void onBrushAngleChange();
|
void onBrushAngleChange();
|
||||||
|
void onCornerRadiusChange(int value);
|
||||||
void onSymmetryModeChange();
|
void onSymmetryModeChange();
|
||||||
void onFgOrBgColorChange(doc::Brush::ImageColor imageColor);
|
void onFgOrBgColorChange(doc::Brush::ImageColor imageColor);
|
||||||
void onOpacityRangeChange();
|
void onOpacityRangeChange();
|
||||||
|
@ -168,6 +169,7 @@ private:
|
||||||
class DynamicsField;
|
class DynamicsField;
|
||||||
class FreehandAlgorithmField;
|
class FreehandAlgorithmField;
|
||||||
class BrushPatternField;
|
class BrushPatternField;
|
||||||
|
class CornerRadiusField;
|
||||||
class EyedropperField;
|
class EyedropperField;
|
||||||
class DropPixelsField;
|
class DropPixelsField;
|
||||||
class AutoSelectLayerField;
|
class AutoSelectLayerField;
|
||||||
|
@ -181,6 +183,7 @@ private:
|
||||||
BrushTypeField* m_brushType;
|
BrushTypeField* m_brushType;
|
||||||
BrushAngleField* m_brushAngle;
|
BrushAngleField* m_brushAngle;
|
||||||
BrushSizeField* m_brushSize;
|
BrushSizeField* m_brushSize;
|
||||||
|
CornerRadiusField* m_cornerRadius;
|
||||||
ui::Label* m_toleranceLabel;
|
ui::Label* m_toleranceLabel;
|
||||||
ToleranceField* m_tolerance;
|
ToleranceField* m_tolerance;
|
||||||
ContiguousField* m_contiguous;
|
ContiguousField* m_contiguous;
|
||||||
|
@ -224,6 +227,7 @@ private:
|
||||||
obs::scoped_connection m_opacityConn;
|
obs::scoped_connection m_opacityConn;
|
||||||
obs::scoped_connection m_freehandAlgoConn;
|
obs::scoped_connection m_freehandAlgoConn;
|
||||||
obs::scoped_connection m_contiguousConn;
|
obs::scoped_connection m_contiguousConn;
|
||||||
|
obs::scoped_connection m_cornerRadiusConn;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
|
|
@ -1834,7 +1834,7 @@ void Editor::updateToolLoopModifiersIndicators(const bool firstFromMouseDown)
|
||||||
// square-aspect/rotation/etc. only when the user presses the
|
// square-aspect/rotation/etc. only when the user presses the
|
||||||
// modifier key again in the ToolLoop (and not before starting
|
// modifier key again in the ToolLoop (and not before starting
|
||||||
// the loop). So Alt+selection will add a selection, but
|
// the loop). So Alt+selection will add a selection, but
|
||||||
// willn't start the square-aspect until we press Alt key
|
// won't start the square-aspect until we press Alt key
|
||||||
// again, or Alt+Shift+selection tool will subtract the
|
// again, or Alt+Shift+selection tool will subtract the
|
||||||
// selection but will not start the rotation until we release
|
// selection but will not start the rotation until we release
|
||||||
// and press the Alt key again.
|
// and press the Alt key again.
|
||||||
|
@ -1847,6 +1847,8 @@ void Editor::updateToolLoopModifiersIndicators(const bool firstFromMouseDown)
|
||||||
modifiers |= int(tools::ToolLoopModifiers::kFromCenter);
|
modifiers |= int(tools::ToolLoopModifiers::kFromCenter);
|
||||||
if (int(action & KeyAction::RotateShape))
|
if (int(action & KeyAction::RotateShape))
|
||||||
modifiers |= int(tools::ToolLoopModifiers::kRotateShape);
|
modifiers |= int(tools::ToolLoopModifiers::kRotateShape);
|
||||||
|
if (int(action & KeyAction::CornerRadius))
|
||||||
|
modifiers |= int(tools::ToolLoopModifiers::kCornerRadius);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,10 @@ const std::vector<KeyShortcutAction>& actions()
|
||||||
I18N_KEY(rotate_shape),
|
I18N_KEY(rotate_shape),
|
||||||
app::KeyAction::RotateShape,
|
app::KeyAction::RotateShape,
|
||||||
app::KeyContext::ShapeTool },
|
app::KeyContext::ShapeTool },
|
||||||
|
{ "CornerRadius",
|
||||||
|
I18N_KEY(corner_radius),
|
||||||
|
app::KeyAction::CornerRadius,
|
||||||
|
app::KeyContext::ShapeTool },
|
||||||
{ "LeftMouseButton",
|
{ "LeftMouseButton",
|
||||||
I18N_KEY(trigger_left_mouse_button),
|
I18N_KEY(trigger_left_mouse_button),
|
||||||
app::KeyAction::LeftMouseButton,
|
app::KeyAction::LeftMouseButton,
|
||||||
|
@ -392,6 +396,7 @@ Key::Key(const KeyAction action, const KeyContext keyContext)
|
||||||
case KeyAction::RotateShape: m_keycontext = KeyContext::ShapeTool; break;
|
case KeyAction::RotateShape: m_keycontext = KeyContext::ShapeTool; break;
|
||||||
case KeyAction::LeftMouseButton:
|
case KeyAction::LeftMouseButton:
|
||||||
case KeyAction::RightMouseButton: m_keycontext = KeyContext::Any; break;
|
case KeyAction::RightMouseButton: m_keycontext = KeyContext::Any; break;
|
||||||
|
case KeyAction::CornerRadius: m_keycontext = KeyContext::ShapeTool; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ enum class KeyAction {
|
||||||
AngleSnapFromLastPoint = 0x00010000,
|
AngleSnapFromLastPoint = 0x00010000,
|
||||||
RotateShape = 0x00020000,
|
RotateShape = 0x00020000,
|
||||||
FineControl = 0x00040000,
|
FineControl = 0x00040000,
|
||||||
|
CornerRadius = 0x00080000,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class WheelAction {
|
enum class WheelAction {
|
||||||
|
|
164
src/doc/algo.cpp
164
src/doc/algo.cpp
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite Document Library
|
// Aseprite Document Library
|
||||||
// Copyright (c) 2018-2022 Igara Studio S.A.
|
// Copyright (c) 2018-2025 Igara Studio S.A.
|
||||||
// Copyright (c) 2001-2018 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.
|
||||||
|
@ -12,6 +12,7 @@
|
||||||
#include "doc/algo.h"
|
#include "doc/algo.h"
|
||||||
|
|
||||||
#include "base/debug.h"
|
#include "base/debug.h"
|
||||||
|
#include "base/pi.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
@ -180,6 +181,167 @@ void algo_line_continuous_with_fix_for_line_brush(int x0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Circle code based on Alois Zingl work released under the MIT
|
||||||
|
// license http://members.chello.at/easyfilter/bresenham.html
|
||||||
|
//
|
||||||
|
// Adapted for Aseprite by Igara Studio S.A.
|
||||||
|
//
|
||||||
|
// Draws a circle of the specified radius divided in 4 slices, adjusting each
|
||||||
|
// slice inside the specified rectangle.
|
||||||
|
// |--r --|
|
||||||
|
//
|
||||||
|
// x1,y1 --> * OOO OOO
|
||||||
|
// O O
|
||||||
|
// O O
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// T O O
|
||||||
|
// r | O O
|
||||||
|
// _ OOO OOO * <-- x2,y2
|
||||||
|
//
|
||||||
|
// If the rectangle is smaller than the circle, it doesn't make any clipping.
|
||||||
|
void algo_sliced_circle(int x1, int y1, int x2, int y2, int r, void* data, AlgoPixel proc)
|
||||||
|
{
|
||||||
|
int x = -r, y = 0, err = 2 - 2 * r; /* II. Quadrant */
|
||||||
|
const int r0 = r;
|
||||||
|
do {
|
||||||
|
proc(x2 - r0 - x, y2 - r0 + y, data); /* I. Quadrant */
|
||||||
|
proc(x1 + r0 - y, y2 - r0 - x, data); /* II. Quadrant */
|
||||||
|
proc(x1 + r0 + x, y1 + r0 - y, data); /* III. Quadrant */
|
||||||
|
proc(x2 - r0 + y, y1 + r0 + x, data); /* IV. Quadrant */
|
||||||
|
r = err;
|
||||||
|
if (r <= y)
|
||||||
|
err += ++y * 2 + 1; /* e_xy+e_y < 0 */
|
||||||
|
if (r > x || err > y)
|
||||||
|
err += ++x * 2 + 1; /* e_xy+e_x > 0 or no 2nd y-step */
|
||||||
|
} while (x < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as algo_sliced_circle but with the parts filled.
|
||||||
|
void algo_sliced_circlefill(int x1, int y1, int x2, int y2, int r, void* data, AlgoHLine proc)
|
||||||
|
{
|
||||||
|
int x = -r, y = 0, err = 2 - 2 * r; /* II. Quadrant */
|
||||||
|
const int r0 = r;
|
||||||
|
do {
|
||||||
|
proc(x2 - r0, y2 - r0 + y, x2 - r0 - x, data); /* I. Quadrant */
|
||||||
|
proc(x1 + r0 - y, y2 - r0 - x, x1 + r0, data); /* II. Quadrant */
|
||||||
|
proc(x1 + r0 + x, y1 + r0 - y, x1 + r0, data); /* III. Quadrant */
|
||||||
|
proc(x2 - r0, y1 + r0 + x, x2 - r0 + y, data); /* IV. Quadrant */
|
||||||
|
r = err;
|
||||||
|
if (r <= y)
|
||||||
|
err += ++y * 2 + 1; /* e_xy+e_y < 0 */
|
||||||
|
if (r > x || err > y)
|
||||||
|
err += ++x * 2 + 1; /* e_xy+e_x > 0 or no 2nd y-step */
|
||||||
|
} while (x < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void algo_arc(int xm, int ym, double sa, double ea, int r, void* data, AlgoPixel proc)
|
||||||
|
{
|
||||||
|
int sx = std::cos(sa) * r;
|
||||||
|
int ex = std::cos(ea) * r;
|
||||||
|
|
||||||
|
int startQuadrant;
|
||||||
|
if (sa <= 0 && sa > -PI / 2) {
|
||||||
|
startQuadrant = 4;
|
||||||
|
}
|
||||||
|
else if (sa <= -PI / 2 && sa >= -PI) {
|
||||||
|
startQuadrant = 3;
|
||||||
|
}
|
||||||
|
else if (sa > 0 && sa < PI / 2) {
|
||||||
|
startQuadrant = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
startQuadrant = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int endQuadrant;
|
||||||
|
if (ea <= 0 && ea > -PI / 2) {
|
||||||
|
endQuadrant = 4;
|
||||||
|
}
|
||||||
|
else if (ea <= -PI / 2 && ea >= -PI) {
|
||||||
|
endQuadrant = 3;
|
||||||
|
}
|
||||||
|
else if (ea > 0 && ea < PI / 2) {
|
||||||
|
endQuadrant = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
endQuadrant = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If start angle and end angle falls in the same quadrant we have to determine
|
||||||
|
// if we have to include the other quadrants or not since the arc is determined
|
||||||
|
// from start angle to end angle in clockwise direction.
|
||||||
|
bool includeQuadrant[4] = { false, false, false, false };
|
||||||
|
if (startQuadrant == endQuadrant) {
|
||||||
|
// If start angle is greater than end angle, include all quadrants for drawing
|
||||||
|
if (sa > ea) {
|
||||||
|
includeQuadrant[0] = true;
|
||||||
|
includeQuadrant[1] = true;
|
||||||
|
includeQuadrant[2] = true;
|
||||||
|
includeQuadrant[3] = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// start angle is less to or equal to end angle then only include one quadrant
|
||||||
|
// for drawing.
|
||||||
|
includeQuadrant[startQuadrant - 1] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (int i = startQuadrant - 1; i < startQuadrant - 1 + 4; ++i) {
|
||||||
|
int q = i % 4;
|
||||||
|
includeQuadrant[q] = true;
|
||||||
|
if (q == endQuadrant - 1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int x = -r, y = 0, err = 2 - 2 * r; /* II. Quadrant */
|
||||||
|
do {
|
||||||
|
if (includeQuadrant[0]) {
|
||||||
|
if ((startQuadrant != 1 && endQuadrant != 1) ||
|
||||||
|
(startQuadrant == 1 && endQuadrant != 1 && -x <= sx) ||
|
||||||
|
(startQuadrant != 1 && endQuadrant == 1 && -x >= ex) ||
|
||||||
|
(startQuadrant == 1 && endQuadrant == 1 &&
|
||||||
|
((sa <= ea && -x <= sx && -x >= ex) || (sa > ea && (-x <= sx || -x >= ex)))))
|
||||||
|
proc(xm - x, ym + y, data); /* I. Quadrant */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeQuadrant[1]) {
|
||||||
|
if ((startQuadrant != 2 && endQuadrant != 2) ||
|
||||||
|
(startQuadrant == 2 && endQuadrant != 2 && -y <= sx) ||
|
||||||
|
(startQuadrant != 2 && endQuadrant == 2 && -y >= ex) ||
|
||||||
|
(startQuadrant == 2 && endQuadrant == 2 &&
|
||||||
|
((sa <= ea && -y <= sx && -y >= ex) || (sa > ea && (-y <= sx || -y >= ex)))))
|
||||||
|
proc(xm - y, ym - x, data); /* II. Quadrant */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeQuadrant[2]) {
|
||||||
|
if ((startQuadrant != 3 && endQuadrant != 3) ||
|
||||||
|
(startQuadrant == 3 && endQuadrant != 3 && x >= sx) ||
|
||||||
|
(startQuadrant != 3 && endQuadrant == 3 && x <= ex) ||
|
||||||
|
(startQuadrant == 3 && endQuadrant == 3 &&
|
||||||
|
((sa <= ea && -x <= -sx && -x >= -ex) || (sa > ea && (-x <= -sx || -x >= -ex)))))
|
||||||
|
proc(xm + x, ym - y, data); /* III. Quadrant */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeQuadrant[3]) {
|
||||||
|
if ((startQuadrant != 4 && endQuadrant != 4) ||
|
||||||
|
(startQuadrant == 4 && endQuadrant != 4 && y >= sx) ||
|
||||||
|
(startQuadrant != 4 && endQuadrant == 4 && y <= ex) ||
|
||||||
|
(startQuadrant == 4 && endQuadrant == 4 &&
|
||||||
|
((sa <= ea && y >= sx && y <= ex) || (sa > ea && (y >= sx || y <= ex)))))
|
||||||
|
proc(xm + y, ym + x, data); /* IV. Quadrant */
|
||||||
|
}
|
||||||
|
|
||||||
|
r = err;
|
||||||
|
if (r <= y)
|
||||||
|
err += ++y * 2 + 1; /* e_xy+e_y < 0 */
|
||||||
|
if (r > x || err > y)
|
||||||
|
err += ++x * 2 + 1; /* e_xy+e_x > 0 or no 2nd y-step */
|
||||||
|
} while (x < 0);
|
||||||
|
}
|
||||||
|
|
||||||
static int adjust_ellipse_args(int& x0, int& y0, int& x1, int& y1, int& hPixels, int& vPixels)
|
static int adjust_ellipse_args(int& x0, int& y0, int& x1, int& y1, int& hPixels, int& vPixels)
|
||||||
{
|
{
|
||||||
// hPixels : straight horizontal pixels added to mid region of the ellipse.
|
// hPixels : straight horizontal pixels added to mid region of the ellipse.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite Document Library
|
// Aseprite Document Library
|
||||||
// Copyright (C) 2018-2021 Igara Studio S.A.
|
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||||
// Copyright (c) 2001-2018 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.
|
||||||
|
@ -48,6 +48,12 @@ void algo_line_continuous_with_fix_for_line_brush(int x1,
|
||||||
void* data,
|
void* data,
|
||||||
AlgoPixel proc);
|
AlgoPixel proc);
|
||||||
|
|
||||||
|
void algo_sliced_circle(int x1, int y1, int x2, int y2, int r, void* data, AlgoPixel proc);
|
||||||
|
|
||||||
|
void algo_sliced_circlefill(int x1, int y1, int x2, int y2, int r, void* data, AlgoHLine proc);
|
||||||
|
|
||||||
|
void algo_arc(int xm, int ym, double sa, double ea, int r, void* data, AlgoPixel proc);
|
||||||
|
|
||||||
void algo_ellipse(int x1,
|
void algo_ellipse(int x1,
|
||||||
int y1,
|
int y1,
|
||||||
int x2,
|
int x2,
|
||||||
|
|
|
@ -175,43 +175,45 @@ void IntEntry::openPopup()
|
||||||
{
|
{
|
||||||
m_slider->setValue(getValue());
|
m_slider->setValue(getValue());
|
||||||
|
|
||||||
// We weren't able to reproduce it, but there are crash reports
|
if (m_useSlider) {
|
||||||
// where this openPopup() function is called and the popup is still
|
// We weren't able to reproduce it, but there are crash reports
|
||||||
// alive, with the slider inside (we have to remove it before
|
// where this openPopup() function is called and the popup is still
|
||||||
// resetting m_popupWindow pointer to avoid deleting the slider
|
// alive, with the slider inside (we have to remove it before
|
||||||
// pointer).
|
// resetting m_popupWindow pointer to avoid deleting the slider
|
||||||
removeSlider();
|
// pointer).
|
||||||
|
removeSlider();
|
||||||
|
|
||||||
m_popupWindow = std::make_unique<TransparentPopupWindow>(
|
m_popupWindow = std::make_unique<TransparentPopupWindow>(
|
||||||
PopupWindow::ClickBehavior::CloseOnClickInOtherWindow);
|
PopupWindow::ClickBehavior::CloseOnClickInOtherWindow);
|
||||||
m_popupWindow->setAutoRemap(false);
|
m_popupWindow->setAutoRemap(false);
|
||||||
m_popupWindow->addChild(m_slider.get());
|
m_popupWindow->addChild(m_slider.get());
|
||||||
m_popupWindow->Close.connect(&IntEntry::onPopupClose, this);
|
m_popupWindow->Close.connect(&IntEntry::onPopupClose, this);
|
||||||
|
|
||||||
fit_bounds(display(),
|
fit_bounds(display(),
|
||||||
m_popupWindow.get(),
|
m_popupWindow.get(),
|
||||||
gfx::Rect(0, 0, 128 * guiscale(), m_popupWindow->sizeHint().h),
|
gfx::Rect(0, 0, 128 * guiscale(), m_popupWindow->sizeHint().h),
|
||||||
[this](const gfx::Rect& workarea,
|
[this](const gfx::Rect& workarea,
|
||||||
gfx::Rect& rc,
|
gfx::Rect& rc,
|
||||||
std::function<gfx::Rect(Widget*)> getWidgetBounds) {
|
std::function<gfx::Rect(Widget*)> getWidgetBounds) {
|
||||||
Rect entryBounds = getWidgetBounds(this);
|
Rect entryBounds = getWidgetBounds(this);
|
||||||
|
|
||||||
rc.x = entryBounds.x;
|
rc.x = entryBounds.x;
|
||||||
rc.y = entryBounds.y2();
|
rc.y = entryBounds.y2();
|
||||||
|
|
||||||
if (rc.x2() > workarea.x2())
|
if (rc.x2() > workarea.x2())
|
||||||
rc.x = rc.x - rc.w + entryBounds.w;
|
rc.x = rc.x - rc.w + entryBounds.w;
|
||||||
|
|
||||||
if (rc.y2() > workarea.y2())
|
if (rc.y2() > workarea.y2())
|
||||||
rc.y = entryBounds.y - entryBounds.h;
|
rc.y = entryBounds.y - entryBounds.h;
|
||||||
|
|
||||||
m_popupWindow->setBounds(rc);
|
m_popupWindow->setBounds(rc);
|
||||||
});
|
});
|
||||||
|
|
||||||
Region rgn(m_popupWindow->boundsOnScreen().createUnion(boundsOnScreen()));
|
Region rgn(m_popupWindow->boundsOnScreen().createUnion(boundsOnScreen()));
|
||||||
m_popupWindow->setHotRegion(rgn);
|
m_popupWindow->setHotRegion(rgn);
|
||||||
|
|
||||||
m_popupWindow->openWindow();
|
m_popupWindow->openWindow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntEntry::closePopup()
|
void IntEntry::closePopup()
|
||||||
|
|
|
@ -27,6 +27,10 @@ public:
|
||||||
virtual int getValue() const;
|
virtual int getValue() const;
|
||||||
virtual void setValue(int value);
|
virtual void setValue(int value);
|
||||||
|
|
||||||
|
// If useSlider is false, then it won't show the slider popup to change its
|
||||||
|
// value.
|
||||||
|
void useSlider(bool useSlider) { m_useSlider = useSlider; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool onProcessMessage(Message* msg) override;
|
bool onProcessMessage(Message* msg) override;
|
||||||
void onInitTheme(InitThemeEvent& ev) override;
|
void onInitTheme(InitThemeEvent& ev) override;
|
||||||
|
@ -42,6 +46,8 @@ protected:
|
||||||
int m_max;
|
int m_max;
|
||||||
std::unique_ptr<PopupWindow> m_popupWindow;
|
std::unique_ptr<PopupWindow> m_popupWindow;
|
||||||
bool m_changeFromSlider;
|
bool m_changeFromSlider;
|
||||||
|
// If true a slider can be used to modify the value.
|
||||||
|
bool m_useSlider = true;
|
||||||
std::unique_ptr<Slider> m_slider;
|
std::unique_ptr<Slider> m_slider;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
Loading…
Reference in New Issue