Drawing, and saving... how?

BlitzMax Forums/BlitzMax Programming/Drawing, and saving... how?

Hello,

The RPG im working on allows you to draw your own maps. However, im not sure how i can save the drawn picture. Basicly, i think what is needed is a function that allows me to draw ON the image for real. Or make it virtual, and render the lines, then only on save merge the 2. Also, the maps should be small in filesize. Basicly they have to be shared with other players, so i guess its better to use monochrome bitmaps or even a custom format. Any ideas?

Maps as in game levels? Couldn't you just save the tile ids?

Oh sorry, no i meen like this:



then they share the paper with others, but only the black lines should be send... and how? or if it doesnt matter you could as well just send the whole map.

I'm sure you can develop this further:
Graphics 800, 600

Global drawing:TDrawing = TDrawing.Create()

Local sPoint:TPoint
Local ePoint:TPoint


While Not KeyDown(KEY_ESCAPE)

	Cls
	
	If MouseDown(1) Then
		Local mx:Int = MouseX()
		Local my:Int = MouseY()
		
		If sPoint = Null Then
			sPoint = TPoint.Create( mx, my )
		Else

			If (mx <> sPoint.X) Or (my <> sPoint.Y) Then
				
				drawing.addPoint(mx, my)
				sPoint = Null
				
			EndIf
			
		EndIf
	EndIf
	
	If MouseHit(2) Then
		Print "Count:"+drawing.pointList.count()
		For Local tempPoint:TPoint = EachIn drawing.pointList.items
			Print "X:"+tempPoint.X+" Y:"+tempPoint.Y
		Next
	EndIf
	
	drawing.draw()
	
	Flip

Wend

End




Type TPoint
	Field X:Int
	Field Y:Int
	
	Function Create:TPoint( X:Int, Y:Int )
		Local temp:TPoint = New TPoint
			temp.X = X
			temp.Y = Y
		Return temp
	End Function
End Type

Type TDrawing
	Field pointList:TObjectList
	
	Method draw()
		If pointList.count() < 1 Then Return
		For Local i:Int = 0 To pointList.count()-2
			
			Local startPoint:TPoint = TPoint(pointList.items[i])
			Local endPoint:TPoint = TPoint(pointList.items[i+1])
			DrawLine(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y)
		Next
	End Method
	
	Method addPoint( X:Int, Y:Int )
		pointList.addLast( TPoint.Create( X, Y ) )
	End Method
	
	Function Create:TDrawing()
		Local temp:TDrawing = New TDrawing
			temp.pointList:TObjectList = TObjectList.Create( 25 )
			
		Return temp
	End Function
End Type



Rem
ObjectList created by Kris Kelly (Perturbatio) Dec 2005
purpose: faster access to a list of objects with less mem usage
it's performance is comparable to a TList when using a small number of items
but when you are using a large amount, it is much better (and uses less memory).

When invoking the create method, you can specify the StepSize, this is the amount that
the Items Array will be increased by each time it is in danger of running out of space.
It is faster to do it in large blocks than in hundreds of little ones.
End Rem

Type TObjectList
	Field Items:Object[]
	Field _Size:Int = 0 'DO NOT MANUALLY MODIFY THIS!!!
	Field StepSize:Int
	
	Method AddFirst(val:Object)
		Local i:Int

		'grow Items array by 1
		'Items = Items[..Items.Length + 1]
		_Size:+1
		'resize in bulk
		If Items.Length < _Size Then Items = Items[.._Size+StepSize]
		
		
		'shift Items to the rightt, overwriting val
		For i = 1 To _Size-1 'Items.Length - 1
			Items[i] = Items[i - 1]
		Next
		
		Items[0] = val
		'no need to return anything here since we know it was added at 0
	End Method
	
	
	Method AddLast:Int(val:Object)
		'grow Items array by 1
		'Items = Items[..Items.Length + 1]
		_Size:+1
		'resize in bulk
		If Items.Length < _Size Then Items = Items[.._Size+StepSize]
		
		'set the last index to val
		'Items[Items.Length - 1] = val

		Items[_Size-1] = val
		
		Return _Size 'Items.Length - 1 'return the index it was added at
	End Method
	
	
	'return the entire list as a concatenated string with optional delimiter
	'because base objects have ToString, cannot override with different parameters
	Method ToDelimString:String(Delim:String = "")
		Local result:String
		Local i:Int
		
		For i = 0 To _Size-2
			result:+Items[i].ToString() + Delim
		Next
		result:+ Items[_Size-1].ToString()
		
		Return result
	End Method
	
	
	Method ToString:String()
		Return ToDelimString() 'just call ToDelimString with no parameters
	End Method
	
	'You could just reference the field _Size (which is what is done throughout the code), 
	'but that could result in an unsafe type if you change it at some later date
	Method Count:Int()
		Return _Size
	End Method
	
	
	'return the first index where the list contains val, else return -1
	Method Contains:Int(val:Object)
		Local i:Int
		
		For i = 0 To _Size-1
			If val = Items[i] Then Return i		
		Next
		
		Return -1
	End Method
	
	
	Function FromObjectArray:TObjectList(val:Object[])
		Local tempList:TObjectList = TObjectList.Create()
		
		Try
			tempList.Items = val
		Catch err:String
			RuntimeError("Error when converting from Object Array to TObjectList, error: ~n"+err$)
			Return Null
		End Try
		
		Return tempList
	End Function
	
Rem	
	Function FromString:TObjectList(val:String, Delim:String)
		Local tempList:TObjectList = TObjectList.Create()
		Local currentChar : String = ""
		Local count : Int = 0
		Local TokenStart : Int = 0
			If Delim.Length <0 Or Delim.Length > 1 Then Return Null
	
			If Len(Delim)<>1 Then Return Null
	
			val = Trim(val)
	
			For count = 0 Until Len(val)
				If val[count..count+1] = delim Then
					tempList.AddLast(val[TokenStart..Count])
					TokenStart = count + 1
				End If
			Next
			tempList.AddLast(val[TokenStart..Count])	
			
		Return tempList
	End Function
EndRem

	'if AutoAddToEnd is true then if the index specified is greater than size, use AddLast
	Method Insert:Int(val:Object, index:Int, AutoAddToEnd:Int = False)
		Local i:Int
		
		'If index is out of range, Return False
		If index < 0 Then Return False
		If index > _Size Then
			If Not AutoAddToEnd Then 
				Return False
			Else
				AddLast(val)
				Return True
			EndIf
		EndIf
		
		'if the index is equal to Size then addlast
		If index = _Size Then 
			AddLast(val)
			Return True
		EndIf

		'resize Items
		'Items = Items[..Items.Length]
		_Size:+1
		'resize in bulk
		If Items.Length < _Size Then Items = Items[.._Size + StepSize]

		
		'shift Items to the right from index
		For i = _Size-1 To index+1 Step -1
			Items[i] = Items[i - 1]
			'Print "index "+ i + " " + items[i]
		Next
		
		'then insert val
		Items[index] = val
		Return True
	End Method
	
	
	Method RemoveByIndex:Int(index:Int)
		Local i:Int

		'shift Items to the left, overwriting index
		For i = index To _Size - 2
			Items[i] = Items[i + 1]
		Next
		
		'shrink Items by 1
		'Items = Items[..Items.Length]
		_Size:-1
		'if the length of items is at least (2 *StepSize) larger than Size, resize the array
		'this should help prevent the size from getting out of control but keep it reasonably fast
		If _Size < Items.Length - (StepSize * 2) Then Items = Items[.._Size]
		If _Size < 0 Then _Size = 0
		'null the end one
		Items[_Size] = Null
	End Method
	
	
	Method RemoveByObject:Int(val:Object, RemoveAll:Int = False)
		Local i:Int

		i = Contains(val)
		While i > -1
			
			RemoveByIndex(i)
			If Not RemoveAll Then Exit
			i = Contains(val)

		Wend
		
		Return True
	End Method
	
	
	Method Clear()
		Items = Items[..0]
		_Size = 0
	End Method

	
	Method ToArray:Object[]()
		Return Items[.._Size-1]
	End Method
	
	
	Method ToList(List:TList Var)
		For Local s:Object = EachIn items
			List.AddLast(s)
		Next
	End Method
	
	
	Method GetStepSize:Int()
		Return StepSize
	End Method
	
	Method SetStepSize(val:Int)
		If val < 1 Then val = 1 'don't allow negative values
		StepSize = val
	End Method
	
	
	Method Sort()
		'Items[.._Size].Sort() 'sort causes a problem with null objects, so have disabled this just now.
	End Method
	
	
	Method Free()
		TObjectList.Destroy(Self)
	End Method

	'returns true if swap occurred	
	Method SwapByIndex:Int(FirstIndex:Int, SecondIndex:Int)
		If FirstIndex<0 Or FirstIndex > _Size-1 Or SecondIndex<0 Or SecondIndex > _Size-1 Then Return False 'if out of bounds then return false
		Local tempObject:Object
		
		tempObject = items[FirstIndex]
		items[FirstIndex] = items[SecondIndex]
		items[SecondIndex] = tempObject
		Return True
		
	End Method
	
	'returns true if swap occurred	
	Method SwapByVal:Int(FirstObject:Object, SecondObject:Object)
		If FirstObject = Null Or SecondObject=Null Then Return False
		Local tempObject:Object
		Local FirstIndex:Int, SecondIndex:Int
		
		FirstIndex = Contains(FirstObject)
		SecondIndex = Contains(SecondObject)
		
		If FirstIndex > -1 And SecondIndex > -1 Then
			tempObject = items[FirstIndex]
			items[FirstIndex] = items[SecondIndex]
			items[SecondIndex] = tempObject
			Return True
		EndIf
		
		Return False
	End Method

	
	Function Destroy(List:TObjectList)
		List.Clear()
		List = Null
		GCCollect
	End Function
	
	
	Function Create:TObjectList(StepSize:Int = 10)
		Local tempList:TObjectList = New TObjectList
		tempList.StepSize = StepSize
		Return tempList
	End Function
End Type



Thanks! I made a modification so it allows mouse stops and a small antialiasing hack.

I guess a list of coords is smaller than a PNG?
THere is one problem tough, you cant erase. On the otherhand, its a medieval game and i think there is no need for it. haha. if they screw up the paper, they need to buy new :) just as in real life. Talking about content-control. That solves itself then, cause none will make crappy 10 tons of drawings.

Graphics 800, 600

Global drawing:TDrawing = TDrawing.Create()

Local sPoint:TPoint
Local ePoint:TPoint
Local FPS:TTicker = New TTicker

SetBlend ALPHABLEND

map:TImage=LoadImage("map.png")

While Not KeyDown(KEY_ESCAPE)

	Cls()
	SetColor 255,255,255
	SetScale 1.4,1.4
	DrawImage(map,15,0)
	SetScale 1,1
	SetColor 0,0,0
	
	If MouseDown(1) Then
		IsMouseDown=True
		Local mx:Int = MouseX()
		Local my:Int = MouseY()
		
		If sPoint = Null Then
			sPoint = TPoint.Create( mx, my )
		Else
			If (mx <> sPoint.X) Or (my <> sPoint.Y) Then	
				drawing.addPoint(mx, my)
				sPoint = Null
			EndIf
		EndIf
	EndIf
	
	If Not MouseDown(1) And IsMouseDown
		IsMouseDown=False
		drawing.addPoint(-1, -1)
	End If

	If MouseHit(2) Then
		Print "Count:"+drawing.pointList.count()
		For Local tempPoint:TPoint = EachIn drawing.pointList.items
			Print "X:"+tempPoint.X+" Y:"+tempPoint.Y
		Next
	EndIf
	
	drawing.draw()
	FPS.update()
	
	SetColor 255,255,255
	DrawText "FPS: " + FPS.count, 10, 10
	Flip

Wend

End

Type TTicker
	Field count:Int
	Field timeout:Float = 1
	Field timer:Float
	Field counter:Int
		 
	Method update() 
		If Self.timer < MilliSecs() 
			Self.timer = MilliSecs() + (Self.timeout * 1000) 
			Self.count = Self.counter
			Self.counter = 0
		Else
			Self.counter:+1
		End If
	End Method
End Type

Type TPoint
	Field X:Int
	Field Y:Int
	
	Function Create:TPoint( X:Int, Y:Int )
		Local temp:TPoint = New TPoint
			temp.X = X
			temp.Y = Y
		Return temp
	End Function
End Type

Type TDrawing
	Field pointList:TObjectList
	
	Method draw()
		If pointList.count() < 1 Then Return
		For Local i:Int = 0 To pointList.count()-2
			Local startPoint:TPoint = TPoint(pointList.items[i])
			Local endPoint:TPoint = TPoint(pointList.items[i+1])
			If startPoint.X <> -1 And startPoint.Y <> -1 And endPoint.X <> -1 And endPoint.Y <> -1
				SetLineWidth 4
				SetAlpha .1
				DrawLine(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y)
				SetLineWidth 3
				SetAlpha .4
				DrawLine(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y)
				SetLineWidth 2
				SetAlpha 1
				DrawLine(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y)
			End If
		Next
	End Method
	
	Method addPoint( X:Int, Y:Int )
		pointList.addLast( TPoint.Create( X, Y ) )
	End Method
	
	Function Create:TDrawing()
		Local temp:TDrawing = New TDrawing
			temp.pointList:TObjectList = TObjectList.Create( 25 )
		Return temp
	End Function
End Type

Rem
ObjectList created by Kris Kelly (Perturbatio) Dec 2005
purpose: faster access to a list of objects with less mem usage
it's performance is comparable to a TList when using a small number of items
but when you are using a large amount, it is much better (and uses less memory).

When invoking the create method, you can specify the StepSize, this is the amount that
the Items Array will be increased by each time it is in danger of running out of space.
It is faster to do it in large blocks than in hundreds of little ones.
End Rem

Type TObjectList
	Field Items:Object[]
	Field _Size:Int = 0 'DO NOT MANUALLY MODIFY THIS!!!
	Field StepSize:Int
	
	Method AddFirst(val:Object)
		Local i:Int

		'grow Items array by 1
		'Items = Items[..Items.Length + 1]
		_Size:+1
		'resize in bulk
		If Items.Length < _Size Then Items = Items[.._Size+StepSize]
		
		
		'shift Items to the rightt, overwriting val
		For i = 1 To _Size-1 'Items.Length - 1
			Items[i] = Items[i - 1]
		Next
		
		Items[0] = val
		'no need to return anything here since we know it was added at 0
	End Method
	
	
	Method AddLast:Int(val:Object)
		'grow Items array by 1
		'Items = Items[..Items.Length + 1]
		_Size:+1
		'resize in bulk
		If Items.Length < _Size Then Items = Items[.._Size+StepSize]
		
		'set the last index to val
		'Items[Items.Length - 1] = val

		Items[_Size-1] = val
		
		Return _Size 'Items.Length - 1 'return the index it was added at
	End Method
	
	
	'return the entire list as a concatenated string with optional delimiter
	'because base objects have ToString, cannot override with different parameters
	Method ToDelimString:String(Delim:String = "")
		Local result:String
		Local i:Int
		
		For i = 0 To _Size-2
			result:+Items[i].ToString() + Delim
		Next
		result:+ Items[_Size-1].ToString()
		
		Return result
	End Method
	
	
	Method ToString:String()
		Return ToDelimString() 'just call ToDelimString with no parameters
	End Method
	
	'You could just reference the field _Size (which is what is done throughout the code), 
	'but that could result in an unsafe type if you change it at some later date
	Method Count:Int()
		Return _Size
	End Method
	
	
	'return the first index where the list contains val, else return -1
	Method Contains:Int(val:Object)
		Local i:Int
		
		For i = 0 To _Size-1
			If val = Items[i] Then Return i		
		Next
		
		Return -1
	End Method
	
	
	Function FromObjectArray:TObjectList(val:Object[])
		Local tempList:TObjectList = TObjectList.Create()
		
		Try
			tempList.Items = val
		Catch err:String
			RuntimeError("Error when converting from Object Array to TObjectList, error: ~n"+err$)
			Return Null
		End Try
		
		Return tempList
	End Function
	
Rem	
	Function FromString:TObjectList(val:String, Delim:String)
		Local tempList:TObjectList = TObjectList.Create()
		Local currentChar : String = ""
		Local count : Int = 0
		Local TokenStart : Int = 0
			If Delim.Length <0 Or Delim.Length > 1 Then Return Null
	
			If Len(Delim)<>1 Then Return Null
	
			val = Trim(val)
	
			For count = 0 Until Len(val)
				If val[count..count+1] = delim Then
					tempList.AddLast(val[TokenStart..Count])
					TokenStart = count + 1
				End If
			Next
			tempList.AddLast(val[TokenStart..Count])	
			
		Return tempList
	End Function
EndRem

	'if AutoAddToEnd is true then if the index specified is greater than size, use AddLast
	Method Insert:Int(val:Object, index:Int, AutoAddToEnd:Int = False)
		Local i:Int
		
		'If index is out of range, Return False
		If index < 0 Then Return False
		If index > _Size Then
			If Not AutoAddToEnd Then 
				Return False
			Else
				AddLast(val)
				Return True
			EndIf
		EndIf
		
		'if the index is equal to Size then addlast
		If index = _Size Then 
			AddLast(val)
			Return True
		EndIf

		'resize Items
		'Items = Items[..Items.Length]
		_Size:+1
		'resize in bulk
		If Items.Length < _Size Then Items = Items[.._Size + StepSize]

		
		'shift Items to the right from index
		For i = _Size-1 To index+1 Step -1
			Items[i] = Items[i - 1]
			'Print "index "+ i + " " + items[i]
		Next
		
		'then insert val
		Items[index] = val
		Return True
	End Method
	
	
	Method RemoveByIndex:Int(index:Int)
		Local i:Int

		'shift Items to the left, overwriting index
		For i = index To _Size - 2
			Items[i] = Items[i + 1]
		Next
		
		'shrink Items by 1
		'Items = Items[..Items.Length]
		_Size:-1
		'if the length of items is at least (2 *StepSize) larger than Size, resize the array
		'this should help prevent the size from getting out of control but keep it reasonably fast
		If _Size < Items.Length - (StepSize * 2) Then Items = Items[.._Size]
		If _Size < 0 Then _Size = 0
		'null the end one
		Items[_Size] = Null
	End Method
	
	
	Method RemoveByObject:Int(val:Object, RemoveAll:Int = False)
		Local i:Int

		i = Contains(val)
		While i > -1
			
			RemoveByIndex(i)
			If Not RemoveAll Then Exit
			i = Contains(val)

		Wend
		
		Return True
	End Method
	
	
	Method Clear()
		Items = Items[..0]
		_Size = 0
	End Method

	
	Method ToArray:Object[]()
		Return Items[.._Size-1]
	End Method
	
	
	Method ToList(List:TList Var)
		For Local s:Object = EachIn items
			List.AddLast(s)
		Next
	End Method
	
	
	Method GetStepSize:Int()
		Return StepSize
	End Method
	
	Method SetStepSize(val:Int)
		If val < 1 Then val = 1 'don't allow negative values
		StepSize = val
	End Method
	
	
	Method Sort()
		'Items[.._Size].Sort() 'sort causes a problem with null objects, so have disabled this just now.
	End Method
	
	
	Method Free()
		TObjectList.Destroy(Self)
	End Method

	'returns true if swap occurred	
	Method SwapByIndex:Int(FirstIndex:Int, SecondIndex:Int)
		If FirstIndex<0 Or FirstIndex > _Size-1 Or SecondIndex<0 Or SecondIndex > _Size-1 Then Return False 'if out of bounds then return false
		Local tempObject:Object
		
		tempObject = items[FirstIndex]
		items[FirstIndex] = items[SecondIndex]
		items[SecondIndex] = tempObject
		Return True
		
	End Method
	
	'returns true if swap occurred	
	Method SwapByVal:Int(FirstObject:Object, SecondObject:Object)
		If FirstObject = Null Or SecondObject=Null Then Return False
		Local tempObject:Object
		Local FirstIndex:Int, SecondIndex:Int
		
		FirstIndex = Contains(FirstObject)
		SecondIndex = Contains(SecondObject)
		
		If FirstIndex > -1 And SecondIndex > -1 Then
			tempObject = items[FirstIndex]
			items[FirstIndex] = items[SecondIndex]
			items[SecondIndex] = tempObject
			Return True
		EndIf
		
		Return False
	End Method

	
	Function Destroy(List:TObjectList)
		List.Clear()
		List = Null
		GCCollect
	End Function
	
	
	Function Create:TObjectList(StepSize:Int = 10)
		Local tempList:TObjectList = New TObjectList
		tempList.StepSize = StepSize
		Return tempList
	End Function
End Type


I guess a list of coords is smaller than a PNG?


Yup, unless they manage to put a coordinate in every pixel location. (I would obviously compress the coordinate list before sending).

I did notice tough, having a big image slows it down from 130 FPS to 30 FPS. 2000 nodes is costing me 100 FPS!! Its a hard call.

Look: http://www.fantasaar.com/forum/index.php/topic,149.msg1088.html#msg1088
Also drawing gets VERY difficult... :( if we could only save it as an image...

If you're just trying to save the *image*, it's really easy:

mypic:TPixmap=GrabPixmap(0,0,640,480)
SavePixmapJPeg(mypic:TPixmap,"myfile.jpg",quality=75")


Or for lossless images, use SavePixmapPNG instead.

ok, that is to capture the whats on screen? (or just the buffer?). i want to capture only the lines. Also, the software freezes like 1 or 2 sec when i do JPG or PNG. So this is not an realtime option. Thanks for you entry tough! I needed a screenshot feature as well ;-)

Mark should put threading in BlitzMax!!!

I'm pretty sure that the drawing could be optimized somewhat in the routine above, I was just demonstrating a concept.

Threading? To be honest, how are you writting an MMOrpg game without knowledge on how to do this type of thing.

Basically, if your saving blacks lines, what is stopping you just splitting up the drawing commands into splines? Or just taking an x,y every so often when drawing, and then using a spline to smooth out the line.

Then you could send a few coords, and produce a large amount of drawn graphics. It is the way live messenger sends hand written messages.

Example:
image 400 x 400
you want to draw a horiontal line in the middle of the image vertically. Using just 8bytes of data?

x1 (short - 2bytes) = 0
y1 (short - 2bytes) = 200
x2 (short - 2bytes) = 400
y2 (short - 2bytes) = 200

total 8 bytes :)

that beats 400 x 4 bytes (over 1k)

skn3[ac], i thank you for your response, but why do yo asume "i know nothing about MMOrpg" and/or threading? Its a bit rude to just throw this at me. I think you are wrong and misinformed.

Ofcorse i would be sending the coords as bytes and not as text, that is rather obvious. The problem is tough, the ammount of drawn lines, not the size in de first place.. it is what slows down BlitzMax terribly. Threading is besides that always good to have. It can gain you a lot of preformance, but its off topic, so lets skip that.

About the splines, this would rather complicate stuff, and still require the coordinates, except it smoothens stuff out. Having said that, the coord itsself as pure data aint the problem, but the drawing of lines. With other words, drawing splines will only slow things down more.

@Perturbatio , ye! Thank you ver much for the code. Ill be spending some more time on finding a way to optimize it. It seems to be a graphical issue tough.. but maybe its the looping and im mistaking. ll post back when i find out.

Are you drawing the map each frame? Because you could create a pixmap in memory, draw the lines onto it and then covert it to an image. Then use this image to draw onto the screen. It would change it so that only one image is needed to be drawn and should speed things up a bit. At least for predrawn maps sent to other people.

hey, yes thats what i hoped to receive an awnser for :) really why i started this topic. I don't know how to draw in memory on pixmaps, and im not even sure wheter that would be fast enough.

If you only do it every few seconds it should boost the performace. Have a look at indiepath render 2 texture module. its most likely better than pixmap steps. They aren't that well suited for that as you must create an image out of it anyway.

hah, the guys are playing already ..
http://www.fantasaar.com/forum/index.php/topic,161.msg1102.html#new

Im currently experimenting with a TBank as TList replacement.

-edit- done.
Graphics 800, 600,, , GRAPHICS_BACKBUFFER

Global drawing:TDrawing = TDrawing.Create()
Local FPS:TTicker = New TTicker
SetBlend ALPHABLEND

Incbin "map.png"
map:TImage=LoadImage("incbin::map.png")
SetLineWidth 3

Global mdrawing:TBank = ClearBank(CreateBank(12040)) 
Global mdrawingoffset:Int = 0
Function ClearBank:TBank(bank:TBank) 
	For i:Int = 0 To BankSize(bank) - 1
		PokeByte(bank, i, 0) 
	Next
	Return bank
End Function

Local DrawStart:Int
Local mx:Int
Local my:Int
Local oldmx:Int
Local oldmy:Int

While Not KeyDown(KEY_ESCAPE) 

	Cls() 
	DrawImage(map, 10, 10) 
	SetColor 100, 50, 0
	
	mx = MouseX()'(MouseX()/4)*4
	my = MouseY()'(MouseY()/4)*4
	If MouseDown(1) And mx > 24 And my > 24 And mx < 580 And my < 520
		IsMouseDown=True
		If Not DrawStart Then
			oldmx = mx
			oldmy = my
			DrawStart = True
			addpoint(mx, my)
		Else
			If (mx <> oldmx) Or (my <> oldmy) 
				DrawStart = False
				addpoint(mx, my) 
			EndIf
		EndIf
	EndIf
	
	If Not MouseDown(1) And IsMouseDown
		IsMouseDown=False
		addpoint(0,0)
	End If

	If MouseHit(2) Then
		SaveBank(mdrawing, "mydrawing.dat") 
	EndIf
	
	If MouseHit(3) Then
		mdrawing = LoadBank("mydrawing.dat") 
		'must reset mdrawingoffset by size X?
	EndIf

	draw()
	FPS.update() 
	
	SetColor 255,255,255
	DrawText "FPS: " + FPS.count, 10, 10
	Rem
	DrawText "X: " + MouseX(), 10, 30
	DrawText "Y: " + MouseY(), 10, 50
	EndRem
	Flip 0

Wend

End

Function addpoint( X:Int, Y:Int)
	PokeShort(mdrawing,mdrawingoffset,  Short(X))
	PokeShort(mdrawing,mdrawingoffset+2,Short(Y))
	mdrawingoffset:+4
End Function

Function draw() 
	Local pointer:Int = 0	
	Repeat
		Local x:Int = PeekShort(mdrawing, pointer) 
		Local y:Int = PeekShort(mdrawing, pointer + 2) 
		Local x2:Int = PeekShort(mdrawing, pointer + 4) 
		Local y2:Int = PeekShort(mdrawing, pointer + 6) 
		pointer:+4
		If X = 0 And Y = 0 And X2 = 0 And Y2 = 0 Exit
		If X > 0 And Y > 0 And X2 > 0 And Y2 > 0
			DrawLine(X, Y, X2, Y2)
		End If
	Forever
End Function

Type TTicker
	Field count:Int
	Field timeout:Float = 1
	Field timer:Float
	Field counter:Int
		 
	Method update() 
		If Self.timer < MilliSecs() 
			Self.timer = MilliSecs() + (Self.timeout * 1000) 
			Self.count = Self.counter
			Self.counter = 0
		Else
			Self.counter:+1
		End If
	End Method
End Type