Fast line drawing


#1

In the project I’m working on, I need a lot (hundreds) of lines of variable thickness to be drawn on the screen. I noticed that the painting becomes excessively slow when using Graphics::drawLine(). I managed to write some code that is exactly 10x (!) faster than JUCE’s drawLine() function, but which has 3 drawbacks:

  1. the line thickness is actually always twice than the thickness parameter I am passing
  2. the ends of the lines are not properly drawn like in JUCE
  3. it seems that the lines’ visual thickness changes a bit when the angle changes.

Jules, would it be possible to create a faster drawLine() function in JUCE? Right now you’re creating a Path and filling it, but I’m sure it can be done much faster.

Anyway, for reference, here’s my code:

[code]// for lineThickness, pass a value that is HALF of the actual thickness
void MyRenderer::drawLineFast(int x0, int y0, int x1, int y1, int lineThickness)
{
int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1;
int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1;

int j1=1-lineThickness;
int j2=lineThickness;

if (dx>=dy)
{
	float y=float(y0);
	float yAdd=float(dy)/float(dx)*float(sy);
	while (x0!=x1)
	{
		x0+=sx;
		y+=yAdd;
		int iY=int(y);

		float k=y-float(iY);

		blendPixelInternal2(x0, iY+j1-1, alpha*(1.0f-k));
		for (int j=j1; j<j2; j++)
			blendPixelInternal2(x0, iY+j, alpha);
		blendPixelInternal2(x0, iY+j2, alpha*k);
	}
}
else
{
	float x=float(x0);
	float xAdd=float(dx)/float(dy)*float(sx);
	while (y0!=y1)
	{
		y0+=sy;
		x+=xAdd;
		int iX=int(x);
		float k=x-float(iX);
		blendPixelInternal2(iX+j1-1, y0, alpha*(1.0f-k));
		for (int j=j1; j<j2; j++)
			blendPixelInternal2(iX+j, y0, alpha);
		blendPixelInternal2(iX+j2, y0, alpha*k);
	}
}

}

void MYRenderer::blendPixelInternal2(int x0, int y0, uint8 alpha_)
{
if (x0>=clipLeft && x0<clipRight && y0>=clipTop && y0<clipBottom)
{
uint8 data = buf+ x0pixelStride + y0*lineStride;
uint8 b_=255-alpha_;
data[0]=((uint16(data[0])b_+balpha_)>>8);
data[1]=((uint16(data[1])b_+galpha_)>>8);
data[2]=((uint16(data[2])b_+ralpha_)>>8);
}
}[/code]


#2

If alpha_ == 255 and b == 255 you get

( 255 *255 ) / 256 = 254

You're getting 254 instead of the correct value 255 (output = input). Although this might not matter for your purposes.

Since you're looking for performance it is possible to replace a multiply with a subtraction:

[code]
data[0] + (((b - data[0]) * alpha) >> 8)
[/code]

If alpha_ == 255 and b == 255 you get

( 255 *255 ) / 256 = 254

You’re getting 254 instead of the correct value 255 (output = input). Although this might not matter for your purposes.

Since you’re looking for performance it is possible to replace a multiply with a subtraction:

data[0] + (((b - data[0]) * alpha) >> 8)

#3

Yeah, in the old days I used to have a non-path based line drawing method, but the inaccuracies at the ends caused problems. It was also important to make sure that the line-drawing looks the same on all platforms (e.g. pixel-for-pixel equivalent to CoreGraphics and other toolkits)

If you can come up with a faster algorithm that produces exactly the same output as the existing method, then that’d be great! But it’d need to be a replacement for the existing method - there’s no way I’d agree to bloat the class with extra methods like “drawLineFast”. (It’d also have to cope with gradient and image fills!)


#4

Thanks for your suggestion, Vinn.

I’ve now also written my own drawImageAt() code and it’s 2.5 to 5x faster than JUCE (for a 60x60 ARGB Image on RGB).

Well I’m more in the search of specialised methods that do what I need in an extremely fast way. I’m not here to rewrite JUCE, but I rather add extensions to it for my needs. Believe it or not, I could go from 1fps (JUCE) to 20fps (my renderer) by using the 2 new functions I did. And that’s exactly why I have been asking so often about all this pluggable renderer stuff.


#5

Zamrate, are you willing to share your speedy DrawImageAt method, is this only for DrawImageAt or also the other DrawImage methods? Like for you speed is an issue for us as well.


#6

Sorry, can’t do that for a few reasons. It’s closed-source (for a company) and it also requires some mods in JUCE, and I’m not even sure if it would work with the latest JUCE anymore.