Fast Line Drawing
A recent post by Simo Santavirta featured an algorithm with an irresistible name: Extremely Fast Line Argorithm. A comment on that article piqued my interest and I decided to test the algorithm out against Adobe’s Graphics.lineTo.
I did a little formatting on the efla function and one substantive tweak to take the BitmapData to draw on as a function parameter. One bit that is different about the AS3 version from the original version by Po-Han Lin is the replacement of Math.abs with a well-known bitwise trick for AS3. The static Math.abs call would certainly be a performance killer.
For the competition, I simply included a Graphics.moveTo/Graphics.lineTo pair and drew the result to a Shape. See here for why Shape is better than Sprite in simple cases like these. I’ve also included test cases at various stage qualities, which affects the lineTo drawing. At the end of the line drawing loop I record the time elapsed, draw the result to a BitmapData, and record the time elapsed again. The reason for the draw to a BitmapData is to achieve parity with the efla algorithm, since it results in lines drawn to a BitmapData. Unfortunately, there is no possibility of converting the efla output to vector graphics to achieve parity the other way. This makes the Graphics.lineTo more flexible overall. But is it faster? We shall see after the test code:
package { import flash.display.*; import flash.text.*; import flash.utils.*; /** * A test comparing the performance of the "Extremely Fast Line Algorithm" (EFLA) to * @author Jackson Dunstan (EFLA by Po-Han Lin, AS3 port by Simo Santavirta) */ public class LineDrawingTest extends Sprite { public function LineDrawingTest() { var logger:TextField = new TextField(); logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); function log(msg:*): void { logger.appendText(msg + "\n"); } var bmd:BitmapData = new BitmapData(1000, 1000, false, 0xffffff); const NUM_ITERATIONS:int = 3000; var i:int; var beforeTime:int; var lineToTime:int; var shape:Shape = new Shape(); var graphics:Graphics = shape.graphics; graphics.lineStyle(1, 0x00ff00, 1); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { efla(0, 0, 999, 999, 0x00ff00, bmd); } log("efla: " + (getTimer()-beforeTime)); stage.quality = StageQuality.LOW; beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { graphics.moveTo(0, 0); graphics.lineTo(999, 999); } lineToTime = getTimer() - beforeTime; bmd.draw(shape); log("(low) lineTo vector: " + lineToTime + ", bitmap: " + (getTimer()-beforeTime)); stage.quality = StageQuality.MEDIUM; beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { graphics.moveTo(0, 0); graphics.lineTo(999, 999); } lineToTime = getTimer() - beforeTime; bmd.draw(shape); log("(medium) lineTo vector: " + lineToTime + ", bitmap: " + (getTimer()-beforeTime)); stage.quality = StageQuality.HIGH; beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { graphics.moveTo(0, 0); graphics.lineTo(999, 999); } lineToTime = getTimer() - beforeTime; bmd.draw(shape); log("(high) lineTo vector: " + lineToTime + ", bitmap: " + (getTimer()-beforeTime)); stage.quality = StageQuality.BEST; beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { graphics.moveTo(0, 0); graphics.lineTo(999, 999); } lineToTime = getTimer() - beforeTime; bmd.draw(shape); log("(best) lineTo vector: " + lineToTime + ", bitmap: " + (getTimer()-beforeTime)); } /** * "Extremely Fast Line Algorithm" * @author Po-Han Lin (original version: http://www.edepot.com/algorithm.html) * @author Simo Santavirta (AS3 port: http://www.simppa.fi/blog/?p=521) * @author Jackson Dunstan (minor formatting) * @param x X component of the start point * @param y Y component of the start point * @param x2 X component of the end point * @param y2 Y component of the end point * @param color Color of the line * @param bmd Bitmap to draw on */ private function efla(x:int, y:int, x2:int, y2:int, color:uint, bmd:BitmapData): void { var shortLen:int = y2-y; var longLen:int = x2-x; if ((shortLen ^ (shortLen >> 31)) - (shortLen >> 31) > (longLen ^ (longLen >> 31)) - (longLen >> 31)) { shortLen ^= longLen; longLen ^= shortLen; shortLen ^= longLen; var yLonger:Boolean = true; } else { yLonger = false; } var inc:int = longLen < 0 ? -1 : 1; var multDiff:Number = longLen == 0 ? shortLen : shortLen / longLen; if (yLonger) { for (var i:int = 0; i != longLen; i += inc) { bmd.setPixel(x + i*multDiff, y+i, color); } } else { for (i = 0; i != longLen; i += inc) { bmd.setPixel(x+i, y+i*multDiff, color); } } } } }
Now for the results:
Environment | efla | lineTo (low) | lineTo (medium) | lineTo (high) | lineTo (best) |
---|---|---|---|---|---|
3.0 Ghz Intel Core 2 Duo, 4 GB RAM, Windows XP | 64 | 1 vector/79 bitmap | 1 vector/325 bitmap | 1 vector/2588 bitmap | 1 vector/5403 bitmap |
UPDATE: The above test should call graphics.clear()
between each lineTo
test. For alternative results, see a comment below.
Since the lineTo calls simply queue drawing for later, it is natural to expect them to execute extremely quickly. As we see with the different stage quality settings, the bitmap drawing takes longer and longer as we raise the quality while the vector time remains constant. So a fair evaluation of the lineTo performance is really the time to draw to the bitmap since that, as previously stated, brings the two tests into parity with an equivalent output. Given that, we can easily see that the efla algorithm is indeed a good deal quicker than its closest equivalent: lineTo on LOW stage quality settings. The difference becomes wide even at MEDIUM stage quality and extreme on HIGH and BEST quality settings. So, if you need a really fast and basic line drawing algorithm, you may as well have a look at efla as it truly is an “Extremely Fast Line Algorithm”.
#1 by simppa on December 4th, 2009 ·
It’s beatable. You should always lock and unlock bitmapData when drawing on it. That will increase the gap between performance results of these line draw methods :)
Thanks for noticing the algo.
#2 by jackson on December 4th, 2009 ·
Locking the BitmapData only means that Bitmap objects using it will not be updated. In my test I am not using any Bitmap objects so the lock is an unnecessary slowdown. I actually get 70-71 milliseconds instead of 64 milliseconds on the same test machine if I include a lock/unlock pair in the test.
#3 by simppa on December 5th, 2009 ·
Ah, ok. Better this way.
#4 by benji on March 12th, 2010 ·
not sure if its relevant, but if i remember rightly the graphics methods are not affected much by the distance of the line drawn..where as any bitmap method would gain a performance boost for shorter differences..
do a find and replace for “999” and change it to “100”
you should here really see the benifits ;)
#5 by jackson on March 12th, 2010 ·
On a 2.0 Ghz Intel Core 2 Duo, 2 GB RAM, Mac OS X 10.6:
So yes, it does seem as though there is a fair amount of overhead. It seems safe to say that if you want to draw a lot of little lines and can live with the reduced feature set (eg. solid colors), you’ll get a very large (6x minimum) speedup by using a BitmapData-based algorithm.
Thanks for the tip!
#6 by limeyd on August 12th, 2010 ·
Awesome tip, why not swap out the setPixels for copypixels or draw, not as good as photoshop but still rocking
#7 by jackson on August 12th, 2010 ·
It’s not my line-drawing algorithm, but what would I copy/draw from?
#8 by skyboy on March 1st, 2011 ·
I decided to optimize this a bit, and the results I got were with my update being consisitantly 55% faster (145 ms vs 323 ms), thought I’d share.
https://github.com/skyboy/AS3-Utilities/blob/master/skyboy/utils/efla.as
#9 by jackson on March 1st, 2011 ·
Cool! This will definitely revitalize
efla
after the (somewhat) lost ground in Flash Player 10.2.#10 by Robert W on April 29th, 2011 ·
Hi guys,
This is great stuff, but I’ve been trying to look around for this EFLA line generator, is there anyway you can control the stroke or thickness of the line? Otherwise it’s pretty much no wonder why its faster right? Because you can only control the color but not the style of it like lineStyle does? Please advice. Thanks.
#11 by jackson on April 29th, 2011 ·
Nope,
efla
doesn’t have those features so I tested it against a simple 1-pixel stroke.Also, check out the performance update in Flash Player 10.2.
#12 by Robert W on April 29th, 2011 ·
right. what a shame… ;( Anyway thanks for that. I just need the performance as well as the look. But the look might be number 1. So back to lineStyle i guess.. Thanks for your help.
#13 by Russ on September 11th, 2011 ·
Anyone landing here looking for how to draw fast lines should definitely look into flash.display.Graphics.drawPath, which essentially bundles up a series of lineTo calls and avoid a lot of function overhead:
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/Graphics.html#drawPath()
Flash >= 10 only, and watch out for transparency issues (if you care) as described very well here:
http://www.flashandmath.com/howtos/pathalpha/
#14 by zozo on January 21st, 2016 ·
Hmm..
Test is incorrect.
Where’s the call ‘graphics.clear()’ before each new test?
After LOW test – graphics contains 3000 (NUM_ITERATIONS) vector lines..
..after MEDIUM – 6000 vector lines..
..HIGH – 9000 lines..
..and finally, we have 12000 VECTOR lines in Shape.graphics!
#15 by jackson on January 21st, 2016 ·
Good catch! Do you get different results when you put in the
graphics.clear()
calls?#16 by zozo on January 22nd, 2016 ·
efla: 47
(low) lineTo vector: 0, bitmap: 31
(medium) lineTo vector: 0, bitmap: 47
(high) lineTo vector: 15, bitmap: 109
(best) lineTo vector: 0, bitmap: 125
And, without ‘Math.round()’ line looks bad:
#17 by zozo on January 22nd, 2016 ·
Sorry, the code above It is not displayed correctly. Perhaps because of the html formatting.
#18 by jackson on January 22nd, 2016 ·
No problem. It is the HTML formatter, but I cleared it up and applied the syntax highlighting.
It looks like your results were broadly similar to the results I got way back in 2009, obviously with a different testing environment (machine, OS, Flash, browser, etc.). The only weird one is the “high” setting which is way more expensive than the others, even “best” which should be slower. Perhaps that’s just noise in the test results. In any case, it seems like the
graphics.clear()
call didn’t change the results much.#19 by zozo on January 23rd, 2016 ·
“..In any case, it seems like the graphics.clear() call didn’t change the results much.”
I don’t agree with this. The difference is very noticeable:
efla: 47
(low) lineTo vector: 0, bitmap: 31
(medium) lineTo vector: 0, bitmap: 47
(high) lineTo vector: 0, bitmap: 109
(best) lineTo vector: 0, bitmap: 125
vs
efla: 64
(low) lineTo vector: 1, bitmap: 79
(medium) lineTo vector: 1, bitmap: 325
(high) lineTo vector: 1, bitmap: 2588
(best) lineTo vector: 1, bitmap: 5403
It is possible to change the table with the results? To avoid the fear of quality :)
P.S. Some mistakes in the code still there.
#20 by jackson on January 23rd, 2016 ·
I’ve updated the article with a link to your comment with these results. It’s definitely a flatter curve for
lineTo
on the bitmap side, so it seems like either thegraphics.clear()
or some other change from the original test (e.g. Flash player version) made a big difference.#21 by Visitor on July 27th, 2021 ·
That algorithm seems flawed e.g: (0,0) – (500,499) and uploader seems suspicious.
https://groups.google.com/g/comp.graphics.algorithms/c/-L0BaCgCn9M/m/hxyxl5JErqQJ
Uploader made a huge flame at google groups
https://groups.google.com/g/comp.graphics.algorithms/c/0L5MGm7kVVY
And someone made a timeline of the happening. (Archived)
http://web.archive.org/web/20041001000000*/http://www.xs4all.nl/~marcone/pohan-efla.html
#22 by Future Me on April 21st, 2022 ·
“Here is a extremely fast line algorithm. Only one division per pixel plot, with one loop that only increments exactly the number of times needed to plot the line. Can anyone beat it? (in speed)”
That’s how the epic has started 20 years ago. It still amazes me.