Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Problem with Transparency Mask

This thread is locked; no one can reply to it. rss feed Print
Problem with Transparency Mask
crowbar
Member #14,200
April 2012

Hello,

I am new to Allegro and I am trying to make a "flashlight"-like effect. Where I put a 50% transparent black png over my background image, then I want to be able to shine multiple "flashlights" over the image and where ever a "flashlight" is, the black png is erased at that location of that frame. I tried using
al_convert_mask_to_alpha(shade, al_map_rgb(255,0,255));
but it was FAR too slow! because I had to update the location of the flash lights every frame and I was constantly recreating the "shade" bitmap.

The attachment (example.jpg) is an example of what I want, just imagine the circles of light moving in real time.

Any suggestions?

Thanks!

Crowbar

Max Savenkov
Member #4,613
May 2004
avatar

You'll have to do this:

1) Create a screen-sized bitmap (once)
2) Paint it transparent black (0,0,0,128)
3) Draw a few filled circles on it with (255,255,255,255), with blending mode set to ALLEGRO_DEST_MINUS_SRC , ALLEGRO_ONE , ALLEGRO_ONE
4) Restore old blending mode
4) Draw your image
5) Draw your bitmap over it

#SelectExpand
1ALLEGRO_BITMAP *old = al_get_target_bitmap(); 2al_set_target_bitmap( mask ); 3al_clear_to_color( al_map_rgba( 0,0,0,128 ) ); 4int a,b,c; 5al_get_blender( &a, &b, &c ); 6al_set_blender(ALLEGRO_DEST_MINUS_SRC , ALLEGRO_ONE , ALLEGRO_ONE); 7// For N spotlights: 8al_draw_filled_circle( x1, y1, radius1, al_map_rgba( 255,255,255,255 ) ); 9... 10al_draw_filled_circle( xN, yN, radiusN, al_map_rgba( 255,255,255,255 ) ); 11al_set_blender(a,b,c); 12al_set_target_bitmap( old ); 13// Draw your scene 14// ... 15al_draw_bitmap( mask, 0, 0, 0 );

As an optimization, you can only clear mask & redraw spotlights if they have moved more than 0 pixels in this frame, or try to limit clearing/re-drawing to dirty rectangles, but it should not be necessary on modern hardware.

crowbar
Member #14,200
April 2012

Max,
Thank you so much, I can't tell you how helpful this was! It works very smooth, this is exactly what I wanted to be able to do!

Out of curiosity, I am wondering how it works, I think I understand just about everything except: "al_set_blender(ALLEGRO_DEST_MINUS_SRC , ALLEGRO_ONE , ALLEGRO_ONE);"

What is a blender, and what do the settings do that you gave it?

Again, thanks so much!

Crowbar

Max Savenkov
Member #4,613
May 2004
avatar

Blender is operation which is used to add pixel colors on source and destination bitmaps. First argument is type of operation, and the rest are multipliers used.

It works like that:

ResultColor = BLENDING_OPERATION( SourceColor * SourceMultiplier, DestinationColor * DestinationMultiplier )

In this case, you get:

RC = DestColor * 1 - SrcColor * 1
(0,0,0,0) = (0,0,0,128) * 1 - (255,255,255,255) * 1

(Blending clamps values, so that they wouldn't be less than 0)

Because you are drawing (255,255,255,255) (Source) over (0,0,0,128) (Destination), your result would be (0,0,0,0) - a complete transparency. So now you have a transparent circle, just as you want.

(In fact, you can use (0,0,0,255) instead of (255,255,255,255) as long as your Destination has only Alpha component to be erased)

crowbar
Member #14,200
April 2012

Max,
Thank you so much, you have been very helpful.

I have another question,
I am now trying to do something slightly different. I've been working with masks for a few days now and I am starting to understand how they work.

This is my current objective:
I now want the circles to act as a mask,so that all you see of the image is where the circle is, like everything that didn't have a black circle on it got erased.
This is my current code:

#SelectExpand
1al_set_target_bitmap(imageCopy);//redraw image (refresh it) 2 al_draw_bitmap(image,0,0,0); 3 4 al_set_target_bitmap(combineOfDraw);//combine all my shapes before blending alpha 5 al_clear_to_color(al_map_rgba(0,0,0,0)); 6 7 al_draw_filled_circle(ev.mouse.x,ev.mouse.y, 200, al_map_rgba ( 255,255,255,255));//this one gets moved by the mouse 8 al_draw_filled_circle(500,500, 150, al_map_rgba ( 255,255,255,255)); 9 al_set_target_bitmap(imageCopy); 10 int a,b,c; 11 al_get_blender(&a,&b,&c);//get default blender 12 al_set_blender(ALLEGRO_ADD, ALLEGRO_ZERO, ALLEGRO_ALPHA);//set blender for "mask" 13 14 15 al_draw_bitmap(combineOfDraw, 0, 0, 0); 16 17 al_set_blender(a,b,c);//reset blender 18 //set target back to back buffer 19 al_set_target_bitmap(al_get_backbuffer(display)); 20 21 //draw! 22 al_draw_bitmap(imageCopy, 0, 0, 0);

It works but, I am having a big problem:
It is really slow. I can't understand why this operation would be any slower than the one Max showed me before.
This is all that is happening: a = d.a * dst + s.a * src (becomes=>) a = d.a * (s.a)+ s.a * (0)
So that where ever a circle is drawn (s), the destination gets its alpha (1), but wherever there is no circle, the destination gets multiplied by its alpha too (0), thus being erased.

Thanks in advance!

Crowbar

UPDATE:

My desired effect is "A in B" as shown in this image: http://en.wikipedia.org/wiki/File:Alpha_compositing.svg

Original site: http://en.wikipedia.org/wiki/Alpha_compositing

Max Savenkov
Member #4,613
May 2004
avatar

I don't think it's blending operation that is slow, but some of the surrounding code.

I'm not quite sure, but I think of two possible problems.

First, I see that this procedure is done when some mouse event is handled (I assume it's MOUSE_MOVE, or whatever Allegro calls it). I don't remember how exactly it works, but it might be that you can get SEVERAL such messages per frame, therefore redoing all the work several times. This could be solved quite easily by only saving new mouse coordinates in message handler code and then using them after ALL messages are handled to construct picture.

Second, you probably would be better off avoiding al_get_backbuffer. I'm not sure, but it may have a performance penalty! Instead, just do as I have done in my example and save previous target bitmap from al_get_target_bitmap().

Hope this helps. If it does not, you may try to use a profiler to see where all the time is wasted. I recommend two open-source profilers for Windows: Very Sleepy and Luke Stackwalker. The later is especially nice, it allowed me to track a nasty little inside Allegro which was causing it to be extremely slow on Intel videocards.

crowbar
Member #14,200
April 2012

Hrm, okay. I will get rid of al_get_backbuffer and see if there is a change. I will look into the Mouse events as well. I was talking to a friend of mine yesterday who is proficient in C++ (but unfamiliar with Allegro) who said that if al_set_target_bitmap() changes the render target, then it is probably the line that is causing the most work (especially since I am calling it 4 times). He said I could achieve the same effect by simply drawing to the target, saving that as a bitmap elsewhere, clearing the target and using it over again, and then blend them all together. Is this plausible. I intend to look into it later today. (but i'm on my laptop right now and can't compile yet)

I might be able to rewrite it as the following, and reduce the amount of al_set_target_bitmap().

#SelectExpand
1ALLEGRO_BITMAP *old = al_get_target_bitmap(); 2al_set_target_bitmap(composite); 3al_draw_bitmap(image,0,0,0); 4ALLEGRO_BITMAP *imageSave = al_get_target_bitmap(); 5 6al_clear_to_color(al_map_rgba(0,0,0,0)); 7al_draw_filled_circle(ev.mouse.x,ev.mouse.y, 200, al_map_rgba ( 255,255,255,255)); 8al_draw_filled_circle(500,500, 150, al_map_rgba ( 255,255,255,255)); 9ALLEGRO_BITMAP *maskSave = al_get_target_bitmap(); 10 11al_clear_to_color(al_map_rgba(0,0,0,0)); 12al_draw_bitmap(imageSave,0,0,0); 13int a,b,c; 14al_get_blender(&a,&b,&c); 15al_set_blender(ALLEGRO_ADD, ALLEGRO_ZERO, ALLEGRO_ALPHA); 16al_draw_bitmap(maskSave,0,0,0); 17 18//revert 19al_set_blender(a,b,c); 20al_set_target_bitmap(old); 21al_draw_bitmap(composite,0,0,0);

This isn't the most efficient way to order the commands in the above code, but I am writing it as a direct translation to my earlier post using the method suggested by m friend. Once I have a chance to compile this I think it may solve my problem.

So, I got rid of the call to al_get_backbuffer and I combined this with my friends suggestion. Then again, I am calling get_target_bitmap() a lot. Is this expensive too?

-Crowbar

UPDATE: I found this: "You can create and manipulate bitmaps in system RAM, or you can write to the special `screen' bitmap which represents the video memory in your graphics card." on http://alleg.sourceforge.net/stabledocs/en/alleg009.html
I think that this is what my friend is talking about. So I assume that al_set_target_bitmap() causes a draw command to draw to the video memory? But now how do I "edit" or write to a bitmap in system RAM? Am I on the right track?

Now I'm reading about memory bitmaps, system bitmaps, video memory bitmaps, and sub-bitmaps, I get the feeling that what ever one I am using is not the right one. The trick is that I need to combine all the circles onto a single bitmap before I blend so that it will erase everything on the current target except where the circles are (including the circles, that's why I pass ALLEGRO_ZERO, ALLEGRO_ALPHA.) I am using the circles as a mask. Should I use "al_hold_bitmap_drawing" or should I just use a different type of bitmap?

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

crowbar - you're looking at the manual for allegro 4, but you're using allegro 5

Here is the Allegro 5 Manual

When A5 is slow, it is usually because you're using memory bitmaps, or not consolidating drawing sources into a single source and using al_hold_bitmap_drawing.

Post the full code for your drawing routine, not just this part.

crowbar
Member #14,200
April 2012

Ok, so here is the code (below) for the one that does work but its really slow . I tried implementing the other code that I posted above, but I got some weird errors, and I haven't learned exception handling in C++ yet, so I will take some time to do that tomorrow and try to figure out the problem.

Edgar,
Is there a place where I can learn about proper use of memory bitmaps and al_hold_bitmap_drawing. I looked up the documentation on al_hold_bitmap_drawing but it didn't give any concrete examples so I'm not sure when I should use it. I kind of understand what it does but I don't understand why its important. So are you saying I should be using it? Could you give me an example of proper use of "non-memory bitmaps" and al_hold_bitmap_drawing? Also, if I shouldn't be using memory bitmaps (which does sound like its the problem) what should I use and how?

Again,
Thanks so much

Note: "shade.png" is irrelevant cause I just end up clearing it before I use it, and "backdrop.jpg" is just any random background. A forest or something would work well to demonstrate what I am trying to do, just something with detail.

#SelectExpand
1#include <stdio.h> 2#include <allegro5\allegro.h> 3#include <allegro5\allegro_image.h> 4#include <allegro5\allegro_primitives.h> 5 6 7int main(void) 8{ 9 //variables 10 int width = 800; 11 int height = 400; 12 bool done = false; 13 int x,y; 14 int imageWidth = 0; 15 int imageHeight = 0; 16 int lightRad = 100; 17 18 //allegro variable 19 ALLEGRO_DISPLAY *display = NULL; 20 ALLEGRO_EVENT_QUEUE *event_queue = NULL; 21 ALLEGRO_BITMAP *image = NULL; 22 ALLEGRO_BITMAP *imageCopy = NULL; 23 ALLEGRO_BITMAP *combineOfDraw = NULL; 24 //program init 25 if(!al_init()) //initialize Allegro 26 return -1; 27 28 display = al_create_display(width, height); //create our display object 29 30 if(!display) //test display object 31 return -1; 32 33 //addon init 34 al_install_keyboard(); 35 al_init_image_addon(); 36 al_install_mouse(); 37 al_init_primitives_addon(); 38 39 image = al_load_bitmap("backdrop.jpg"); 40 imageCopy = al_load_bitmap("backdrop.jpg"); 41 42 imageWidth = al_get_bitmap_width(image); 43 imageHeight = al_get_bitmap_height(image); 44 45 x = width / 2 - imageWidth / 2; 46 y = height / 2 - imageHeight / 2; 47 48 combineOfDraw = al_load_bitmap("shade.png"); 49 50 51 event_queue = al_create_event_queue(); 52 53 al_register_event_source(event_queue, al_get_mouse_event_source()); 54 al_register_event_source(event_queue, al_get_keyboard_event_source()); 55 56 while(!done) 57 { 58 ALLEGRO_EVENT ev; 59 al_wait_for_event(event_queue, &ev); 60 61 62 if(ev.type == ALLEGRO_EVENT_MOUSE_AXES) 63 { 64 //fprintf(stderr, "x: %d, y: %d\n", ev.mouse.x, ev.mouse.y); 65 66 al_set_target_bitmap(imageCopy);//redraw image (refresh it) 67 al_draw_bitmap(image,0,0,0); 68 69 al_set_target_bitmap(combineOfDraw);//combine all my shapes before blending alpha 70 al_clear_to_color(al_map_rgba(0,0,0,0)); 71 //al_draw_filled_rectangle(50,50,500,500,al_map_rgba(215,124,25,0)); 72 al_draw_filled_circle(ev.mouse.x,ev.mouse.y, 200, al_map_rgba ( 255,255,255,255)); 73 al_draw_filled_circle(500,500, 150, al_map_rgba ( 255,255,255,255)); 74 al_set_target_bitmap(imageCopy); 75 int a,b,c; 76 al_get_blender(&a,&b,&c);//get default blender 77 al_set_blender(ALLEGRO_ADD, ALLEGRO_ZERO, ALLEGRO_ALPHA);//set blender for "mask" 78 /* 79 al_set_blender(ALLEGRO_ADD, ALLEGRO_ZERO, ALLEGRO_ALPHA); works perfectly, but the problem is 80 that it only considers pixels that you are drawing, (like the circle), not the empty space 81 and I need it to consider the (0,0,0,0) space 82 */ 83 //this right now works like I thought it would, BLENDING HAPPENS AS SOON AS YOU DRAW 84 //there for, when I draw the rectangle (with alpha 0, on ALLEGRO_ZERO, ALLEGRO_ADD) 85 //it subtracts "image" where it overlaps, like I thought 86 //but then when I draw filled_circle in an attempt to not erase where it is 87 //the rectangle has ALREADY been subtracted, so now all the pixels there are alpha = 0 88 //solution? Draw the alpha = 0 (rectangle) and the alpha = 1 (or 255, circles) on 89 //and imageFile THEN draw that imageFile to "image" with blend options ALLEGRO_ZERO 90 //ALLEGRO_ALPHA 91 /*al_draw_filled_rectangle(50,50,500,500,al_map_rgba(215,124,25,0)); 92 al_draw_filled_circle(ev.mouse.x,ev.mouse.y, 200, al_map_rgba ( 255,255,255,255)); 93 al_draw_filled_circle(500,500, 150, al_map_rgba ( 255,255,255,255));*/ 94 al_draw_bitmap(combineOfDraw, 0, 0, 0); 95 96 al_set_blender(a,b,c);//reset blender 97 /////////////// 98 //set target back to back buffer 99 al_set_target_bitmap(al_get_backbuffer(display)); 100 101 //draw! 102 al_draw_bitmap(imageCopy, 0, 0, 0); 103 104 105 106 } 107 else if(ev.type == ALLEGRO_EVENT_KEY_DOWN) 108 { 109 110 switch(ev.keyboard.keycode) 111 { 112 case ALLEGRO_KEY_ESCAPE: 113 done = true; 114 break; 115 } 116 } 117 118 al_flip_display(); 119 al_clear_to_color(al_map_rgb(255,255,200)); 120 } 121 al_destroy_event_queue(event_queue); 122 123 al_destroy_bitmap(image); 124 al_destroy_bitmap(imageCopy); 125 al_destroy_bitmap(combineOfDraw); 126 al_destroy_display(display); //destroy our display object 127 128 return 0; 129}

Max Savenkov
Member #4,613
May 2004
avatar

This code works resonably fast on my machine. But it could be done in a more simple way:

#SelectExpand
1 if(ev.type == ALLEGRO_EVENT_MOUSE_AXES) 2 { 3 // Save blender 4 int a, b, c; 5 al_get_blender( &a, &b, &c ); 6 7 // Draw image to imageCopy, but set Alpha of all pixels 8 // to 0. set_separete_blender allows us to set different rules 9 // for blending RGB component of colors and Alpha component. 10 al_set_separate_blender( a,b, c, ALLEGRO_ADD, ALLEGRO_ZERO, ALLEGRO_ZERO ); 11 ALLEGRO_BITMAP *old = al_get_target_bitmap(); 12 al_set_target_bitmap( imageCopy ); 13 al_draw_bitmap( image, 0, 0, 0 ); 14 15 // Draw circle on imageCopy, but only set Alpha of pixels inside 16 // it. 17 al_set_separate_blender( ALLEGRO_ADD, ALLEGRO_ZERO, ALLEGRO_ONE, ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ZERO ); 18 al_draw_filled_circle( ev.mouse.x, ev.mouse.y, 100, al_map_rgba(0,0,0,255) ); 19 20 // Now we are drawing on backbuffer again 21 al_set_target_bitmap( old ); 22 23 // Draw imageCopy on backbuffer in such way that only pixels 24 // with non-zero Alpha are drawn. 25 al_set_blender( ALLEGRO_ADD, ALLEGRO_ALPHA , ALLEGRO_ONE ); 26 al_draw_bitmap( imageCopy,0,0,0 ); 27 28 // Restore blender 29 al_set_blender( a,b ,c ); 30 }

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

crowbar said:

Edgar,
Is there a place where I can learn about proper use of memory bitmaps and al_hold_bitmap_drawing. I looked up the documentation on al_hold_bitmap_drawing but it didn't give any concrete examples so I'm not sure when I should use it. I kind of understand what it does but I don't understand why its important. So are you saying I should be using it? Could you give me an example of proper use of "non-memory bitmaps" and al_hold_bitmap_drawing? Also, if I shouldn't be using memory bitmaps (which does sound like its the problem) what should I use and how?

In A5, there really is no proper use of memory bitmaps - they are slow as molasses...

Thing is though, bitmaps are video bitmaps by default, unless you load them before you create the display, and unless they aren't auto converted to video bitmaps by the latest A5 code (A5.0.6 or later?).

al_hold_bitmap_drawing is for when you are drawing multiple images off of the same parent bitmap. You get a speed up because you don't have to continually rebind the texture that is being drawn. For example, drawing many sub bitmaps off of a parent bitmap (like tiles on a tilemap...).

As for your drawing code, the only thing I can see to be improved is the number of calls to al_set_target_bitmap, which are expensive... - Draw to combineOfDraw first, then draw to imageCopy, and then draw to the backbuffer and flip the display. Right now you are drawing to imageCopy, then combineOfDraw, then imageCopy, and then the backbuffer, which is one more step than you need...

When you say it is slow, do you mean it doesn't respond quickly to mouse moves? Because that is the main time that you are drawing to the screen.

crowbar
Member #14,200
April 2012

Max,
For some reason that code does not get me the same result that mine did. It looks much different, and the circle still lags far behind the mouse (so its still really slow for me)

Edgar,
Yes, I am using allegro 5.0.6.

I did try getting rid of that extra call, but it did me no good. As an experiment to see if the al_set_target_bitmap calls were the thing slowing me down, I added a bunch of them to see what it would do, but it didn't slow enough to make me think that those lines are the ones that keep the circle from following the mouse in real time.

Yes, what I mean is that the circle lags far behind the mouse, it doesn't keep up with it which is what happens if you just draw a black circle on the position of the mouse without any special blending. In my final product I will be drawing according to time (probably 30 frames/sec) but I used the mouse just for testing purposes.

Crowbar

Max Savenkov
Member #4,613
May 2004
avatar

I'm out of ideas then. For me, both version work very good. Did you try profiling?

crowbar
Member #14,200
April 2012

I finally found what was slowing it down. (No, I haven't gotten to profiling yet, to be honest I forgot you mentioned it, so I will be looking into that). But what I finally figured out is that the thing that was making it take so long was simply using the blending (ALLEGRO_DEST_MINUS_SRC, ALLEGRO_ONE, ALLEGRO_ONE) or (ALLEGRO_ADD, ALLEGRO_ZERO, ALLEGRO_ALPHA) on any bitmap. The program runs just fine when I blend on a bitmap that has been cleared
after a call to al_clear_to_color(al_map_rgba(0,0,0,128)); for instance.

But for whatever reason, trying to draw a bitmap on any other bitmap with these blending options is taking forever.

Now that I found the problem, I might not need to use the profiler after all, but I will still look into it.

I'm going to do some more digging and see if I can't figure out why

Max Savenkov
Member #4,613
May 2004
avatar

What's your hardware? If blending a single bitmap takes a very long time it's probably the case of Allegro taking a software code branch. Do you, by chance, run it on Intel GMA video card? If so, then probably your problem is the same one I encountered a month ago. There is a slight bug in Allegro, which will be fixed in 5.0.7 or 5.2, which leads Allegro to have very slow performance on video cards which do not support separate alpha blending, even when it does not need it.

crowbar
Member #14,200
April 2012

Im using ATI Radeon HD 4850

Max Savenkov
Member #4,613
May 2004
avatar

That's it, then. According to this, HD4850 does not support separate alpha blend, which leads to VERY bad Allegro performance because of bug.

You can wait for the next version of Allegro, or compile your own version of Allegro with fix included (just remove a check for _al_d3d_supports_separate_alpha_blend in d3d_draw_bitmap_region in win/d3d_bmp.cpp)

crowbar
Member #14,200
April 2012

Ahhh, that's very helpful to know. Once I started to understand how the blending works I was wondering why performance would be so bad. Do you know when the Allegro update is coming out? I've got a lot of stuff I cant work on in the mean time so I don't need to compile it myself, ill just wait.

Crowbar

Max Savenkov
Member #4,613
May 2004
avatar

Unfortunately, I don't know when the next version will come out. So we both will have to wait for it.

Go to: