Realtime Pizmap Pan/zoom

BlitzMax Forums/BlitzMax Programming/Realtime Pizmap Pan/zoom

neilo(Posted 2006) [#1]
Hi All,

I present below my code for realtime pixmap panning/zooming:
Type PixmapZoomer
	Field bg:TPixmap					' Background to render into
	Field pm:TPixmap					' Loaded pixmap
	Field width:Int,height:Int
	Field aspectRatio:Float
	Field iWidth:Int,iHeight:Int
	Field scale:Float
	Const FPS=60
		
	Function Create:PixmapZoomer(width:Int,height:Int,filename:String="")
		Local pz:PixmapZoomer=New PixmapZoomer
		pz.width=width
		pz.height=height
		pz.bg=CreatePixmap(width,height,PF_RGB888)
		If filename<>""
			pz.Load fileName
		EndIf
		Return pz
	End Function
	
	Method Load(fileName:String)
		Local q:TPixmap=LoadPixmap(fileName)
		pm=ConvertPixmap(q,PF_RGB888)
		iWidth=PixmapWidth(pm)
		iHeight=PixmapHeight(pm)
		aspectRatio=Float(width)/Float(height)
		scale=Float(width)/Float(iWidth)
	End Method
	
	Method GetCorrectWidth:Int(height:Int)
		Return Int(Float(height)*aspectRatio)
	End Method
	
	Method GetCorrectHeight:Int(width:Int)
		Return Int(Float(width)/aspectRatio)
	End Method
	
	Method Perform(r1:rect,r2:rect,t:Int)
		' Zoom pm from r1 to r2 in time t(millisecs)
		Local nSteps:Float=(t*FPS)/1000
		Local xStep:Float=Float(r2.x-r1.x)/nSteps
		Local yStep:Float=Float(r2.y-r1.y)/nSteps
		Local wStep:Float=Float(r2.width-r1.width)/nSteps
		Local hStep:Float=Float(r2.height-r1.height)/nSteps
		Local x:Float,y:Float,w:Float,h:Float
		
		x=Float(r1.x)
		y=Float(r1.y)
		w=Float(r1.width)
		h=Float(r1.height)
		For Local i:Int=0 To nSteps
			GetRectFast x,y,w,h
			DrawPixmap bg,0,0
			Flip
			x:+xStep
			y:+yStep
			w:+wStep
			h:+hStep
		Next
	End Method
	
	Method GetRect(sx:Int,sy:Int,sWidth:Int,sHeight:Int)
		If pm=Null Then Return
		
		If sWidth>iWidth And sHeight>iHeight
			sWidth=iWidth
			sHeight=GetCorrectHeight(sWidth)
			If sHeight>iHeight Then
				sHeight=iHeight
				sWidth=GetCorrectWidth(sHeight)
			EndIf
		ElseIf sWidth>iWidth Or sHeight=0
			sWidth=iWidth
			sHeight=GetCorrectHeight(sWidth)
			If sHeight>iHeight Then
				sHeight=iHeight
				sWidth=GetCorrectWidth(sHeight)
			EndIf
		ElseIf sHeight>iHeight Or sWidth=0
			sHeight=iHeight
			sWidth=GetCorrectWidth(sHeight)
			If sWidth>iWidth Then
				sWidth=iWidth
				sHeight=GetCorrectHeight(sWidth)
			EndIf
		EndIf
		GetRectFast sx,sy,sWidth,sHeight
	End Method
	
	Method GetRectFast(sx:Int,sy:Int,sWidth:Int,sHeight:Int)
		Local endY:Int=sy+sHeight-1
		Local endX:Int=sx+sWidth-1
		Local yStep:Float=sHeight/Float(height)
		Local xStep:Float=sWidth/Float(width)
		Local pixel:Byte Ptr
		Local pixel2:Byte Ptr
		Local pitch2:Int=bg.pitch
		Local bgXStart:Byte Ptr
		
		Local y:Float=sy
		Local x:Float
		bgXStart=bg.PixelPtr(0,0)
		pixel2=bgXStart
		While y<=endY
			x=sx 
			While x<=endX
				pixel=pm.PixelPtr(x,y)
				pixel2[0]=pixel[0]
				pixel2[1]=pixel[1]
				pixel2[2]=pixel[2]
				x:+xStep
				pixel2:+3
			Wend
			y:+yStep
		Wend
	End Method
End Type

Essentially, I needed to implement a "Ken Burns" effect on arbitarily lage bitmaps, and render them to a pixmap which is them dumped to the screen.

The code is not without some limitations. The maximum zoom is to a 1:1 pixel level (by design), and there is absolutly no error checking at run time (as I use my authoring application to ensure that I don't go over the bounds of the pixmap).

Speed is achieved by reading directly from one pixmap and writing to the other. Since the destination pixmap (bg) has a direct 1:1 relation to the screen, I can speed up by getting the pointer to (0,0) and only ever increasing it.

GetRectFast() takes it's parameters as (x,y,width,height) from within the source bitmap, which can be any arbitary size, and scales the bitmap to fit on the screen by pixel skipping. Since the image is in motion, there is no real need to perform some sort of pixel averaging over the skipped pixels.

GetRect() is used by the authoring application to ensure that pixmap coordinates are valid and have the correct aspect ratio.

This code could stand futher refinement, and a few more options, but as it stands it suits my needs well.

Enjoy!

Neil


Yan(Posted 2006) [#2]
Thanks for sharing, but it's better to put stuff like this in the code archives as they will just get buried and eventually lost in the forums.


BlackSp1der(Posted 2006) [#3]
'rect' type isn't defined.
and why you don't use RESIZEPIXMAP?
Method Perform(r1:rect,r2:rect,t:Int)



ImaginaryHuman(Posted 2006) [#4]
Probably would be faster to write into an Image and draw the image.


neilo(Posted 2006) [#5]
@BlackSp1der,

'rect' type isn't defined.


rect is a simple x,y,width,height structure.

and why you don't use RESIZEPIXMAP?


One reason: speed.

Actually, there are a few reasons, but it ultimately comes down to speed.

ResizePixmap() stretches / shrinks a bitmap, but only an entire bitmap. I could use PixmapWindow() to create the window on the original image, but my speed tests indicated that this was slower. My function, while somewhat limited, is quite fast - 67ms to resize any given bitmap while in debug, and much, much faster when running without.

@AngelDaniel,

Probably would be faster to write into an Image and draw the image.


Not according to my speed tests. I spent a day writing, testing and rewriting the core routine, and working out the math needed for direct byte access.

Neil