Drawing one image onto another

BlitzMax Forums/BlitzMax Programming/Drawing one image onto another

GfK(Posted 2009) [#1]
I'm currently drawing to backbuffer, then drawing the second image on top, then grabbing it into a pixmap. Works, but I wonder if there's a better way? Plus I've heard tales about GrabPixmap not working on some hardware.

Couple of points to consider:

1. I need to draw the second image onto the first with alpha.
2. I don't need to do it on-the-fly.
3. The code needs to draw several images onto another, with a grabPixmap at the end. Repeat x 108.


Jur(Posted 2009) [#2]
I am using the following function to combine several layers of tiles into one tile.



The first index of image array is the bottom layer. All images must be of the same size. For images with diferent sizes, the read-write pixel loop should be changed.

Jure


Derron(Posted 2009) [#3]
in an older library I use:
Function ARGB_Alpha:Int(ARGB:Int)
 Return (argb Shr 24) & $ff
 'Return Int((ARGB & $FF000000:Int) / $1000000:Int) 
End Function

Function ARGB_Red:Int(ARGB:Int)
  Return (argb Shr 16) & $ff
' Return Int((ARGB & $00FF0000:Int) / $10000:Int)
End Function

Function ARGB_Green:Int(ARGB:Int)
  Return (argb Shr 8) & $ff
' Return Int((ARGB & $0000FF00:Int) / $100:Int)
End Function

Function ARGB_Blue:Int(ARGB:Int)
 Return (argb & $ff) 
' Return (ARGB & $000000FF:Int)
End Function

Function ARGB_Color:Int(alpha:Int,red:Int,green:Int,blue:Int)
 Return (Int(alpha * $1000000) + Int(RED * $10000) + Int(green * $100) + Int(blue)) 
End Function

Function DrawOnPixmap(image:TImage, framenr:Int = 0, Pixmap:TPixmap, x:Int, y:Int, alpha:Float = 1.0, light:Float = 1.0, multiply:Int = 0) 
      Local TempPix:TPixmap = Null
	  If image = Null Then Throw "image doesnt exist"
	  If framenr = 0 Then TempPix = LockImage(image) 
      If framenr > 0 Then TempPix = LockImage(image, Framenr) 
	  For Local i:Int = 0 To ImageWidth(image) - 1
	    For Local j:Int = 0 To ImageHeight(image) - 1
		  If x + i < pixmap.width And y + j < pixmap.Height 'And i >= x And j >= y
			Local sourcepixel:Int = ReadPixel(TempPix, i, j) 
			Local destpixel:Int = ReadPixel(pixmap, x+i,y+j)
			Local destA:Float = ARGB_Alpha(destpixel) 
			Local sourceA:Float = ARGB_Alpha(sourcepixel) * alpha
			If sourceA = 255 Then destA = 0
			'remove comment to remove unneeded calculations 
			'but only when light/alpha not used!
'			If sourceA <> 255 And sourceA <> 0
				Local destR:Float = ARGB_Red(destpixel) 
				Local destG:Float = ARGB_Green(destpixel) 
				Local destB:Float = ARGB_Blue(destpixel) 
				Local SourceR:Float = ARGB_Red(Sourcepixel) 
				Local SourceG:Float = ARGB_Green(Sourcepixel) 
				Local SourceB:Float = ARGB_Blue(Sourcepixel) 
					Local AlphaSum:Int = destA + sourceA
					If multiply = 1
						sourceR = (sourceR * light * sourceA / AlphaSum) + destA / AlphaSum * (destR * destA / AlphaSum) 
						sourceG = (sourceG * light * sourceA / AlphaSum) + destA / AlphaSum * (destG * destA / AlphaSum) 
						sourceB = (sourceB * light * sourceA / AlphaSum) + destA / AlphaSum * (destB * destA / AlphaSum) 
					Else
						sourceR = (sourceR * light * sourceA / AlphaSum) + (destR * destA / AlphaSum) 
						sourceG = (sourceG * light * sourceA / AlphaSum) + (destG * destA / AlphaSum) 
						sourceB = (sourceB * light * sourceA / AlphaSum) + (destB * destA / AlphaSum) 
					EndIf
					If AlphaSum > 255 Then AlphaSum = 255
					sourcepixel = ARGB_Color(AlphaSum, SourceR, sourceG, sourceB) 
'			EndIf
			If SourceA <> 0 Then WritePixel(Pixmap, x + i, y + j, sourcepixel) 
		  EndIf
		Next
	  Next
	  If framenr = 0 UnlockImage(image)
	  If framenr > 0 UnlockImage(image, framenr)
End Function



It draws an image into a pixmap.


bye MB


GfK(Posted 2009) [#4]
Isn't the pixel-by-pixel approach incredibly slow?


tonyg(Posted 2009) [#5]
I really think somebody should nail the RTT omission.
Can you use the pixmap paste method? I never know what allows alpha and what doesn't. Maybe a memcopy would work using the pixelptr value.
I wrote some truly horrible code here which might be of use.


Grey Alien(Posted 2009) [#6]
I used one of these two (can't remember which) in Fairway. The image was pre-calced before the screen was shown, wasn't slow enough to notice:

' -----------------------------------------------------------------------------
' ccCopyImageRect: Copies part of an image onto another image
' by James Chamblin
' -----------------------------------------------------------------------------
Function ccCopyImageRect(Source:TImage,SX:Int,SY:Int,SWidth:Int,SHeight:Int,Dest:TImage,DX:Int,DY:Int)
	'get the pixmap for the images
	Local SourcePix:TPixmap = LockImage(Source)
	Local DestPix:TPixmap = LockImage(Dest)
	
	'find the dimentions
	Local SourceWidth:Int = PixmapWidth(SourcePix)
	Local SourceHeight:Int = PixmapHeight(SourcePix)
	Local DestWidth:Int = PixmapWidth(DestPix)
	Local DestHeight:Int = PixmapHeight(DestPix)
	
	If SX < SourceWidth And SY < SourceHeight And DX < DestWidth And DY < DestHeight 'make sure rects are on image
		If SX+SWidth > SourceWidth Then SWidth = SourceWidth - SX 'bound the coordinates to the image area
		If SY+SHeight > SourceHeight Then SHeight = SourceHeight - SY
		If DX+SWidth > DestWidth Then SWidth = DestWidth - DX 'Make sure coordinates will fit into the destination
		If DY+SHeight > DestHeight Then SHeight = DestHeight - DY
		
		'find the pitch
		Local SourcePitch:Int = PixmapPitch(SourcePix)
		Local DestPitch:Int = PixmapPitch(DestPix)
	
		'pointers To the first pixel of pixmaps
		Local SourcePtr:Byte Ptr = PixmapPixelPtr(SourcePix) + SY * SourcePitch + SX * 4
		Local DestPtr:Byte Ptr = PixmapPixelPtr(DestPix) + DY * DestPitch + DX * 4
		
		'copy pixels over one line at a time
		For Local i:Int = 1 To SHeight
			MemCopy(DestPtr,SourcePtr,SWidth*4)
			SourcePtr :+ SourcePitch
			DestPtr :+ DestPitch
		Next
	End If
	
	'unlock the buffers
	UnlockImage(Source)
	UnlockImage(Dest)
End Function

' -----------------------------------------------------------------------------
' ccCopyImageToImage: Copies one TImage to another with a variety of modes
' by Dave Munsie
' -----------------------------------------------------------------------------
Function ccCopyImageToImage(Source:TImage,SX:Int,SY:Int,SWidth:Int,SHeight:Int,Dest:TImage,DX:Int,DY:Int,flags:Int=0)
  '
  ' flags: 0 = Normal  1 = Mirror  2 = Flip  3 = Mirror and Flip
  '
  Local SourcePix:TPixmap = LockImage(Source)
  Local DestPix:TPixmap = LockImage(Dest)
  Local SourceWidth:Int = PixmapWidth(SourcePix)
  Local SourceHeight:Int = PixmapHeight(SourcePix)
  Local DestWidth:Int = PixmapWidth(DestPix)
  Local DestHeight:Int = PixmapHeight(DestPix)
  If SX < SourceWidth And SY < SourceHeight And DX < DestWidth And DY < DestHeight 
  If SX+SWidth > SourceWidth Then SWidth = SourceWidth - SX 
  If SY+SHeight > SourceHeight Then SHeight = SourceHeight - SY
  If DX+SWidth > DestWidth Then SWidth = DestWidth - DX 
  If DY+SHeight > DestHeight Then SHeight = DestHeight - DY
  Select flags
   Case 0 ' Normal
    For Local py:Int = 0 To SHeight-1 
     For Local px:Int = 0 To SWidth- 1
      WritePixel(DestPix,DX+px,DY+py,ReadPixel(SourcePix,SX+px,SY+py))
     Next 
    Next
   Case 1 ' Mirror
    For Local py:Int = 0 To SHeight-1 
     For Local px:Int = 0 To SWidth- 1
      WritePixel(DestPix,DX+px,DY+py,ReadPixel(SourcePix,(SX+(SWidth-1))-px,SY+py))
     Next
    Next
   Case 2 ' Flip
    For Local py:Int = 0 To SHeight-1 
     For Local px:Int = 0 To SWidth- 1
      WritePixel(DestPix,DX+px,DY+py,ReadPixel(SourcePix,SX+px,(SY+(SHeight-1))-py))
     Next
    Next
   Case 3 ' Mirror and Flip
    For Local py:Int = 0 To SHeight-1 
     For Local px:Int = 0 To SWidth- 1
      WritePixel(DestPix,DX+px,DY+py,ReadPixel(SourcePix,(SX+(SWidth-1))-px,(SY+(SHeight-1))-py))
     Next
    Next
  End Select
 EndIf
 UnlockImage(Source)
 UnlockImage(Dest)
End Function




GfK(Posted 2009) [#7]
Can you use the pixmap paste method?
I *think* I may have tried that. It's months since I did this stuff and only now am I getting to fixing it up properly. :/

I'll give it a try.

[edit] No alpha with tPixmap.Paste().


Grey Alien(Posted 2009) [#8]
The code above works with alpha.


GfK(Posted 2009) [#9]
The code above works with alpha.

Yeah I'll have to try those tomorrow and see how they shape up speedwise.

[edit] Just tried them now. Neither of them do what I want (and they're both largely the same anyway). The zero alpha pixels of the image obliterate anything else that was there before, when those pixels should remain untouched. That's exactly the same result as tPixmap.paste().

MichaelB's works, but it isn't fast. 32ms to paste a 128x128 image. Its saving grace is that it avoids the need for GrabPixmap.


Grey Alien(Posted 2009) [#10]
Oh, I just checked and I used ccDrawOnPixmap for Fairway and it seemed fine with a transparent image on another image, but perhaps I missed something.


ImaginaryHuman(Posted 2009) [#11]
You don't really need to define your own code for copying one pixmap onto another - Blitz already provides pixmap windows/static pixmaps, where you can say that one pixmap is a window within an existing 2nd pixmap, and then using pixmap.paste should I presume clip to the window. Whether you call memcopy to copy a whole row of pixels, or do it in your own loop, probably isn't much different seeming as the bottleneck is memory accesses.


Derron(Posted 2009) [#12]
Like mentioned within my Code...
To speed it up you have to remove some comments - it's only for me using the method to precalculate some sprites which get dynamically positioned on some backgroundsprites.

If you "deactivate" the calculations for multiplying and lightning my code should get a small boost - but then you end up ignoring alpha channels, but since its "pixel by pixel" it will never win the race ;D.

I don't think there is a proper "faster" way of doing it without touching the whole part of allocated memory, pointers and some magic calculations with no need to read RGBA separately.


So one my test if he/she can calculate the resulting color as ONE integer without separating RGBA - this will speed up things.


But like mentioned, I don't use my code for real-time, just for precalculation. And yes, it's no problem to colorize the sprites giving additional parameters for percentively change RGB-Values (eg creating different colorized playersprites placed on one image for performance issues).


bye MB


tonyg(Posted 2009) [#13]
.. in case you're still looking this might help.


ImaginaryHuman(Posted 2009) [#14]
Somewhere in some old thread I posted cost to do an alphablended zooming rotating pixmap onto another using pointer access, it should be pretty quick.

See here:

http://www.blitzbasic.com/Community/posts.php?topic=59342#661050


GfK(Posted 2009) [#15]
Just a quick note - decided to use MichaelB's code but with some tiny modifications. I've added in a scaling parameter, and also modified it to take into account an image's handle. I don't need rotation (yet) so I didn't add it.

Function ARGB_Alpha:Int(ARGB:Int)
 Return (argb Shr 24) & $ff
 'Return Int((ARGB & $FF000000:Int) / $1000000:Int) 
End Function

Function ARGB_Red:Int(ARGB:Int)
  Return (argb Shr 16) & $ff
' Return Int((ARGB & $00FF0000:Int) / $10000:Int)
End Function

Function ARGB_Green:Int(ARGB:Int)
  Return (argb Shr 8) & $ff
' Return Int((ARGB & $0000FF00:Int) / $100:Int)
End Function

Function ARGB_Blue:Int(ARGB:Int)
 Return (argb & $ff) 
' Return (ARGB & $000000FF:Int)
End Function

Function ARGB_Color:Int(alpha:Int,red:Int,green:Int,blue:Int)
 Return (Int(alpha * $1000000) + Int(RED * $10000) + Int(green * $100) + Int(blue)) 
End Function

Function DrawOnPixmap(image:TImage, framenr:Int = 0, scale:Float, Pixmap:TPixmap, x:Int, y:Int, alpha:Float = 1.0, light:Float = 1.0) 
      Local TempPix:TPixmap = Null
	  If image = Null Then Throw "image doesnt exist"
	  If framenr = 0 Then TempPix = LockImage(image) 
      If framenr > 0 Then TempPix = LockImage(image, Framenr)
	  If scale <> 1
	  	TempPix = ResizePixmap(TempPix,PixmapWidth(TempPix) * scale,PixmapHeight(TempPix) * scale)
	  EndIf
	  x:-(image.handle_x * scale)
	  y:-(image.handle_y * scale)
	  For Local i:Int = 0 To PixmapWidth(tempPix ) - 1
	    For Local j:Int = 0 To PixmapHeight(tempPix ) - 1
		  If x + i < Pixmap.width And y + j < Pixmap.Height
			Local sourcepixel:Int = ReadPixel(TempPix, i,j)
			Local destpixel:Int = ReadPixel(Pixmap, x+i,y+j)
			Local destA:Float = ARGB_Alpha(destpixel) 
			Local sourceA:Float = ARGB_Alpha(sourcepixel) * alpha
			If sourceA = 255 Then destA = 0
			'remove comment to remove unneeded calculations 
			'but only when light/alpha not used!
'			If sourceA <> 255 And sourceA <> 0
				Local destR:Float = ARGB_Red(destpixel) 
				Local destG:Float = ARGB_Green(destpixel) 
				Local destB:Float = ARGB_Blue(destpixel) 
				Local SourceR:Float = ARGB_Red(Sourcepixel) 
				Local SourceG:Float = ARGB_Green(Sourcepixel) 
				Local SourceB:Float = ARGB_Blue(Sourcepixel) 
					Local AlphaSum:Int = destA + sourceA

					sourceR = (sourceR * light * sourceA / AlphaSum) + destA / AlphaSum * (destR * destA / AlphaSum) 
					sourceG = (sourceG * light * sourceA / AlphaSum) + destA / AlphaSum * (destG * destA / AlphaSum) 
					sourceB = (sourceB * light * sourceA / AlphaSum) + destA / AlphaSum * (destB * destA / AlphaSum) 
					If AlphaSum > 255 Then AlphaSum = 255
					sourcepixel = ARGB_Color(AlphaSum, SourceR, sourceG, sourceB) 
'			EndIf
			If SourceA <> 0 Then WritePixel(Pixmap, x + i, y + j, sourcepixel) 
		  EndIf
		Next
	  Next
	  If framenr = 0 UnlockImage(image)
	  If framenr > 0 UnlockImage(image, framenr)
End Function