Fill an area with an image

BlitzMax Forums/BlitzMax Programming/Fill an area with an image

I want to fill an area of any position, width and height, with a repeated, tiled image.

How is this possible?

I've been told to avoid SetViewport because it doesn't work on some cards. Is this true?

Yes it's true I've seen it with my own eyes.

' -----------------------------------------------------------------------------
' ccDrawImageArea: Draws part of an image.
' (by Ian Duff)
' -----------------------------------------------------------------------------
Function ccDrawImageArea(image:TImage, x#, y#, rx#, ry#, rw#, rh#, theframe=0)
 'Note that this code works fine in DirectX or OpenGL on PCs - it autodetects (Grey Alien).
 'Warning: make sure that none of your images have pixels right on the edge otherwise
 'when drawing clipped, they may leave smear in the clipped area! (Grey Alien).
  Local origin_x#, origin_y# ; GetOrigin (origin_x, origin_y)
  Local tw = ccDrawImageAreaPow2Size(image.width)
  Local th = ccDrawImageAreaPow2Size(image.height)
  Local rw1#  = rx + rw
  Local rh1#  = ry + rh
  Local x0# = -image.handle_x, x1# = x0 + rw
  Local y0# = -image.handle_y, y1# = y0 + rh
  
  If rw1 > image.width
    x1 = x0 + rw + image.width - rw1
    rw1 = image.width
  EndIf
   
  If rh1 > image.height
    y1 = y0 + rh + image.height - rh1
    rh1 = image.height
  EndIf
?Win32
  If TD3D7ImageFrame(image.frame(theframe))
    Local frame:TD3D7ImageFrame = TD3D7ImageFrame(image.frame(theframe))
    
    frame.setUV(rx / tw, ry / th, rw1 / tw, rh1 / th)
	frame.Draw x0, y0, x1, y1, x + origin_x, y + origin_y
    frame.setUV(0, 0, image.width / Float(tw), image.height / Float(th))
  Else
?
    Local frameA:TGLImageFrame = TGLImageFrame (image.frame(theframe))
    'Protect against frameA being null due to alt+tab. (Grey Alien)
	If frameA<>Null Then
	    frameA.u0 = rx / tw
	    frameA.v0 = ry / th
	    frameA.u1 = rw1 / tw
	    frameA.v1 = rh1 / th
	    
	    frameA.Draw x0, y0, x1, y1, x + origin_x, y + origin_y
	    
	    frameA.u0 = 0
	    frameA.v0 = 0
	    frameA.u1 = image.width / Float(tw)
	    frameA.v1 = image.height / Float(th)
	EndIf
?Win32
  EndIf
?
  
  Function ccDrawImageAreaPow2Size%(n)
    Local ry = 1
    
    While ry < n
      ry :* 2
    Wend
    
    Return ry
  End Function
End Function


Calc how many whole tiles you can fit and draw them, then use the above code for the partial rects. The above code doesn't work with rotated textures.

...Or...

Enable texture repeat and draw a textured poly with its UVs set accordingly.

Enable texture repeat and draw a textured poly with its UVs set accordingly.
...how do I do that, then?

You'll need DX and OpenGL API calls for that (probably). I've no idea how, never delved into it that far. The expert techies round here seem to always deliver the goods ;-)

Strict

'SetGraphicsDriver GLMax2DDriver()  

Graphics 800, 600, 0

Local img:TImage = LoadImage(getenv_("BMXPATH") + "/samples/hitoro/gfx/boing.png", FILTEREDIMAGE | MIPMAPPEDIMAGE)
Assert img Else "Oi...Where's yer samples gone?"

SetTextureRepeat(img)

Repeat
	Cls
	
	Local tileScale# = 0.75 - (Sin(MilliSecs() / 40.0) * 0.25)
	Local UMax# = 740.0 / (img.pixmaps[0].width * tileScale#)
	Local VMax# = 540.0 / (img.Pixmaps[0].height * tileScale#)
	
	'SetTextureRepeat(img)' Strictly speaking you should set and reset when needed, I spose.
	DrawTexturedPoly([30.0, 30.0, 0.0, 0.0, ..				'TL
										770.0, 30.0, UMax#, 0.0, ..			'TR
										770.0, 570.0, UMax#, VMax#, ..	'BR
						  			30.0, 570.0, 0.0, VMax#], ..		'BL
										img)
	 'SetTextureRepeat(img, false)
	
	Flip
Until KeyHit(KEY_ESCAPE) Or AppTerminate()

End

' Under DX, *ALL* images drawn will have repeating textures. Generally not a problem but be forewarned.
Function SetTextureRepeat(image:TImage, rep=True)
	Assert (image.width = Pow2Size(image.width)) And (image.height = Pow2Size(image.height)),..
					"Image width and height must be a power of two"
	Local D3D7Driver:TD3D7Max2DDriver = TD3D7Max2DDriver(_max2dDriver)
	
	If rep
?Win32 
		If D3D7Driver
			'DX
			D3D7Driver.device.SetTextureStageState 0, D3DTSS_ADDRESS, D3DTADDRESS_WRAP
		Else
?
			'GL		
			glBindTexture GL_TEXTURE_2D, TGLImageFrame(image.Frame(0)).name
			glTexParameteri GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT
			glTexParameteri GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT
?Win32
		EndIf
?
	Else
?Win32
		If D3D7Driver
			'DX
			D3D7Driver.device.SetTextureStageState 0, D3DTSS_ADDRESS, D3DTADDRESS_CLAMP
		Else
?
			'GL
			glBindTexture GL_TEXTURE_2D, TGLImageFrame(image.Frame(0)).name
			glTexParameteri GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE
			glTexParameteri GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE
?Win32
		EndIf
?
	EndIf
End Function

Function DrawTexturedPoly(xyuv#[], image:TImage, frame=0, fan=1)
	Local textureWidthRatio! = Double(image.width) / Pow2Size(image.width)
	Local textureHeightRatio! = Double(image.height) / Pow2Size(image.height)
	Local hx#, hy#, ox#, oy#
	
	GetHandle(hx#, hy#)
	GetOrigin(ox#, oy#)
?Win32
	Local DXDriver:TD3D7Max2DDriver = TD3D7Max2DDriver(_max2dDriver)	
	If DXDriver
		'DX
		Local verts#[(xyuv#.length / 4) * 6]
		Local vCount
		
		For Local c=0 Until xyuv#.length Step 4
			verts#[vCount] = ((xyuv#[c] - hx#) * DXDriver.ix#) + ((xyuv#[c + 1] - hy#) * DXDriver.iy#) + ox#
			verts#[vCount + 1] = ((xyuv#[c] - hx#) * DXDriver.jx#) + ((xyuv#[c + 1] - hy#) * DXDriver.jy#) + oy#
			verts#[vCount + 2] = 0
			(Int Ptr(Float Ptr(verts#)))[vCount + 3] = DXDriver.drawcolor
			verts#[vCount + 4] = xyuv#[c + 2] * textureWidthRatio!
			verts#[vCount + 5] = xyuv#[c + 3] * textureHeightRatio!
			
			vCount :+ 6
		Next

			DXDriver.SetActiveFrame(TD3D7ImageFrame(image.Frame(frame)))
			If fan
				DXDriver.device.DrawPrimitive(D3DPT_TRIANGLEFAN, D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1, verts#, xyuv#.length / 4, 0)
			Else
				DXDriver.device.DrawPrimitive(D3DPT_TRIANGLESTRIP, D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1, verts#, xyuv#.length / 4, 0)
			EndIf
	Else
?
		'GL
		'Can't get at GLMax2D's transform info, so...
		Local sx#, sy#
		GetScale(sx#, sy#)
		
		glPushMatrix()
	
		glBindTexture(GL_TEXTURE_2D, TGLImageFrame(image.Frame(frame)).name)
		glEnable(GL_TEXTURE_2D)	
		
		glTranslatef(ox#, oy#, 0.0)		
		glRotatef(GetRotation(), 0.0, 0.0, 1.0)
		glScalef(sx#, sy#, 1.0) 
		
		If fan Then glBegin(GL_TRIANGLE_FAN) Else glBegin(GL_TRIANGLE_STRIP)
		
		For Local c=0 Until xyuv#.length Step 4
			glTexCoord2f(xyuv#[c + 2] * textureWidthRatio!, xyuv#[c + 3] * textureHeightRatio!)
			glVertex2f(xyuv#[c] - hx#, xyuv#[c + 1] - hy#)
		Next
		
		glEnd
	
		glDisable(GL_TEXTURE_2D)
		glPopMatrix()
?Win32
	EndIf
?
End Function

Function Pow2Size(n)
	Local t = 1
	
	While t < n
		t :* 2
	Wend
	
	Return t
End Function


Hotness! Looks like this works with rotated textures too yes?

Now GfK, you better say thanks. ;-)

You can rotate the poly using Max2D's normal transformation commands.

If you wanted to rotate the 'tiles', you'd need to manually rotate the poly's UVs.

I tried something like that before with the code I posted at the top but it just wasn't working how I expected. I wanted to rotate the original texture, capture a small part of it in a vertical rectangle (so effectively the rotation was clipped to a rectangle) and then draw it on-screen. It was turdish.