Code archives/Graphics/DrawBank

This code has been declared by its author to be Public Domain code.

Download source code

DrawBank by BlitzSupport2009
I couldn't find a previous example of this, oddly enough, though someone's surely done it. Not much use for real time games, but may come in handy for... other stuff.

If anyone can speed this up then please post here! (I get about 10 milliseconds for the boing.png image from BlitzMax "RockOut", on an Intel Core2Duo 6300 @ 1.86 GHz with Geforce 7600 GS, and about 330 milliseconds for any image that completely fills the screen without a mask.)

EDIT: Added background mask hack/fix and trivial, worthless optimisations.
f$ = "boing.png"

Graphics 1024, 768, 0, 2

; IMPORTANT! Required by DrawBank!

Global GW = GraphicsWidth ()
Global GH = GraphicsHeight ()
Global DB_RGB

SetDrawBankMask 255, 0, 255

Function SetDrawBankMask (r, g, b)

	tempbuffer = GraphicsBuffer ()
	temp = CreateImage (1, 1)

	SetBuffer ImageBuffer (temp)

	Plot 0, 0: GetColor 0, 0
	tr = ColorRed ()
	tg = ColorGreen ()
	tb = ColorBlue ()

	Color r, g, b: Plot 0, 0: GetColor 0, 0
	r = ColorRed ()
	g = ColorGreen ()
	b = ColorBlue ()

	FreeImage temp

	DB_RGB = ((r Shl 16) + (g Shl 8) + b) Or ~$00FFFFFF

	SetBuffer tempbuffer

	Color tr, tg, tb
	
End Function

Function DrawBank (bank, x, y, width, height, center = 0, masked = True)
	
	; Centre image on screen if required...
	
	If center
		x = x - width / 2
		y = y - height / 2
	EndIf
	
	; Alpha mask...
	
	mask = ((r Shl 16) + (g Shl 8) + b) Or ~$00FFFFFF

	; Scan across image...
	
	For w = 0 To width - 1

		; x-position plus current 'pixel' NOT off right of screen?

		If x + w < GW

			; Not off left of screen?
			
			If x + w > -1

				; Scan down image...
				
				For h = 0 To height - 1

					; Not off bottom of screen?
					
					If y + h < GH
	
						; Not off top of screen?
						
						If y + h > -1

							; Get current value in bank...
							
							argb = PeekInt (bank, pointer)
							
							; Assuming most cases use transparent mask...
							
							If argb <> DB_RGB
								WritePixelFast x + w, y + h, argb
							Else
								; User wants mask drawn...
								If Not masked
									WritePixelFast x + w, y + h, argb
								EndIf
							EndIf
							
							; Move to next value in bank (argb = 4 bytes = integer, hence PeekInt above)...
							
							pointer = pointer + 4

						Else
							
							; Off top of screen, so skip all pixels until on screen...
							
							h = h + (-y) - 1
							pointer = pointer + (h Shl 2) + 4
							
						EndIf	

					Else
						
						; Off bottom of screen, stop drawing this column...
						
						pointer = pointer + (height - h) Shl 2
						Exit
					
					EndIf
					
				Next

			Else
				
				; Off left of screen, so skip all pixels until on screen...
				
				pointer = pointer + (height - h) Shl 2
				
			EndIf

		Else
			
			; Off right of screen, so stop drawing...
			
			Exit
			
		EndIf
		
	Next
	
End Function

Function ImageToBank (bufferImage)

	; Slightly modified from http://www.blitzbasic.com/codearcs/codearcs.php?code=396
	; By Perturbatio
	
	bankImage = CreateBank ()
	
	bufOldBuffer = GraphicsBuffer ()
		
	SizeOfImage = ImageWidth (bufferImage) * ImageHeight (bufferImage)
	
	ResizeBank bankImage, (SizeOfImage * 4)
	
	SetBuffer ImageBuffer (bufferImage)
	LockBuffer ImageBuffer (bufferImage)

	For iLoopX = 0 To ImageWidth (bufferImage) - 1
		For iLoopY = 0 To ImageHeight (bufferImage) - 1
			PokeInt bankImage, ibankPointer, ReadPixelFast (iLoopX, iLoopY)
			iBankPointer = iBankPointer + 4
		Next
	Next

	UnlockBuffer ImageBuffer (bufferImage)
	SetBuffer bufOldBuffer
	
	Return bankImage
	
End Function

; Test...

ClsColor 64, 96, 128

SetBuffer BackBuffer ()

image = LoadImage (f$)

width = ImageWidth (image)
height = ImageHeight (image)

bank = ImageToBank (image)
FreeImage image

Repeat

	Cls
	
	mx = MouseX ()
	my = MouseY ()
	
	If KeyDown (203) Then xs# = xs - 0.1
	If KeyDown (205) Then xs = xs + 0.1
	If KeyDown (200) Then ys# = ys - 0.1
	If KeyDown (208) Then ys = ys + 0.1

	If mx <> lastx
		x# = mx
		xs = 0
	Else
		x = x + xs
	EndIf
	
	If my <> lasty
		y# = my
		ys = 0
	Else
		y = y + ys
	EndIf
	
	lastx = mx
	lasty = my
	
	LockBuffer BackBuffer ()
	
		ms = MilliSecs ()
		DrawBank bank, x, y, width, height, 1
		ticks = MilliSecs () - ms
	
	UnlockBuffer BackBuffer ()

	Text 20, 20, ticks

	Flip
	
Until KeyHit (1)

End

Comments

Matty2009
I draw from banks all the time in my 2D games, though have my own routines (which I'd be willing to share if they were tidy enough), and even on an older PC if it's done right can still be very quick - for the right sorts of graphics, and it saves on video memory since you are simply pixelplotting from system memory.


BlitzSupport2009
Good point regarding video memory, and I suppose it's fine for board/grid-based games...

I tried another angle on this by pre-calculating the rectangle to be scanned 'inside' the bank, skipping tons of If/Then checks within the nested loops, and I got a staggering increase of... well, zero per-cent.

Looks like this is limited more by the actual drawing rather than the plotting algorithm...

EDIT: Oops, background mask was failing on different bit-depth screens. Ungodly hack added -- SetDrawBankMask (r, g, b)!




DareDevil2009
Hi! have optimized all function and organize




BlitzSupport2009
Wow, that's excellent. You got boing.png from 10-12 ms down to 2-3 ms! I also tried a picture larger than the display, which took 300+ ms before, and now takes only 23 ms!


Me: Looks like this is limited more by the actual drawing rather than the plotting algorithm...


Boy, do I feel stupid...

However, there's one minor problem, which I addressed in my update: if you use a non-zero RGB mask (eg. 255, 0, 255) then the results may be wrong in full-screen mode.

For example, your code works fine with Graphics w, h, 0, 2, but with Graphics w, h Blitz will choose the fastest bit-depth, which is 16 in my case, and the mask colours are then incorrect when you read them back, so the mask is drawn when it shouldn't be. (You can test this by forcing the full-screen bit-depth.)

I'm sure it's possible to calculate the correct values for 16/24/32-bit, rather than using my clumsy pixel read/write routine.

Anyway, many thanks for your contribution, which I'll be trying to understand!

Please could you consider declaring your code as Public Domain so that people can use it without worry or restriction?


DareDevil2009
Pleace test the modify function

Thanks!





DareDevil2009
PS: the modify and all code is public domain ;)

good work


BlitzSupport2009
Hi DareDevil,

Thanks very much! I found a couple of minor problems in the last update:

Function BankImage_SetMask(a_BankImage, r, g, b)
	Local l_IdBank = 3*4

; Should be...

Function BankImage_SetMask(a_BankImage, r, g, b)
	Local l_IdBank = 2*4 ; CORRECT = 2*4


Function BankImage_SetAlpha(a_BankImage, Alpha%=0 )
	;===>
	Local l_IdBank = 4*4

; Should be...

Function BankImage_SetAlpha(a_BankImage, Alpha%=0 )
	;===>
	Local l_IdBank = 3*4 ; CORRECT = 3*4


A slightly more difficult problem is that the mask still doesn't work in 16-bit mode here. After a lot of trial-and-error, it seems to me that you have to use my plot/read pixel method to get the correct RGB values. This is because you can't tell what exact display mode is being used: for example, plotting 255, 0, 255 in 16-bit mode, then reading back, gives 248, 0, 248 for me.

It seems that I am probably being given an RGB565 mode in 16-bit depth, ie. 5 red bits, 6 green bits and 5 blue bits (so 255, 0, 255 becomes 248, 0, 248). I'm not totally sure, but I think this depends on the graphics card/drivers, but you may get a different 16-bit format instead of RGB565 on a different graphics card, and you can't tell how to read the bits since you don't know the RGB format.

Since there's no way to tell what RGB format you have from within Blitz, I think you have to use the plot/read method for now to determine the correct mask values. I think I'll have a look at the Blitz3D SDK source and see if I can get any ideas...


DareDevil2009
I had forgotten the format rgb 555 and 565, of your system plot and read are more effective

;)


Perturbatio2009
I wrote this: ImageToBank, BankToImage functions Many, many moons ago (when I first started using B3D I think).

It's vaguely in the same vein.


BlitzSupport2009
Perturbatio: I know -- I ripped ImageToBank off and credited you -- thanks! (See above.)

On the subject of determining the backbuffer format, I think I'm getting somewhere, as I've just managed to create a PureBasic DLL that gets the DirectDraw DDSURFACEDESC2 information, which includes some useful stuff:

dwHeight.l
dwWidth.l
dwRefreshRate.l
ddpfPixelFormat.DDPIXELFORMAT


It correctly gets width/height/refresh rate (only in full-screen?), and I think I can get the correct mask information from the pixel format structure, though that's a job for another day...

If anyone has PB 4 and wants to try it (sorry, I don't have FTP at the moment, and it's really not worth a download anyway), this is what I've got so far... not much, but it seems to work!

; Blitz test code (in same folder as dd7test.dll)...

Graphics 640, 480, 0, 1
result = Test (SystemProperty ("DirectDraw7"))
Print result


; Blitz dd7test.decls file (plain text) -> goes in Blitz\userlibs folder...

.lib "dd7test.dll"

Test% (device)





Code Archives Forum