Canvas or Image Shift

BlitzMax Forums/BlitzMax Programming/Canvas or Image Shift

I'm encountering similiar issues with pixmaps vs images vs alpha vs scrolling.

My attempts to implement an 'area map' that utilises an alpha-based, pixel-based 'fog of war' effect have ended poorly, mainly because TImage pixels cannot be manipulated directly. I have to use a TPixmap, alter every pixel, and then 'load' it into a TImage for every render pass. For the scrolling effect I modify the viewport based on the player's position, because using TPixmaps for scrolling proved far too slow. Here's some rough sample code:

SuperStrict

Global ScreenWidth:Int = 1024
Global ScreenHeight:Int = 768

Graphics ScreenWidth,  ScreenHeight, 32
SetBlend ALPHABLEND

Local AreaMap:TAreaMap = New TAreaMap
AreaMap.create(512, 512)
'AreaMap.createFromPixmap(LoadPixmap("map.png"))
AreaMap.createFogPat(50, 50)
AreaMap.setPlayerXZ(AreaMap.Map.width * 0.5, AreaMap.Map.height * 0.5)

SetClsColor 255, 255, 255

While Not KeyHit(KEY_ESCAPE)
	
	Local oldtime:Int = MilliSecs()
	
	Cls
		
	If KeyDown(KEY_LEFT) Then AreaMap.setPlayerX(AreaMap.PlayerX - 2)
	
	If KeyDown(KEY_RIGHT) Then AreaMap.setPlayerX(AreaMap.PlayerX + 2)

	If KeyDown(KEY_UP) Then AreaMap.setPlayerZ(AreaMap.PlayerZ - 2)
	
	If KeyDown(KEY_DOWN) Then AreaMap.setPlayerZ(AreaMap.PlayerZ + 2)

	AreaMap.renderMap()

	SetColor 0, 0, 0
	DrawText "Press cursor keys to move. ESC exits.", AreaMap.ViewPortX, AreaMap.ViewPortZ + AreaMap.ViewPortHeight + 10
	DrawText "Time taken:" + String(MilliSecs() - oldtime), AreaMap.ViewPortX, AreaMap.ViewPortZ + AreaMap.ViewPortHeight + 30

	Flip

	Delay 10

Wend

End

Type TAreaMap

	Field Map:TPixmap
	
	Field FogPatArr:Byte[,]
	Field FogPatWidth:Int
	Field FogPatHeight:Int

	Field PlayerX:Int
	Field PlayerZ:Int
	
	Field ViewportX:Int = 50
	Field ViewportZ:Int = 50
	Field ViewportWidth:Int = 256
	Field ViewportHeight:Int = 256

	Method create(w:Int, h:Int)

		Map = CreatePixmap(w, h, PF_RGBA8888)

		SeedRnd MilliSecs()

		For Local x:Int = 0 To Map.width - 1
			For Local z:Int = 0 To Map.height - 1
				WritePixel(Map, x, z, Int((0 Shl 24) | (Rand(0, 255) Shl 16) | (Rand(0, 255) Shl 8) | (Rand(0, 255))))
			Next
		Next
		
		For Local x:Int = 0 To Map.width - 1
			For Local z:Int = 0 To 2
				WritePixel(Map, x, z, 0)
			Next
		Next
		
		For Local x:Int = 0 To Map.width - 1
			For Local z:Int = Map.height - 3 To Map.height - 1
				WritePixel(Map, x, z, 0)
			Next
		Next
				
		For Local z:Int = 0 To Map.height - 1
			For Local x:Int = 0 To 2
				WritePixel(Map, x, z, 0)
			Next
		Next
		
		For Local z:Int = 0 To Map.height - 1
			For Local x:Int = Map.width - 3 To Map.width - 1
				WritePixel(Map, x, z, 0)
			Next
		Next		
				
	EndMethod
	
	Method createFromPixmap(pm:TPixmap)
	
		Map = CreatePixmap(pm.width, pm.height, PF_RGBA8888)
		Map = ConvertPixmap(pm, PF_RGBA8888)
			
		For Local x:Int = 0 To Map.width - 1
			For Local z:Int = 0 To Map.height - 1
										
				Local px:Int = ReadPixel(Map, x, z)		
				Local pxb:Byte = px Shr 16
				Local pxg:Byte = px Shr 8
				Local pxr:Byte = px
		
				WritePixel(Map, x, z, Int(0 Shl 24 | pxb Shl 16 | pxg Shl 8 | pxr))
			Next
		Next
	
	EndMethod

	Method createFogPat(w:Int, h:Int)
	
		FogPatWidth = w
		FogPatHeight = h

		FogPatArr = New Byte[FogPatWidth, FogPatHeight]

		Cls
	
		SetColor 0, 0, 255
		DrawOval 0, 0, FogPatWidth, FogPatHeight
	
		Local pm:TPixmap = GrabPixmap(0, 0, FogPatWidth, FogPatHeight)
		
		For Local x:Int = 0 To FogPatWidth - 1
			For Local z:Int = 0 To FogPatWidth - 1
				FogPatArr[x, z] = Byte(ReadPixel(pm, x, z))
			Next
		Next
	
	EndMethod

	Method applyFogPat(cx:Int, cz:Int)

		Local mapx:Int = cx - (FogPatWidth * 0.5) 
		For Local x:Int = 0 To (FogPatWidth - 1)
	
			Local mapz:Int = cz - (FogPatHeight * 0.5)
			For Local z:Int = 0 To (FogPatHeight - 1)
		
				If (mapx >= 0) And (mapx < (Map.width - 1))
					If (mapz >= 0) And (mapz < (Map.height - 1))
						WritePixel(Map, mapx, mapz, ReadPixel(Map, mapx, mapz) | (FogPatArr[x, z] Shl 24))
					EndIf
				EndIf
			
				mapz :+ 1
			Next
		
			mapx :+ 1
		Next

	EndMethod

	Method setPlayerXZ(posx:Int, posz:Int)
	
		PlayerX = posx
		If PlayerX < 0 Then PlayerX = 0
		If PlayerX >= Map.width Then PlayerX = (Map.width - 1)

		PlayerZ = posz
		If PlayerZ < 0 Then PlayerZ = 0
		If PlayerZ >= Map.height Then PlayerZ = (Map.height - 1)
	
		For Local n:Int = 1 To 5
			applyFogPat(PlayerX, PlayerZ)
		Next
		
	EndMethod
	
	Method setPlayerX(posx:Int)
	
		PlayerX = posx
		If PlayerX < 0 Then PlayerX = 0
		If PlayerX >= Map.width Then PlayerX = (Map.width - 1)
		
		applyFogPat(PlayerX, PlayerZ)
	
	EndMethod

	Method setPlayerZ(posz:Int)

		PlayerZ = posz
		If PlayerZ < 0 Then PlayerZ = 0
		If PlayerZ >= Map.height Then PlayerZ = (Map.height - 1)
		
		applyFogPat(PlayerX, PlayerZ)

	EndMethod

	Method renderMap()

		SetAlpha 1.0

		SetColor 128, 128, 128

		DrawRect ViewPortX, ViewportZ, ViewportWidth, ViewportHeight

		SetColor 255, 255, 255
		
		SetViewport ViewPortX, ViewportZ, ViewportWidth, ViewportHeight
		
		DrawImage LoadImage(Map), ViewPortX - PlayerX + ViewportWidth * 0.5, ViewportZ - PlayerZ + ViewportHeight * 0.5 
	
		SetAlpha 0.75
		
		DrawOval ViewPortX + (ViewportWidth * 0.5) - 5, ViewportZ + (ViewportHeight * 0.5) - 5, 10, 10
	
		SetViewport 0, 0, ScreenWidth, ScreenHeight
		
	EndMethod

End Type


The visual results are what I wanted to achieve, but the performance is poor (though less so when not compiled in 'debug' mode).

In light of this, I have to use a less pleasing visual workaround, but the performance is far better because the code only renders TImages and rectangles. Example:

SuperStrict

Global ScreenWidth:Int = 1024
Global ScreenHeight:Int = 768
Graphics ScreenWidth,  ScreenHeight, 32

Local AreaMap:TAreaMap = New TAreaMap
AreaMap.create(512, 512)
'AreaMap.createFromPixmap(LoadPixmap("map.png"))
AreaMap.setPlayerXZ(AreaMap.Map.width * 0.5, AreaMap.Map.height * 0.5)

SetClsColor 255, 255, 255 

While Not KeyHit(KEY_ESCAPE)
	
	Local oldtime:Int = MilliSecs()
	
	Cls
		
	If KeyDown(KEY_LEFT) Then AreaMap.setPlayerX(AreaMap.PlayerX - 2)
	
	If KeyDown(KEY_RIGHT) Then AreaMap.setPlayerX(AreaMap.PlayerX + 2)

	If KeyDown(KEY_UP) Then AreaMap.setPlayerZ(AreaMap.PlayerZ - 2)
	
	If KeyDown(KEY_DOWN) Then AreaMap.setPlayerZ(AreaMap.PlayerZ + 2)

	AreaMap.renderMap()

	SetColor 0, 0, 0
	DrawText "Press cursor keys to move. ESC exits.", AreaMap.ViewPortX, AreaMap.ViewPortZ + AreaMap.ViewPortHeight + 10
	DrawText "Time taken:" + String(MilliSecs() - oldtime), AreaMap.ViewPortX, AreaMap.ViewPortZ + AreaMap.ViewPortHeight + 30
	
	Flip

	Delay 10

Wend

End

Type TAreaMap

	Field Map:TImage
	
	Field PlayerX:Int
	Field PlayerZ:Int
	
	Field ViewportX:Int = 50
	Field ViewportZ:Int = 50
	Field ViewportWidth:Int = 256
	Field ViewportHeight:Int = 256

	Field CellWidth:Int = 16
	Field CellHeight:Int = 16
	Field Grid:Int[,]
	Field GridWidth:Int
	Field GridHeight:Int
	
	Method create(w:Int, h:Int)

		Local pm:TPixmap = CreatePixmap(w, h, PF_RGBA8888)

		SeedRnd MilliSecs()

		For Local x:Int = 0 To pm.width - 1
			For Local z:Int = 0 To pm.height - 1
				WritePixel(pm, x, z, Int((0 Shl 24) | (Rand(0, 255) Shl 16) | (Rand(0, 255) Shl 8) | (Rand(0, 255))))
			Next
		Next
		
		For Local x:Int = 0 To pm.width - 1
			For Local z:Int = 0 To 2
				WritePixel(pm, x, z, 0)
			Next
		Next
		
		For Local x:Int = 0 To pm.width - 1
			For Local z:Int = pm.height - 3 To pm.height - 1
				WritePixel(pm, x, z, 0)
			Next
		Next
				
		For Local z:Int = 0 To pm.height - 1
			For Local x:Int = 0 To 2
				WritePixel(pm, x, z, 0)
			Next
		Next
		
		For Local z:Int = 0 To pm.height - 1
			For Local x:Int = pm.width - 3 To pm.width - 1
				WritePixel(pm, x, z, 0)
			Next
		Next	
		
		Map = LoadImage(pm)
		
		initGrid()
		
	EndMethod
		
	Method createFromPixmap(pm:TPixmap)
	
		Map = LoadImage(pm)
	
		initGrid()
	
	EndMethod

	Method initGrid()
	
		GridWidth = (Map.Width / CellWidth)
		GridHeight = (Map.Height / CellHeight)
	
		Grid = New Int[GridWidth, GridHeight]
	
	EndMethod

	Method setPlayerXZ(posx:Int, posz:Int)
	
		PlayerX = posx
		If PlayerX < 0 Then PlayerX = 0
		If PlayerX >= Map.width Then PlayerX = (Map.width - 1)

		PlayerZ = posz
		If PlayerZ < 0 Then PlayerZ = 0
		If PlayerZ >= Map.height Then PlayerZ = (Map.height - 1)
	
		setGridArea()
	
	EndMethod
	
	Method setPlayerX(posx:Int)
	
		PlayerX = posx
		If PlayerX < 0 Then PlayerX = 0
		If PlayerX >= Map.width Then PlayerX = (Map.width - 1)
		
		setGridArea()
		
	EndMethod

	Method setPlayerZ(posz:Int)

		PlayerZ = posz
		If PlayerZ < 0 Then PlayerZ = 0
		If PlayerZ >= Map.height Then PlayerZ = (Map.height - 1)
		
		setGridArea()
		
	EndMethod

	Method setGridArea()
	
		Local gx:Int = Floor(PlayerX / CellWidth)
		Local gz:Int = Floor(PlayerZ / CellHeight)
	
		setgridXZ(gx, gz)
		setgridXZ(gx + 1, gz)
		setgridXZ(gx - 1, gz)
		setgridXZ(gx, gz + 1)
		setgridXZ(gx, gz - 1)
		setgridXZ(gx, gz + 1)
		setgridXZ(gx + 1, gz - 1)
		setgridXZ(gx + 1, gz + 1)
		setgridXZ(gx - 1, gz - 1)
		setgridXZ(gx - 1, gz + 1)

	EndMethod

	Method setGridXZ(gx:Int, gz:Int)
	
		If (gx >= 0) And (gz >= 0)
			If (gx < GridWidth) And (gz < GridHeight)
				Grid[gx, gz] = True
			EndIf
		EndIf
	
	EndMethod

	Method renderMap()

		SetBlend SOLIDBLEND

		SetColor 128, 128, 128

		DrawRect ViewPortX, ViewportZ, ViewportWidth, ViewportHeight

		SetColor 255, 255, 255
				
		SetViewport ViewPortX, ViewportZ, ViewportWidth, ViewportHeight
		
		DrawImage Map, ViewPortX - PlayerX + ViewportWidth * 0.5, ViewportZ - PlayerZ + ViewportHeight * 0.5 
		
		'SetBlend SHADEBLEND
		SetColor 128, 128, 128
		
		For Local gx:Int = 0 To GridWidth - 1
			For Local gz:Int = 0 To GridHeight - 1
				If Not Grid[gx, gz] Then DrawRect ViewPortX - PlayerX + ViewportWidth * 0.5 + (gx * CellWidth), ViewportZ - PlayerZ + ViewportHeight * 0.5 + (gz * CellHeight), CellWidth, CellHeight
			Next
		Next
		
		SetBlend ALPHABLEND
		SetColor 255, 255, 255
		SetAlpha 0.75
		DrawOval ViewPortX + (ViewportWidth * 0.5) - 5, ViewportZ + (ViewportHeight * 0.5) - 5, 10, 10
		
		SetViewport 0, 0, ScreenWidth, ScreenHeight
		
	EndMethod

End Type


Hopefully my post has not been too off-topic...

I suppose the lesson here is you will need to re-think/compromise your designs to workaround BlitzMax's limitations and exploit its strenths. However, this is true for all computer languages.