Blob Blame Raw


#include "toonz/rasterbrush.h"

#include <vector>
#include <tcurves.h>

using namespace std;

namespace
{

//=====================================================================================
// forward declaration
void lightPixel(const TRasterCM32P &ras, const TPoint &pix, double distance, int styleId, bool checkAntialiasedPixel);

class ConeSubVolume
{

	const static double m_values[21];

public:
	ConeSubVolume()
	{
	}

	//calcola il sottovolume di un cono di raggio e volume unitario in base
	static double compute(double cover)
	{
		double x = (10 * tcrop(cover, -1.0, 1.0)) + 10;
		assert(0 <= x && x <= 20);
		int i = tfloor(x);
		if (i == 20)
			return m_values[i];
		else
			//Interpolazione lineare.
			return (-(x - (i + 1)) * m_values[i]) - (-(x - i) * m_values[i + 1]);
	}
};

const double ConeSubVolume::m_values[] = {

	1.0, 0.99778, 0.987779, 0.967282, 0.934874,
	0.889929, 0.832457, 0.763067, 0.683002, 0.594266,
	0.5, 0.405734, 0.316998, 0.236933, 0.167543,
	0.110071, 0.0651259, 0.0327182, 0.0122208, 0.00221986,
	0.0};

//========================================================================================================

//Permette di disegnare un disco pieno
class Disk
{
	TPointD m_centre;
	double m_radius;
	bool m_doAntialias;

	//Traccia una linea tra due punti per riempire il disco
	void fill(const TRasterCM32P &ras, const TPoint &p1, const TPoint &p2, int styleId) const
	{
		if (p1.y == p2.y) {
			int xMax, xMin;
			if (p1.x > p2.x) {
				xMax = p1.x;
				xMin = p2.x;
			} else {
				xMin = p1.x;
				xMax = p2.x;
			}
			TPixelCM32 color(styleId, 0, 0);
			for (int i = xMin; i <= xMax; i++)
				ras->pixels(p1.y)[i] = color;
		} else {
			int yMax, yMin;
			if (p1.y > p2.y) {
				yMax = p1.y;
				yMin = p2.y;
			} else {
				yMin = p1.y;
				yMax = p2.y;
			}
			TPixelCM32 color(styleId, 0, 0);
			for (int i = yMin; i <= yMax; i++)
				ras->pixels(i)[p1.x] = color;
		}
	}

	//Calcola la distanza di un pixel dal centro "reale"
	inline double distancePointToCentre(const TPoint &point) const
	{
		double d = sqrt((point.x - m_centre.x) * (point.x - m_centre.x) + (point.y - m_centre.y) * (point.y - m_centre.y));
		return d;
	}

	//Calcola la distanza tra un punto della cironferenza ideale ed il pixel che la approssima
	//Inoltre calcola la distanza dei pixel vicini per gestire l'antialias
	void computeDistances(double distances[3], const TPoint &point, const TPoint &centre, bool upperPoint) const
	{
		TPoint p = point - centre;
		if (upperPoint) {
			double d = distancePointToCentre(point);
			distances[0] = d - m_radius;
			d = distancePointToCentre(TPoint(point.x, point.y + 1));
			distances[1] = d - m_radius;
			d = distancePointToCentre(TPoint(point.x, point.y - 1));
			distances[2] = d - m_radius;
			return;
		} else {
			double d = distancePointToCentre(point);
			distances[0] = d - m_radius;
			d = distancePointToCentre(TPoint(point.x + 1, point.y));
			distances[1] = d - m_radius;
			d = distancePointToCentre(TPoint(point.x - 1, point.y));
			distances[2] = d - m_radius;
			return;
		}
	}

	//Illumina otto pixel simmetrici per ottenere la circonferenza del disco.
	//Per ogni pixel illumina anche i vicini per gestire l'antialias.
	void makeAntiAliasedDiskBorder(const TRasterCM32P &ras,
								   const TPoint &centre,
								   const TPoint &point,
								   double distancesAntialias[3],
								   int styleId,
								   int maxPointToFill) const
	{
		computeDistances(distancesAntialias, point + centre, centre, true);
		lightPixel(ras, point + centre, distancesAntialias[0], styleId, false);
		lightPixel(ras, TPoint(point.x, point.y + 1) + centre, distancesAntialias[1], styleId, false);
		lightPixel(ras, TPoint(point.x, point.y - 1) + centre, distancesAntialias[2], styleId, false);

		computeDistances(distancesAntialias, TPoint(point.y, point.x) + centre, centre, false);
		lightPixel(ras, TPoint(point.y, point.x) + centre, distancesAntialias[0], styleId, false);
		lightPixel(ras, TPoint(point.y + 1, point.x) + centre, distancesAntialias[1], styleId, false);
		lightPixel(ras, TPoint(point.y - 1, point.x) + centre, distancesAntialias[2], styleId, false);

		computeDistances(distancesAntialias, TPoint(-point.x, -point.y) + centre, centre, true);
		lightPixel(ras, TPoint(-point.x, -point.y) + centre, distancesAntialias[0], styleId, false);
		lightPixel(ras, TPoint(-point.x, -point.y - 1) + centre, distancesAntialias[2], styleId, false);
		lightPixel(ras, TPoint(-point.x, -point.y + 1) + centre, distancesAntialias[1], styleId, false);

		computeDistances(distancesAntialias, TPoint(-point.y + centre.x, point.x + centre.y), centre, false);
		lightPixel(ras, TPoint(-point.y, point.x) + centre, distancesAntialias[0], styleId, false);
		lightPixel(ras, TPoint(-point.y - 1, point.x) + centre, distancesAntialias[2], styleId, false);
		lightPixel(ras, TPoint(-point.y + 1, point.x) + centre, distancesAntialias[1], styleId, false);

		if ((point.x + centre.x) != (centre.x)) {
			computeDistances(distancesAntialias, TPoint(point.y, -point.x) + centre, centre, false);
			lightPixel(ras, TPoint(point.y, -point.x) + centre, distancesAntialias[0], styleId, false);
			lightPixel(ras, TPoint(point.y + 1, -point.x) + centre, distancesAntialias[1], styleId, false);
			lightPixel(ras, TPoint(point.y - 1, -point.x) + centre, distancesAntialias[2], styleId, false);

			computeDistances(distancesAntialias, TPoint(point.x, -point.y) + centre, centre, true);
			lightPixel(ras, TPoint(point.x, -point.y) + centre, distancesAntialias[0], styleId, false);
			lightPixel(ras, TPoint(point.x, -point.y - 1) + centre, distancesAntialias[2], styleId, false);
			lightPixel(ras, TPoint(point.x, -point.y + 1) + centre, distancesAntialias[1], styleId, false);

			computeDistances(distancesAntialias, TPoint(-point.y, -point.x) + centre, centre, false);
			lightPixel(ras, TPoint(-point.y, -point.x) + centre, distancesAntialias[0], styleId, false);
			lightPixel(ras, TPoint(-point.y - 1, -point.x) + centre, distancesAntialias[2], styleId, false);
			lightPixel(ras, TPoint(-point.y + 1, -point.x) + centre, distancesAntialias[1], styleId, false);

			computeDistances(distancesAntialias, TPoint(-point.x, point.y) + centre, centre, true);
			lightPixel(ras, TPoint(-point.x, point.y) + centre, distancesAntialias[0], styleId, false);
			lightPixel(ras, TPoint(-point.x, point.y + 1) + centre, distancesAntialias[1], styleId, false);
			lightPixel(ras, TPoint(-point.x, point.y - 1) + centre, distancesAntialias[2], styleId, false);
		}
		if (maxPointToFill <= point.y - 2) {
			fill(ras, TPoint(point.x, maxPointToFill) + centre, TPoint(point.x, point.y - 2) + centre, styleId);
			fill(ras, TPoint(maxPointToFill, point.x) + centre, TPoint(point.y - 2, point.x) + centre, styleId);
			fill(ras, TPoint(point.x, -maxPointToFill) + centre, TPoint(point.x, -point.y + 2) + centre, styleId);
			fill(ras, TPoint(-maxPointToFill, -point.x) + centre, TPoint(-point.y + 2, -point.x) + centre, styleId);

			if (point.x != 0) {
				fill(ras, TPoint(maxPointToFill, -point.x) + centre, TPoint(point.y - 2, -point.x) + centre, styleId);
				fill(ras, TPoint(-point.x, -maxPointToFill) + centre, TPoint(-point.x, -point.y + 2) + centre, styleId);
				fill(ras, TPoint(-maxPointToFill, point.x) + centre, TPoint(-point.y + 2, point.x) + centre, styleId);
				fill(ras, TPoint(-point.x, maxPointToFill) + centre, TPoint(-point.x, point.y - 2) + centre, styleId);
			}
		}
	}

	void makeNoAntiAliasedDiskBorder(const TRasterCM32P &ras,
									 const TPoint &centre,
									 const TPoint &point,
									 int styleId,
									 int maxPointToFill) const
	{
		if (((int)(m_radius * 2)) % 2 == 0) {
			lightPixel(ras, TPoint(point.x - 1, point.y) + centre, -1, styleId, false);
			lightPixel(ras, TPoint(point.y - 1, -point.x + 1) + centre, -1, styleId, false);
			lightPixel(ras, TPoint(-point.x, -point.y + 1) + centre, -1, styleId, false);
			lightPixel(ras, TPoint(-point.y, point.x) + centre, -1, styleId, false);
		} else {
			lightPixel(ras, point + centre, -1, styleId, false);
			lightPixel(ras, TPoint(point.y, -point.x) + centre, -1, styleId, false);
			lightPixel(ras, TPoint(-point.x, -point.y) + centre, -1, styleId, false);
			lightPixel(ras, TPoint(-point.y, point.x) + centre, -1, styleId, false);
		}

		if ((point.x + centre.x) != (centre.x)) {
			if (((int)(m_radius * 2)) % 2 == 0) {
				lightPixel(ras, TPoint(point.y - 1, point.x) + centre, -1, styleId, false);
				lightPixel(ras, TPoint(point.x - 1, -point.y + 1) + centre, -1, styleId, false);
				lightPixel(ras, TPoint(-point.y, -point.x + 1) + centre, -1, styleId, false);
				lightPixel(ras, TPoint(-point.x, point.y) + centre, -1, styleId, false);
			} else {
				lightPixel(ras, TPoint(point.y, point.x) + centre, -1, styleId, false);
				lightPixel(ras, TPoint(point.x, -point.y) + centre, -1, styleId, false);
				lightPixel(ras, TPoint(-point.y, -point.x) + centre, -1, styleId, false);
				lightPixel(ras, TPoint(-point.x, point.y) + centre, -1, styleId, false);
			}
		}
		if (maxPointToFill <= point.y - 1) {
			if (((int)(m_radius * 2)) % 2 == 0) {
				fill(ras, TPoint(point.x - 1, 0) + centre, TPoint(point.x - 1, point.y) + centre, styleId);
				fill(ras, TPoint(0, -point.x + 1) + centre, TPoint(point.y - 1, -point.x + 1) + centre, styleId);
				fill(ras, TPoint(-point.x, 0) + centre, TPoint(-point.x, -point.y + 1) + centre, styleId);
				fill(ras, TPoint(0, point.x) + centre, TPoint(-point.y, point.x) + centre, styleId);
				if (point.x != 0) {
					fill(ras, TPoint(0, point.x) + centre, TPoint(point.y - 1, point.x) + centre, styleId);
					fill(ras, TPoint(point.x - 1, 0) + centre, TPoint(point.x - 1, -point.y + 1) + centre, styleId);
					fill(ras, TPoint(0, -point.x + 1) + centre, TPoint(-point.y, -point.x + 1) + centre, styleId);
					fill(ras, TPoint(-point.x, 0) + centre, TPoint(-point.x, point.y) + centre, styleId);
				}
			} else {
				fill(ras, TPoint(point.x, 0) + centre, TPoint(point.x, point.y) + centre, styleId);
				fill(ras, TPoint(0, -point.x) + centre, TPoint(point.y, -point.x) + centre, styleId);
				fill(ras, TPoint(-point.x, 0) + centre, TPoint(-point.x, -point.y) + centre, styleId);
				fill(ras, TPoint(0, point.x) + centre, TPoint(-point.y, point.x) + centre, styleId);

				if (point.x != 0) {
					fill(ras, TPoint(0, point.x) + centre, TPoint(point.y, point.x) + centre, styleId);
					fill(ras, TPoint(point.x, -0) + centre, TPoint(point.x, -point.y) + centre, styleId);
					fill(ras, TPoint(0, -point.x) + centre, TPoint(-point.y, -point.x) + centre, styleId);
					fill(ras, TPoint(-point.x, 0) + centre, TPoint(-point.x, point.y) + centre, styleId);
				}
			}
		}
	}

public:
	Disk()
		: m_centre(0.0, 0.0), m_radius(1.0), m_doAntialias(true)
	{
	}

	Disk(const TThickPoint &p, bool doAntialias)
		: m_doAntialias(doAntialias)
	{
		if (m_doAntialias) {
			m_centre = TPointD(p.x, p.y);
			m_radius = p.thick * 0.5;
		} else {
			m_centre = TPointD(tround(p.x), tround(p.y));
			m_radius = tround(p.thick) * 0.5;
		}
	}

	//Disegna un disco
	void draw(const TRasterCM32P &ras, int styleId) const
	{
		double distances[3];
		int maxPointToFill = 0;
		TPoint centre = convert(m_centre);
		TPoint point;
		if (m_doAntialias) {
			TPoint point(0, tround(m_radius));
			int d = 1 - tround(m_radius), dE = 3, dSE = -2 * tround(m_radius) + 5;
			makeAntiAliasedDiskBorder(ras, centre, point, distances, styleId, maxPointToFill);

			while (point.y > point.x) {
				if (d < 0) {
					d += dE;
					dE += 2;
					dSE += 2;
					point.x++;
					maxPointToFill++;
				} else {
					d += dSE;
					dE += 2;
					dSE += 4;
					point.x++;
					point.y--;
					maxPointToFill++;
				}
				makeAntiAliasedDiskBorder(ras, centre, point, distances, styleId, maxPointToFill);
			}
		} else {
			TPoint point(0, tround(m_radius - 0.5));
			int d = 3 - 2 * (int)m_radius;
			while (point.y > point.x) {
				makeNoAntiAliasedDiskBorder(ras, centre, point, styleId, maxPointToFill);
				if (d < 0)
					d = d + 4 * point.x + 6;
				else {
					d = d + 4 * (point.x - point.y) + 10;
					point.y--;
				}
				maxPointToFill++;
				point.x++;
			}
			if (point.x == point.y)
				makeNoAntiAliasedDiskBorder(ras, centre, point, styleId, maxPointToFill);
		}
	}

	TPointD getCentre() const
	{
		return m_centre;
	}

	double getRadius() const
	{
		return m_radius;
	}

	void setCentre(const TPointD &centre)
	{
		m_centre = centre;
	}

	void setCentre(double x, double y)
	{
		m_centre.x = x;
		m_centre.y = y;
	}

	void setRadius(double radius)
	{
		m_radius = radius;
	}
};

//===============================================================================================================

//Calcola la distanza di una curva al parametro "t" dalla retta che unisce i punti nei parametri "tPrevious" e "tNext"
double findChordalDeviation(const TQuadratic &quadratic, double t, double tPrevious, double tNext)
{
	TPointD pPrevious = quadratic.getPoint(tPrevious);
	TPointD pNext = quadratic.getPoint(tNext);
	TPointD p = quadratic.getPoint(t);
	TPointD P(p - pPrevious), Q(pNext - pPrevious);
	double PQ = P * Q;
	double QQ = Q * Q;
	return norm(P - (PQ / QQ) * Q);
}

//Accende un pixel calcolandone l'intensita'
/*-- 筆先のアンチエイリアス部分の描画 --*/
void lightPixel(const TRasterCM32P &ras, const TPoint &pix, double distance, int styleId, bool checkAntialiasedPixel)
{
	TPixelCM32 pixel = ras->pixels(pix.y)[pix.x];
	double volumeParziale = ConeSubVolume::compute(distance);

	/*- 現在のToneに乗算していく -*/
	int newTone = tround((double)pixel.getTone() * (1.0 - volumeParziale));
	assert(newTone >= 0 && newTone <= 255);
	ras->pixels(pix.y)[pix.x] = TPixelCM32(styleId, pixel.getPaint(), newTone);
}

bool isDiskNecessaryNecessary(const TQuadratic &quadratic, double tCurrent, double lastT, double nextT, bool &idLastDiskDrown)
{
	TPoint currentPoint = convert(quadratic.getPoint(tCurrent));
	TPoint lastPoint = convert(quadratic.getPoint(lastT));
	TPoint nextPoint = convert(quadratic.getPoint(nextT));
	TPoint lastDiff = currentPoint - lastPoint;
	TPoint nextDiff = currentPoint - nextPoint;
	TPoint otherDiff = lastPoint - nextPoint;
	bool isLastNear = lastDiff.x == 0 || lastDiff.y == 0;
	bool isNextNear = nextDiff.x == 0 || nextDiff.y == 0;
	bool isOtheNear = otherDiff.x == 0 || otherDiff.y == 0;
	if (isLastNear && isNextNear && !isOtheNear && idLastDiskDrown) {
		idLastDiskDrown = false;
		return false;
	}
	return true;
}

//Disegna una porzione di del tratto (un piccolo arco)
void makeLittleArch(const TRasterCM32P &ras, const Disk &disk1, const Disk &disk2,
					const Disk &disk3, int styleId, bool doAntialias)
{
	TPointD center1 = disk1.getCentre();
	TPointD center2 = disk2.getCentre();
	TPointD center3 = disk3.getCentre();
	TQuadratic quadratic(center1, center2, center3);

	disk1.draw(ras, styleId);

	double length = quadratic.getLength();
	if (length < 2)
		return;
	double t = 0, step = 1 / (length * 1.5), t2 = quadratic.getT(center2);
	bool idLastDiskDrown = true;
	for (t = step; t < 1; t += step) {
		TPointD center = quadratic.getPoint(t);
		double radius = disk1.getRadius() + (disk3.getRadius() - disk1.getRadius()) * t;
		Disk disk(TThickPoint(center, radius * 2), doAntialias);
		bool drawDisk = true;
		if (!doAntialias)
			drawDisk = isDiskNecessaryNecessary(quadratic, t, t - step, t + step > 1 ? 1 : t + step, idLastDiskDrown);
		if (t != 1 && drawDisk) {
			disk.draw(ras, styleId);
			idLastDiskDrown = true;
		}
	}
	disk3.draw(ras, styleId);
}

//Disegna un piccolo segmento, invece di un archetto.
void makeLittleSegment(const TRasterCM32P &ras, const Disk &disk1, const Disk &disk2,
					   int styleId, bool doAntialias)
{
	TPointD center1 = disk1.getCentre();
	TPointD center2 = disk2.getCentre();
	TPointD middle = (center1 + center2) * 0.5;
	double raius = (disk1.getRadius() + disk2.getRadius()) * 0.5;
	Disk disk(TThickPoint(middle, raius * 2), doAntialias);
	makeLittleArch(ras, disk1, disk, disk2, styleId, doAntialias);
}

//=============================================================================

} // namespace

//=============================================================================

//Preso un vettore di punti, disegna una pennelata che li approssima in un raster trasparente
void rasterBrush(const TRasterCM32P &rasBuffer, const vector<TThickPoint> &points, int styleId, bool doAntialias)
{
	int i, n = points.size();
	if (n == 0)
		return;
	else if (n == 1) {
		Disk disk(points[0], doAntialias);
		disk.draw(rasBuffer, styleId);
		return;
	} else if (n == 2) {
		makeLittleSegment(rasBuffer, Disk(points[0], doAntialias), Disk(points[1], doAntialias), styleId, doAntialias);
		return;
	} else if (n == 4) {
		makeLittleArch(rasBuffer,
					   Disk(points[0], doAntialias),
					   Disk(points[1], doAntialias),
					   Disk(points[2], doAntialias),
					   styleId, doAntialias);
		makeLittleSegment(rasBuffer, Disk(points[2], doAntialias), Disk(points[3], doAntialias), styleId, doAntialias);
		return;
	} else {
		for (i = 0; i + 2 < n; i += 2)
			makeLittleArch(rasBuffer,
						   Disk(points[i], doAntialias),
						   Disk(points[i + 1], doAntialias),
						   Disk(points[i + 2], doAntialias),
						   styleId, doAntialias);
	}
}