Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411629 Posts in 69392 Topics- by 58447 Members - Latest Member: sinsofsven

May 11, 2024, 01:02:21 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Pixel-precise drawing with Actionscript
Pages: [1]
Print
Author Topic: Pixel-precise drawing with Actionscript  (Read 4623 times)
raigan
Level 5
*****


View Profile
« on: January 14, 2010, 04:00:07 PM »

I'm having some problems while drawing lines via Actionscript; the result is meant to look like pixel art, so I've turned off anti-aliasing, however this is causing some graphical glitches where things are rounded incorrectly.

Examples of the errors I'm seeing are e.g a polygon defined by a chain of points connected by lines has holes at the corners (i.e empty gaps between where one line ends and the next starts), or 45deg lines having a slight "shift" partway along their length rather than being a perfect chain of diagonally-adjacent pixels.

I can't seem to find anything via Google on how to get pixel-perfect aliased line drawing working in Flash, and no matter what I try (shifting coordinates by half a pixel, etc) nothing works.

Does anyone have any advice/experience with this sort of thing?
Logged
Ivan
Owl Country
Level 10
*


alright, let's see what we can see


View Profile
« Reply #1 on: January 14, 2010, 04:07:27 PM »

use BitmapData?
Logged

http://polycode.org/ - Free, cross-platform, open-source engine.
ChevyRay
Guest
« Reply #2 on: January 14, 2010, 04:25:18 PM »

You mean you're trying to get it to look like this?

In that example there, I just have a BitmapData buffer that I clear every frame, and then re-draw the line pixel-by-pixel using a version of the Bresenham line-drawing algorithm.

Depending on how your game works, you'd want to change this setup. For example, in that one I've got there, there's a lot of blank canvas just sitting around. But if I had lots of objects drawing lines all over the place at the same depth, it wouldn't be a problem and would work just fine. But if I had something that drew just a still line (not one that moved around like that), I'd probably just want to create a single bitmap just big enough to contain it, and then draw the line on that bitmap and place it on the stage.
« Last Edit: January 14, 2010, 04:28:34 PM by ChevyRay » Logged
raigan
Level 5
*****


View Profile
« Reply #3 on: January 14, 2010, 06:00:12 PM »

I am using bitmapData, I should have mentioned; basically I'm generating a bunch of iso-style sprites by drawing lines into a bitmap then flood-filling.

Of course, when there are gaps in the corners between lines the flood-filling breaks, and even when that doesn't happen nothing is the perfect straight-diagonal that you see in pixel-art.. there are ugly places where the line shifts down by a pixel.

@Chevy: if you drew a rhombus out of four such lines, could you guarantee that the corners all met? If so then that could be a pretty good option.. I was just trying to get something simple+fast working.

Since I only need lines at 45deg angles I can probably get away with something much simpler than Bresenham.. but this is turning into much more work than I thought Sad

Thanks.
Logged
st33d
Guest
« Reply #4 on: January 15, 2010, 04:44:03 AM »

Flash's stroke tries to draw between pixel positions. I discovered this when trying to design a menu system with boxes that have outlines. The outlining stroke would always be off the pixel. I could only get an on-pixel result by setting the stroke width to 2.

Have you tried drawing the lines and adding 0.5 to the positions? I haven't tried this myself mind, I went with no stroke boxes drawn on top of each other in the end.

(just read the first post again  Facepalm)
« Last Edit: January 15, 2010, 05:39:12 AM by st33d » Logged
cougarten
Level 0
**


View Profile
« Reply #5 on: January 15, 2010, 05:12:38 AM »

If you don't use filters and other effects, try setting the quality of your swf to low ( _quality = "LOW";)this might get you just what you wanted, but test how movement will look, resizing and tilting might get ugly because of pixel doubling and such.

Also round your coordinates to full pixels so the shapes won't begin between pixels (at least for pixel fonts /bitmaps very important).

since flash is vector based, prebuild pixel grafics might be a very good Idea if the content does not have to be generated on runtime.

hope that helps,

greets,
me
Logged
st33d
Guest
« Reply #6 on: January 15, 2010, 05:15:15 AM »

Ah, don't bother with adding small fractions to the coords. I really have no idea what's going on in the line drawing algorithm that Flash uses:

Code:
package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.StageQuality;
import flash.events.Event;

[SWF(width = "640", height = "480", frameRate="30", backgroundColor = "#CCCCCC")]

/**
* ...
* @author Aaron Steed, robotacid.com
*/
public class Main extends Sprite {

public function Main():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}

private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
var adjust:Number = -0.0;
stage.quality = StageQuality.LOW;
graphics.lineStyle(1, 0);


// wtf?
graphics.moveTo(0, 4);
graphics.lineTo(5, -1);


/*graphics.lineTo(9+adjust, 4+adjust);
graphics.lineTo(4+adjust, 9+adjust);
graphics.lineTo(0+adjust, 4+adjust);*/
var bitmap:Bitmap = new Bitmap(new BitmapData(20, 20, true, 0xffffff00));
bitmap.bitmapData.draw(this);
addChild(bitmap);
bitmap.x = bitmap.y = 20;
bitmap.scaleX = bitmap.scaleY = 8;
}

}

}

Seriously? I have to draw to (5, -1) to get a pixel on (0, 4), what the hell?
Logged
ஒழுக்கின்மை (Paul Eres)
Level 10
*****


Also known as रिंकू.


View Profile WWW
« Reply #7 on: January 15, 2010, 05:18:08 AM »

have you tried creating a single dot with two partially transparent dots on either side of it, and drawing that stretched? that's an old trick that works in game maker. maybe it'll work in flash too.
Logged

st33d
Guest
« Reply #8 on: January 15, 2010, 05:36:01 AM »

Fuck it.

Code:
package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.StageQuality;
import flash.events.Event;

[SWF(width = "640", height = "480", frameRate="30", backgroundColor = "#CCCCCC")]

/**
* ...
* @author Aaron Steed, robotacid.com
*/
public class Main extends Sprite {

public function Main():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}

private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
var adjust:Number = -0.0;
stage.quality = StageQuality.LOW;
graphics.lineStyle(1, 0);


// wtf?
/*graphics.moveTo(0, 4);
graphics.lineTo(5, -1);*/



/*graphics.lineTo(9+adjust, 4+adjust);
graphics.lineTo(4+adjust, 9+adjust);
graphics.lineTo(0+adjust, 4+adjust);*/
var bitmap:Bitmap = new Bitmap(new BitmapData(20, 20, true, 0xffffff00));
bitmap.bitmapData.draw(this);
addChild(bitmap);
bitmap.x = bitmap.y = 20;
bitmap.scaleX = bitmap.scaleY = 8;

Bresenham.line(0, 4, 4, 0, bitmap.bitmapData, 0xFFFF0000);
Bresenham.line(5, 0, 9, 4, bitmap.bitmapData, 0xFFFF0000);
Bresenham.line(9, 4, 5, 8, bitmap.bitmapData, 0xFFFF0000);
Bresenham.line(4, 8, 0, 4, bitmap.bitmapData, 0xFFFF0000);
}

private function loop(e:Event = null):void{
}

}

}

Code:
package {
import flash.display.BitmapData;

/**
* ...
* @author steed
*/
public class Bresenham {

private static var xd:int, yd:int;
private static var i:int, x:int, y:int;
private static var octant:int;
private static var error:int;
private static var tempx:int, tempy:int;

public function Bresenham() {

}
// draws a line from x1,y1 to x2,y2
public static function line(x1:Number, y1:Number, x2:Number, y2:Number, bitmap:BitmapData, col:uint):void{
xd = x2 - x1;
yd = y2 - y1;
octant = -1;
// establish octant:
// imagine an eight spoke wheel, each area between the spokes requires different rules to scan
// so counting round from below the east spoke clockwise we see what area or octant we are in
if (x2 > x1 && y2 > y1 && Math.abs(yd) < Math.abs(xd)) octant = 0;
if (x2 > x1 && y2 > y1 && Math.abs(yd) > Math.abs(xd)) octant = 1;
if (x2 < x1 && y2 > y1 && Math.abs(yd) > Math.abs(xd)) octant = 2;
if (x2 < x1 && y2 > y1 && Math.abs(yd) < Math.abs(xd)) octant = 3;
if (x2 < x1 && y2 < y1 && Math.abs(yd) < Math.abs(xd)) octant = 4; // reversal of octant 0
if (x2 < x1 && y2 < y1 && Math.abs(yd) > Math.abs(xd)) octant = 5; // reversal of octant 1
if (x2 > x1 && y2 < y1 && Math.abs(yd) > Math.abs(xd)) octant = 6; // reversal of octant 2
if (x2 > x1 && y2 < y1 && Math.abs(yd) < Math.abs(xd)) octant = 7; // reversal of octant 3
// bresenham works climbing a gradient inaccurately, this inaccuracy is rectified by the error
// variable - the payoff being that we stick to integers and get a whopping speed increase
error = 0;
//flip co-ords of octants above 3
if (octant > 3){
tempx = x1;
tempy = y1;
x1 = x2;
y1 = y2;
x2 = tempx;
y2 = tempy;
xd = x2 - x1;
yd = y2 - y1;
}
x = x1;
y = y1;
if(octant == 4 || octant == 0){
y = y1;
for (x = x1; x <= x2; x++){
bitmap.setPixel32(x, y, col);

if((error + yd)<<1 < xd){
error += yd;
} else {
y++;
error += yd - xd;
}
}
} else if(octant == 5 || octant == 1){
x = x1;
for (y = y1; y <= y2; y++){
bitmap.setPixel32(x, y, col);

if((error + xd)<<1 < yd){
error += xd;
} else {
x++;
error += xd - yd;
}
}
} else if(octant == 6 || octant == 2){
x = x1;
xd = Math.abs(xd);
for (y = y1; y <= y2; y++){
bitmap.setPixel32((x1-(x-x1)), y, col);

if((error + xd)<<1 < yd){
error += xd;
} else {
x++;
error += xd - yd;
}
}
} else if(octant == 7 || octant == 3){
y = y1;
yd = Math.abs(yd);
xd = Math.abs(xd);
for (x = x2; x <= x1; x++){
bitmap.setPixel32((x1-(x-x2)), y, col);

if((error + yd)<<1 < xd){
error += yd;
} else {
y++;
error += yd - xd;
}
}
} else {
//line must be on division of octants
// horizontal
if (y1 == y2){
y = y1;
if (x1 > x2){
tempx = x1;
x1 = x2;
x2 = tempx;
}
for(x = x1; x <= x2; x++){
bitmap.setPixel32(x, y, col);

}
}
// vertical
if (x1 == x2){
x = x1;
if (y1 > y2){
tempy = y1;
y1 = y2;
y2 = tempy;
}
for(y = y1; y <= y2; y++){
bitmap.setPixel32(x, y, col);

}
}
// diagonal
if (Math.abs(yd) == Math.abs(xd)){
x = x1;
y = y1;
for(i = 0; i <= Math.abs(xd); i++){
bitmap.setPixel32(x, y, col);

if (x2 > x1){
  x++;
} else {
  x--;
}
if (y2 > y1){
  y++;
} else {
  y--;
}
}
}
}
}
}

}

A solution in it's full ugly glory. The Bresenham class could be optimised, but if you're drawing a bunch of iso cubes, then you could simply create a template and copyPixel that around.
Logged
raigan
Level 5
*****


View Profile
« Reply #9 on: January 15, 2010, 06:13:28 AM »

Wow, thanks! I'm just sitting down to pull apart the world-drawing code right now Smiley

I guess Adobe/MM assumed that people would be using high quality most of the time and wouldn't care about pixel accuracy when drawing lines Sad
« Last Edit: January 15, 2010, 06:16:47 AM by raigan » Logged
st33d
Guest
« Reply #10 on: January 15, 2010, 07:50:04 AM »

I knew I'd find a practical use for that class someday.
Logged
raigan
Level 5
*****


View Profile
« Reply #11 on: January 15, 2010, 01:05:46 PM »

Woo, it's working! Thanks again; it does seem pretty stupid that I have to hand-plot pixels when there's a powerful drawing API built in.. curse you, Adobe!

Of course, generating the test graphics was the easy part.. Smiley
Logged
jotapeh
Level 10
*****


View Profile
« Reply #12 on: January 15, 2010, 06:43:54 PM »

I am using bitmapData, I should have mentioned; basically I'm generating a bunch of iso-style sprites by drawing lines into a bitmap then flood-filling.

I know you already have a solution, but here is a thought:

If you wanted the softer, anti-aliased lines - but also wanted the flood fill - you could basically just draw a polygon and use the beginFill() and endFill() calls to fill it.

Obviously this may not be the look you are going for, but might be a fun look to play with.
Logged
Chromanoid
Level 10
*****



View Profile
« Reply #13 on: January 16, 2010, 03:56:32 PM »

I am using bitmapData, I should have mentioned; basically I'm generating a bunch of iso-style sprites by drawing lines into a bitmap then flood-filling.

maybe it would be better to draw filled polygons instead of/in addition to drawing lines.
you could  port this
http://alienryderflex.com/polygon_fill/
easily to as3.
Logged
raigan
Level 5
*****


View Profile
« Reply #14 on: January 17, 2010, 06:34:04 AM »

@jotapeh: I definitely want to try an iso-style world with smooth/vector art sometime, but this project has to be bitmap-based for a few reasons.

@Chromanoid: Actually, I'm using the flood-fill approach specifically to avoid having to explicitly form polygonal data (i.e CCW list of verts). This is because Flash can't draw polygons with holes, and I need to draw lots of polygons with holes which are procedurally placed, so to draw them as actual polygons (which would require adding edges to join the hole contours with the outside contour, making sure the edges don't cross other holes, etc) seemed like a lot more work than just drawing the polygon edges and floodfilling Smiley
« Last Edit: January 17, 2010, 10:28:24 AM by raigan » Logged
Glaiel-Gamer
Guest
« Reply #15 on: January 19, 2010, 08:45:01 PM »

plus there's a built in floodfill in the bitmap data class, and unless they did something retarded (i wouldn't put it past adobe) I can't see how it couldn't be at least as fast as one you'd write in actionscript
Logged
st33d
Guest
« Reply #16 on: January 20, 2010, 01:48:45 AM »

The bitmapdata functions are lightning fast, they're compiled methods. Writing your own floodfill in Flash is a bit silly.
Logged
Chromanoid
Level 10
*****



View Profile
« Reply #17 on: January 20, 2010, 04:56:17 AM »

plus there's a built in floodfill in the bitmap data class, and unless they did something retarded (i wouldn't put it past adobe) I can't see how it couldn't be at least as fast as one you'd write in actionscript
that is true, but in my opinion it is more comfortable to have a polygon render algorithm (when you can afford the possible performance loss). especially if you want to do the following which i had to do a while ago: you have a polygon mask that should delete parts of an image with unpredictable content in an aliased way.

Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic