I am using the function al_draw_rectangle to create a rectangle from (200, 150) to (449, 349). The rectangle's width is 250 pixels, and the height is 200 pixels. Here is the drawing code:
//test draw void draw() { al_draw_filled_rectangle(0, 0, 640, 480, al_map_rgb(255, 255, 255)); al_draw_rectangle(200, 150, 200+250-1, 150+200-1, al_map_rgb(0, 0, 0), 1); }
However, when I zoom into the picture, the rectangle starts from (199, 149) (the cursor is over the red pixel):
{"name":"Wvqcv.png","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/6\/a\/6a6255b35cf08de2c944c948838070f7.png","w":1280,"h":998,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/6\/a\/6a6255b35cf08de2c944c948838070f7"}
The A5 manual says:
The basic rule that determines which pixels are associated with which shape is then as follows: a pixel is treated to belong to a shape if the pixel's center is located in that shape.
However, the center of pixel (199, 149) is not inside the shape. The center of this pixel is (199.5, 149.5), which is outside of the range (200.0-201.0, 150.0-151.0).
What gives? is this normal? is it an A5 bug?
If I use thickness = 0, then the rectangle still starts from the pixel (199, 149), with the exception that the pixel at that specific location is not drawn.
Outlined versions need the 0.5 offsets. See the second diagram + code here: http://www.allegro.cc/manual/5/primitives.html.
So, when I draw an outlined shape, I need to add 0.5 to all the coordinates.
What about filled shapes? I draw a filled rectangle from (200,150) to (449, 349), but the rectangle is drawn up to (448, 348):
{"name":"eupaR.png","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/e\/2\/e2d2235e40e6dfff9015881b421ef495.png","w":1280,"h":998,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/e\/2\/e2d2235e40e6dfff9015881b421ef495"}
So I guess at for filled shapes, I need to add the offset 0.5 to the right and bottom coordinates. Am I correct?
No, there is no offset for filled shapes. And outlined shapes also are filled - they simply specify the center and not the outer outline. So if you use a thickness of 1 that means the filled shape extends 0.5 in all directions and the offset is only to counter that - but in general there never is any extra offset.
I think it is best to imagine what happens exactly instead of memorising the offsets. Study the second diagram + code in the link I gave you and it should be apparent why the output you get is what you get.
The short version is that integer pixel coordinates refer to the top-left corner of the pixel and not its centre.
I think it is best to imagine what happens exactly instead of memorising the offsets. Study the second diagram + code in the link I gave you and it should be apparent why the output you get is what you get.
I have studied both diagrams and read the relevant documentation page many times, but I can't seem to pinpoint the exact rules of drawing.
So, let me write down the complete set of rules of drawing:
if the shape is outlined, then the coordinates refer to the pixel center.
if the shape is not outlined, then the coordinates refer to the pixel top-left corner.
a pixel is drawn if its center is within the shape coordinates.
Am I correct? are the above the only rules I need to remember for drawing?
They always refer to the exact position (with 0.0/0.0 being the top-left corner of the top-most and left-most pixel). But when you draw an outlined shape, say the red line in the diagram from (2, 1) to (6, 1) then you need to take the thickness into account. It has a thickness of 2, so you really draw a rectangle from (2, 1 - 1) to (6, 1 + 1) so a rectangle from (2, 0) to (6, 2). Any any pixels who's pixel centers are within that mathematical shape will be lit.
[edit: thickness of al_draw_line only extends along the normal, fixed my numbers ]
Am I correct? are the above the only rules I need to remember for drawing?
No. Understanding what pixels are drawn given the inputs you give to the functions is a two step process.
Understanding how the actual polygon is formed
Understanding what pixels are drawn given the actual polygon
Combining the two steps into one rule is possible, but only after you understand the two steps above.
For a filled rectangle, the actual polygon is formed exactly from the coordinates you pass to the function:
al_draw_filled_rectangle(0, 0, 100, 100, c)
will produce a rectangular polygon that spans from (0, 0) to (100, 100). Now given that the coordinate system is such that integer coordinates refer to the top left corner of the pixel, this means that pixels 0 through 99 will be shaded in a horizontal line. Pixel 100 has its center at 100.5 which is outside the polygon.
For an outlined rectangle, you have to imagine two borders being formed, the outer one and the inner one: the only pixels that will be drawn are those inside the outer border and outside the inner one. Both borders are in effect offset from the rectangle you pass by thickness/2. I.e. this call:
al_draw_rectangle(0, 0, 100, 100, c, 1)
Will have an inner border of (0.5, 0.5) to (99.5, 99.5) and an outer border of (-0.5, -0.5, 100.5, 100.5). Those 0.5's are bad because it means that the borders lie on the centers of the pixels. What happens in that case is implementation defined. To avoid that, you introduce an offset:
al_draw_rectangle(0.5, 0.5, 99.5, 99.5, c, 1)
This call will have these borders: inner: (1, 1, 99, 99), outer: (0, 0, 100, 100). Now you can see that the pixels that'll get drawn (again in a horizontal line) are pixel 0 (center at (0.5, 0.5) falls between 0 and 1) and pixel 99 (center at (99.5, 99.5) falls between 99 and 100).
So yeah... those are the "rules". Makes sure you get what would happen if your thickness was 2 instead of 1.
So yeah... those are the "rules".
To which rules are you referring to? to the ones you wrote or to the ones I wrote?
The ones I wrote. They are in quotes because they are not at the same level of rules that the rules you wrote were.
So, are these the complete set of rules?
if the shape is outlined, then the coordinates refer to the line center.
if the shape is not outlined, then the coordinates refer to the pixel top-left corner.
a pixel is drawn if its center is within the shape coordinates.
I changed the 'pixel center' to 'line center' in the first rule, in order to account for the line thickness, as per your comment.
Well, the second rule is not quite right. What does it predict when you do this:
al_draw_filled_rectangle(0.25, 0.25, 10.25, 10.25) ?
Well, the second rule is not quite right. What does it predict when you do this:
It draws a filled rectangle from (0,0) to (9, 9).
The center of the pixel at the coordinate 0.25 is within the shape, and therefore the pixel 0 is drawn.
The center of the pixel at the coordinate 10.25 is not within the shape, and therefore the pixel 10 is not included into the shape.
How would you write the 2nd rule?
Well, thing is... the coordinate system A5 uses always has integer coordinates correspond to the top-left corner of a pixel. I'd guess the second rule would be better put as coordinates refer to the shape edge, or something.
Thanks!
I came in late, but the first point I'll make is that it's very helpful to turn on sub-pixel rendering. It makes it a lot clearer to see what's going on under there. Not having sub-pixel rendering can really obfuscate what's happening, especially because it's at the unit level, and can lead to much confusion.
The second point, and the real key in my opinion, is that when drawing outlines, you're drawing with a stroke aligned to the center of the coordinates.
A filled rectangle, drawn at (3, 3) to (17, 16):
{"name":"603584","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/2\/7\/27f1091587815b3ccf5c41f143a389f5.png","w":532,"h":504,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/2\/7\/27f1091587815b3ccf5c41f143a389f5"}
The outline, using the same coordinates. The stroke has a pixel width of 1 and is centered on the coordinates. The is what allegro does when drawing strokes:
{"name":"603585","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/3\/b\/3ba01788136cfe2eca1eebfe58072d10.png","w":532,"h":504,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/3\/b\/3ba01788136cfe2eca1eebfe58072d10"}
Here is an example of a different stroke alignment, where the stroke is inside the shape (stroke with of 1.5):
{"name":"603586","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/8\/6\/861866a517208fede80ea51858341e76.png","w":532,"h":504,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/8\/6\/861866a517208fede80ea51858341e76"}
This is what people typically assume will happen. In this case, it is a top and left aligned stroke:
{"name":"603587","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/1\/0\/10682a9252e67b554d7c981fce6b4b3a.png","w":532,"h":504,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/1\/0\/10682a9252e67b554d7c981fce6b4b3a"}
Since the alignment is centered with allegro, putting a +0.5 on your coordinates (when drawing with a stroke-width of 1px) will tuck the centered stroke inside a whole pixel when plotted to the screen, assuming you have no transformations. The result is that it will behave more like a pixel-coordinate rendering system rather than a position-coordinate.
And finally, my third point - The fact that the position-coordinates are also in pixel widths can erroneously make it seem like you are plotting directly to pixel-coordinates. Don't let that confuse you.
That's the way I see it, anyway.
I thought the reason for all this was because particular video cards have some differences in how they render a line, and these "rules" are trying to achieve some sort of compromise that will do what you expected.
I thought the reason for all this was because particular video cards have some differences in how they render a line, and these "rules" are trying to achieve some sort of compromise that will do what you expected.
No, sub-pixel rendering just looks way better (and is not possible with integer-only coordinates of course). Filled shapes are always rendered exactly no matter which video card draws them (except when the outline of your shape happens to hit the exact center of a pixel).
I'm bookmarking that page. I need to go over my GUI rendering code...
After quite a bit of searching I haven't found anything explaining how to enable sub-pixel rendering. Given how lines are drawn now, I would think it would be on by default.
It's not supported by all cards, so enabling it by default seems tenuous. Anyway, you can look at 'ex_multisample' to see how it is done. I don't know why wiki uses the 'sub-pixel rendering' term... it'd be easier to relate to the actual feature if it referred to it as multi-sampling.
Ah okay, I was thinking about multisampling, but I wasn't sure if there was some feature that I just wasn't seeing.
The term "sub pixel" refers to antialiasing (drawing screen pixels according to how close they are to where they "should" be) and multisampling is "sub texel" which refers to grabbing source texels (bitmap pixels) according to how close, right?
I think Allegro should also offer a primitives add-on that draws pixel coordinates. It's hard to remember all these details. I had to read this thread again in order to be reminded of what I should do in each case, and perhaps I am not the only one. The primitives add-on is good for games, where it pixel coordinates do not matter, but it is not so appropriate for drawing UIs, where discrete coordinates are required. It would be quite helpful to a lot of people, I think.
I created macros for myself: algui_draw_hline(), etc.
But I'm not sure that they really belong in Allegro.
This is harder than trying to understand the IE6 box model
Is all this complication due to the way opengl renders or something?
Read Mark's article. It's supposed to clear this up.
I have to agree with axilmar on this. Having some sort of toggle would be nice. Selecting what type of system you wish to use.
Although writing your own would be no big deal I guess. I may write a few of my own functions for pixel co-ordinates. I haven't used A5 that much yet, just getting started, there is a function to plot a pixel right? So I could use that and create my own for lines, circles etc.
I would also like to see compatibility functions for 2D drawing. Simple wrappers for the new al_draw calls that mimic the behaviour of A4's drawing functions. Once my GUI library is ready to be ported to A5, I would have to write them anyway, as that would be much simpler than replacing all the drawing function calls.
Just created a nice line drawing function. It's pretty fast if anyone wants to use it.
I looked up Bresenham's line algorithm online and adopted what I found. I plan on adding more to this for circles (using similar algorithm's as I find 'em).
There's nothing here specifying a particular bitmap or screen to draw on yet.
A5_PixelDraw.h...
A5_PixelDraw.cpp...
I haven't used A5 that much yet, just getting started, there is a function to plot a pixel right? So I could use that and create my own for lines, circles etc.
Just be sure to lock your bitmap first before drawing the pixels, then unlock it when you're done (drawing the pixels). Otherwise you'll get a really slow render.
Oh yeah!! I forgot about that, thanks Mark. I'll modify my function. I had that in mind before I started on this function and got so wrapped up in making it that I forgot about locking the bitmap! .
Hmmm, I don't require a bitmap in my function... I s'pose I should so I can lock it?
It runs REALLY fast, at least in a simple test I done as is.
Either lock the target bitmap or tell the programmer to do it before calling any of the functions.
You're always drawing to a bitmap. If you haven't explicitly defined a bitmap then it will be the ALLEGRO_BITMAP* you can get from al_get_backbuffer().
Either lock the target bitmap or tell the programmer to do it before calling any of the functions.
Yes, that.
I overloaded the function so that you can pass a bitmap, display or nothing. If you pass a display or bitmap, it will lock it. If you pass nothing, you have to lock it. I like this. Should be fun to do up some more.
deleted old code
Edit: Fixed. The above code now has both a working line and rect function, all overloaded and working properly. When I tried the functions normally, without locking any bitmaps or displays, it still drew so many lines that my screen was almost solid colour from them after only a few seconds.
While such an add-on does not technically belong to the Allegro library itself, I think it would be very helpful if it is in the core distribution, and I don't think it would be such a great effort. A couple of days, perhaps, for a veteran Allegro developer.
Why not copy the functions from Allegro 4, if the goal is to emulate the behaviour of Allegro 4... Also, there is no overloading in C.
And thirdly, anyone advocating a mode switch please provide a floating point versions of both line drawing and circle drawing functions. I literally spent months trying to get them working, and failed. Separate functions are fine as long as they take integer coordinates though.
Another thing to check is if it is any faster to draw these things using ALLEGRO_PRIM_POINTS.
EDIT: Also, all rectangular shapes (rectangles and horizontal + vertical lines) should be implemented using al_draw calls. There's way too much FUD about the current al_draw functions.
Wouldn't it be faster to just do all rendering to a memory bitmap instead? The A5 software line resterizer with width set to 0 is likely close to how A4 lines look anyway. Something like:
al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP); screen = al_create_bitmap(320, 200); al_set_target_bitmap(screen); ... yay, i can use software rasterization for everything ...
And then at the end of each frame just draw that bitmap. It means there wouldn't be any locking or texture up and down loading. You'd even have direct pixel access to the screen, just like in A4.
I just created those on the fly as I read this topic actually. I have no desire to redo all the Allegro 4 functions. I'll probably create a few useful functions for myself. Using C++ for a change because I do like overloading for this type of thing.
I may just rename them so they can be interchanged with A4 functions. Would make porting some of my own stuff easier. I may try my hand at wrapping them around the A5 line() functions etc... <shrug> gives me something to do.
Edit: after more thinking, I may just redo these in C and leave the locking etc... up to the user. Would make my life easier.
Okay, I redone them in 'C'. There are 3 functions that all take a bitmap perimeter, they all lock the bitmap and draw very fast. For example, I tested the a5_rectfill() from 10,10 - 790-590 and it was instant, at least on my machine. To draw to the display and not a bitmap, you simply get the bitmap from the backbuffer with ALLEGRO_BITMAP *bitmap = al_get_backbuffer(display);. This seems to be a nice compromise and matches the Allegro 4 functions. I don't think I have the skill to duplicate Allegro 4 functions, but I can make some simple drawing functions to plot pixels like A4 did.