Fix polygon rasterization

This fix is intended to create a polygon that matches with its preview
in one drawing step (needed when we want to draw with Contour or
Polygon tool with custom brushes with alpha content). Before this fix,
the polygon was being created in a first step, and then a second step
that patches the contour (over writing the Image with an extra
joinStroke execution of the entire contour).

- Added createUnion function in polygon.cpp to force drawing of the
  input points in each scan line render.
- Added algo_line_continuous inside polygon function to interpolate
  holes between input vertices.
- Deleted extra joinStroke execution in fillStroke function in class
  IntertwineAsLines and  class IntertwineAsPixelPerfect inside
  intertwines.h
- Added Stroke::erase function to Stroke class. It is needed inside
  IntertwineAsPixelPerfect class to get a clear m_pts (without the
  extra points due to mini L shapes, due to pixel perfect process). In
  fillStroke function in class  IntertwineAsPixelPerfect inside
  intertwiners.h, when it  executed, m_pts is delivered to polygon
  function instead of stroke argument in order to pass a pixel perfect
  processed vector instead of stroke vector which is a raw vector of
  vertices.
This commit is contained in:
Gaspar Capello 2019-05-31 12:08:46 -03:00 committed by David Capello
parent 1ac3148f72
commit 8c55d34e32
4 changed files with 175 additions and 119 deletions

View File

@ -150,17 +150,6 @@ public:
joinStroke(loop, stroke);
return;
}
// Don't draw the contour to avoid double drawing the filled
// polygon and the contour when we use a custom brush and we use
// the alpha compositing ink with opacity < 255 or the custom
// brush has semi-transparent pixels.
if (loop->getBrush()->type() != BrushType::kImageBrushType) {
// TODO if we fix the doc::algorithm::polygon to draw the exact
// scanlines, we can finally remove this joinStroke()
joinStroke(loop, stroke);
}
// Fill content
doc::algorithm::polygon(stroke.size(), (const int*)&stroke[0], loop, (AlgoHLine)doPointshapeHline);
}
@ -470,7 +459,7 @@ public:
&& (m_pts[c+1].x == m_pts[c].x || m_pts[c+1].y == m_pts[c].y)
&& m_pts[c-1].x != m_pts[c+1].x
&& m_pts[c-1].y != m_pts[c+1].y) {
++c;
m_pts.erase(c);
}
// We must ignore to print the first point of the line after
@ -488,19 +477,8 @@ public:
joinStroke(loop, stroke);
return;
}
// Don't draw the contour to avoid double drawing the filled
// polygon and the contour when we use a custom brush and we use
// the alpha compositing ink with opacity < 255 or the custom
// brush has semi-transparent pixels.
if (loop->getBrush()->type() != BrushType::kImageBrushType) {
// TODO if we fix the doc::algorithm::polygon to draw the exact
// scanlines, we can finally remove this joinStroke()
joinStroke(loop, stroke);
}
// Fill content
doc::algorithm::polygon(stroke.size(), (const int*)&stroke[0], loop, (AlgoHLine)doPointshapeHline);
doc::algorithm::polygon(m_pts.size(), (const int*)&m_pts[0], loop, (AlgoHLine)doPointshapeHline);
}
};

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
@ -34,6 +35,13 @@ void Stroke::offset(const gfx::Point& delta)
p += delta;
}
void Stroke::erase(int index)
{
ASSERT(0 <= index && index < m_points.size());
if (0 <= index && index < m_points.size())
m_points.erase(m_points.begin()+index);
}
gfx::Rect Stroke::bounds() const
{
if (m_points.empty())

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
@ -52,6 +53,9 @@ namespace app {
// Displaces all X,Y coordinates the given delta.
void offset(const gfx::Point& delta);
// Erase the point "index".
void erase(int index);
// Returns the bounds of the stroke (minimum/maximum position).
gfx::Rect bounds() const;

View File

@ -1,15 +1,15 @@
// Aseprite Document Library
// Copyright (c) 2019 Igara Studio S.A.
// Copyright (c) 2001-2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
//
// TODO rewrite this algorithm from scratch
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "doc/algo.h"
#include "doc/algorithm/polygon.h"
#include "gfx/point.h"
@ -18,126 +18,192 @@
namespace doc {
using namespace gfx;
static void addPointsWithoutDuplicatingLastOne(int x, int y, std::vector<gfx::Point>* pts)
{
const gfx::Point newPoint(x, y);
if (pts->empty() ||
*(pts->end()-1) != newPoint) {
pts->push_back(newPoint);
}
}
// polygon() is an adaptation from Matthieu Haller code of GD library
// THANKS to Kirsten Schulz for the polygon fixes!
/* The intersection finding technique of this code could be improved */
/* by remembering the previous intertersection, and by using the slope. */
/* That could help to adjust intersections to produce a nice */
/* interior_extrema. */
// createUnion() joins a single scan point "x" (a pixel in other words)
// to the input vector "pairs".
// Each pair of elements from "pairs" vector is a representation
// of a scan segment.
// An added scan point "x" to the "pairs" vector is represented
// by two consecutive values of "x".
// Note: "pairs" must be sorted prior execution of this function.
static void createUnion(std::vector<int>& pairs,
const int x,
int& ints)
{
bool unionDone = false;
for (int i=0; i < ints - (ints%2); i+=2) {
// Case:
// pairs[i] pairs[i+1]
// O --------- O
// -x-
if (x < pairs[i]) {
pairs.insert(pairs.begin()+i, x);
pairs.insert(pairs.begin()+i, x);
ints+=2;
unionDone = true;
break;
}
// Case:
// pairs[i] pairs[i+1]
// O --------- O
// -x-
else if (x == pairs[i] - 1) {
pairs[i] = x;
unionDone = true;
break;
}
// Case:
// pairs[i] pairs[i+1]
// O --------- O
// -x-
else if (x == pairs[i+1] + 1) {
unionDone = true;
if (i + 2 <= ints - 2) {
if (pairs[i+2] == x) {
// Simplification:
pairs.erase(pairs.begin() + (i+1));
pairs.erase(pairs.begin() + (i+1));
ints-=2;
break;
}
}
pairs[i+1] = x;
break;
}
// Case:
// pairs[i] pairs[i+1]
// O --------- O
// -x-
else if (x >= pairs[i] && x <= pairs[i+1]) {
unionDone = true;
break;
}
}
// Case:
// pairs[i] pairs[i+1]
// O --------- O
// -x-
if (x > pairs[ints-1] && !unionDone) {
if (pairs.size() == ints) {
pairs.push_back(x);
pairs.push_back(x);
}
else {
pairs.insert(pairs.begin() + ints, x);
pairs.insert(pairs.begin() + ints, x);
}
ints+=2;
}
}
void algorithm::polygon(int vertices, const int* points, void* data, AlgoHLine proc)
{
int n = vertices;
if (!n)
if (!vertices)
return;
int i;
int j;
int index;
// We load locally the points to "verts" and remove the duplicate points
// to manage easily the input vector, by the way, we find
// "ymin" and "ymax" to use them later like vertical scan limits
// on the scan line loop.
int ymin = *(points+1);
int ymax = *(points+1);
std::vector<gfx::Point> verts(1);
verts[0].x = *points;
verts[0].y = *(points+1);
int verticesAux = vertices;
for (int i=2; i < vertices*2; i+=2) {
int last = verts.size() - 1;
if (verts[last].x == *(points+i) && verts[last].y == *(points + (i+1))) {
verticesAux--;
continue;
}
verts.push_back(gfx::Point(*(points + i), *(points + (i+1))));
ASSERT(last + 1 == verts.size() - 1);
if (verts[last+1].y < ymin)
ymin = verts[last+1].y;
if (verts[last+1].y > ymax)
ymax = verts[last+1].y;
}
vertices = verticesAux;
std::vector<gfx::Point> pts;
for (int c=0; c < verts.size(); ++c) {
if (c == verts.size() - 1) {
algo_line_continuous(verts[verts.size()-1].x,
verts[verts.size()-1].y,
verts[0].x,
verts[0].y,
(void*)&pts,
(AlgoPixel)&addPointsWithoutDuplicatingLastOne);
pts.pop_back();
}
else {
algo_line_continuous(verts[c].x,
verts[c].y,
verts[c+1].x,
verts[c+1].y,
(void*)&pts,
(AlgoPixel)&addPointsWithoutDuplicatingLastOne);
}
}
// Scan Line Loop:
int y;
int miny, maxy;
int x1, y1;
int x2, y2;
int ind1, ind2;
int ints;
std::vector<int> polyInts(n);
std::vector<Point> p(n);
for (i = 0; (i < n); i++) {
p[i].x = points[i*2];
p[i].y = points[i*2+1];
}
miny = p[0].y;
maxy = p[0].y;
for (i = 1; (i < n); i++) {
if (p[i].y < miny) miny = p[i].y;
if (p[i].y > maxy) maxy = p[i].y;
}
/* 2.0.16: Optimization by Ilia Chipitsine -- don't waste time offscreen */
/* 2.0.26: clipping rectangle is even better */
// if (miny < im->cy1) {
// miny = im->cy1;
// }
// if (maxy > im->cy2) {
// maxy = im->cy2;
// }
/* Fix in 1.3: count a vertex only once */
for (y = miny; (y <= maxy); y++) {
/*1.4 int interLast = 0; */
/* int dirLast = 0; */
/* int interFirst = 1; */
/* 2.0.26+ int yshift = 0; */
std::vector<int> polyInts(pts.size());
for (y = ymin; y <= ymax; y++) {
ints = 0;
for (i = 0; (i < n); i++) {
for (int i=0; i < pts.size(); i++) {
if (!i) {
ind1 = n - 1;
ind1 = pts.size() - 1;
ind2 = 0;
}
else {
ind1 = i - 1;
ind2 = i;
}
y1 = p[ind1].y;
y2 = p[ind2].y;
y1 = pts[ind1].y;
y2 = pts[ind2].y;
if (y1 < y2) {
x1 = p[ind1].x;
x2 = p[ind2].x;
x1 = pts[ind1].x;
x2 = pts[ind2].x;
}
else if (y1 > y2) {
y2 = p[ind1].y;
y1 = p[ind2].y;
x2 = p[ind1].x;
x1 = p[ind2].x;
y2 = pts[ind1].y;
y1 = pts[ind2].y;
x2 = pts[ind1].x;
x1 = pts[ind2].x;
}
else {
else
continue;
}
/* Do the following math as float intermediately, and round to ensure
* that Polygon and FilledPolygon for the same set of points have the
* same footprint. */
if ((y >= y1 && y < y2) ||
(y == ymax && y > y1 && y <= y2)) {
polyInts[ints] = (int) ((float)((y - y1)*(x2 - x1)) / (float)(y2 - y1) + 0.5f + (float)x1);
ints++;
}
}
if ((y >= y1) && (y < y2)) {
polyInts[ints++] = (int) ((float) ((y - y1) * (x2 - x1)) /
(float) (y2 - y1) + 0.5 + x1);
}
else if ((y == maxy) && (y > y1) && (y <= y2)) {
polyInts[ints++] = (int) ((float) ((y - y1) * (x2 - x1)) /
(float) (y2 - y1) + 0.5 + x1);
}
}
/*
2.0.26: polygons pretty much always have less than 100 points,
and most of the time they have considerably less. For such trivial
cases, insertion sort is a good choice. Also a good choice for
future implementations that may wish to indirect through a table.
*/
for (i = 1; (i < ints); i++) {
index = polyInts[i];
j = i;
while ((j > 0) && (polyInts[j - 1] > index)) {
polyInts[j] = polyInts[j - 1];
j--;
}
polyInts[j] = index;
}
for (i = 0; (i < (ints)); i += 2) {
#if 0
int minx = polyInts[i];
int maxx = polyInts[i + 1];
#endif
/* 2.0.29: back to gdImageLine to prevent segfaults when
performing a pattern fill */
proc(polyInts[i], y, polyInts[i + 1], data);
std::sort(polyInts.begin(), polyInts.begin() + ints);
for (int i=0; i < pts.size(); i++) {
if (pts[i].y == y)
createUnion(polyInts, pts[i].x, ints);
}
for (int i=0; i < ints; i+=2)
proc(polyInts[i], y, polyInts[i+1], data);
}
}