Code archives/Graphics/Picture Viewer with scoll and zoom

This code has been declared by its author to be Public Domain code.

Download source code

Picture Viewer with scoll and zoom by WendellM2005
I wrote this as a prototype for a viewer of large (image-based) maps that can be easily scrolled and zoomed via mouse buttons/wheel. As such, it has several techniques that might be useful when starting a similar project of your own.

It uses Tesuji's routines to divide a large image file up into smaller tiles to efficiently use video memory. It also integrates a hooked redraw event within the main type rather than as a stand-alone function.
Rem

	A sample picture viewer, mostly to explore type-enclosed event handling and 
	Tesuji's 256x256 tiled handling of large images.  Also an experiment of
	mouse dragging and right-click popup menu (and mouse wheel) for scaling.
	
	by Wendell Martin, December 27, 2005

End Rem

Framework BRL.D3D7Max2D
Import BRL.System
Import BRL.FileSystem
Import BRL.Pixmap
Import BRL.MaxGUI
Import BRL.Win32MaxGUI

Import BRL.PNGLoader
Import BRL.BMPLoader
Import BRL.JPGLoader

SetGraphicsDriver D3D7Max2DDriver()

AppTitle = "Picture Viewer (Drag with left mouse, zoom with wheel or right click)"


' Load an image and tile in 256x256 chunks:

Const FRAGSIZE = 256 ' maximum image fragment size

Local imageUrl:String
Local pixmap:TPixmap

Local filter$="Picture Files (*.bmp,jpeg,jpg,png):bmp,jpeg,jpg,png;All Files:*"
imageUrl = RequestFile( "Load Picture",filter$, False, CurrentDir()+"/" )
pixmap:TPixmap = LoadPixmap(imageUrl)
If pixmap = Null Then End ' user didn't choose an image, so quit

Global img:BigImage = BigImage.Create(pixmap)

img.scale = 1


' Set up window and viewer:

Local winW# = GadgetWidth( Desktop() ) * .75
Local winH# = GadgetHeight( Desktop() ) * .75
Local offsetX# = ( GadgetWidth( Desktop() ) - winW ) / 2.0
Local offsetY# = ( GadgetHeight( Desktop() ) - winH ) / 2.0
Local style = WINDOW_TITLEBAR|WINDOW_RESIZABLE|WINDOW_CLIENTCOORDS ' ClientCoords for Maximize

Global Win:TGadget=CreateWindow( AppTitle, offsetX,offsetY, WinW,WinH, Null, style)
SetMinWindowSize Win,200,200

Local Viewer:TViewer = New TViewer.create( 0,0, winW,winH, Win )

Local Quit:Int = False


' Main loop:

Repeat
	WaitEvent()
	Select EventSource()
		Case Win
			Select EventID()
				Case EVENT_WINDOWCLOSE
					Quit=True
			End Select
	End Select
Until Quit=True
End


Type TViewer

	Field  can:TGadget
	Global drag
	Global startx, starty
	Global endx, endy
	Field Popup_Gadget:TGadget
	Field Popup_Menu:TGadget
	
	Method Create:TViewer( x,y, w,h, group:TGadget )
		can = CreateCanvas( x,y, w,h, group )
		SetGadgetLayout can, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_ALIGNED ' Lock the size 
		Popup_Menu=CreateMenu( "", Null, Popup_Gadget )
		CreateMenu "10%", 10, popup_menu
		CreateMenu "25%", 25, popup_menu
		CreateMenu "50%", 50, popup_menu
		CreateMenu "75%", 75, popup_menu
		CreateMenu "100%", 100, popup_menu
		CreateMenu "150%", 150, popup_menu
		CreateMenu "200%", 200, popup_menu
		CreateMenu "400%", 400, popup_menu
		CreateMenu "1000%", 1000, popup_menu
		AddHook EmitEventHook, EventHook, Self
		Return Self
	End Method
	
	Function EventHook:Object( id, data:Object, context:Object )
		If data <> Null Then Return TViewer(context).EventHandler( TEvent(data) )
	End Function

	Method EventHandler:Object ( event:TEvent )
		If event.source = can And event.id = EVENT_GADGETPAINT Then
		  SetGraphics CanvasGraphics(can)
		  SetViewport 0,0,GadgetWidth(can),GadgetHeight(can)
		SetBlend maskblend
		  Cls
			img.render
		  Flip
		ElseIf event.id = EVENT_MOUSEDOWN
			If event.data = MOUSE_LEFT Then
				drag = True
				SetPointer POINTER_HAND
				startx = event.x
				starty = event.y
			ElseIf event.data = MOUSE_RIGHT Then
				PopupWindowMenu( Win, popup_menu )
			EndIf
		ElseIf event.id = EVENT_MOUSEUP Then
			drag = False
			SetPointer POINTER_DEFAULT
		ElseIf drag And (event.id = EVENT_MOUSEMOVE) Then
			endx = event.x
			endy = event.y
			img.x :+ endx  - startx
			img.y :+ endy - starty
			startx = endx
			starty = endy
			Cls
			img.render
			Flip
		ElseIf event.id = EVENT_MOUSEWHEEL Then
			If event.data >0 Then
				img.scale :* 1.1
				If img.scale > 10 Then
					img.scale = 10
				Else
					img.x :* 1.1
					img.y :* 1.1
				EndIf
			End If
			If event.data <0 Then
				img.scale :/ 1.1
				If img.scale < 0.1 Then
					img.scale = 0.1
				Else
					img.x :/ 1.1
					img.y :/ 1.1
				EndIf
			EndIf
			Cls
			img.render
			Flip
		ElseIf event.id = EVENT_MENUACTION Then 
			Local scale:Float = event.data / 100.0
			Local deltascale:Float = scale / img.scale
			img.scale :* deltascale
			img.x :* deltascale
			img.y :* deltascale
			Cls
			img.render
			Flip
		Else
			Return event ' not handled here, so pass along
		EndIf
	End Method
			
End Type


' Tesuji's tile functions below:

' =============================
' Image Fragment
' =============================

Type ImageFragment

    Field img:TImage
    Field x,y
    Field rotation:Float = 0
    Field angle:Double
    Field distance:Double

    ' ----------------------------------
    ' constructor
    ' ----------------------------------
    Function create:ImageFragment(pmap:TPixmap,x:Float,y:Float,w,h)
    
        Local frag:ImageFragment = New ImageFragment
        frag.img = LoadImage(PixmapWindow(pmap,x,y,w,h),0|FILTEREDIMAGE)
        x = (pmap.width*.5) - x
        y = (pmap.height*.5) - y
        frag.x = x
        frag.y = y
        frag.angle = ATan2(y,x)-180
        frag.distance = Sqr(x*x + y*y)

        Return frag
 
    End Function

    ' --------------------
    ' Draw individual tile
    ' --------------------
    Method render(scale:Float,xoff:Float=0,yoff:Float=0,rot:Float=0)

        SetRotation rot
        Local d:Float = Self.distance*scale
        SetScale(scale,scale)
        DrawImage(Self.img,(Cos(rot+Self.angle)*d)+xoff,(Sin(rot+Self.angle)*d)+yoff )

    End Method


End Type


' ==================================
' Big Image
' ==================================

Type BigImage 

    Field pixmap:TPixmap
    Field px,py
    Field fragments:TList
    Field scale:Float = 1
    Field width
    Field height
    Field x:Float = 0
    Field y:Float = 0
    Field rotation:Float = 0

    ' ----------------------------------
    ' constructor
    ' ----------------------------------
    Function create:BigImage(p:TPixmap)

        Local bi:BigImage = New BigImage
        bi.pixmap = p
        bi.width = p.width
        bi.height = p.height
        bi.fragments = CreateList()
        bi.load()

        Return bi

    End Function

    ' -------------------------------------
    ' convert pixmap into image fragments
    ' -------------------------------------
    Method load()

        Local px = 0
        Local py = 0
        Local loading = True

        While (loading)

            'FlushMem
            Local w = FRAGSIZE
            If Self.pixmap.width - px < FRAGSIZE w = Self.pixmap.width - px
            Local h = FRAGSIZE
            If Self.pixmap.height - py < FRAGSIZE h = Self.pixmap.height - py
            Local f1:ImageFragment = ImageFragment.create(Self.pixmap,px,py,w,h)
            ListAddLast Self.fragments,f1
            px:+FRAGSIZE
            If px >= Self.pixmap.width
                px = 0
                py:+FRAGSIZE
                If py >= Self.pixmap.height loading = False
            End If
        
        Wend

    End Method

    ' -----------------
    ' Draw entire image
    ' -----------------
    Method render()

        SetOrigin(GraphicsWidth()*.5,GraphicsHeight()*.5)
        For Local f:ImageFragment = EachIn Self.fragments
            f.render(Self.scale,Self.x,Self.y,Self.rotation)
        Next
        SetOrigin(0,0)

    End Method

End Type

Comments

None.

Code Archives Forum