Improve aspect of ellipses for 16x16 or bigger sizes (fix #2217)

Now algo_ellipse() function supports additional parameters to create
rounded squares in the future.
This commit is contained in:
Gaspar Capello 2021-06-11 14:22:19 -03:00 committed by David Capello
parent 65fabf3a68
commit 1eace24891
7 changed files with 131 additions and 47 deletions

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -313,7 +313,7 @@ public:
const double angle = loop->getController()->getShapeAngle();
if (ABS(angle) < 0.001) {
algo_ellipse(x1, y1, x2, y2, loop, (AlgoPixel)doPointshapePoint);
algo_ellipse(x1, y1, x2, y2, 0, 0, loop, (AlgoPixel)doPointshapePoint);
}
else {
draw_rotated_ellipse((x1+x2)/2, (y1+y2)/2,
@ -343,7 +343,7 @@ public:
const double angle = loop->getController()->getShapeAngle();
if (ABS(angle) < 0.001) {
algo_ellipsefill(x1, y1, x2, y2, loop, (AlgoHLine)doPointshapeHline);
algo_ellipsefill(x1, y1, x2, y2, 0, 0, loop, (AlgoHLine)doPointshapeHline);
}
else {
fill_rotated_ellipse((x1+x2)/2, (y1+y2)/2,

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2018-2020 Igara Studio S.A.
// Copyright (c) 2018-2021 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -171,64 +171,147 @@ void algo_line_continuous_with_fix_for_line_brush(int x0, int y0, int x1, int y1
}
}
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 = std::max(hPixels, 0);
// vPixels : straight vertical pixels added to mid region of the ellipse.
vPixels = std::max(vPixels, 0);
// Conditioning swapped points
if (x0 > x1)
std::swap(x0, x1);
if (y0 > y1)
std::swap(y0, y1);
int w = x1 - x0 + 1;
int h = y1 - y0 + 1;
// hDiameter is the horizontal diameter of a circunference
// without the addition of straight pixels.
int hDiameter = w - hPixels;
// vDiameter is the vertical diameter of a circunference
// without the addition of straight pixels.
int vDiameter = h - vPixels;
// Manual adjustment
if (w == 8 || w == 12 || w == 22)
hPixels++;
if (h == 8 || h == 12 || h == 22)
vPixels++;
hPixels = (hDiameter > 5 ? hPixels : 0);
vPixels = (vDiameter > 5 ? vPixels : 0);
if ((hDiameter % 2 == 0) && (hDiameter > 5))
hPixels--;
if ((vDiameter % 2 == 0) && (vDiameter > 5))
vPixels--;
x1 -= hPixels;
y1 -= vPixels;
return h;
}
// 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
void algo_ellipse(int x0, int y0, int x1, int y1, void* data, AlgoPixel proc)
void algo_ellipse(int x0, int y0, int x1, int y1,
int hPixels, int vPixels,
void* data, AlgoPixel proc)
{
long a = abs(x1-x0), b = abs(y1-y0), b1 = b&1; // diameter
double dx = 4*(1.0-a)*b*b, dy = 4*(b1+1)*a*a; // error increment
double err = dx+dy+b1*a*a, e2; // error of 1.step
int h = adjust_ellipse_args(x0, y0, x1, y1, hPixels, vPixels);
if (x0 > x1) { x0 = x1; x1 += a; } // if called with swapped points
if (y0 > y1) y0 = y1; // .. exchange them
y0 += (b+1)/2; y1 = y0-b1; // starting pixel
a = 8*a*a; b1 = 8*b*b;
long a = abs(x1-x0);
long b = abs(y1-y0); // diameter
long b1 = b&1;
double dx = 4*(1.0-a)*b*b; // error increment
double dy = 4*(b1+1)*a*a; // error increment
double err = dx + dy + b1*a*a; // error of 1.step
double e2;
y0 += (b+1)/2;
y1 = y0-b1; // starting pixel
a = 8*a*a;
b1 = 8*b*b;
int initialY0 = y0;
int initialY1 = y1;
int initialX0 = x0;
int initialX1 = x1 + hPixels;
do {
proc(x1, y0, data); // I. Quadrant
proc(x0, y0, data); // II. Quadrant
proc(x0, y1, data); // III. Quadrant
proc(x1, y1, data); // IV. Quadrant
proc(x1 + hPixels, y0 + vPixels, data); // I. Quadrant
proc(x0, y0 + vPixels, data); // II. Quadrant
proc(x0, y1, data); // III. Quadrant
proc(x1 + hPixels, y1, data); // IV. Quadrant
e2 = 2*err;
if (e2 <= dy) { y0++; y1--; err += dy += a; } // y step
if (e2 >= dx || 2*err > dy) { x0++; x1--; err += dx += b1; } // x step
} while (x0 <= x1);
while (y0-y1 <= b) { // too early stop of flat ellipses a=1
proc(x0-1, y0, data); // -> finish tip of ellipse
proc(x1+1, y0++, data);
proc(x0-1, y1, data);
proc(x1+1, y1--, data);
while (y0 + vPixels - y1 + 1 <= h) { // too early stop of flat ellipses a=1
proc(x0 - 1, y0 + vPixels, data); // -> finish tip of ellipse
proc(x1 + 1 + hPixels, y0++ + vPixels, data);
proc(x0 - 1, y1, data);
proc(x1 + 1 + hPixels, y1--, data);
}
// Extra horizontal straight pixels
if (hPixels > 0) {
for (int i = x0; i < x1 + hPixels + 1; i++) {
proc(i, y1 + 1, data);
proc(i, y0 + vPixels - 1, data);
}
}
// Extra vertical straight pixels
if (vPixels > 0) {
for (int i = initialY1 + 1; i < initialY0 + vPixels; i++) {
proc(initialX0, i, data);
proc(initialX1, i, data);
}
}
}
void algo_ellipsefill(int x0, int y0, int x1, int y1, void* data, AlgoHLine proc)
void algo_ellipsefill(int x0, int y0, int x1, int y1,
int hPixels, int vPixels,
void* data, AlgoHLine proc)
{
long a = abs(x1-x0), b = abs(y1-y0), b1 = b&1; // diameter
int h = adjust_ellipse_args(x0, y0, x1, y1, hPixels, vPixels);
long a = abs(x1-x0), b = abs(y1-y0), b1 = b&1; // diameter
double dx = 4*(1.0-a)*b*b, dy = 4*(b1+1)*a*a; // error increment
double err = dx+dy+b1*a*a, e2; // error of 1.step
if (x0 > x1) { x0 = x1; x1 += a; } // if called with swapped points
if (y0 > y1) y0 = y1; // .. exchange them
y0 += (b+1)/2; y1 = y0-b1; // starting pixel
y0 += (b+1)/2; y1 = y0-b1; // starting pixel
a = 8*a*a; b1 = 8*b*b;
int initialY0 = y0;
int initialY1 = y1;
int initialX0 = x0;
int initialX1 = x1 + hPixels;
do {
proc(x0, y0, x1, data);
proc(x0, y1, x1, data);
proc(x0, y0 + vPixels, x1 + hPixels, data);
proc(x0, y1, x1 + hPixels, data);
e2 = 2*err;
if (e2 <= dy) { y0++; y1--; err += dy += a; } // y step
if (e2 >= dx || 2*err > dy) { x0++; x1--; err += dx += b1; } // x step
} while (x0 <= x1);
while (y0-y1 <= b) { // too early stop of flat ellipses a=1
proc(x0-1, y0, x0-1, data); // -> finish tip of ellipse
proc(x1+1, y0++, x1+1, data);
proc(x0-1, y1, x0-1, data);
proc(x1+1, y1--, x1+1, data);
while (y0 + vPixels - y1 + 1 < h) { // too early stop of flat ellipses a=1
proc(x0-1, ++y0 + vPixels, x0-1, data); // -> finish tip of ellipse
proc(x1+1 + hPixels, y0 + vPixels, x1+1 + hPixels, data);
proc(x0-1, --y1, x0-1, data);
proc(x1+1 + hPixels, y1, x1+1 + hPixels, data);
}
if (vPixels > 0) {
for (int i = initialY1 + 1; i < initialY0 + vPixels; i++)
proc(initialX0, i, initialX1, data);
}
}
@ -324,7 +407,7 @@ static void draw_rotated_ellipse_rect(int x0, int y0, int x1, int y1, double zd,
double w = xd*yd;
if (zd == 0)
return algo_ellipse(x0, y0, x1, y1, data, proc);
return algo_ellipse(x0, y0, x1, y1, 0, 0, data, proc);
if (w != 0.0)
w = (w-zd) / (w+w); // squared weight of P1

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -38,8 +38,8 @@ namespace doc {
void algo_line_continuous(int x1, int y1, int x2, int y2, void *data, AlgoPixel proc);
void algo_line_continuous_with_fix_for_line_brush(int x1, int y1, int x2, int y2, void *data, AlgoPixel proc);
void algo_ellipse(int x1, int y1, int x2, int y2, void *data, AlgoPixel proc);
void algo_ellipsefill(int x1, int y1, int x2, int y2, void *data, AlgoHLine proc);
void algo_ellipse(int x1, int y1, int x2, int y2, int hPixels, int vPixels, void *data, AlgoPixel proc);
void algo_ellipsefill(int x1, int y1, int x2, int y2, int hPixels, int vPixels, 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);

View File

@ -1,4 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2021 Igara Studio S.A.
// Copyright (c) 2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -41,7 +42,7 @@ void modify_selection(const SelectionModifier modifier,
std::unique_ptr<doc::Image> kernel(doc::Image::create(IMAGE_BITMAP, size, size));
doc::clear_image(kernel.get(), 0);
if (brush == doc::kCircleBrushType)
doc::fill_ellipse(kernel.get(), 0, 0, size-1, size-1, 1);
doc::fill_ellipse(kernel.get(), 0, 0, size-1, size-1, 0, 0, 1);
else
doc::fill_rect(kernel.get(), 0, 0, size-1, size-1, 1);
doc::put_pixel(kernel.get(), radius, radius, 0);

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -328,7 +328,7 @@ void Brush::regenerate()
switch (m_type) {
case kCircleBrushType:
fill_ellipse(m_image.get(), 0, 0, size-1, size-1, BitmapTraits::max_value);
fill_ellipse(m_image.get(), 0, 0, size-1, size-1, 0, 0, BitmapTraits::max_value);
break;
case kSquareBrushType:

View File

@ -282,16 +282,16 @@ void draw_line(Image* image, int x1, int y1, int x2, int y2, color_t color)
algo_line_continuous(x1, y1, x2, y2, &data, (AlgoPixel)pixel_for_image);
}
void draw_ellipse(Image* image, int x1, int y1, int x2, int y2, color_t color)
void draw_ellipse(Image* image, int x1, int y1, int x2, int y2, int extraXPxs, int extraYPxs, color_t color)
{
Data data = { image, color };
algo_ellipse(x1, y1, x2, y2, &data, (AlgoPixel)pixel_for_image);
algo_ellipse(x1, y1, x2, y2, extraXPxs, extraYPxs, &data, (AlgoPixel)pixel_for_image);
}
void fill_ellipse(Image* image, int x1, int y1, int x2, int y2, color_t color)
void fill_ellipse(Image* image, int x1, int y1, int x2, int y2, int extraXPxs, int extraYPxs, color_t color)
{
Data data = { image, color };
algo_ellipsefill(x1, y1, x2, y2, &data, (AlgoHLine)hline_for_image);
algo_ellipsefill(x1, y1, x2, y2, extraXPxs, extraYPxs, &data, (AlgoHLine)hline_for_image);
}
namespace {

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2018-2019 Igara Studio S.A.
// Copyright (c) 2018-2021 Igara Studio S.A.
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -38,8 +38,8 @@ namespace doc {
void fill_rect(Image* image, const gfx::Rect& rc, color_t c);
void blend_rect(Image* image, int x1, int y1, int x2, int y2, color_t c, int opacity);
void draw_line(Image* image, int x1, int y1, int x2, int y2, color_t c);
void draw_ellipse(Image* image, int x1, int y1, int x2, int y2, color_t c);
void fill_ellipse(Image* image, int x1, int y1, int x2, int y2, color_t c);
void draw_ellipse(Image* image, int x1, int y1, int x2, int y2, int extraXPxs, int extraYPxs, color_t color);
void fill_ellipse(Image* image, int x1, int y1, int x2, int y2, int extraXPxs, int extraYPxs, color_t color);
bool is_plain_image(const Image* img, color_t c);
bool is_empty_image(const Image* img);