Copy From 1 TImage To Another?

BlitzMax Forums/BlitzMax Beginners Area/Copy From 1 TImage To Another?

Is there a way to copy a rect section from 1 TImage to another TImage? Thanks.

You can lock the image to get a pixmap, which you can pass to PixmapWindow() to select a rectangular region, then use LoadImage() to load the pixmap into the other TImage

RectImage:TImage = LoadImage(PixmapWindow(LockImage(FirstImage),10,10,32,32))


or the tpixmap paste method or drawimage/grabimage.

DrawImage and GrabImage is probably much faster.

hello.

DrawImage and GrabImage involve plus data movement.
Please, if is plus faster i need understanding.

thanks,
Paposo

Yikes.. looks like it's going to be more difficult than I had planned. I've never used pixmaps, or locked images, so not quite sure where to go.

In a nut shell I have 2 textures (TImage) loaded, each 512x512 and I want to be able to copy any size rect from one to the other. Speed isn't an issue since it will happen between levels and not during game play. I use transparent png files so hopefully any solutions eventually will support that as well.

hmm.. in the copy function it looks as if I need to:

a) Convert both textures to Pixmaps.
b) Copy from 1 pixmap to the other as needed.
c) Convert the resulting pixmap back to the texture.

Notice I posted in the beginner section, :) so any help is much appreciated. Thanks.

Should be easy to adapt it to your needs...
think i didn't care about destinations alpha (only sources alpha).

To make it a DrawImageOnImage you will need to lock the given destination pixmap and use that instead of a given pixmap. The Rect thing has to be put into the for-loops (from i:int = xstart to xend).


Function ARGB_Alpha:Int(ARGB:Int)
 Return Int((ARGB & $FF000000:Int) / $1000000:Int)
End Function

Function ARGB_Red:Int(ARGB:Int)
 Return Int((ARGB & $00FF0000:Int) / $10000:Int)
End Function

Function ARGB_Green:Int(ARGB:Int)
 Return Int((ARGB & $0000FF00:Int) / $100:Int)
End Function

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

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

Function DrawOnPixmap(image:TImage,framenr:Int=0, Pixmap:TPixmap, x:Int, y:Int)
      Local TempPix:TPixmap = Null
	  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+1 < 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:Int = ARGB_Alpha(destpixel)
			Local sourceA:Int = ARGB_Alpha(sourcepixel)
			If sourceA <> -1 Then 
				If sourceA< -1 Then sourceA = -sourceA
				Local destR:Int = ARGB_Red(destpixel)
				Local destG:Int = ARGB_Green(destpixel)
				Local destB:Int = ARGB_Blue(destpixel)
				Local SourceR:Int = ARGB_Red(Sourcepixel)
				Local SourceG:Int = ARGB_Green(Sourcepixel)
				Local SourceB:Int = ARGB_Blue(Sourcepixel)
				sourceR = Int( Float(sourceA/255.0)*sourceR) + Int(Float((255-sourceA)/255.0)*destR)
				sourceG = Int( Float(sourceA/255.0)*sourceG) + Int(Float((255-sourceA)/255.0)*destG)
				sourceB = Int( Float(sourceA/255.0)*sourceB) + Int(Float((255-sourceA)/255.0)*destB)
				sourcepixel = ARGB_Color(255, 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




bye
MB

I looked into this a while back and then realised I could code my game a different way without the need to copy an image.

It might be best to keep a copy of both images in pixmaps and whenever the pixmaps are modified, then reload the pixmaps back into the images. That way you won't need to GrabImage() or LockImage() first.

This example uses MemCopy() to copy a row of pixels from one image to the next. Just use your own image in the example and point Const Filename to that image. The second image is just the first one flipped vertically. Click on either image to copy the cursor square from one to the other .

Strict
Const Filename:String = "169a.jpg" 'name of image to load

Graphics 800,600

Local Pix1:TPixmap = LoadPixmap(Filename) 'load the images into pixmaps
Local Pix2:TPixmap = YFlipPixmap(LoadPixmap(Filename)) 'flip second copy

If PixmapFormat(Pix1) <> PF_RGBA8888 Then Pix1 = ConvertPixmap(Pix1,PF_RGBA8888) 'make sure the pixmaps are 32 bit format
If PixmapFormat(Pix2) <> PF_RGBA8888 Then Pix2 = ConvertPixmap(Pix2,PF_RGBA8888)

Local PWidth1:Int = PixmapWidth(Pix1) 'Dimentions of pixmaps
Local PHeight1:Int = PixmapHeight(Pix1)
Local PWidth2:Int = PixmapWidth(Pix2)
Local PHeight2:Int = PixmapHeight(Pix2)

Local Dirty:Int = True 'flag to indicate if pixmaps have been altered
Local Image1:TImage, Image2:TImage 'TImages to display

HideMouse()
While Not KeyHit(KEY_ESCAPE) And Not AppTerminate()
	Local MX:Int = MouseX() 'store the mouse coordinates
	Local MY:Int = MouseY()
	
	If Dirty 'Pixmaps have been modified
		Image1 = LoadImage(Pix1) 'load the pixmaps into the Images
		Image2 = LoadImage(Pix2)
		Dirty = False
	End If
	
	Cls 'draw the images
	SetColor 255,255,255
	DrawImage Image1,0,0
	DrawImage Image2,400,0
	
	SetColor 255,0,0
	DrawLine MX,MY,MX,MY+63 'draw bounding box
	DrawLine MX,MY,MX+63,MY
	DrawLine MX+63,MY,MX+63,MY+63
	DrawLine MX,MY+63,MX+63,MY+63
	Flip
	
	If MouseHit(1) 'check for mouse button press
		Local Source:Byte Ptr, Dest:Byte Ptr
		Local SourcePitch:Int, DestPitch:Int
		If MX >= 0 And MX+64 < PWidth1 And MY >= 0 And MY+64 < PHeight1 'mouse is on first image
			Source = PixmapPixelPtr(Pix1) + MY*PixmapPitch(Pix1) + MX*4 'source points to first pixel
			Dest = PixmapPixelPtr(Pix2) + MY*PixmapPitch(Pix2)  + MX*4'dest points to first pixel
			SourcePitch = PixmapPitch(pix1) 'Number of bytes from one line to the next
			DestPitch = PixmapPitch(Pix2)
			Dirty = True 'modification needs to take place
		Else If MX >= 400 And MX+64 < PWidth2+400 And MY >= 0 And MY+64 < PHeight2 'Mouse is on 2nd image
			Source = PixmapPixelPtr(Pix2) + MY*PixmapPitch(Pix2) + (MX-400)*4
			Dest = PixmapPixelPtr(Pix1) + MY*PixmapPitch(Pix1)  + (MX-400)*4
			SourcePitch = PixmapPitch(pix2)
			DestPitch = PixmapPitch(Pix1)

			Dirty = True
		End If
		
		If Dirty 'Check to see if pixmaps need to be modified	
			For Local i:Int = 1 To 64
				MemCopy(Dest,Source,256) '256 bytes = 64 pixels
				Source :+ SourcePitch 'point to next line
				Dest :+ DestPitch 'point to next line
			Next
		End If
	End If
Wend




Thanks TomToad, the demo seems to work, but any way to wrap this up into one nice function such as:

CopyImageToImage(srcTImage:TImage,srcx:int,srcy:int,srcwidth:int,srcheight:int,dstTImage,dstx:int,dsty:int)

I would be most grateful if this could be done. And then you need to post it in the code archive for all to use. :) Thanks!

Anything you do with a pixmap is going to be much slower than anything you do with images. DrawImage and GrabImage are probably faster regardless of whether it seems like you're shifting more data around. The GPU is usually 10 times or more faster than dealing with pixmaps. The pixmaps have to be transferred over the graphics bus which is quite a slow process.

hehe if someone "functionalises" it, I'll snaffle it too :-)

I always found GrabImage to be quite slow (especially on an 800x600 screen) like 200+ms, because it has to come down from VRAM to RAM which the GFX cards are not really designed for.

If you're not worried about masking then you can do something like this:

SuperStrict
Graphics 800,600,0
Local image1:TImage = LoadImage("max.png")
Local image2:TImage = LoadImage("brllogo-thin.png")
Local image3:TImage = tg_copyimagerect(image1,image2,10,10,80,20,100,100,0,0,0)
While Not KeyHit(KEY_ESCAPE)
	Cls
	DrawImage image3 , MouseX() , MouseY()
	Flip
Wend
Function tg_copyimagerect:TImage(to_image:TImage , from_image:TImage , from_x:Int , from_y:Int , size_x:Int , size_y:Int , ..
	to_x:Int , to_y:Int , red:Int , green:Int , blue:Int)
	Local temp_from_pixmap:TPixmap = LockImage(from_image) 
    Local temp_pixmap_window:TPixmap = PixmapWindow(temp_from_pixmap , from_x , from_y , size_x , size_y)
    UnlockImage(from_image)
	Local temp_to_pixmap:TPixmap = LockImage(to_image)
	temp_to_pixmap.paste(temp_pixmap_window , to_x , to_y)
	SetMaskColor red,green,blue
	Return LoadImage(temp_to_pixmap)	
End Function


Otherwise you can try this which I have cleaned up slightly:

SuperStrict
Graphics 640,480
Local image1:TImage=LoadImage("max.png")
Local image2:TImage=LoadImage("light.png")
While Not KeyHit(key_escape)
	Cls
	If MouseHit(1) image1:TImage=tg_copyimagerect(image1,image2,0,0,150,150)
	DrawImage image1,0,0
	Flip
Wend
WaitKey()
Function tg_copyimagerect:TImage(imagea:TImage , imageb:TImage , from_x:Int , from_y:Int , size_x:Int,size_y:Int,mask_r:Int = 0, mask_g:Int=0,mask_b:Int=0)
	Local t1:Int = MilliSecs()
	Local argb:Int=intcolor(mask_r,mask_g,mask_b)
  If from_x + ImageWidth(imageb) > ImageWidth(imagea) Or from_y + ImageHeight(imageb) > ImageHeight(imagea) RuntimeError("Imagea to big to fit in imageb")
  Local mypixmap2:TPixmap=LockImage(imageb)
  UnlockImage(imageb)
  Local mypixmap1:TPixmap=LockImage(imagea)
  Local mypixelptr2:Int Ptr = Int Ptr(mypixmap2.pixelptr(0,0))
  Local mypixelptr1:Int Ptr = Int Ptr(mypixmap1.pixelptr(from_x,from_y))
  Local from_value:Int = from_x * from_y
  Local to_value:Int = (from_x + size_x) * (from_y + size_y)
  For Local my_x:Int = from_value To to_value
     If mypixelptr2[0] <> argb 
         If mypixelptr2[0] <> 0 mypixelptr1[0]=mypixelptr2[0]
    EndIf
     mypixelptr1:+1
     mypixelptr2:+1
 Next
Local t2:Int = MilliSecs()
Print (t2-t1)
  Return LoadImage(mypixmap1)
'  end_func=MilliSecs()
End Function
Function IntColor:Int(R:Int,G:Int,B:Int,A:Int=255)
'returns argb value from red, green, blue.
     Return A Shl 24 | R Shl 16 | G Shl 8 | B Shl 0
End Function


GrabImage grabs a pixmap as well as an turning it into an image? It shouldn't have to. For example glCopyTexSubImage2D copies an area from the backbuffer into an existing texture without ever going to main memory and it's fast enough that you could do it at least a few times per frame at 60hz.

thinking about this today and realized there was no need to store an extra copy of the pixmaps since the TImage already had a pixmap copy. When you LockImage(), it returns the copy of the pixmap, and the next time you draw the image, the pixmap will be copied into VRAM automatically.
So here is a Functionalized version, with some bounds checking as well.
Strict
Const Filename:String = "169a.jpg" 'name of image to load

Graphics 800,600

Local Image1:TImage = LoadImage(Filename) 'load the images into pixmaps
Local Image2:TImage = LoadImage(YFlipPixmap(LockImage(Image1))) 'flip second copy
UnlockImage(Image1)

Local Image1Width:Int = ImageWidth(Image1)
Local Image1Height:Int = ImageHeight(Image1)
Local Image2Width:Int = ImageWidth(Image2)
Local Image2Height:Int = ImageHeight(Image2)

While Not KeyHit(KEY_ESCAPE) And Not AppTerminate()
	Local MX:Int = MouseX()
	Local MY:Int = MouseY()
	Cls
	SetColor 255,255,255
	
	DrawImage Image1,0,0
	DrawImage Image2,400,0
	
	SetColor 255,0,0
	DrawLine MX,MY,MX+64,MY
	DrawLine MX,MY,MX,MY+64
	DrawLine MX+64,MY,MX+64,MY+64
	DrawLine MX,MY+64,MX+64,MY+64
	
	Flip
	If MouseHit(1)
		If MX >= 0 And MX < Image1Width And MY >= 0 And MY < Image1Height
			CopyImageRect(Image1,MX,MY,64,64,Image2,MX,MY)
		Else If MX - 400 >= 0 And MX - 400 < Image2Width And MY >= 0 And MY < Image2Height
			CopyImageRect(Image2,MX-400,MY,64,64,Image1,MX-400,MY)
		End If
	End If
Wend


Function CopyImageRect(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


Mr.Toad, someday when I get all BlitxMax braniac, I hope to return the favor. This is a very valuable function I'm sure others will have a need for at some point. Thank you for taking the time to code this. :)

Do you know if there is any scenario where this will not work? (16,24,32bit modes?, OGL or DX?) It appears to be cross platform compatible.

One thing I like about it, you could use it to copy over new sprite images from a source texture onto your game texture without having to change your main loop code. Speed is not an issue since this would be done between levels, etc. ;)

Hot stuff. Thanks TomToad! Hope you don't mind but I've added it to my framework and credited you.

ImaginaryHuman: Yeah it is, but first its a GL only function and second I'm not sure if it is OpenGL 1.2 compliant which is what BM enforces on Max2D.
If one used the DX surface commands the whole stuff including setBuffer would be implementable again and work but then the GL user would be pissed again.
You see the problem? :)

@MGE Developer:
I tried it out with 16, 24, and 32 bit modes. Both OpenGL and DirectX works. I couldn't test 24 bit on OpenGL because my card apparently only supports 16 and 32 bit modes with OpenGL.
I did think of one possible problem. It would be with byte alignment. Shouldn't be a problem now since I believe BMax is optimized for 32 bit on all platforms, but if it were to change and BMax were optimized for 64 bit, then the above code will break. I tried to locate a byte-align field for the pixmaps, but there doesn't seem to be one.
You could go the safe route and rewrite the above to use ReadPixel and WritePixel in a loop, but that would be much slower than MemCopy() and would bog down the program if you needed to copy a lot of data each loop.

@Grey Alien:
Thanks. Glad you can find some use for it. :)
In all your studying of BMAX, by chance have you figured out how to find the byte alignment of a pixmap?

TomToad: No I'm afraid not. BMax is pretty big and most of my study has been high level objects or Windows API calls rather than graphics functions. I pretty sure something like that was possible in BPlus. Why not start a thread about it?

Using TomToad's suggestion of using Read/Write pixel, here's a modified version complete with 4 copy modes; Normal, Mirror,Flip,Mirror&Flip. Speed ofcourse is slower, but using it real time was never my intended use. Thanks again TomToad, this is the exact routine I had in mind when the topic was first created. :)

Function CopyImageToImage(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


Hey so that's more advanced cool. Presume you've tested it. Now MGE Developer, may I place this in my framework please?

I wonder if a render to texture routine would make this usable for realtime?

Yes but it will fail on a lot of systems (ie nearly any intel or other onboard which means 70%+ of all users)

Why does render to texture fail on some systems?

Why does render to texture fail on some systems?
Not a problem with GFX cards nowadays, even integrated intel!

Explain that to those users not waisting money on new systems every 2 years.
Even thought, I bought my tablet in Jan 07 and have a GMA900 in, so don't bet on anything beeing available.

Until recently intel didn't even fullfill the T&L compliant level (GMA900 definitely does not or its drivers are just that crappy that they break the T part over and over again). And even now, Intel only supports exactly that part that is needed to get Microsofts Vista ready logo, not the least bit more *Intel X3100 CPU to VGA adapter erm GPU* (ie its ok for aero but still a class 4 GPU with any other onboard from SiS, S3 and Intel and far far behind even the worst ATI or NVIDIA onboard performance and capability wise. Even GeForce 4200 Ti were worlds faster)

Yes glCopyTexSubImage2D is OpenGL only, so no DX implementation, but it is a part of GL 1.2 and can be compiled with standard BlitzMax without any extensions. It's way faster than anything that could be done with the CPU.

If you are going to go with the CPU version, then I recommend getting rid of WritePixel and ReadPixel and replace them with pointer access ie basepointer[offset]=whatever. That removes the overhead of having to `look up` the pixmap for every single writepixel call and to calculate the offset based on coordinates. For a small routine that you're calling many thousands of times like that you should be looking to optimize it. Keeping one pointer and doing pointer access will show you a speed boost.

"Now MGE Developer, may I place this in my framework please?"

Ofcourse! I added it to mine as well. ;)

thx

*b0mp*

Function CopyImageToImage(Source:TImage ,SX:Int,SY:Int,SWidth:Int,SHeight:Int,Dest:TImage Var,DX:Int,DY:Int,flags:Int=0)

It needs Var :P I randomly needed a function like this today.. \o/