Tiling a timage to another Timage.

BlitzMax Forums/BlitzMax Beginners Area/Tiling a timage to another Timage.

Ryan Burnside(Posted 2008) [#1]
Recently I've started work on a project that deals with timages.

I was wondering if anyone could provide a function.

I'm looking to tile one timage across another Timage. Lets call them image1 and image2.

Image2 must be at least the size of image1. If it is not image2 must be resized to the size of image1. Now image1 has 17 subimages in the animation.

I hope I'm making sense.

Image1 is a dither pattern that is 4x4 pixels with 17 different fill patterns and image2 is a base picture. Basically I want to iterate through each 4x4 pixel section of the base image and replace it with one of the 17 dither patterns measuring 4x4 pixels. The problem comes in the dither pattern going "off the edge" of the base picture. The pattern chosen is determined by the average of all pixels in the 4x4 square. The grayscale version of each pixel in the 4x4 square is simply (red+green+blue)/3. This value is set for the red,green and blue. The 16 values then are averaged and divided by 255 to get the brush image pattern for the 4x4 square.

Here is the dither pattern it goes from pure black 0 to pure white 16.

This is the 17 image dither brush pattern.


The function should not modify the original image but rather return a new timage that is the original image with the 4x4 dithering applied.

I cannot work from the back buffer for this project. Image data must be done with pixel pointers.

I'm really sorry if you are lost. I tried to help as I asked for help. Please try if you think that you can help me with this.

Take a base picture that is atleast 4x4 pixels. Divide that up into 4x4 pixel blocks. Convert those blocks to grayscale by averaging their RGB components then setting all RGB values to that averaged color. Choose the proper subimage of the gradient pattern. Replace that block with the gradient pattern subimage.


Dreamora(Posted 2008) [#2]
1. There is no pixel pointer + TImage -> you must use lockimage which returns a TPixmap and use that

2. Rest is either memory arithmetic, readpixel / writepixel or TPixmap.paste and do exactly what you say "work block by block". The rest shouldn't be hard. if you have distinct keys for the patterns you could store them in a TMap and simply lookup the needed one when replacing.


Ryan Burnside(Posted 2008) [#3]
Ok I've got some code working.

It returns a dithered image in about 30-40 milliseconds.

My main problem is adjusting for images that are not even divisable by 4.

Function round:Float(n:Float) 
	Local r:Float = n Mod 2.0
	If r >=.5
		Return Ceil(n) 
	Else
		Return Floor(n) 
	EndIf
End Function

Function ditherfilter:timage(source:TImage, brush:TImage) 
	Local sourcePixmap:TPixmap = LockImage(source) 
	Local sourcePixmapPtr:Byte Ptr = PixmapPixelPtr(sourcePixmap, 0, 0) 
	Local tmpPixmap:TPixmap = CopyPixmap(sourcePixmap) 
	Local tmpPixmapPtr:Byte Ptr = PixmapPixelPtr(tmpPixmap,0,0)
	Local x:Int,y:Int

	Local w:Int = tmpPixmap.width
	Local h:Int = tmpPixmap.height
	Local extra_width:Float = w - (Floor(w / 4) * 4) 
	Local extra_height:Float = h - (Floor(h / 4) * 4) 
	For x = 0 To w - 1 Step 4' this is a 4 pixel jump thgouth the image
		For y = 0 To h - 1 Step 4 ' this is a 4 pixel jump thgouth the image
			' set the component totals to be averaged upon completion
			Local total_red:Float = 0
			Local total_green:Float = 0
			Local total_blue:Float = 0
			Local pixels_scanned:Float = 0
			Local gray:Float = 0
			Local subimage:Float = 0
			'average the color of each 4x4 square
			For Local x2:Int = 0 To 3
				For Local y2:Int = 0 To 3
				' get the color of each pixel of 16
				Local red:Float = sourcePixmapPtr[(x + x2) * 4 + (y + y2) * 4 * w] 
				Local green:Float = sourcePixmapPtr[(x + x2) * 4 + (y + y2) * 4 * w + 1] 
				Local blue:Float = sourcePixmapPtr[(x + x2) * 4 + (y + y2) * 4 * w + 2] 
				total_red:+red
				total_green:+green
				total_blue:+blue
				pixels_scanned:+1
				Next
			Next
			
			'draw the 4x4 fill
			total_red:/pixels_scanned
			total_green:/pixels_scanned
			total_blue:/pixels_scanned
			gray = (total_red + total_green + total_blue) / 3
			For Local x2:Int = 0 To 3
				For Local y2:Int = 0 To 3
				subimage = round(gray / 255 * 16) 
				Local brush:TPixmap = LockImage(brush, subimage, 0, 0) 
				Local brushPixmapPtr:Byte Ptr = PixmapPixelPtr(brush, 0, 0) 
				
				Local w2:Int = brush.width
				tmpPixmapPtr[(x + x2) * 4 + (y + y2) * 4 * w] = brushPixmapPTr[x2 * 4 + y2 * 4 * w2] 
				tmpPixmapPtr[(x + x2) * 4 + (y + y2) * 4 * w + 1] = brushPixmapPTr[x2 * 4 + y2 * 4 * w2 + 1] 
				tmpPixmapPtr[(x + x2) * 4 + (y + y2) * 4 * w + 2] = brushPixmapPTr[x2 * 4 + y2 * 4 * w2 + 2]            		'alpha
				Next
			Next
		Next
	Next
	Local tmpImage:TImage = LoadImage(tmpPixmap)
	Return tmpImage
End Function


Could sombody patch up my work please? I would like the dither pattern to extend over the edges and keep the original size of the image.


Bremer(Posted 2008) [#4]
To get around images not divisable by 4, you need to check to make sure, that the pixel are not outside the pixmap.

Function round:Float(n:Float) 
	Local r:Float = n Mod 2.0
	If r >=.5
		Return Ceil(n) 
	Else
		Return Floor(n) 
	EndIf
End Function

Function ditherfilter:timage(source:TImage, brush:TImage) 
	Local sourcePixmap:TPixmap = LockImage(source) 
	Local sourcePixmapPtr:Byte Ptr = PixmapPixelPtr(sourcePixmap, 0, 0) 
	Local tmpPixmap:TPixmap = CopyPixmap(sourcePixmap) 
	Local tmpPixmapPtr:Byte Ptr = PixmapPixelPtr(tmpPixmap,0,0)
	Local x:Int,y:Int

	Local w:Int = tmpPixmap.width
	Local h:Int = tmpPixmap.height
	Local extra_width:Float = w - (Floor(w / 4) * 4) 
	Local extra_height:Float = h - (Floor(h / 4) * 4) 
	For x = 0 To w - 1 Step 4' this is a 4 pixel jump thgouth the image
		For y = 0 To h - 1 Step 4 ' this is a 4 pixel jump thgouth the image
			' set the component totals to be averaged upon completion
			Local total_red:Float = 0
			Local total_green:Float = 0
			Local total_blue:Float = 0
			Local pixels_scanned:Float = 0
			Local gray:Float = 0
			Local subimage:Float = 0
			'average the color of each 4x4 square
			For Local x2:Int = 0 To 3
				If x+x2 < w Then
					For Local y2:Int = 0 To 3
						If y+y2 < h Then
							' get the color of each pixel of 16
							Local red:Float = sourcePixmapPtr[(x + x2) * 4 + (y + y2) * 4 * w] 
							Local green:Float = sourcePixmapPtr[(x + x2) * 4 + (y + y2) * 4 * w + 1] 
							Local blue:Float = sourcePixmapPtr[(x + x2) * 4 + (y + y2) * 4 * w + 2] 
							total_red:+red
							total_green:+green
							total_blue:+blue
							pixels_scanned:+1
						End If
					Next
				End If
			Next
			
			'draw the 4x4 fill
			total_red:/pixels_scanned
			total_green:/pixels_scanned
			total_blue:/pixels_scanned
			gray = (total_red + total_green + total_blue) / 3
			For Local x2:Int = 0 To 3
				If x+x2 < w Then
					For Local y2:Int = 0 To 3
						If y+y2 < h Then
							subimage = round(gray / 255 * 16) 
							Local brush:TPixmap = LockImage(brush, subimage, 0, 0) 
							Local brushPixmapPtr:Byte Ptr = PixmapPixelPtr(brush, 0, 0) 
							Local w2:Int = brush.width
							tmpPixmapPtr[(x + x2) * 4 + (y + y2) * 4 * w] = brushPixmapPTr[x2 * 4 + y2 * 4 * w2] 
							tmpPixmapPtr[(x + x2) * 4 + (y + y2) * 4 * w + 1] = brushPixmapPTr[x2 * 4 + y2 * 4 * w2 + 1] 
							tmpPixmapPtr[(x + x2) * 4 + (y + y2) * 4 * w + 2] = brushPixmapPTr[x2 * 4 + y2 * 4 * w2 + 2]            		'alpha
						End If
					Next
				End If
			Next
		Next
	Next
	Local tmpImage:TImage = LoadImage(tmpPixmap)
	Return tmpImage
End Function



Dreamora(Posted 2008) [#5]
the case that an image is not divideable by 4 does not exist for images > 2x2
images must always be power of 2 on hardware and you sure have >= 3x3 pixels :)

you can load other images but internally they get expanded to next power of two and then UV modified to only show "your area" so for memory and performance efficiency make the images power of two directly


Ryan Burnside(Posted 2008) [#6]
Thanks for everything.