Pixmap and Blend modes.
BlitzMax Forums/BlitzMax Beginners Area/Pixmap and Blend modes.
| ||
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 |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
Hmm.. I'd like to see where you take this. |
| ||
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. |
| ||
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 |
| ||
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. |
| ||
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. |
| ||
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 |
| ||
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 |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
Also replace your I could be mistaken, but I believe the compiler does this for you. /4 with Shr 2 |
| ||
That'd be nice, I wonder what other optimizations it does? |
| ||
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. |
| ||
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... |
| ||
Are you guys still trying to get an` alpha blend` of two pixmaps working? |
| ||
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. |
| ||
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. |