tahoma2d/toonz/sources/common/trop/brush.cpp
2018-08-30 17:18:43 +09:00

649 lines
21 KiB
C++

#include <memory>
#include <cstring>
#include "trop.h"
//=======================================================================
class HalfCord {
std::unique_ptr<int[]> m_array;
int m_radius;
public:
HalfCord(int radius) : m_radius(radius), m_array(new int[radius + 1]) {
assert(radius >= 0);
memset(m_array.get(), 0, (m_radius + 1) * sizeof(int));
float dCircle = 1.25f - m_radius; // inizializza decision variable
int y = m_radius; // inizializzazione indice scanline
int x = 0; // inizializzazione indice colonna
do {
m_array[y] = std::max(x, m_array[y]);
m_array[x] = y;
if (dCircle <= 0) {
dCircle = dCircle + 2 * x + 3;
} else {
y--;
dCircle = dCircle + 2 * (x - y) + 5;
}
x++;
} while (y >= x);
}
inline int getCord(int x) {
assert(0 <= x && x <= m_radius);
return m_array[x];
};
private:
// not implemented
HalfCord(const HalfCord &);
HalfCord &operator=(const HalfCord &);
};
//=======================================================================
void TRop::brush(TRaster32P ras, const TPoint &aa, const TPoint &bb, int radius,
const TPixel32 &col) {
TPoint a = aa;
TPoint b = bb;
if (a.y > b.y) std::swap(a, b); // a e' piu' in basso di b
int lx = ras->getLx();
int ly = ras->getLy();
ras->lock();
// ----- radius = 0
if (radius == 0) {
// k = +1/-1 se il rettangolo e' inclinato positivamente
// (0<=m)/negativamente (m<0)
// (se k<0 viene fatta una riflessione sulle ascisse prima di tornare alle
// coordinate "di schermo")
int k = 1;
int dy = b.y - a.y;
int dx = b.x - a.x;
if (dx < 0) {
dx = -dx;
k = -1;
}
assert(dx >= 0);
assert(dy >= 0);
double m; // m sara' definita solo per dx!=0)
if (dx > 0) {
m = dy / (double)dx;
}
// double length = sqrt(dx*dx + dy*dy);
const int alpha = dy, beta = -dx;
const int incE = alpha;
const int incNE = alpha + beta;
const int incN = beta;
// N.B. le coordinate sono relative ad un sist. di rif. con l'origine in a
// l'eq. della retta e' alpha * x + beta * y = 0
int yMin = std::max(a.y, 0) - a.y; // clipping y + cambio riferimento
int yMax = std::min(b.y, ly - 1) - a.y; // (trasporto dell'origine in a)
if (dx > 0 && m <= 1) {
// midpoint algorithm
TPoint segm;
if (dy == 0) // segmento orizzontale: inizializza segm
{
segm.x = 0;
segm.y = yMin;
} else // 0<m<=1 : inizializza segm
{
segm.x = tceil((yMin - 0.5) / m);
segm.y = yMin;
}
int dSegm = tfloor(alpha * (segm.x + 1) + beta * (segm.y + 0.5));
while (segm.y <= yMax) {
int count =
0; // i trati orizzontali di segm vengono disegnati in "blocco"
while (dSegm < 0 && segm.x <= dx) // Est: segm.x<=dx evita il ciclo
{ // infinito quando m=0 (incE=0)
dSegm = dSegm + incE;
segm.x++;
count++;
}
// NordEst
int xMin, xMax;
if (k > 0) {
xMin = std::max(
{a.x + segm.x - count, a.x, 0}); // clipping x + ritorno alle
xMax = std::min(
{a.x + segm.x, b.x, lx - 1}); // coordinate "di schermo"
} else {
xMin = std::max({a.x - segm.x, a.x - dx,
0}); // clipping x + riflessione + ritorno
xMax = std::min({a.x - segm.x + count, a.x,
lx - 1}); // alle coordinate "di schermo"
}
TPixel32 *p = ras->pixels(segm.y + a.y) + xMin;
TPixel32 *q = p + (xMax - xMin);
while (p <= q) *p++ = col;
dSegm = dSegm + incNE;
segm.x++;
segm.y++;
}
} else // m>1 oppure segmento verticale
{
// midpoint algorithm
TPoint segm;
if (dx == 0) // segmento verticale: inizializza segm
{
segm.x = 0;
segm.y = yMin;
} else // m>1 : inizializza segm
{
segm.x = tround(yMin / m);
segm.y = yMin;
}
int dSegm = tfloor(alpha * (segm.x + 0.5) + beta * (segm.y + 1));
while (segm.y <= yMax) {
int xMin, xMax;
if (k > 0) {
xMin = std::max(a.x + segm.x, 0); // clipping x + ritorno alle
xMax = std::min(a.x + segm.x, lx - 1); // coordinate "di schermo"
} else {
xMin =
std::max(a.x - segm.x, 0); // clipping x + riflessione + ritorno
xMax =
std::min(a.x - segm.x, lx - 1); // alle coordinate "di schermo"
}
TPixel32 *p = ras->pixels(segm.y + a.y) + xMin;
TPixel32 *q = p + (xMax - xMin);
while (p <= q) *p++ = col;
if (dSegm <= 0) // NordEst
{
dSegm = dSegm + incNE;
segm.x++;
} else // Nord
{
dSegm = dSegm + incN;
}
segm.y++;
}
}
ras->unlock();
return;
}
HalfCord halfCord(radius);
int x, y;
// ----- punti iniziali coincidenti: disegna un cerchio
if (a == b) {
int yMin = std::max(a.y - radius, 0); // clipping y
int yMax = std::min(a.y + radius, ly - 1); // clipping y
for (y = yMin; y <= yMax; y++) {
int deltay = abs(y - a.y);
int xMin = std::max(a.x - halfCord.getCord(deltay), 0); // clipping x
int xMax =
std::min(a.x + halfCord.getCord(deltay), lx - 1); // clipping x
TPixel32 *p = ras->pixels(y) + xMin;
TPixel32 *q = p + (xMax - xMin);
while (p <= q) *p++ = col;
}
ras->unlock();
return;
}
// ----- rettangolo orizzontale (a.y = b.y, a.x != b.x)
if (a.y == b.y) {
int yMin = std::max((a.y - radius), 0); // clipping y
int yMax = std::min((a.y + radius), ly - 1); // clipping y
int xLeft = std::min(a.x, b.x);
int xRight = std::max(a.x, b.x);
for (y = yMin; y <= yMax; y++) {
int deltay = abs(y - a.y);
int xMin = std::max(xLeft - halfCord.getCord(deltay), 0); // clipping x
int xMax =
std::min(xRight + halfCord.getCord(deltay), lx - 1); // clipping x
TPixel32 *p = ras->pixels(y) + xMin;
TPixel32 *q = p + (xMax - xMin);
while (p <= q) *p++ = col;
}
ras->unlock();
return;
}
// ----- rettangolo verticale (a.x = b.x, a.y != b.y)
if (a.x == b.x) {
int xMin = std::max(a.x - radius, 0); // clipping x
int xMax = std::min(a.x + radius, lx - 1); // clipping x
for (x = xMin; x <= xMax; x++) {
int deltax = abs(x - a.x);
int yMin = std::max(a.y - halfCord.getCord(deltax), 0); // clipping y
int yMax =
std::min(b.y + halfCord.getCord(deltax), ly - 1); // clipping y
if (yMin <= yMax) {
TPixel32 *p = ras->pixels(yMin) + x;
TPixel32 *q = ras->pixels(yMax) + x;
int wrap = ras->getWrap();
while (p <= q) {
*p = col;
p += wrap;
}
}
}
ras->unlock();
return;
}
// ----- rettangolo inclinato
// k = +1/-1 se il rettangolo e' inclinato positivamente/negativamente
int k = 1;
int dx = b.x - a.x;
if (dx < 0) {
dx = -dx;
k = -1;
}
int dy = b.y - a.y;
assert(dx > 0);
assert(dy > 0);
double length = sqrt((double)(dx * dx + dy * dy));
const double m = dy / (double)dx;
// punto di tangenza superiore nel sistema di riferimento del cerchio
TPointD up(-radius * dy / length, radius * dx / length);
// semi-ampiezza orizzontale delle "calotte" circolari
int halfAmplCap = tfloor(-up.x);
// A meno di intersezioni relative tra le diverse zone:
// le scanline della "calotta" circolare superiore sono
// (b.y+cutExt,b.y+radius]
// le scanline del trapezoide circolare superiore sono [b.y-cutIn,b.y+cutExt]
// le scanline del parallelogramma sono (a.y+cutIn,b.y-cutIn)
// le scanline del trapezoide circolare inferiore sono [a.y-cutExt,a.y+cutIn]
// le scanline della "calotta" circolare inferiore sono
// [a.y-radius,a.y-cutExt)
int cutExt, cutIn;
// vertici del parallelogramma
TPointD rightUp;
TPointD rightDown;
TPointD leftUp;
TPointD leftDown;
double mParall; // coeff. angolare parallelogramma
// NOTA BENE: halfAmplCap=0 <=> (radius=0 (caso a parte) , 1)
if (radius > 1) {
for (cutExt = radius;
cutExt >= 0 && halfCord.getCord(cutExt) <= halfAmplCap; cutExt--)
;
cutIn = cutExt; // vedi else successivo
rightUp.x = dx + halfCord.getCord(cutIn);
rightUp.y = dy - cutIn;
rightDown.x = halfCord.getCord(cutIn);
rightDown.y = -cutIn;
leftUp.x = dx - halfCord.getCord(cutIn);
leftUp.y = dy + cutIn;
leftDown.x = -halfCord.getCord(cutIn);
leftDown.y = cutIn;
mParall = dy / (double)dx;
} else // N.B. cutExt != cutIn solo quando radius=1
{
cutExt = radius; // radius=1 => halfAmplCap=0 (non ci sono mai le
// "calotte" circolari)
cutIn = 0; // anche per radius=1 il limite "interno" dei trapezoidi
// circolari e' < radius
rightUp.x = dx - up.x;
rightUp.y = dy - up.y;
rightDown.x = -up.x;
rightDown.y = -up.y;
leftUp.x = dx + up.x;
leftUp.y = dy + up.y;
leftDown.x = up.x;
leftDown.y = up.y;
mParall = m;
}
// ----- riempie "calotte" circolari
// ----- riempie "calotta" circolare inferiore
int yMin = std::max(a.y - radius, 0); // clipping y
int yMax = std::min(a.y - cutExt - 1, ly - 1); // clipping y
for (y = yMin; y <= yMax; y++) {
int r = halfCord.getCord(a.y - y);
int xMin = std::max(a.x - r, 0); // clipping x
int xMax = std::min(a.x + r, lx - 1); // clipping x
TPixel32 *p = ras->pixels(y) + xMin;
TPixel32 *q = p + (xMax - xMin);
while (p <= q) *p++ = col;
}
// ----- riempie "calotta" circolare superiore
yMin = std::max(b.y + cutExt + 1, 0); // clipping y
yMax = std::min(b.y + radius, ly - 1); // clipping y
for (y = yMin; y <= yMax; y++) {
int r = halfCord.getCord(y - b.y);
int xMin = std::max(b.x - r, 0); // clipping x
int xMax = std::min(b.x + r, lx - 1); // clipping x
TPixel32 *p = ras->pixels(y) + xMin;
TPixel32 *q = p + (xMax - xMin);
while (p <= q) *p++ = col;
}
// ----- riempie trapezoidi
// (se k<0 viene fatta una riflessione sulle ascisse prima di tornare alle
// coordinate "di schermo")
// limite destro assoluto delle scanline trapezoide:
int xSegmMax = tround(dx - up.x); // coordinata x del punto di tangenza
// inferiore sul cerchio superiore
// limite sinistro assoluto delle scanline:
int xSegmMin = tround(up.x); // coordinata x del punto di tangenza superiore
// sul cerchio inferiore
// ----- riempie trapezoide inferiore
// N.B. le coordinate sono relative ad un sist. di rif. con l'origine sul
// centro
// del cerchio inferiore
yMin = std::max(a.y - cutExt, 0) - a.y; // clipping y
yMax = std::min({a.y + cutIn, b.y - cutIn - 1, ly - 1}) - a.y; // clipping y
// l'eq. della retta e' alpha * x + beta * y + gammaRight = 0
const int alpha = dy, beta = -dx;
const double gammaRight = rightDown.y * dx - rightDown.x * dy;
const int incE = alpha;
const int incNE = alpha + beta;
const int incN = beta;
if (m <= 1) {
// midpoint algorithm; le scanline vengono disegnate solo
// sul NordEst. L'ultima scanline non viene disegnata
TPoint segmRight(
tceil((yMin + 0.5 - rightDown.y) / mParall + rightDown.x) - 1, yMin);
int dSegmRight = tfloor(alpha * (segmRight.x + 1) +
beta * (segmRight.y + 0.5) + gammaRight);
while (segmRight.y <= yMax) {
if (dSegmRight < 0) // Est
{
dSegmRight = dSegmRight + incE;
segmRight.x++;
} else // NordEst
{
int xMin, xMax;
if (k > 0) {
xMin = std::max(a.x - halfCord.getCord(abs(segmRight.y)),
0); // clipping x
xMax = std::min(a.x + std::min(segmRight.x, xSegmMax),
lx - 1); // clipping x
} else {
xMin = std::max(a.x - std::min(segmRight.x, xSegmMax),
0); // clipping x + ritorno alle
xMax = std::min(a.x + halfCord.getCord(abs(segmRight.y)),
lx - 1); // coordinate "di schermo"
}
TPixel32 *p = ras->pixels(segmRight.y + a.y) + xMin;
TPixel32 *q = p + (xMax - xMin);
while (p <= q) *p++ = col;
dSegmRight = dSegmRight + incNE;
segmRight.x++;
segmRight.y++;
}
}
} else // m>1
{
// midpoint algorithm; le scanline vengono disegnate sempre
TPoint segmRight(tround((yMin - rightDown.y) / mParall + rightDown.x),
yMin);
int dSegmRight = tfloor(alpha * (segmRight.x + 0.5) +
beta * (segmRight.y + 1) + gammaRight);
while (segmRight.y <= yMax) {
int xMin, xMax;
if (k > 0) {
xMin = std::max(a.x - halfCord.getCord(abs(segmRight.y)),
0); // clipping x
xMax = std::min(a.x + segmRight.x, lx - 1); // clipping x
} else {
xMin = std::max(a.x - segmRight.x,
0); // clipping x + ritorno alle coordinate
xMax = std::min(a.x + halfCord.getCord(abs(segmRight.y)),
lx - 1); // "di schermo"
}
TPixel32 *p = ras->pixels(segmRight.y + a.y) + xMin;
TPixel32 *q = p + (xMax - xMin);
while (p <= q) *p++ = col;
if (dSegmRight <= 0) // NordEst
{
dSegmRight = dSegmRight + incNE;
segmRight.x++;
} else // Nord
{
dSegmRight = dSegmRight + incN;
}
segmRight.y++;
}
}
// ----- riempie trapezoide superiore
// N.B. le coordinate sono relative ad un sist. di rif. con l'origine sul
// centro
// del cerchio superiore
yMin = std::max({b.y - cutIn, a.y + cutIn + 1, 0}) - b.y; // clipping y
yMax = std::min(b.y + cutExt, ly - 1) - b.y; // clipping y
// l'eq. della retta e' alpha * x + beta * y + gammaLeft = 0
const double gammaLeft = leftDown.y * dx - leftDown.x * dy;
if (m <= 1) {
// midpoint algorithm; le scanline vengono disegnate solo
// sul NordEst. L'ultima scanline non viene disegnata
TPoint segmLeft(tceil((yMin - 0.5 - leftDown.y) / mParall + leftDown.x),
yMin);
int dSegmLeft = tfloor(alpha * (segmLeft.x + 1) +
beta * (segmLeft.y + 0.5) + gammaLeft);
while (segmLeft.y <= yMax) {
int xMin, xMax;
if (k > 0) {
xMin = std::max(b.x + std::max(segmLeft.x, xSegmMin - dx),
0); // clipping x
xMax = std::min(b.x + halfCord.getCord(abs(segmLeft.y)),
lx - 1); // clipping x
} else {
xMin = std::max(b.x - halfCord.getCord(abs(segmLeft.y)),
0); // clipping x + ritorno alle
xMax = std::min(b.x - std::max(segmLeft.x, xSegmMin - dx),
lx - 1); // coordinate "di schermo"
}
TPixel32 *p = ras->pixels(segmLeft.y + b.y) + xMin;
TPixel32 *q = p + (xMax - xMin);
while (p <= q) *p++ = col;
while (dSegmLeft < 0) {
dSegmLeft = dSegmLeft + incE;
segmLeft.x++;
}
dSegmLeft = dSegmLeft + incNE;
segmLeft.x++;
segmLeft.y++;
}
} else // m>1
{
// midpoint algorithm; le scanline vengono disegnate sempre
TPoint segmLeft(tround((yMin - leftDown.y) / mParall + leftDown.x), yMin);
int dSegmLeft = tfloor(alpha * (segmLeft.x + 0.5) +
beta * (segmLeft.y + 1) + gammaLeft);
while (segmLeft.y <= yMax) {
int xMin, xMax;
if (k > 0) {
xMin = std::max(b.x + segmLeft.x, 0); // clipping x
xMax = std::min(b.x + halfCord.getCord(abs(segmLeft.y)),
lx - 1); // clipping x
} else {
xMin = std::max(b.x - halfCord.getCord(abs(segmLeft.y)),
0); // clipping x + ritorno alle
xMax = std::min(b.x - segmLeft.x, lx - 1); // coordinate "di schermo"
}
TPixel32 *p = ras->pixels(segmLeft.y + b.y) + xMin;
TPixel32 *q = p + (xMax - xMin);
while (p <= q) *p++ = col;
if (dSegmLeft <= 0) // NordEst
{
dSegmLeft = dSegmLeft + incNE;
segmLeft.x++;
} else // Nord
{
dSegmLeft = dSegmLeft + incN;
}
segmLeft.y++;
}
}
// ----- parallelogramma (in alternativa a "parallelogrammoide circolare")
// N.B. le coordinate sono relative ad un sist. di rif. con l'origine sul
// centro
// del cerchio inferiore
// retta destra di equaz. alpha * x + beta * y + gammaRight = 0
// retta sinistra di equaz. alpha * x + beta * y + gammaLeft = 0
yMin = std::max(a.y + cutIn + 1, 0) - a.y; // clipping y
yMax = std::min(b.y - cutIn - 1, ly - 1) - a.y; // clipping y
if (m <= 1) {
// midpoint algorithm; le scanline vengono disegnate solo
// sul NordEst. L'ultima scanline non viene disegnata
TPoint segmRight(
tceil((yMin + 0.5 - rightDown.y) / mParall + rightDown.x) - 1, yMin);
TPoint segmLeft =
TPoint(tceil((yMin - 0.5 - leftDown.y) / mParall + leftDown.x), yMin);
int dSegmRight = tfloor(alpha * (segmRight.x + 1) +
beta * (segmRight.y + 0.5) + gammaRight);
int dSegmLeft = tfloor(alpha * (segmLeft.x + 1) +
beta * (segmLeft.y + 0.5) + gammaLeft);
while (segmRight.y <= yMax) {
if (dSegmRight < 0) // segmRight a Est
{
dSegmRight = dSegmRight + incE;
segmRight.x++;
} else // segmRight a NordEst
{
int xMin, xMax;
if (k > 0) {
xMin =
std::max(a.x + std::max(segmLeft.x, xSegmMin), 0); // clipping x
xMax = std::min(a.x + std::min(segmRight.x, xSegmMax),
lx - 1); // clipping x
} else {
xMin = std::max(a.x - std::min(segmRight.x, xSegmMax),
0); // clipping x + ritorno alle
xMax = std::min(a.x - std::max(segmLeft.x, xSegmMin),
lx - 1); // coordinate "di schermo"
}
TPixel32 *p = ras->pixels(segmRight.y + a.y) + xMin;
TPixel32 *q = p + (xMax - xMin);
while (p <= q) *p++ = col;
dSegmRight = dSegmRight + incNE;
segmRight.x++;
segmRight.y++;
while (dSegmLeft < 0) // segmLeft a Est
{
dSegmLeft = dSegmLeft + incE;
segmLeft.x++;
}
// segmLeft a NordEst
dSegmLeft = dSegmLeft + incNE;
segmLeft.x++;
segmLeft.y++;
}
}
} else // m>1
{
// midpoint algorithm; le scanline vengono disegnate sempre
TPoint segmRight(tround((yMin - rightDown.y) / mParall + rightDown.x),
yMin);
TPoint segmLeft(tround((yMin - leftDown.y) / mParall + leftDown.x), yMin);
int dSegmRight = tfloor(alpha * (segmRight.x + 0.5) +
beta * (segmRight.y + 1) + gammaRight);
int dSegmLeft = tfloor(alpha * (segmLeft.x + 0.5) +
beta * (segmLeft.y + 1) + gammaLeft);
while (segmRight.y <= yMax) {
int xMin, xMax;
if (k > 0) {
xMin = std::max(a.x + segmLeft.x, 0); // clipping x
xMax = std::min(a.x + segmRight.x, lx - 1); // clipping x
} else {
xMin = std::max(a.x - segmRight.x, 0); // clipping x + ritorno alle
xMax = std::min(a.x - segmLeft.x, lx - 1); // coordinate "di schermo"
}
TPixel32 *p = ras->pixels(segmRight.y + a.y) + xMin;
TPixel32 *q = p + (xMax - xMin);
while (p <= q) *p++ = col;
if (dSegmRight <= 0) // segmRight a NordEst
{
dSegmRight = dSegmRight + incNE;
segmRight.x++;
} else // segmRight a Nord
{
dSegmRight = dSegmRight + incN;
}
segmRight.y++;
if (dSegmLeft <= 0) // segmLeft a NordEst
{
dSegmLeft = dSegmLeft + incNE;
segmLeft.x++;
} else // segmLeft a Nord
{
dSegmLeft = dSegmLeft + incN;
}
}
}
// ---- parallelogrammoide circolare (in alternativa a parallelogramma)
// N.B. coordinate di schermo (riflessione per k<0 )
yMin = std::max(b.y - cutIn, 0);
yMax = std::min(a.y + cutIn, ly - 1);
for (y = yMin; y <= yMax; y++) {
int xMin, xMax;
if (k > 0) {
xMin = std::max(a.x - halfCord.getCord(abs(y - a.y)), 0); // clipping x
xMax = std::min(b.x + halfCord.getCord(abs(b.y - y)),
lx - 1); // clipping x
} else {
xMin = std::max(b.x - halfCord.getCord(abs(b.y - y)),
0); // clipping x + ritorno alle
xMax = std::min(a.x + halfCord.getCord(abs(y - a.y)),
lx - 1); // coordinate "di schermo"
}
TPixel32 *p = ras->pixels(y) + xMin;
TPixel32 *q = p + (xMax - xMin);
while (p <= q) *p++ = col;
}
ras->unlock();
}