On Thu, Jul 5, 2012 at 11:20 AM, Ian Thomson <ian.thom...@iongeo.com> wrote:

> On 05/07/12 16:12, Josiah Bryan wrote:
> > So, my Qt friends, is there a better way to fill a triangle with a color
> > specified for each vertex, interpolated across the triangle? Some method
> > that takes *less* than 4 milliseconds *per triangle*? Did I just miss
> > something in the Qt gradient routines that would do this quite easily?
> > Any ideas?
>
> You might want to draw your triangles on to a QImage, using
> QImage::scanLine to manipulate the pixels directly, and then put the
> QImage on to the QWidget at the end. I would imagine this would be
> faster than QPainter::fillRect.
>
>
Wow - that was faster.  379ms for 1K triangles. Much much improved - so
much so that I don't think I'll need to optimize further for now. (Revised
code attached incase anyone wants to use it.)

Thanks for your tip - I *should* have thought of that myself, don't know
why I just made it more complex to begin with. Anyways, thanks!


-- 
Josiah Bryan
765-215-0511
josiahbr...@gmail.com
//#include "MainWindow.h"


#include <QtGui>

class MainWindow : public QWidget
{
public:
	MainWindow();
	
protected:

};


class TRGBFloat {
public:
	float R;
	float G;
	float B;
};

bool fillTriColor(QImage *img, QVector<QPointF> points, QList<QColor> colors)
{
	if(!img)
		return false;
	if(img->format() != QImage::Format_ARGB32_Premultiplied)
		return false;
		
	double imgWidth  = img->width();
	double imgHeight = img->height();
	
	/// The following routine translated from Delphi, originally found at:
	// http://www.swissdelphicenter.ch/en/showcode.php?id=1780
	
	double  LX, RX, Ldx, Rdx,// : Single;
		Dif1, Dif2;// : Single;
	
	TRGBFloat LRGB, RRGB, RGB, RGBdx, LRGBdy, RRGBdy;// : TRGBFloat;
		
	QColor RGBT;// : RGBTriple;                      
	//QList<QColor> Scan; // : PRGBTripleArray;
	double y, x, ScanStart, ScanEnd;// : integer;
	bool Right; // : boolean;
	QPointF tmpPoint;
	QColor tmpColor;// : TPointColor;
	
	
	 // sort vertices by Y
	int Vmax = 0;
	if (points[1].y() > points[0].y())
		Vmax = 1;
	if (points[2].y() > points[Vmax].y())
		 Vmax = 2;
		 
	if(Vmax != 2)
	{
		tmpPoint = points[2];
		points[2] = points[Vmax];
		points[Vmax] = tmpPoint;
		
		tmpColor = colors[2];
		colors[2] = colors[Vmax];
		colors[Vmax] = tmpColor;
	}
		
	if(points[1].y() > points[0].y())
		Vmax = 1;
	else 
		Vmax = 0;
		
	if(Vmax == 0)
	{
		tmpPoint = points[1];
		points[1] = points[0];
		points[0] = tmpPoint;
		
		tmpColor = colors[1];
		colors[1] = colors[0];
		colors[0] = tmpColor;
	}
	
	Dif1 = points[2].y() - points[0].y();
	if(Dif1 == 0)
		Dif1 = 0.001; // prevent div by 0 error
	
	Dif2 = points[1].y() - points[0].y();
	if(Dif2 == 0)
		Dif2 = 0.001;

	//work out if middle point is to the left or right of the line
	// connecting upper and lower points
	if(points[1].x() > (points[2].x() - points[0].x()) * Dif2 / Dif1 + points[0].x())
		Right = true;
	else	Right = false;
	
	   // calculate increments in x and colour for stepping through the lines
	if(Right)
	{
		Ldx = (points[2].x() - points[0].x()) / Dif1;
		Rdx = (points[1].x() - points[0].x()) / Dif2;
		LRGBdy.B = (colors[2].blue()  - colors[0].blue())  / Dif1;
		LRGBdy.G = (colors[2].green() - colors[0].green()) / Dif1;
		LRGBdy.R = (colors[2].red()   - colors[0].red())   / Dif1;
		RRGBdy.B = (colors[1].blue()  - colors[0].blue())  / Dif2;
		RRGBdy.G = (colors[1].green() - colors[0].green()) / Dif2;
		RRGBdy.R = (colors[1].red()   - colors[0].red())   / Dif2;
	}
	else
	{
// 		Ldx := (V[1].X - V[0].X) / Dif2;
// 		Rdx := (V[2].X - V[0].X) / Dif1;
// 		RRGBdy.B := (V[2].RGB.B - V[0].RGB.B) / Dif1;
// 		RRGBdy.G := (V[2].RGB.G - V[0].RGB.G) / Dif1;
// 		RRGBdy.R := (V[2].RGB.R - V[0].RGB.R) / Dif1;
// 		LRGBdy.B := (V[1].RGB.B - V[0].RGB.B) / Dif2;
// 		LRGBdy.G := (V[1].RGB.G - V[0].RGB.G) / Dif2;
// 		LRGBdy.R := (V[1].RGB.R - V[0].RGB.R) / Dif2;

		Ldx = (points[1].x() - points[0].x()) / Dif1;
		Rdx = (points[2].x() - points[0].x()) / Dif2;
		
		RRGBdy.B = (colors[2].blue()  - colors[0].blue())  / Dif1;
		RRGBdy.G = (colors[2].green() - colors[0].green()) / Dif1;
		RRGBdy.R = (colors[2].red()   - colors[0].red())   / Dif1;
		LRGBdy.B = (colors[1].blue()  - colors[0].blue())  / Dif2;
		LRGBdy.G = (colors[1].green() - colors[0].green()) / Dif2;
		LRGBdy.R = (colors[1].red()   - colors[0].red())   / Dif2;
	}

	LRGB.R = colors[0].red();
	LRGB.G = colors[0].green();
	LRGB.B = colors[0].blue();
	RRGB = LRGB;
	
	LX = points[0].x();
	RX = points[0].x();
	
	// fill region 1
	for(y = points[0].y(); y <= points[1].y() - 1; y++)
	{	
		// y clipping
		if(y > imgHeight - 1)
			break;
			
		if(y < 0)
		{
			LX = LX + Ldx;
			RX = RX + Rdx;
			LRGB.B = LRGB.B + LRGBdy.B;
			LRGB.G = LRGB.G + LRGBdy.G;
			LRGB.R = LRGB.R + LRGBdy.R;
			RRGB.B = RRGB.B + RRGBdy.B;
			RRGB.G = RRGB.G + RRGBdy.G;
			RRGB.R = RRGB.R + RRGBdy.R;
			continue;
		}
		
		// Scan = ABitmap.ScanLine[y];
		
		// calculate increments in color for stepping through pixels
		Dif1 = RX - LX + 1;
		if(Dif1 == 0)
			Dif1 = 0.001;
		RGBdx.B = (RRGB.B - LRGB.B) / Dif1;
		RGBdx.G = (RRGB.G - LRGB.G) / Dif1;
		RGBdx.R = (RRGB.R - LRGB.R) / Dif1;
		
		// x clipping
		if(LX < 0)
		{
			ScanStart = 0;
			RGB.B = LRGB.B + (RGBdx.B * fabs(LX));
			RGB.G = LRGB.G + (RGBdx.G * fabs(LX));
			RGB.R = LRGB.R + (RGBdx.R * fabs(LX));
		}
		else
		{
			RGB = LRGB;
			ScanStart = LX; //round(LX);
		} 
		
		if(RX - 1 > imgWidth - 1)
			ScanEnd = imgWidth - 1;
		else	ScanEnd = RX - 1; //round(RX) - 1;
		

		// scan the line
		QRgb* scanline = (QRgb*)img->scanLine((int)y);
		for(x = ScanStart; x <= ScanEnd; x++)
		{
			//RGBT.rgbtBlue := trunc(RGB.B);
			//RGBT.rgbtGreen := trunc(RGB.G);
			//RGBT.rgbtRed := trunc(RGB.R);
			//Scan[x] := RGBT;
			
			//p->fillRect(QRectF(x,y,1.,1.),QColor((int)RGB.R, (int)RGB.G, (int)RGB.B));
			scanline[(int)x] = qRgb((int)RGB.R, (int)RGB.G, (int)RGB.B);
			
			RGB.B = RGB.B + RGBdx.B;
			RGB.G = RGB.G + RGBdx.G;
			RGB.R = RGB.R + RGBdx.R;
		}
		
		/// It's faster to fill individual pixel rectangles than to use a liner gradient for the entire line
		/// Testing on my machine showed 1k tri in 7sec with liner gradient, and 1k tri in 4sec with the above for(x) loop

// 		ScanStart = LX; //round(LX);
// 		ScanEnd = RX - 1; //round(RX) - 1;
// 		QLinearGradient gradient(ScanStart, y, ScanEnd, y);
// 		gradient.setColorAt(0.0, QColor((int)LRGB.R, (int)LRGB.G, (int)LRGB.B));
// 		gradient.setColorAt(1.0, QColor((int)RRGB.R, (int)RRGB.G, (int)RRGB.B));
// 		
// 		double length = (ScanEnd - ScanStart);
// 		p->fillRect(QRectF(ScanStart, y, length, 1.), QBrush(gradient)); 
		
		// increment edge x positions
		LX = LX + Ldx;
		RX = RX + Rdx;
		
		// increment edge colours by the y colour increments
		LRGB.B = LRGB.B + LRGBdy.B;
		LRGB.G = LRGB.G + LRGBdy.G;
		LRGB.R = LRGB.R + LRGBdy.R;
		RRGB.B = RRGB.B + RRGBdy.B;
		RRGB.G = RRGB.G + RRGBdy.G;
		RRGB.R = RRGB.R + RRGBdy.R;
	}

	
	Dif1 = points[2].y() - points[1].y();
	if(Dif1 == 0)
		Dif1 = 0.001;
		
	// calculate new increments for region 2
	if(Right)
	{
		Rdx = (points[2].x() - points[1].x()) / Dif1;
		RX  = points[1].x();
		RRGBdy.B = (colors[2].blue()  - colors[1].blue())  / Dif1;
		RRGBdy.G = (colors[2].green() - colors[1].green()) / Dif1;
		RRGBdy.R = (colors[2].red()   - colors[1].red())   / Dif1;
		RRGB.R = colors[1].red();
		RRGB.G = colors[1].green();
		RRGB.B = colors[1].blue();
	}
	else
	{
		Ldx = (points[2].x() - points[1].x()) / Dif1;
		LX  = points[1].x();
		LRGBdy.B = (colors[2].blue()  - colors[1].blue())  / Dif1;
		LRGBdy.G = (colors[2].green() - colors[1].green()) / Dif1;
		LRGBdy.R = (colors[2].red()   - colors[1].red())   / Dif1;
		LRGB.R = colors[1].red();
		LRGB.G = colors[1].green();
		LRGB.B = colors[1].blue();
	}

	// fill region 2
	for(y = points[1].y(); y < points[2].y() - 1; y++)
	{
		// y clipping
		if(y > imgHeight - 1)
			break;
		if(y < 0)
		{
			LX = LX + Ldx;
			RX = RX + Rdx;
			LRGB.B = LRGB.B + LRGBdy.B;
			LRGB.G = LRGB.G + LRGBdy.G;
			LRGB.R = LRGB.R + LRGBdy.R;
			RRGB.B = RRGB.B + RRGBdy.B;
			RRGB.G = RRGB.G + RRGBdy.G;
			RRGB.R = RRGB.R + RRGBdy.R;
			continue;
		}
		
		//Scan := ABitmap.ScanLine[y];
		
		Dif1 = RX - LX + 1;
		if(Dif1 == 0)
			Dif1 = 0.001;
		
		RGBdx.B = (RRGB.B - LRGB.B) / Dif1;
		RGBdx.G = (RRGB.G - LRGB.G) / Dif1;
		RGBdx.R = (RRGB.R - LRGB.R) / Dif1;
		
		// x clipping
		if(LX < 0)
		{
			ScanStart = 0;
			RGB.B = LRGB.B + (RGBdx.B * fabs(LX));
			RGB.G = LRGB.G + (RGBdx.G * fabs(LX));
			RGB.R = LRGB.R + (RGBdx.R * fabs(LX));
		}
		else
		{
			RGB = LRGB;
			ScanStart = LX; //round(LX);
		}
		
		if(RX - 1 > imgWidth - 1)
			ScanEnd = imgWidth - 1;
		else 	ScanEnd = RX - 1; //round(RX) - 1;
		
		// scan the line
		QRgb* scanline = (QRgb*)img->scanLine((int)y);
		for(x = ScanStart; x < ScanEnd; x++)
		{
			//RGBT.rgbtBlue := trunc(RGB.B);
			//RGBT.rgbtGreen := trunc(RGB.G);
			//RGBT.rgbtRed := trunc(RGB.R);
			//Scan[x] := RGBT;
			//p->fillRect(QRectF(x,y,1.,1.),QColor((int)RGB.R, (int)RGB.G, (int)RGB.B));
			scanline[(int)x] = qRgb((int)RGB.R, (int)RGB.G, (int)RGB.B);
			
			RGB.B = RGB.B + RGBdx.B;
			RGB.G = RGB.G + RGBdx.G;
			RGB.R = RGB.R + RGBdx.R;
		}

// 		ScanStart = LX; //round(LX);
// 		ScanEnd = RX - 1; //round(RX) - 1;
// 		QLinearGradient gradient(ScanStart, y, ScanEnd, y);
// 		gradient.setColorAt(0.0, QColor((int)LRGB.R, (int)LRGB.G, (int)LRGB.B));
// 		gradient.setColorAt(1.0, QColor((int)RRGB.R, (int)RRGB.G, (int)RRGB.B));
// 		
// 		double length = (ScanEnd - ScanStart);
// 		p->fillRect(QRectF(ScanStart, y, length, 1.), QBrush(gradient));
// 		
		
		LX = LX + Ldx;
		RX = RX + Rdx;
		
		LRGB.B = LRGB.B + LRGBdy.B;
		LRGB.G = LRGB.G + LRGBdy.G;
		LRGB.R = LRGB.R + LRGBdy.R;
		RRGB.B = RRGB.B + RRGBdy.B;
		RRGB.G = RRGB.G + RRGBdy.G;
		RRGB.R = RRGB.R + RRGBdy.R;
	}
	
	return true;
}

MainWindow::MainWindow()
	: QWidget()
{
	// Setup the layout of the window
	QVBoxLayout *vbox = new QVBoxLayout(this);
	vbox->setContentsMargins(0,0,0,0);
	
	QImage img(640,480,QImage::Format_ARGB32_Premultiplied);
	 
	QPainter p(&img);
	
	p.fillRect(img.rect(), Qt::black);
	
	QList<QColor> colors = QList<QColor>()
		<< Qt::red
		<< Qt::green
		<< Qt::blue;
		
	double imgWidth  = img.width();
	double imgHeight = img.height();


	QVector<QPointF> points = QVector<QPointF>()
		<< QPointF(10, 10)
		<< QPointF(imgWidth - 10, imgHeight / 2)
		<< QPointF(imgWidth / 2,  imgHeight - 10);

// 	p.setBrush(colors[0]);
// 	//p.setPen(colors[1]);
// 	p.drawConvexPolygon(points);

	fillTriColor(&img, points, colors);
	
	#define randRange(range) ( range * ((float) rand () / RAND_MAX) )
	
	QTime t;
	t.start();
	int max = 1000;
	for(int i=0; i<max; i++)
	{
		QVector<QPointF> points = QVector<QPointF>()
			<< QPointF(randRange(imgWidth), randRange(imgHeight))
			<< QPointF(randRange(imgWidth), randRange(imgHeight))
			<< QPointF(randRange(imgWidth), randRange(imgHeight));
		
		fillTriColor(&img, points, colors);
	}
	
	int time = t.elapsed();
	
	double msPerTri = (double)time / (double)max;
	qDebug() << "ms per tri: "<<msPerTri<<", total time elapsed for"<<max<<"triangles:"<<time; 
	
	img.save("sample.jpg");

	QLabel *label = new QLabel();
	label->setPixmap(QPixmap::fromImage(img));
	vbox->addWidget(label);
	
	// Adjust window for a 4:3 aspect ratio
	resize(640, 480);
}
_______________________________________________
Interest mailing list
Interest@qt-project.org
http://lists.qt-project.org/mailman/listinfo/interest

Reply via email to