Grabbing image with Alpha Channel

BlitzMax Forums/BlitzMax Programming/Grabbing image with Alpha Channel

Mauft(Posted 2009) [#1]
So, drawing a room takes around 1200 draw commands for me, that is 40x30 tiles. I figured out it would be faster if I could turn this into 4 images which are of size 20x15 tiles. The basic idea was to draw each 20x15 part of the room on the screen and GrabImage it. Whilst I successfully managed to create 4 big images which when pieced together gave the same effect that the 1200 draws above, the GrabImage omitted alpha channel.
The only way I seem to be able to accomplish it is by copying the tiles pixel-by-pixel to a pixmap, but when you want to copy 480000 pixels it is kinda slow, and I wanted to be able to perform this on-fly every couple of seconds. I saw this topic: http://www.blitzmax.com/Community/posts.php?topic=63266 but that solution won't work for me (and 32-bit color depth didn't make the GrabImage grab the alpha too).
I also thought about 'prerendering' all the room data and saving it to disk at the first run but I don't like this solution. So I wanted to ask if there is any other way to do it?

EDIT: Oh well, I just noticed I forgot about adding MASKEDIMAGE flag. It works, in DX7 at least, both in 16 and 32 bit.
Ok, adding MASKEDIMAGE allows me to grab transparency color but not alpha channel unfortunately, so any better and working ideas are welcome.


ImaginaryHuman(Posted 2009) [#2]
For OpenGl at least, look at glReadPixels() ... you CAN use it to read all channels including alpha. I use it to read the alpha channel's value, after having written something to the destination alpha channel, to test that the alpha channel really exists.

Which gives me an idea ..... have you actually drawn anything to the alpha channel of the screen? What do you use to render your images? Just DrawImage? And in what drawing mode, AlphaBlend? I don't think (not sure) any of the Blitz drawing modes write anything to the screen's alpha channel. you could try enabling drawing to the alpha channel, in opengl. Only when there is something stored in the *screens* alpha channel (not the image you drew necessarily), will you get anything back when you read the image.


Mauft(Posted 2009) [#3]
Unfortunately my graphics card doesn't support OpenGL... Well, it supported before but not ever since I moved to Win 7 RC (from Beta 1) and installed latest drivers so I can't test it now... But I will look into changing drivers, perhaps I can somehow make it work.

But I have found another, quite ugly but working way to do it using image locking and TPixmap.Paste method. I am yet to test its speed but there is no visible lag when doing it so I am full of hope.

EDIT: Tested, and it takes from 4-5 milliseconds to complete. Not bad, I expected worse, on 60 FPS it takes only one third of the computing power.
EDIT2:Heh, I downgraded the Driver and OpenGL works. Yes, I am using the usual max2d drawing functions with AlphaBlend, and it's not working with OpenGL too. Unfortunately my knowledge about it is very sparse so I don't know how to activate drawing to alpha channel.


Pete Rigz(Posted 2009) [#4]
So I take it that your room has semi transparent stuff in it? Grabbing images with alpha is a bit tricky in Blitz and unfortunately not really a real-time thing. What you have to remember is that the colour blitz draws to the backbuffer will be the colour after alpha has been applied. That means that when you grab the colour you need to restore it to its original value if you want to then paste it down again.

Its possible, but it might not be fast enough for you. I think for it to be fast enough some changes would have to happen to the way blitz renders stuff.

Anyway, for a start you need to create your graphics with an extra flag on top of the default ones called alphabuffer:

graphics 800,600,0,0,GRAPHICS_BACKBUFFER | GRAPHICS_DEPTHBUFFER | GRAPHICS_ALPHABUFFER


Then you need a new cls command that will make blitz clear the alpha channel: (by default it makes fills the alpha channel):

Function ClsWithAlpha(r:Float = 0.0, g:Float = 0.0, b:Float = 0.0, a:Float = 1.0)
	glClearColor r, g, b, a
	glClear GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT
End Function


So you can cls the screen with ClsWithAlpha(0,0,0,0) instead of a normal cls.

You also need to call glewinit() to give you access to some more openGL extensions so that you can do a couple of alternative blendmodes:

Function SetBlendEXT(blend:Int)
	Select blend
		Case ALPHABLEND
			glEnable GL_BLEND
			glBlendFuncSeparateEXT(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
			glDisable GL_ALPHA_TEST
		Case LIGHTBLEND
			glEnable GL_BLEND
			glBlendFuncSeparateEXT(GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
			glDisable GL_ALPHA_TEST
	End Select
End Function


You need to call that instead of the normal setblend in order to separate how the alpha and colour is blended to the screen.

So, then once you've drawn what you want to the screen and grabbed your pixmap you then have to restore the colour values to what they were before alpha was blended using this function:

Function restorecolor:TPixmap(pixmap:TPixmap)
	
	For Local x:Int = 0 To pixmap.width - 1
		For Local y:Int = 0 To pixmap.height - 1
			Local RGBA:Int = ReadPixel(pixmap, x, y)
			Local a:Int = (RGBA Shr 24) & $ff
			Local r:Int = (RGBA Shr 16) & $ff
			Local g:Int = (RGBA Shr 8) & $ff
			Local b:Int = RGBA & $ff
			If a
				r = Maximum(255, Float(r) / (Float(a) / 255))
				g = Maximum(255, Float(g) / (Float(a) / 255))
				b = Maximum(255, Float(b) / (Float(a) / 255))
				RGBA = (a Shl 24) | (r Shl 16) | (g Shl 8) | b
			End If
			WritePixel(pixmap, x, y, RGBA)
		Next
	Next
	Return pixmap
End Function


You can then just loadimage(restoredpixmap) and drawimage that to the screen.

So yeh, that'd probably be slower then just drawing 1200 images :) Describe in more detail the rooms you're drawing and there may be another solution to speed things up.


ImaginaryHuman(Posted 2009) [#5]
Nice


Mauft(Posted 2009) [#6]
Yea, very interesting. But even without testing I can see it is slower than using TPixmap.paste() method (it has far less code). And also yesterday I found even faster way to do it, function in CodeArchive http://www.blitzbasic.com/codearcs/codearcs.php?code=2109 which instead of writing byte by byte uses MemCopy(), and so TPixmap.paste() took 4-5 milliseconds to complete and CopyImageRect() only 1-2 (when copying 192 25x25 tiles).
So yeh, that'd probably be slower then just drawing 1200 images :) Describe in more detail the rooms you're drawing and there may be another solution to speed things up.

Actually the 1200 was miscalculation, I want to do 768 draws (32x24 tiles of size 25x25 on 800x600 screen). Anyway, I wanted to make an exploration platformer with infinite scrolling, so there is no room transitions like in Metroid, and I wanted to leave as much for other graphic effects (that's why I planned to compress these 768 draws into around 9 draws of bigger images which turned out to be notably faster). But after realizing how much trouble this is going to be I just gave up on this idea and will resort to the good old rooms.
Thanks for help though, that was surely enlightening :)