Pixmap and Blend modes.

BlitzMax Forums/BlitzMax Beginners Area/Pixmap and Blend modes.

tonyg(Posted 2005) [#1]
Anybody know how I would 'blend' the 2 pixmaps together?
Graphics 640,480,0
Global image1:Timage=LoadImage("i1.png")  '512*512
Global image2:TImage=LoadImage("max.png")
Global image3:TImage=LoadImage("max.png")
Global start_func:Int
Global end_func:Int
drawbuffer(image1,image2,0,0)
DrawText end_func-start_func,600,0
drawbuffer(image1,image3,40,40)
DrawText end_func-start_func,600,20
DrawImage image1,0,0
Flip
WaitKey()
Function drawbuffer(imagea:TImage,imageb:TImage,x:Int,y:Int)
  start_func=MilliSecs()
  mypixmap1:TPixmap=LockImage(imagea)
  mypixmap2:TPixmap=LockImage(imageb)
  mypixmap1.paste(mypixmap2,x,y)
  UnlockImage(imageb)
  UnlockImage(imagea)
  end_func=MilliSecs()
End Function



TartanTangerine (was Indiepath)(Posted 2005) [#2]
Pixmaps are a collection of pixels right? so to mix to pixmaps you will need to cycle though each of the pixels in each map and calculate the result from that information. It's just like mixing 2 images in Blitz2d.

Or you draw the two images to the backbuffer using normal commands, apply your blendmodes etc. (the images will be drawn as quads) and then use grabpixmap to get the result.


tonyg(Posted 2005) [#3]
Yep, that's what I've been doing. The 'lure' is that creating a pixmap using lockimage is sooooo quick. The same operation (oK with masking) to use grabpixmmap takes 200 times as long.
If it can't be done (with WP) then it can't be done.


TartanTangerine (was Indiepath)(Posted 2005) [#4]
Is it because Pixmaps are stored in videomemory? maybe simple pixmap operations are handled in videomem but when you grabpixmap it pulls from vidmem into sysmem and then back again.


tonyg(Posted 2005) [#5]
If I could get maskpixmap to work then it might be an 'ok' solution to imagebuffers.
<edit> maskpixmap is working OK, it's mypixmap.paste which isn't respecting the masked pixels as it does a memcopy.


altitudems(Posted 2005) [#6]
Hmm.. I'd like to see where you take this.


tonyg(Posted 2005) [#7]
Not much further to be honest.
Turns out using read/write pixel is quicker than using the grabimage/grabpixmap
This takes 6 ms on my 2700+, 9800pro 2G Ram system...
Graphics 640,480,0
Global image1:Timage=LoadImage("i1.png")  '512*512
Global image2:TImage=LoadImage("max.png")
Global start_func:Int
Global end_func:Int
drawbuffer(image1,image2,0,0)
DrawText end_func-start_func,600,0
DrawImage image1,0,0
Flip
WaitKey()
Function drawbuffer(imagea:TImage,imageb:TImage,x:Int,y:Int)
  start_func=MilliSecs()
  If x + ImageWidth(imageb) > ImageWidth(imagea) Or y + ImageHeight(imageb) > ImageHeight(imagea) RuntimeError("Imagea to big to fit in imageb")
  mypixmap2:TPixmap=LockImage(imageb)
  UnlockImage(imageb)
'  mypixmap2=MaskPixmap(mypixmap2,0,0,0)
  mypixmap1:TPixmap=LockImage(imagea)
	For Local wide=0 To PixmapWidth(mypixmap2)
		For Local high=0 To PixmapHeight(mypixmap2)
			Local RGB=ReadPixel(mypixmap2,wide,high)
			If rgb <> 0 WritePixel (mypixmap1,x+wide,y+high,rgb)
		Next
	Next
	Rem
  mypixmap1:TPixmap=LockImage(imagea)
  mypixmap1.paste(mypixmap2,x,y)
    End Rem
  UnlockImage(imagea)
  end_func=MilliSecs()
End Function

needs tidying up for specific maskcolors or maskpixmap but I don't know how much quicker I need it. I suppose you could add blend modes with the Writepixel stuff but I only want to paste images.


ImaginaryHuman(Posted 2005) [#8]
You'd be better off getting the base pointer of the pixmap memory with PixmapPtr() and then using a pointer variable such as `writeto:Int Ptr` with casting such as If rgb<>0 writeto[0]=rgb, and handle the x and y locations incrementing by using your own counters.

If you jump into WritePixel for every pixel, it has to look up the pixmap, find the base pointer, add your x coordinate (which you have to also calculate before you can pass it), take your y coord (also needing constant calculation) and multiply it by the row bytes then add it to the total, THEN access the pixel at the memory address. Lots of work. Just keep your own pointer and increment it with each write, by 1 (since it's an int pointer, it actually adds 4 to the pointer). That should be a lot faster.

Here is a cut-and-paste of part of an alphablending routine ... the main part. You'll need to set up the variables. It might not be the fastest on the planet but I can confirm that it works.

Also to answer previous uncertaintly, yes pixmaps are entirely in main memory. When you grab a pixmap you are dumping the backbuffer to a pixmap in main memory, which goes over the graphics bus. Similarly when you draw a pixmap or convert it into an image, it goes over the bus from main memory to graphics memory.

This code just does one ROW of pixels, once set up. You'd need a for/next loop to do all rows.

				Local MRowBytesLeft:Int		'Row byte counter
				Local MSourcePointer4:Int Ptr=DataBankIntPtr+MSourceXYSkip		'For copying Int data from
				Local MDestPointer4:Int Ptr=MDest.DataBankIntPtr+MDestXYSkip		'For copying Int data to
				Local MSourceData:Int		'For storing source pixel data
				Local MDestData:Int		'For storing destination pixel data
				Local MFilterRed:Int=$FF000000		'To mask off Red data
				Local MFilterGreen:Int=$00FF0000		'To mask off Green data
				Local MFilterBlue:Int=$0000FF00		'To mask off Blue data
				Local MFilterAlpha:Int=$000000FF		'To mask off Alpha
				Local MDestRed:Int		'To compose Red
				Local MDestGreen:Int		'To compose Green
				Local MDestBlue:Int		'To compose Blue
				Local MAlpha:Int		'To get source alpha
				Local MSourceRatio:Float		'To calculate amount of source color
				Local MDestRatio:Float		'To calculate amount of dest color
				For Local MRow:Int=0 To MRows
					MRowBytesLeft=MRowBytes		'Initialize
While MRowBytesLeft>2		'Loopcounter=3 or more (at least 4 bytes/1 pixel to go)
						MSourceData=MSourcePointer4[0]		'Get source pixel data RGBA
						MDestData=MDestPointer4[0]		'Get destination pixel data RGBA
						MAlpha=MSourceData & MFilterAlpha		'Get source alpha
						MSourceRatio=(1.0/256.0)*MAlpha		'Calculate amount of source color
						MDestRatio=1.0-MSourceRatio		'Calculate amount of dest color
						MDestRed=Int((((MDestData & MFilterRed) Shr 24) *MDestRatio) + (((MSourceData & MFilterRed) Shr 24) *MSourceRatio))
						MDestGreen=Int((((MDestData & MFilterGreen) Shr 16) *MDestRatio) + (((MSourceData & MFilterGreen) Shr 16) *MSourceRatio))
						MDestBlue=Int((((MDestData & MFilterBlue) Shr 8) *MDestRatio) + (((MSourceData & MFilterBlue) Shr 8) *MSourceRatio))
						MDestPointer4[0]=(MDestData & MFilterAlpha) | (MDestRed Shl 24) | (MDestGreen Shl 16) | (MDestBlue Shl 8)		'AlphaBlend 1 pixel
						MRowBytesLeft:-4		'Just done 4 bytes/1 pixel
						MSourcePointer4:+1		'Jump past 1 pixel
						MDestPointer4:+1		'Jump past 1 pixel
					Wend
					MSourcePointer4:+MSourceRowSkip		'Next row in source
					MDestPointer4:+MDestRowSkip		'Next row in dest
     Next



Haramanai(Posted 2005) [#9]
Wohou. I am really lost in your code AngleDaniel! Their must be some code outside of your post that can explain the what is : MDest , DataBankIntPtr , MSourceXYSkip ....

If you can post some easier code , like just printng the RGBA of evry pixel to the output window it will be much easier for me and other beginers to follow.


tonyg(Posted 2005) [#10]
I now have it behaving as if I'm doing a pixmap.paste.
What I need to do next is read the 4 bytes at mypixmapptr2, convert them to ARGB and, if <> mask_argb memcopy them.
Graphics 640,480,0
Global image1:Timage=LoadImage("i1.png")  '512*512
Global image2:TImage=LoadImage("max.png")
Global start_func:Int
Global end_func:Int
drawbuffer(image1,image2,0,0)
DrawText end_func-start_func,600,0
DrawImage image1,0,0
Flip
WaitKey()
Function drawbuffer(imagea:TImage,imageb:TImage,x:Int,y:Int)
  If x + ImageWidth(imageb) > ImageWidth(imagea) Or y + ImageHeight(imageb) > ImageHeight(imagea) RuntimeError("Imagea to big to fit in imageb")
  start_func=MilliSecs()
  mypixmap2:TPixmap=LockImage(imageb)
  UnlockImage(imageb)
  mypixmap1:TPixmap=LockImage(imagea)
  Local mypixelptr2:Byte Ptr = mypixmap2.pixelptr(0,0)
  Local mypixelptr2backup:Byte Ptr = mypixelptr2
  Local mypixelptr1:Byte Ptr = mypixmap1.pixelptr(x,y)
  Local mypixelptr1backup:Byte Ptr = mypixelptr1
  For my_x=0 To ((mypixmap2.width)*(mypixmap2.height))*4
     MemCopy (mypixelptr1,mypixelptr2,4)
     mypixelptr1:+1
     mypixelptr2:+1
     If mypixelptr2 = mypixelptr2backup+mypixmap2.pitch
         mypixelptr1 = mypixelptr1backup+mypixmap1.pitch
         mypixelptr1backup=mypixelptr1
         mypixelptr2backup=mypixelptr2
     EndIf
  Next
  UnlockImage(imagea)
  end_func=MilliSecs()
End Function

so far I've saved about 3ms on doing a full writepixel so I doubt this is going to make much of a saving.
Maybe I can check for concurrent pixels which match the maskargb and skip them all and then the same for pixels to copy.
If I can optimize anything else then let me know.


tonyg(Posted 2005) [#11]
Might still be a bit quicker by storing concurrent transparent/non-transparent pixels. This takes 2ms.
Graphics 640,480,0
Global image1:Timage=LoadImage("i1.png")  '512*512
Global image2:TImage=LoadImage("max.png")
Global image3:Timage=LoadImage("clogo1.png")
Global start_func:Int
Global end_func:Int
Local red,green,blue:Int
GetMaskColor(red,green,blue)
argb:Int=intcolor(red,green,blue)
start_func=MilliSecs()
drawbuffer(image1,image2,0,0,argb)
'drawbuffer(image1,image3,0,60,argb)
end_func=MilliSecs()
DrawText end_func-start_func,600,0
DrawImage image1,0,0
Flip
WaitKey()
Function drawbuffer(imagea:TImage,imageb:TImage,x:Int,y:Int,argb:Int)
  If x + ImageWidth(imageb) > ImageWidth(imagea) Or y + ImageHeight(imageb) > ImageHeight(imagea) RuntimeError("Imagea to big to fit in imageb")
'  start_func=MilliSecs()
  mypixmap2:TPixmap=LockImage(imageb)
  UnlockImage(imageb)
  mypixmap1:TPixmap=LockImage(imagea)
  Local mypixelptr2:Int Ptr = Int Ptr(mypixmap2.pixelptr(0,0))
  Local mypixelptr2backup:Int Ptr = mypixelptr2
  Local mypixelptr1:Int Ptr = Int Ptr(mypixmap1.pixelptr(x,y))
  Local mypixelptr1backup:Int Ptr = mypixelptr1
  For my_x=0 To ((mypixmap2.width)*(mypixmap2.height))
     If Var mypixelptr2 <> argb 
        If Var mypixelptr2<>0 MemCopy (Byte Ptr(mypixelptr1),Byte Ptr(mypixelptr2),4)
    EndIf
     mypixelptr1:+1
     mypixelptr2:+1
     If mypixelptr2 = mypixelptr2backup+(mypixmap2.pitch/4)
         mypixelptr1 = mypixelptr1backup+(mypixmap1.pitch/4)
         mypixelptr1backup=mypixelptr1
         mypixelptr2backup=mypixelptr2
     EndIf
  Next
  UnlockImage(imagea)
'  end_func=MilliSecs()
End Function
Function IntColor(R,G,B,A=255)
'returns argb value from red, green, blue.
     Return A Shl 24 | R Shl 16 | G Shl 8 | B Shl 0
End Function



ImaginaryHuman(Posted 2005) [#12]
It came from this method:

Method CopyArea(MDest:Bitmap,MMode:Int=0,MColor:Int=$FFFFFFFF,MWidth:Int=0,MHeight:Int=0,MSourceX:Int=0,MSourceY:Int=0,MDestX:Int=0,MDestY:Int=0,MClip:Int=False)


and includes this at the top:

Local MRows:Int=MHeight-1 'How many rows to copy, clip at bottom of dest
Local MSourceRowBytes:Int=Width 'How many Int's in the source `Bitmap` per row
Local MDestRowBytes:Int=MDest.Width 'How many Int's in the dest `Bitmap` per row
Local MSourceXYSkip:Int=MSourceX+(MSourceY*MSourceRowBytes) 'How many bytes to skip vertically to get to top left of source area
Local MDestXYSkip:Int=MDestX+(MDestY*MDestRowBytes) 'How many bytes to skip vertically to get to top left of dest area
Local MSourceRowSkip:Int=Width-MWidth 'Skip amount in Int's, per row to account for Width difference in source
Local MDestRowSkip:Int=MDest.Width-MWidth 'Skip amount in Int's, per row to account for Width difference in dest
Local MRowBytes:Int=MWidth Shl 2 'Bytes per row to copy


tonyg(Posted 2005) [#13]
Did anybody have any feedback on this? I don't have much faith in my programming dexterity so if anybody can come up with improvements I'd appreciate it.
Normally, I've missed something completely obvious.


ImaginaryHuman(Posted 2005) [#14]
In your code above, you shouldn't need to use MemCopy, that's pretty slow jumping in and out for every pixel.

Use [0] on the end of a pointer, ie

If Var mypixelptr2<>0 mypixelptr2[0]=mypixelptr1[0]

For which you will need to use an Int pointer to point to 4-byte pixels, rather than a byte pointer - so that each time 4 bytes are copied instead of one. It should be faster than the memcopy version, I would think.


tonyg(Posted 2005) [#15]
Thanks AngelDaniel.
Replacing...
        If Var mypixelptr2<>0 MemCopy (Byte Ptr(mypixelptr1),Byte Ptr(mypixelptr2),4)

with...
If Var mypixelptr2<>0 mypixelptr1[0]=mypixelptr2[0]

knocks off another ms.
1ms is good enough for me.


ImaginaryHuman(Posted 2005) [#16]
Also replace your

/4

with

Shr 2

Also if you test for when the pixmap.pitch is the same as the pixmap.width (ie there is nothing to skip), you should be able to get rid of having to test whether to skip past the end bytes or whatever.


FlameDuck(Posted 2005) [#17]
Also replace your

/4

with

Shr 2
I could be mistaken, but I believe the compiler does this for you.


ImaginaryHuman(Posted 2005) [#18]
That'd be nice, I wonder what other optimizations it does?


Hotcakes(Posted 2005) [#19]
Yes, I'm also under the impression the full range of BlitzPC products makes as many optimisations when it comes to constant values as possible. So statements like 2^7/8*65783.0753 presumably aren't very CPU intensive at run time... It only -needs- to calculate at run time when a variable is involved.


Chris C(Posted 2005) [#20]
almost sure that gcc does this even at -O1, can this option be passed to the compiler, there used to be a trick with import or somthing...


ImaginaryHuman(Posted 2005) [#21]
Are you guys still trying to get an` alpha blend` of two pixmaps working?


tonyg(Posted 2005) [#22]
Are you guys still trying to get an` alpha blend` of two pixmaps working?

I'm not sure anybody was trying to use alpha blend just maskblend as quickly as possible.


ImaginaryHuman(Posted 2005) [#23]
oh. Blend usually means merging the color values. Otherwise you call it Masking. I was showing a way to blend the color values together so that the alpha channel is used to make one of the pixmaps semi-transparent.