Dark Bit Factory & Gravity
PROGRAMMING => Other languages => Blitz => Topic started by: zawran on July 10, 2009
-
I was bored so I got the idea of making the start of a software render framework that uses the build-in pixmap feature of Blitzmax to render the screen buffer. Blitzmax has two ways of storing images. One is the regular image format, the other is pixmaps. Regular image format does not allow for direct manipulation of the image data, pixmaps does. So what I did was setup a type to handle the image buffers and some methods to manipulate these. So far its more or less just code to make these, load images into them, and simple scrolling and copying. I will when time permits add more manipulation methods, but everyone is free to assist and add to this if they like. The code is public domain and you may use it as you like, no strings attached, no warranties provided.
The framework type itself:
' The following code is public domain, use it as you like, in parts or as it is. Feel free to modify as you see fit.
' No warranties are provided for the code.
' All code written by Johnny Bremer (aka. Zawran), unless otherwise mentioned
Type zImageBuffer
Global zImageBufferList:TList = CreateList()
Field link:TLink ' link pointer
Field buffer:TPixmap ' buffer where pixmap is stored
Field width:Int ' width of image
Field height:Int ' height of image
Field pitch:Int ' pitch of image
Field bcount:Int ' byte count of each pixel 3/4 depending on alpha
Field mask:Int[3] ' masking color array
' create a new image buffer
Function make:zImageBuffer(width:Int,height:Int,maskrgb:Int=$000000,fillrgb:Int=$FF000000)
Local tmp:zImageBuffer = New zImageBuffer
tmp.buffer = CreatePixmap(width,height,PF_RGBA8888)
tmp.width = tmp.buffer.width
tmp.height = tmp.buffer.height
tmp.pitch = PixmapPitch(tmp.buffer)
tmp.bcount = tmp.pitch / tmp.width
tmp.mask[0] = (maskrgb Shr 16) & 255
tmp.mask[1] = (maskrgb Shr 8) & 255
tmp.mask[2] = maskrgb & 255
ClearPixels(tmp.buffer,fillrgb)
tmp.link = ListAddLast(zImageBufferList,tmp)
Return tmp
End Function
' load an image and create an image buffer from that
Function loadfile:zImageBuffer(path:String,maskrgb:Int=$000000)
Local tmp:zImageBuffer = New zImageBuffer
tmp.buffer = LoadPixmap(path)
tmp.width = tmp.buffer.width
tmp.height = tmp.buffer.height
tmp.pitch = PixmapPitch(tmp.buffer)
tmp.bcount = tmp.pitch / tmp.width
tmp.mask[0] = (maskrgb Shr 16) & 255
tmp.mask[1] = (maskrgb Shr 8) & 255
tmp.mask[2] = maskrgb & 255
tmp.link = ListAddLast(zImageBufferList,tmp)
Return tmp
End Function
' copy an already existing image buffer
Function copy:zImageBuffer(from:zImageBuffer)
Local tmp:zImageBuffer = New zImageBuffer
tmp.buffer = CopyPixmap(from.buffer)
tmp.width = tmp.buffer.width
tmp.height = tmp.buffer.height
tmp.pitch = PixmapPitch(tmp.buffer)
tmp.bcount = tmp.pitch / tmp.width
tmp.mask = from.mask
tmp.link = ListAddLast(zImageBufferList,tmp)
Return tmp
End Function
Function rotatecopy:zImageBuffer(from:zImageBuffer,angle:Int)
Local tmp:zImagebuffer = zImageBuffer.copy(from)
tmp.clear()
Local r:Byte,g:Byte,b:Byte,a:Byte
Local wh:Int = (tmp.width/2)-1
Local hh:Int = (tmp.height/2)-1
For Local y:Int = 0 Until tmp.height
For Local x:Int = 0 Until tmp.width
Local tx:Int = (x-wh) * Cos(angle) - (y-hh) * Sin(angle) + wh
Local ty:Int = (y-hh) * Cos(angle) + (x-wh) * Sin(angle) + hh
If tx > -1 And tx < tmp.width And ty > -1 And ty < tmp.height Then
from.getpixel(tx,ty,r,g,b,a)
tmp.setpixel(x,y,r,g,b,a)
End If
Next
Next
Return tmp
End Function
Method setmask(maskrgb:Int=$000000)
Self.mask[0] = (maskrgb Shr 16) & 255
Self.mask[1] = (maskrgb Shr 8) & 255
Self.mask[2] = maskrgb & 255
End Method
' copy a rectangle from one image buffer to another as a block without masking
Method copyrectblock(from:zImageBuffer,x:Int,y:Int,w:Int,h:Int,tx:Int,ty:Int)
Local pixTargetPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(from.buffer)
For Local yy:Int = 0 Until h
For Local xx:Int = 0 Until w
' is target pixel within the target buffer
If tx+xx > -1 And ty+yy > -1 And tx+xx < Self.width And ty+yy < Self.height Then
' is source pixel within the source buffer
If x+xx > -1 And y+yy > -1 And x+xx < from.width And y+yy < from.height Then
pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount]
pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount+1] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+1]
pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount+2] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+2]
' check to see if both images include alpha value that needs to be copied as well
If Self.bcount = 4 And from.bcount = 4 Then pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount+3] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+3]
End If
End If
Next
Next
End Method
' copy a rectangle from one image buffer to another using masking
Method copyrect(from:zImageBuffer,x:Int,y:Int,w:Int,h:Int,tx:Int,ty:Int)
Local pixTargetPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(from.buffer)
For Local yy:Int = 0 Until h
For Local xx:Int = 0 Until w
' is target pixel within the target buffer
If tx+xx > -1 And ty+yy > -1 And tx+xx < Self.width And ty+yy < Self.height Then
' is source pixel within the source buffer
If x+xx > -1 And y+yy > -1 And x+xx < from.width And y+yy < from.height Then
If pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount] <> from.mask[0] Or pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+1] <> from.mask[1] Or pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+2] <> from.mask[2] Then
pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount]
pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount+1] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+1]
pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount+2] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+2]
' check to see if both images include alpha value that needs to be copied as well
If Self.bcount = 4 And from.bcount = 4 Then pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount+3] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+3]
End If
End If
End If
Next
Next
End Method
Method setpixel(x:Int,y:Int,r:Byte,g:Byte,b:Byte,a:Byte=255)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
If x > -1 And x < Self.width And y > -1 And y < Self.height Then
pixPtr[y*Self.pitch+x*Self.bcount] = r
pixPtr[y*Self.pitch+x*Self.bcount+1] = g
pixPtr[y*Self.pitch+x*Self.bcount+2] = b
If Self.bcount = 4 Then pixPtr[y*Self.pitch+x*Self.bcount+3] = a
End If
End Method
Method getpixel(x:Int,y:Int,r:Byte Var,g:Byte Var,b:Byte Var,a:Byte Var)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
If x > -1 And x < Self.width And y > -1 And y < Self.height Then
r = pixPtr[y*Self.pitch+x*Self.bcount]
g = pixPtr[y*Self.pitch+x*Self.bcount+1]
b = pixPtr[y*Self.pitch+x*Self.bcount+2]
If Self.bcount = 4 Then a = pixPtr[y*Self.pitch+x*Self.bcount+3]
End If
End Method
Method noise(x:Int,y:Int,width:Int,height:Int,noiselevel:Int)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
For Local ty:Int = y To y+height-1
For Local tx:Int = x To x+width-1
If tx > -1 And tx < Self.width And ty > -1 And ty < Self.height Then
Local offset:Int = ty*Self.pitch+tx*Self.bcount
Local n:Int = Rnd(-noiselevel,noiselevel)
If pixPtr[offset] + n < 0 Then
pixPtr[offset] = 0
Else
If pixPtr[offset] + n > 255 Then
pixPtr[offset] = 255
Else
pixPtr[offset] :+ n
End If
End If
If pixPtr[offset+1] + n < 0 Then
pixPtr[offset+1] = 0
Else
If pixPtr[offset+1] + n > 255 Then
pixPtr[offset+1] = 255
Else
pixPtr[offset+1] :+ n
End If
End If
If pixPtr[offset+2] + n < 0 Then
pixPtr[offset+2] = 0
Else
If pixPtr[offset+2] + n > 255 Then
pixPtr[offset+2] = 255
Else
pixPtr[offset+2] :+ n
End If
End If
End If
Next
Next
End Method
Method colorSubtract(x:Int,y:Int,width:Int,height:Int,r:Byte,g:Byte,b:Byte,a:Byte)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
For Local ty:Int = y To y+height-1
For Local tx:Int = x To x+width-1
If tx > -1 And tx < Self.width And ty > -1 And ty < Self.height Then
Local offset:Int = ty*Self.pitch+tx*Self.bcount
If pixPtr[offset] - r < 0 Then
pixPtr[offset] = 0
Else
pixPtr[offset] = pixPtr[offset]-r
End If
If pixPtr[offset+1] - g < 0 Then
pixPtr[offset+1] = 0
Else
pixPtr[offset+1] = pixPtr[offset+1]-g
End If
If pixPtr[offset+2] - b < 0 Then
pixPtr[offset+2] = 0
Else
pixPtr[offset+2] = pixPtr[offset+2]-b
End If
If Self.bcount = 4 Then
If pixPtr[offset+3] - a < 0 Then
pixPtr[offset+3] = 0
Else
pixPtr[offset+3] = pixPtr[offset+3]-a
End If
End If
End If
Next
Next
End Method
Method colorAddition(x:Int,y:Int,width:Int,height:Int,r:Byte,g:Byte,b:Byte,a:Byte)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
For Local ty:Int = y To y+height-1
For Local tx:Int = x To x+width-1
If tx > -1 And tx < Self.width And ty > -1 And ty < Self.height Then
Local offset:Int = ty*Self.pitch+tx*Self.bcount
If r + pixPtr[offset] > 255 Then
pixPtr[offset] = 255
Else
pixPtr[offset] = pixPtr[offset]+r
End If
If g + pixPtr[offset+1] > 255 Then
pixPtr[offset+1] = 255
Else
pixPtr[offset+1] = pixPtr[offset+1]+g
End If
If b + pixPtr[offset+2] > 255 Then
pixPtr[offset+2] = 255
Else
pixPtr[offset+2] = pixPtr[offset+2]+b
End If
If Self.bcount = 4 Then
If a + pixPtr[offset+3] > 255 Then
pixPtr[offset+3] = 255
Else
pixPtr[offset+3] = pixPtr[offset+3]+a
End If
End If
End If
Next
Next
End Method
Method drawBlock(toImg:zImagebuffer,x:Int,y:Int)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
For Local ty:Int = 0 Until Self.height
For Local tx:Int = 0 Until Self.width
If tx+x > -1 And tx+x < toImg.width And ty+y > -1 And ty+y < toImg.height Then
Local offsetS:Int = ty*Self.pitch+tx*Self.bcount
Local offsetD:Int = (ty+y)*toImg.pitch+(tx+x)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
Next
Next
End Method
Method drawMasked(toImg:zImageBuffer,x:Int,y:Int)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
For Local ty:Int = 0 Until Self.height
For Local tx:Int = 0 Until Self.width
If tx+x > -1 And tx+x < toImg.width And ty+y > -1 And ty+y < toImg.height Then
If pixSourcePtr[ty*Self.pitch+tx*Self.bcount] <> Self.mask[0] Or pixSourcePtr[ty*Self.pitch+tx*Self.bcount+1] <> Self.mask[1] Or pixSourcePtr[ty*Self.pitch+tx*Self.bcount+2] <> Self.mask[2] Then
Local offsetS:Int = ty*Self.pitch+tx*Self.bcount
Local offsetD:Int = (ty+y)*toImg.pitch+(tx+x)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
End If
Next
Next
End Method
Method drawScaledBlock(toImg:zImageBuffer,x:Int,y:Int,scaleX:Float,scaleY:Float)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
Local sw:Int = Self.width * scaleX
Local sh:Int = Self.height * scaleY
Local deltaX:Float = Self.width / Float(sw)
Local deltaY:Float = Self.height / Float(sh)
Local cx:Float=0.0,cy:Float=0.0
For Local ty:Int = 0 Until sh
cx = 0.0
For Local tx:Int = 0 Until sw
If tx+x > -1 And tx+x < toImg.width And ty+y > -1 And ty+y < toImg.height Then
Local offsetS:Int = Int(cy)*Self.pitch+Int(cx)*Self.bcount
Local offsetD:Int = (ty+y)*toImg.pitch+(tx+x)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
cx :+ deltaX
Next
cy :+ deltaY
Next
End Method
Method drawScaledMasked(toImg:zImageBuffer,x:Int,y:Int,scaleX:Float,scaleY:Float)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
Local sw:Int = Self.width * scaleX
Local sh:Int = Self.height * scaleY
Local deltaX:Float = Self.width / Float(sw)
Local deltaY:Float = Self.height / Float(sh)
Local cx:Float=0.0,cy:Float=0.0
For Local ty:Int = 0 Until sh
cx = 0.0
For Local tx:Int = 0 Until sw
If tx+x > -1 And tx+x < toImg.width And ty+y > -1 And ty+y < toImg.height Then
If pixSourcePtr[Int(cy)*Self.pitch+Int(cx)*Self.bcount] <> Self.mask[0] Or pixSourcePtr[Int(cy)*Self.pitch+Int(cx)*Self.bcount+1] <> Self.mask[1] Or pixSourcePtr[Int(cy)*Self.pitch+Int(cx)*Self.bcount+2] <> Self.mask[2] Then
Local offsetS:Int = Int(cy)*Self.pitch+Int(cx)*Self.bcount
Local offsetD:Int = (ty+y)*toImg.pitch+(tx+x)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
End If
cx :+ deltaX
Next
cy :+ deltaY
Next
End Method
' save an image buffer to file
Method savefile(path:String="image.png",comp:Int=5)
SavePixmapPNG(Self.buffer,path,comp)
End Method
' clear an image buffer with a set RGBA color
Method clear(rgba:Int=$FF000000)
ClearPixels(Self.buffer,rgba)
End Method
' draw an image buffer to screen
Method blitToScreen(x:Int=0,y:Int=0)
DrawPixmap(Self.buffer,x,y)
End Method
' remove an image buffer
Method remove()
Self.buffer = Null
RemoveLink(Self.link)
End Method
Method line(x1:Float,y1:Float,x2:Float,y2:Float,r:Byte,g:Byte,b:Byte)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local steps:Float,xI:Float,i:Int
x2 :- x1
y2 :- y1
If Abs(x2) > Abs(y2) Then
steps = Abs(x2)
Else
steps = Abs(y2)
End If
xI = Float(x2 / steps)
y2 = Float(y2 / steps)
For x2 = 0 To steps
If Int(y1) > -1 And Int(y1) < Self.height Then
If Int(x1) > -1 And Int(x1) < Self.width Then
Local offset:Int = Int(y1)*Self.pitch+Int(x1)*Self.bcount
pixPtr[offset] = r
pixPtr[offset+1] = g
pixPtr[offset+2] = b
If Self.bcount = 4 Then pixPtr[offset+3] = 255
End If
End If
x1 :+ xI
y1 :+ y2
Next
End Method
Method rect(x1:Int,y1:Int,x2:Int,y2:Int,r:Byte,g:Byte,b:Byte)
For Local i:Int = y1 To y2
Self.line(x1,i,x2,i,r,g,b)
Next
End Method
Method mirror(direction:Int)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local tmp:Byte,x:Int,y:Int
Select direction
Case 0 ' horizontal
For x = 0 To (Self.width/2)-1
For y = 0 Until Self.height
tmp = pixPtr[y*Self.pitch+x*Self.bcount]
pixPtr[y*Self.pitch+x*Self.bcount] = pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount]
pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount] = tmp
tmp = pixPtr[y*Self.pitch+x*Self.bcount+1]
pixPtr[y*Self.pitch+x*Self.bcount+1] = pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+1]
pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+1] = tmp
tmp = pixPtr[y*Self.pitch+x*Self.bcount+2]
pixPtr[y*Self.pitch+x*Self.bcount+2] = pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+2]
pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+2] = tmp
If Self.bcount = 4 Then
tmp = pixPtr[y*Self.pitch+x*Self.bcount+3]
pixPtr[y*Self.pitch+x*Self.bcount+3] = pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+3]
pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+3] = tmp
End If
Next
Next
Case 1 ' vertical
For y = 0 To (Self.height/2)-1
For x = 0 Until Self.width
tmp = pixPtr[y*Self.pitch+x*Self.bcount]
pixPtr[y*Self.pitch+x*Self.bcount] = pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount]
pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount] = tmp
tmp = pixPtr[y*Self.pitch+x*Self.bcount+1]
pixPtr[y*Self.pitch+x*Self.bcount+1] = pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+1]
pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+1] = tmp
tmp = pixPtr[y*Self.pitch+x*Self.bcount+2]
pixPtr[y*Self.pitch+x*Self.bcount+2] = pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+2]
pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+2] = tmp
If Self.bcount = 4 Then
tmp = pixPtr[y*Self.pitch+x*Self.bcount+3]
pixPtr[y*Self.pitch+x*Self.bcount+3] = pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+3]
pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+3] = tmp
End If
Next
Next
Case 2 ' horizontal + vertical
Self.mirror(0)
Self.mirror(1)
End Select
End Method
' shift the content of an image buffer in one of four directions: 0=left, 1=right, 2=up, 3=down
Method shift(direction:Int,pixels:Int)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Select direction
Case 0 ' left
Local moveSize:Int = (Self.width-pixels)*Self.bcount
Local offset:Int = pixels*Self.bcount
For Local y:Int = 0 Until Self.height
MemCopy(pixPtr,pixPtr+offset,moveSize)
pixPtr :+ pitch
Next
Case 1 ' right
Local pixSize:Int = pitch/Self.width
Local moveSize:Int = (Self.width-pixels)*Self.bcount
Local offset:Int = pixels*Self.bcount
For Local y:Int = 0 Until Self.height
MemCopy(pixPtr+offset,pixPtr,moveSize)
pixPtr :+ pitch
Next
Case 2 ' up
Local pixSize:Int = pitch/Self.width
For Local y:Int = 0 To Self.height-1-pixels
MemCopy(pixPtr,pixPtr+pitch*pixels,pitch)
pixPtr :+ pitch
Next
Case 3 ' down
Local pixSize:Int = pitch/Self.width
pixPtr :+ ((Self.width*Self.bcount)*(Self.height-1))
For Local y:Int = 0 To Self.height-1-pixels
MemCopy(pixPtr,pixPtr-(pitch*pixels),pitch)
pixPtr :- pitch
Next
End Select
End Method
End Type
Type zSpriteSheet
Global zSpriteSheetList:TList = CreateList()
Field link:TLink ' link pointer
Field image:zImageBuffer ' image buffer where sprites pixmap is stored
Field spriteCount:Int ' number of sprites on sheet
Field spriteX:Int[] ' x position of top left pixel in sprite
Field spriteY:Int[] ' y position of top left pixel in sprite
Field spriteW:Int[] ' width of sprite
Field spriteH:Int[] ' height of sprite
' create a new image buffer
Function loadfontsheet:zSpriteSheet(path:String,maskrgb:Int=$000000)
Local tmp:zSpriteSheet = New zSpriteSheet
tmp.image = zImageBuffer.loadfile(path,maskrgb)
Local f:String = Left(path,Len(path)-3)+"fxb"
Local filein:TStream = OpenFile(f)
Local t:Int = ReadInt(filein) ' font height not used
tmp.spriteCount = ReadInt(filein)
tmp.spriteX = New Int[tmp.spriteCount]
tmp.spriteY = New Int[tmp.spriteCount]
tmp.spriteW = New Int[tmp.spriteCount]
tmp.spriteH = New Int[tmp.spriteCount]
For Local i:Int = 0 Until tmp.spriteCount
t = ReadInt(filein) ' ascii code not used
tmp.spriteX[i] = ReadInt(filein)
tmp.spriteY[i] = ReadInt(filein)
tmp.spriteW[i] = ReadInt(filein)
tmp.spriteH[i] = ReadInt(filein)
Next
tmp.link = ListAddLast(zSpriteSheetList,tmp)
Return tmp
End Function
Method draw(toImg:zImageBuffer,x:Int,y:Int,sprite:Int)
toImg.copyrect(Self.image,Self.spriteX[sprite],Self.spriteY[sprite],Self.spriteW[sprite],Self.spriteH[sprite],x,y)
End Method
' remove sprite sheet
Method remove()
Self.image.remove() ' remove sprite image
RemoveLink(Self.link) ' remove sprite sheet
End Method
End Type
Quick short test code.
SuperStrict
Framework BRL.GLMax2D
Import BRL.Pixmap
Import BRL.PNGLoader
SetGraphicsDriver GLMax2DDriver()
Include "zImageBuffer.bmx"
Graphics 640,480
Global timerUpd:Int = MilliSecs()
Global timerFrq:Int = 1000/30 ' 30 fps
' create an image buffer to store the screen in
Global screenBuffer:zImageBuffer = zImageBuffer.make(640,480)
screenBuffer.clear($FF000000)
' load an image into a new image buffer
Global tmpo:zImageBuffer = zImageBuffer.loadfile("background.png")
' copy half of the loaded image into the screen buffer image
screenBuffer.copyRect(tmpo,320,0,320,480,0,0)
While Not KeyHit(KEY_ESCAPE)
Cls
screenBuffer.draw()
Flip
While MilliSecs() < timerUpd+timerFrq
Wend
timerUpd = MilliSecs()
screenBuffer.shift(3,1)
Wend
End
Compiled example and source code can be found here: http://zac-interactive.dk/temp/srf.zip (http://zac-interactive.dk/temp/srf.zip)
-
You always pick the nicest pics to use in your things :)
K+ for sharing this nice program.
-
You always pick the nicest pics to use in your things
Hehe, thanks :)
[edit] I added a couple of things. There is a new method for the framework, which will mirror the image buffer horizontal, vertical or both. I also added masking color to the image buffer type so that a masking color can be set for the buffers. This also ment that I had to make an additional copyrect method which will use the masking colors when copying the rect from one image buffer to another. I therefore renamed the old copyrect method to copyrectblock. I also added a method to allow for changing the masking color seperately.
' The following code is public domain, use it as you like, in parts or as it is. Feel free to modify as you see fit.
' No warranties are provided for the code, if it works then fine, if not then ah well, I tried :)
Type zImageBuffer
Global zImageBufferList:TList = CreateList()
Field link:TLink ' link pointer
Field buffer:TPixmap ' buffer where pixmap is stored
Field width:Int ' width of image
Field height:Int ' height of image
Field pitch:Int ' pitch of image
Field bcount:Int ' byte count of each pixel 3/4 depending on alpha
Field mask:Int[3] ' masking color array
' create a new image buffer
Function make:zImageBuffer(width:Int,height:Int,maskrgb:Int=$000000)
Local tmp:zImageBuffer = New zImageBuffer
tmp.buffer = CreatePixmap(width,height,PF_RGBA8888)
tmp.width = tmp.buffer.width
tmp.height = tmp.buffer.height
tmp.pitch = PixmapPitch(tmp.buffer)
tmp.bcount = tmp.pitch / tmp.width
tmp.mask[0] = (maskrgb Shr 16) & 255
tmp.mask[1] = (maskrgb Shr 8) & 255
tmp.mask[2] = maskrgb & 255
tmp.link = ListAddLast(zImageBufferList,tmp)
Return tmp
End Function
' load an image and create an image buffer from that
Function loadfile:zImageBuffer(path:String,maskrgb:Int=$000000)
Local tmp:zImageBuffer = New zImageBuffer
tmp.buffer = LoadPixmap(path)
tmp.width = tmp.buffer.width
tmp.height = tmp.buffer.height
tmp.pitch = PixmapPitch(tmp.buffer)
tmp.bcount = tmp.pitch / tmp.width
tmp.mask[0] = (maskrgb Shr 16) & 255
tmp.mask[1] = (maskrgb Shr 8) & 255
tmp.mask[2] = maskrgb & 255
tmp.link = ListAddLast(zImageBufferList,tmp)
Return tmp
End Function
' copy an already existing image buffer
Function copy:zImageBuffer(from:zImageBuffer)
Local tmp:zImageBuffer = New zImageBuffer
tmp.buffer = CopyPixmap(from.buffer)
tmp.width = tmp.buffer.width
tmp.height = tmp.buffer.height
tmp.pitch = PixmapPitch(tmp.buffer)
tmp.bcount = tmp.pitch / tmp.width
tmp.mask = from.mask
tmp.link = ListAddLast(zImageBufferList,tmp)
Return tmp
End Function
Method setmask(maskrgb:Int=$000000)
Self.mask[0] = (maskrgb Shr 16) & 255
Self.mask[1] = (maskrgb Shr 8) & 255
Self.mask[2] = maskrgb & 255
End Method
' copy a rectangle from one image buffer to another as a block without masking
Method copyrectblock(from:zImageBuffer,x:Int,y:Int,w:Int,h:Int,tx:Int,ty:Int)
Local pixTargetPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(from.buffer)
For Local yy:Int = 0 Until h
For Local xx:Int = 0 Until w
' is target pixel within the target buffer
If tx+xx > -1 And ty+yy > -1 And tx+xx < Self.width And ty+yy < Self.height Then
' is source pixel within the source buffer
If x+xx > -1 And y+yy > -1 And x+xx < from.width And y+yy < from.height Then
pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount]
pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount+1] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+1]
pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount+2] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+2]
' check to see if both images include alpha value that needs to be copied as well
If Self.bcount = 4 And from.bcount = 4 Then pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount+3] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+3]
End If
End If
Next
Next
End Method
' copy a rectangle from one image buffer to another using masking
Method copyrect(from:zImageBuffer,x:Int,y:Int,w:Int,h:Int,tx:Int,ty:Int)
Local pixTargetPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(from.buffer)
For Local yy:Int = 0 Until h
For Local xx:Int = 0 Until w
' is target pixel within the target buffer
If tx+xx > -1 And ty+yy > -1 And tx+xx < Self.width And ty+yy < Self.height Then
' is source pixel within the source buffer
If x+xx > -1 And y+yy > -1 And x+xx < from.width And y+yy < from.height Then
If pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount] <> from.mask[0] Or pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+1] <> from.mask[1] Or pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+2] <> from.mask[2] Then
pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount]
pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount+1] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+1]
pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount+2] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+2]
' check to see if both images include alpha value that needs to be copied as well
If Self.bcount = 4 And from.bcount = 4 Then pixTargetPtr[(ty+yy)*Self.pitch+(tx+xx)*Self.bcount+3] = pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+3]
End If
End If
End If
Next
Next
End Method
' save an image buffer to file
Method savefile(path:String="image.png",comp:Int=5)
SavePixmapPNG(Self.buffer,path,comp)
End Method
' clear an image buffer with a set RGBA color
Method clear(rgba:Int=$FF000000)
ClearPixels(Self.buffer,rgba)
End Method
' draw an image buffer to screen
Method draw(x:Int=0,y:Int=0)
DrawPixmap(Self.buffer,x,y)
End Method
' remove an image buffer
Method remove()
Self.buffer = Null
RemoveLink(Self.link)
End Method
Method line(x1:Float,y1:Float,x2:Float,y2:Float,r:Byte,g:Byte,b:Byte)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local steps:Int,xI:Float,i:Int
x2 :- x1
y2 :- y1
If Abs(x2) > Abs(y2) Then
steps = Abs(x2)
Else
steps = Abs(y2)
End If
xI = Float(x2 / steps)
y2 = Float(y2 / steps)
For i = 1 To steps
If Int(y1) > -1 And Int(y1) < Self.height Then
If Int(x1) > -1 And Int(x1) < Self.width Then
pixPtr[Int(y1)*Self.pitch+Int(x1)*Self.bcount] = r
pixPtr[Int(y1)*Self.pitch+Int(x1)*Self.bcount+1] = g
pixPtr[Int(y1)*Self.pitch+Int(x1)*Self.bcount+2] = b
If Self.bcount = 4 Then pixPtr[Int(y1)*Self.pitch+Int(x1)*Self.bcount+3] = 255
End If
End If
x1 :+ xI
y1 :+ y2
Next
End Method
Method rect(x1:Int,y1:Int,x2:Int,y2:Int,r:Byte,g:Byte,b:Byte)
For Local i:Int = y1 To y2
Self.line(x1,i,x2,i,r,g,b)
Next
End Method
Method mirror(direction:Int)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local tmp:Byte,x:Int,y:Int
Select direction
Case 0 ' horizontal
For x = 0 To (Self.width/2)-1
For y = 0 Until Self.height
tmp = pixPtr[y*Self.pitch+x*Self.bcount]
pixPtr[y*Self.pitch+x*Self.bcount] = pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount]
pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount] = tmp
tmp = pixPtr[y*Self.pitch+x*Self.bcount+1]
pixPtr[y*Self.pitch+x*Self.bcount+1] = pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+1]
pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+1] = tmp
tmp = pixPtr[y*Self.pitch+x*Self.bcount+2]
pixPtr[y*Self.pitch+x*Self.bcount+2] = pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+2]
pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+2] = tmp
If Self.bcount = 4 Then
tmp = pixPtr[y*Self.pitch+x*Self.bcount+3]
pixPtr[y*Self.pitch+x*Self.bcount+3] = pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+3]
pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+3] = tmp
End If
Next
Next
Case 1 ' vertical
For y = 0 To (Self.height/2)-1
For x = 0 Until Self.width
tmp = pixPtr[y*Self.pitch+x*Self.bcount]
pixPtr[y*Self.pitch+x*Self.bcount] = pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount]
pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount] = tmp
tmp = pixPtr[y*Self.pitch+x*Self.bcount+1]
pixPtr[y*Self.pitch+x*Self.bcount+1] = pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+1]
pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+1] = tmp
tmp = pixPtr[y*Self.pitch+x*Self.bcount+2]
pixPtr[y*Self.pitch+x*Self.bcount+2] = pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+2]
pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+2] = tmp
If Self.bcount = 4 Then
tmp = pixPtr[y*Self.pitch+x*Self.bcount+3]
pixPtr[y*Self.pitch+x*Self.bcount+3] = pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+3]
pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+3] = tmp
End If
Next
Next
Case 2 ' horizontal + vertical
Self.mirror(0)
Self.mirror(1)
End Select
End Method
' shift the content of an image buffer in one of four directions: 0=left, 1=right, 2=up, 3=down
Method shift(direction:Int,pixels:Int)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Select direction
Case 0 ' left
Local moveSize:Int = (Self.width-pixels)*Self.bcount
Local offset:Int = pixels*Self.bcount
For Local y:Int = 0 Until Self.height
MemCopy(pixPtr,pixPtr+offset,moveSize)
pixPtr :+ pitch
Next
Case 1 ' right
Local pixSize:Int = pitch/Self.width
Local moveSize:Int = (Self.width-pixels)*Self.bcount
Local offset:Int = pixels*Self.bcount
For Local y:Int = 0 Until Self.height
MemCopy(pixPtr+offset,pixPtr,moveSize)
pixPtr :+ pitch
Next
Case 2 ' up
Local pixSize:Int = pitch/Self.width
For Local y:Int = 0 To Self.height-1-pixels
MemCopy(pixPtr,pixPtr+pitch*pixels,pitch)
pixPtr :+ pitch
Next
Case 3 ' down
Local pixSize:Int = pitch/Self.width
pixPtr :+ ((Self.width*Self.bcount)*(Self.height-1))
For Local y:Int = 0 To Self.height-1-pixels
MemCopy(pixPtr,pixPtr-(pitch*pixels),pitch)
pixPtr :- pitch
Next
End Select
End Method
End Type
-
Awesome, just awesome dude.
Have some karma.
-
Looking good :)
I made a start on a small pixmap lib to allow me to draw to a pixmap as if it were to the screen awhile back, will have look maybe the line drawing code will be useful, not sure what else i had added to it.
[Edit] Well I only had line and rectangle drawing coded, i will post the whole thing including example here and then people can adapt the code into zawrans lib or use it as is if they want.
SuperStrict
'SetGraphicsDriver GLMax2DDriver()
SetGraphicsDriver D3D7Max2DDriver()
Const XRES:Int=640
Const YRES:Int=480
Graphics XRES,YRES,0
'-------------------------------------------------------------------
' Extension of TPixmap with extra drawing commands
'-------------------------------------------------------------------
Type TTDpixmap Extends TPixmap
'-------------------------------------------------------------------
' NAME : Create()
' PURPOSE : This Function Creates a custom pixmap
' INPUTS : width:Int,height:Int,format:Int,align:Int
' RETURNS : TTDpixmap.
'-------------------------------------------------------------------
Function Create:TTDPixmap( width:Int,height:Int,format:Int,align:Int=4 )
Local pitch:Int=width*BytesPerPixel[format]
pitch=(pitch+(align-1))/align*align
Local capacity:Int=pitch*height
Local pixmap:TTDpixmap=New TTDpixmap
pixmap.pixels=MemAlloc( capacity )
pixmap.width=width
pixmap.height=height
pixmap.pitch=pitch
pixmap.format=format
pixmap.capacity=capacity
Return pixmap
End Function
'-----------------------------------------------------------------------
' NAME : Line()
' PURPOSE : This Function draws a line
' INPUTS : x1:Float,y1:Float,x2:Float,y2:Float,r:Int,g:Int,b:Int
' RETURNS : Nothing.
' NOTES : multi format, could incrase speed by setting fixed
'-----------------------------------------------------------------------
Method Line(x1:Float,y1:Float,x2:Float,y2:Float,r:Int,g:Int,b:Int)
Local argb:Int=r Shl 16 + g Shl 8 + b
Local xI:Float, steps:Int
Local p:Byte Ptr
'---
x2 = x2-x1
y2 = y2-y1
If Abs(x2)>Abs(y2)
steps=Abs(x2)
Else
steps=Abs(y2)
EndIf
xI = x2 / steps
y2 = y2 / steps
For x2 = 0 To steps
If x1>=0 And x1<width And y1>=0 And y1<height
' Could maybe speed up by ensuring line is inside pixmap rather than each pixel
' Also maybe just pass r,g,b rather than split back out from argb for some lol
p=PixelPtr(x1,y1)
Select format
Case PF_A8
p[0]=argb Shr 24
Case PF_I8
p[0]=( (argb Shr 16 & $ff)+(argb Shr 8 & $ff)+(argb & $ff) )/3
Case PF_RGB888
p[0]=argb Shr 16 ; p[1]=argb Shr 8 ; p[2]=argb
Case PF_BGR888
p[0]=argb ; p[1]=argb Shr 8 ; p[2]=argb Shr 16
Case PF_RGBA8888
p[0]=argb Shr 16 ; p[1]=argb Shr 8 ; p[2]=argb ; p[3]=argb Shr 24
Case PF_BGRA8888
p[0]=argb ; p[1]=argb Shr 8 ; p[2]=argb Shr 16 ; p[3]=argb Shr 24
End Select
x1=x1+xI
y1=y1+y2
EndIf
Next
End Method
'---------------------------------------------------------------------------
' NAME : Rect()
' PURPOSE : This Function draws a rectangle
' INPUTS : x1:Int,y1:Int,width:Int,height:Int,r:Int,g:Int,b:Int,filled:Int
' RETURNS : Nothing.
' NOTES : Uses Line drawing atm, could increase speed by directly drawing
'---------------------------------------------------------------------------
Method Rect(x1:Int,y1:Int,width:Int,height:Int,r:Int,g:Int,b:Int,filled:Int=True)
Local lopy:Int
If filled=True
For lopy=y1 To (y1+height)
line(x1,lopy,x1+width,lopy,r,g,b)
Next
Else
line(x1,y1,x1+width,y1,r,g,b) 'top
line(x1,y1+height,x1+width,y1+height,r,g,b) 'bottom
line(x1,y1,x1,y1+height,r,g,b) 'left
line(x1+width,y1,x1+width,y1+height,r,g,b) 'right
EndIf
End Method
EndType
'Local testmap:TTDPixmap=TTDPixmap.Create(XRES,YRES/2,PF_RGBA8888) ' Use for GL mode, slow/no show in DX
Local testmap:TTDPixmap=TTDPixmap.Create(XRES,YRES/2,PF_BGRA8888) ' Use for DX mode, slow GL (converts to RGBA)
testmap.ClearPixels(0)
Local iFramecounter_counter:Int
Local iFramecounter_time:Int
Local iFramecounter_framerate:Int
Repeat
Cls
'testmap.Line(Rand(0,XRES),Rand(0,YRES/2),Rand(0,XRES),Rand(0,YRES/2),Rand(0,255),Rand(0,255),Rand(0,255))
'testmap.rect(Rand(0,XRES),Rand(0,YRES/2),Rand(0,50),Rand(0,50),Rand(0,255),Rand(0,255),Rand(0,255),True)
testmap.rect(Rand(0,XRES),Rand(0,YRES/2),Rand(1,50),Rand(1,50),Rand(0,255),Rand(0,255),Rand(0,255),False)
DrawPixmap(testmap,0,50)
DrawText("FPS : "+iFramecounter_framerate,0,0)
Flip
If KeyHit(KEY_SPACE)
testmap.ClearPixels(0)
EndIf
'Framecounter--------------------------------------------
iFramecounter_counter=iFramecounter_counter+1
If iFramecounter_time=0 Then iFramecounter_time=MilliSecs()
If iFramecounter_time+1001 <MilliSecs() Then
iFramecounter_framerate=iFramecounter_counter
iFramecounter_counter=0
iFramecounter_time=MilliSecs()
EndIf
'Framecounter--------------------------------------------
Until KeyDown(KEY_ESCAPE)
End
If I get a chance I will look into some other drawing features, but it shouldnt be to hard to get the drawing functions to work in zawrans code base.
Cheers
Jon
-
have some karma :)
-
Thanks guys.
Here are line and rect methods for the framework:
Method line(x1:Float,y1:Float,x2:Float,y2:Float,r:Byte,g:Byte,b:Byte)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local steps:Int,xI:Float,i:Int
x2 :- x1
y2 :- y1
If Abs(x2) > Abs(y2) Then
steps = Abs(x2)
Else
steps = Abs(y2)
End If
xI = Float(x2 / steps)
y2 = Float(y2 / steps)
For i = 1 To steps
If Int(y1) > -1 And Int(y1) < Self.height Then
If Int(x1) > -1 And Int(x1) < Self.width Then
pixPtr[Int(y1)*Self.pitch+Int(x1)*Self.bcount] = r
pixPtr[Int(y1)*Self.pitch+Int(x1)*Self.bcount+1] = g
pixPtr[Int(y1)*Self.pitch+Int(x1)*Self.bcount+2] = b
If Self.bcount = 4 Then pixPtr[Int(y1)*Self.pitch+Int(x1)*Self.bcount+3] = 255
End If
End If
x1 :+ xI
y1 :+ y2
Next
End Method
Method rect(x1:Int,y1:Int,x2:Int,y2:Int,r:Byte,g:Byte,b:Byte)
For Local i:Int = y1 To y2
Self.line(x1,i,x2,i,r,g,b)
Next
End Method
-
I made a smallish demo to show how the copy rect functions can be used to draw sprites and sinus a logo.
SuperStrict
Framework BRL.GLMax2D
Import BRL.Pixmap
Import BRL.PNGLoader
SetGraphicsDriver GLMax2DDriver()
Include "zImageBuffer.bmx"
Graphics 640,480
Global timerUpd:Int = MilliSecs()
Global timerFrq:Int = 1000/30 ' 30 fps
' create an image buffer to store the screen in
Global screenBuffer:zImageBuffer = zImageBuffer.make(640,480)
screenBuffer.clear($FF000000)
' load a logo image into a new image buffer
Global logoImg:zImageBuffer = zImageBuffer.loadfile("flames.png")
' create a temporary buffer where the logo is rendered to before rendering it to the screen buffer
Global tmpImg:zImageBuffer = zImageBuffer.make(360,256)
' load a sphere image into a new image buffer
Global sphereImg:zImageBuffer = zImageBuffer.loadfile("spheresmall.png")
Global angle1:Int = 0
Global angle2:Int = 0
While Not KeyHit(KEY_ESCAPE)
' clear screen buffer
screenBuffer.clear()
' clear temporary image buffer
tmpImg.clear()
' draw logo into temporary buffer offset with a horizontal sinus
sinusImageHorizontal(logoImg,tmpImg,0,32,angle1,2,15)
' draw temporary buffer into screenbuffer offset with a vertical sinus
sinusImageVertical(tmpImg,screenBuffer,140,0,angle2,1,64)
For Local i:Int = 0 To 7
screenBuffer.copyrect(sphereImg,0,0,128,128,288+Sin(angle1+i*45)*120,280+Cos(angle1+i*45)*120)
Next
' draw screenbuffer to the screen
screenBuffer.draw()
Flip
While MilliSecs() < timerUpd+timerFrq
Wend
timerUpd = MilliSecs()
angle1 :+ 4
angle2 :+ 8
Wend
End
Function sinusImageHorizontal(fromImg:zImageBuffer,toImg:zImageBuffer,x:Int,y:Int,angleStart:Int,angleJump:Int,freq:Int)
For Local lx:Int = 0 Until fromImg.width
toImg.copyrect(fromImg,lx,0,1,fromImg.height,x+lx,y+Sin(angleStart)*freq)
angleStart :+ angleJump
Next
End Function
Function sinusImageVertical(fromImg:zImageBuffer,toImg:zImageBuffer,x:Int,y:Int,angleStart:Int,angleJump:Int,freq:Int)
For Local ly:Int = 0 Until fromImg.height
toImg.copyrect(fromImg,0,ly,fromImg.width,1,x+Sin(angleStart)*freq,y+ly)
angleStart :+ angleJump
Next
End Function
You can get exe, source and image files from here: http://zac-interactive.dk/temp/srf_demo1.zip (http://zac-interactive.dk/temp/srf_demo1.zip)
[edit] forgot to mention that the flame logo is some random graphics I picked up using a google search, the same with the sphere. There were no mention on the page who had made them though.
-
Cool looking spheres :)
I will be acquiring a copy of Bmax soon (thanks hotshot) so I'll be able to have a play with your renderlib properly, I am looking forwad to it too.
-
Nice example, seems to be nice and smooth as well :)
One thing that I noticed looking thru the zImageBuffer type, with the added line and rectangle methods there isnt actually one to draw just single pixels, might be worth adding a wrapper method for it for ease of use, while it would likely add a little speed overhead if used alot it would give a few options for pixel manipulation of the buffers, perhaps have read and write methods ?
-
there isnt actually one to draw just single pixels
Good idea, so I added those:
Method setpixel(x:Int,y:Int,r:Byte,g:Byte,b:Byte,a:Byte=255)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
If x > -1 And x < Self.width And y > -1 And y < Self.height Then
pixPtr[y*Self.pitch+x*Self.bcount] = r
pixPtr[y*Self.pitch+x*Self.bcount+1] = g
pixPtr[y*Self.pitch+x*Self.bcount+2] = b
If Self.bcount = 4 Then pixPtr[y*Self.pitch+x*Self.bcount+3] = a
End If
End Method
Method getpixel(x:Int,y:Int,r:Byte Var,g:Byte Var,b:Byte Var,a:Byte Var)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
If x > -1 And x < Self.width And y > -1 And y < Self.height Then
r = pixPtr[y*Self.pitch+x*Self.bcount]
g = pixPtr[y*Self.pitch+x*Self.bcount+1]
b = pixPtr[y*Self.pitch+x*Self.bcount+2]
If Self.bcount = 4 Then a = pixPtr[y*Self.pitch+x*Self.bcount+3]
End If
End Method
I have updated the first post with the framework file as it is currently.
-
Runs perfectly smooth here. Well done, mate!
:clap:
-
Thanks :) I am going to expand a little bit on the framework during this weekend since my saturday seems to be fairly open. Probably also an example with a bitmap scroller.
-
The code is clean and simple
You have done very well with your code mr zawran ;)
-
Thanks, I just added noise, color addition and subtraction methods:
Method noise(x:Int,y:Int,width:Int,height:Int,noiselevel:Int)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
For Local ty:Int = y To y+height-1
For Local tx:Int = x To x+width-1
If tx > -1 And tx < Self.width And ty > -1 And ty < Self.height Then
Local offset:Int = ty*Self.pitch+tx*Self.bcount
Local n:Int = Rnd(-noiselevel,noiselevel)
If pixPtr[offset] + n < 0 Then
pixPtr[offset] = 0
Else
If pixPtr[offset] + n > 255 Then
pixPtr[offset] = 255
Else
pixPtr[offset] :+ n
End If
End If
If pixPtr[offset+1] + n < 0 Then
pixPtr[offset+1] = 0
Else
If pixPtr[offset+1] + n > 255 Then
pixPtr[offset+1] = 255
Else
pixPtr[offset+1] :+ n
End If
End If
If pixPtr[offset+2] + n < 0 Then
pixPtr[offset+2] = 0
Else
If pixPtr[offset+2] + n > 255 Then
pixPtr[offset+2] = 255
Else
pixPtr[offset+2] :+ n
End If
End If
End If
Next
Next
End Method
Method colorSubtract(x:Int,y:Int,width:Int,height:Int,r:Byte,g:Byte,b:Byte,a:Byte)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
For Local ty:Int = y To y+height-1
For Local tx:Int = x To x+width-1
If tx > -1 And tx < Self.width And ty > -1 And ty < Self.height Then
Local offset:Int = ty*Self.pitch+tx*Self.bcount
If pixPtr[offset] - r < 0 Then
pixPtr[offset] = 0
Else
pixPtr[offset] = pixPtr[offset]-r
End If
If pixPtr[offset+1] - g < 0 Then
pixPtr[offset+1] = 0
Else
pixPtr[offset+1] = pixPtr[offset+1]-g
End If
If pixPtr[offset+2] - b < 0 Then
pixPtr[offset+2] = 0
Else
pixPtr[offset+2] = pixPtr[offset+2]-b
End If
If Self.bcount = 4 Then
If pixPtr[offset+3] - a < 0 Then
pixPtr[offset+3] = 0
Else
pixPtr[offset+3] = pixPtr[offset+3]-a
End If
End If
End If
Next
Next
End Method
Method colorAddition(x:Int,y:Int,width:Int,height:Int,r:Byte,g:Byte,b:Byte,a:Byte)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
For Local ty:Int = y To y+height-1
For Local tx:Int = x To x+width-1
If tx > -1 And tx < Self.width And ty > -1 And ty < Self.height Then
Local offset:Int = ty*Self.pitch+tx*Self.bcount
If r + pixPtr[offset] > 255 Then
pixPtr[offset] = 255
Else
pixPtr[offset] = pixPtr[offset]+r
End If
If g + pixPtr[offset+1] > 255 Then
pixPtr[offset+1] = 255
Else
pixPtr[offset+1] = pixPtr[offset+1]+g
End If
If b + pixPtr[offset+2] > 255 Then
pixPtr[offset+2] = 255
Else
pixPtr[offset+2] = pixPtr[offset+2]+b
End If
If Self.bcount = 4 Then
If a + pixPtr[offset+3] > 255 Then
pixPtr[offset+3] = 255
Else
pixPtr[offset+3] = pixPtr[offset+3]+a
End If
End If
End If
Next
Next
End Method
I probably won't be adding anymore tonight, its creeping towards midnight, and I'm a bit tired. But I will try and make more code for it tomorrow.
-
Burning the midnight oil tonight eh Zawran? :)
-
Burning the midnight oil tonight eh Zawran?
Yes I was up a bit late last night, but I got too tired to continue, so I have picked it up again this morning, and I have begun working on adding sprite sheets, which will make it easier to use bitmap fonts later on. I am not completely done yet, so for now its a little bit of a hack, but its working. I will fine tune things later today, probably sometime after lunch :)
(http://zac-interactive.dk/temp/srf2.png)
http://zac-interactive.dk/temp/srf_demo2.zip (http://zac-interactive.dk/temp/srf_demo2.zip)
-
Nice and shiny :)
I like the idea of using interference as a background for the eflection.
-
Very nice library, works fine here
-
Yeah, cool water mirror fx.
-
Thanks guys. I just added a function that will copy an image and rotate it to the angle provided.
Function rotatecopy:zImageBuffer(from:zImageBuffer,angle:Int)
Local tmp:zImagebuffer = zImageBuffer.copy(from)
tmp.clear()
Local r:Byte,g:Byte,b:Byte,a:Byte
Local wh:Int = (tmp.width/2)-1
Local hh:Int = (tmp.height/2)-1
For Local y:Int = 0 Until tmp.height
For Local x:Int = 0 Until tmp.width
Local tx:Int = (x-wh) * Cos(angle) - (y-hh) * Sin(angle) + wh
Local ty:Int = (y-hh) * Cos(angle) + (x-wh) * Sin(angle) + hh
If tx > -1 And tx < tmp.width And ty > -1 And ty < tmp.height Then
from.getpixel(tx,ty,r,g,b,a)
tmp.setpixel(x,y,r,g,b,a)
End If
Next
Next
Return tmp
End Function
I am a little stuck on ideas to expand on it, so I will take suggestions should there be any :)
-
Would there be any worth in adding polygons to it? :)
-
Would there be any worth in adding polygons to it?
Yes that could be an option. I will most likely clean up the code a little bit, optimise the pixel manipulations where possible, complete the bitmap font stuff and then I can add a polygon fill method. I would come in handy should someone want to make some vector effects :) I have all the code for it, it just takes time finding it in my archieves and converting to bmax.
At some point I might decide it to be finished, and perhaps take another look at my opengl stuff, but for now this is keeping me busy and its quite fun actually, so I will stick with it for a while yet. It might serve me well for possible entries to challenges, although the current one with the magnification have not sparked any ideas for me yet.
Which reminds me, that I should do a method that draws an image zoomed :)
-
I have just added four methods to the framework. Until now I have only had the copyrect methods, but when you want to draw the entire image buffer into another buffer, then the framework should already know the starting point and width/height of the image. So I added two regular drawing methods, one that draws the image without masking and one that does. And I complemented these with two methods that draws images scaled.
Method drawBlock(toImg:zImagebuffer,x:Int,y:Int)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
For Local ty:Int = 0 Until Self.height
For Local tx:Int = 0 Until Self.width
If tx+x > -1 And tx+x < toImg.width And ty+y > -1 And ty+y < toImg.height Then
Local offsetS:Int = ty*Self.pitch+tx*Self.bcount
Local offsetD:Int = (ty+y)*toImg.pitch+(tx+x)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
Next
Next
End Method
Method drawMasked(toImg:zImageBuffer,x:Int,y:Int)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
For Local ty:Int = 0 Until Self.height
For Local tx:Int = 0 Until Self.width
If tx+x > -1 And tx+x < toImg.width And ty+y > -1 And ty+y < toImg.height Then
If pixSourcePtr[ty*Self.pitch+tx*Self.bcount] <> Self.mask[0] Or pixSourcePtr[ty*Self.pitch+tx*Self.bcount+1] <> Self.mask[1] Or pixSourcePtr[ty*Self.pitch+tx*Self.bcount+2] <> Self.mask[2] Then
Local offsetS:Int = ty*Self.pitch+tx*Self.bcount
Local offsetD:Int = (ty+y)*toImg.pitch+(tx+x)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
End If
Next
Next
End Method
Method drawScaledBlock(toImg:zImageBuffer,x:Int,y:Int,scaleX:Float,scaleY:Float)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
Local sw:Int = Self.width * scaleX
Local sh:Int = Self.height * scaleY
Local deltaX:Float = Self.width / Float(sw)
Local deltaY:Float = Self.height / Float(sh)
Local cx:Float=0.0,cy:Float=0.0
For Local ty:Int = 0 Until sh
cx = 0.0
For Local tx:Int = 0 Until sw
If tx+x > -1 And tx+x < toImg.width And ty+y > -1 And ty+y < toImg.height Then
Local offsetS:Int = Int(cy)*Self.pitch+Int(cx)*Self.bcount
Local offsetD:Int = (ty+y)*toImg.pitch+(tx+x)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
cx :+ deltaX
Next
cy :+ deltaY
Next
End Method
Method drawScaledMasked(toImg:zImageBuffer,x:Int,y:Int,scaleX:Float,scaleY:Float)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
Local sw:Int = Self.width * scaleX
Local sh:Int = Self.height * scaleY
Local deltaX:Float = Self.width / Float(sw)
Local deltaY:Float = Self.height / Float(sh)
Local cx:Float=0.0,cy:Float=0.0
For Local ty:Int = 0 Until sh
cx = 0.0
For Local tx:Int = 0 Until sw
If tx+x > -1 And tx+x < toImg.width And ty+y > -1 And ty+y < toImg.height Then
If pixSourcePtr[Int(cy)*Self.pitch+Int(cx)*Self.bcount] <> Self.mask[0] Or pixSourcePtr[Int(cy)*Self.pitch+Int(cx)*Self.bcount+1] <> Self.mask[1] Or pixSourcePtr[Int(cy)*Self.pitch+Int(cx)*Self.bcount+2] <> Self.mask[2] Then
Local offsetS:Int = Int(cy)*Self.pitch+Int(cx)*Self.bcount
Local offsetD:Int = (ty+y)*toImg.pitch+(tx+x)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
End If
cx :+ deltaX
Next
cy :+ deltaY
Next
End Method
I have updated the first post with the current framework file.
-
So as it does the scaled images now does that mean that you'll be able to enter the Magnification challenge? :)
-
does that mean that you'll be able to enter the Magnification challenge?
Perhaps, I am not quite sure what I would make though. But I will take another look at the rules and try and think of something.
-
Well there aren't many entries at the moment so there's plenty of ideas left I guess!
-
Well I just did a quick one, I hope people like it. Its not much, but it has something to do with magnification at least :)
-
I finally got some time away from work to tinker with this a bit. This really simplifies coding pixmaps and manipulating them! I'm looking forward to doing some low res work.
-
I have been having a bit of fun with it myself the last couple of days, and I am about halfway done with making an intro. I am looking forward to see, what kind of trickery you can come up with.
-
I finally managed to run a quick test of my own. I must say that it is VERY good for that low res feeling.
I do have two concerns however.
1 The line function sometimes has breaks in the results.
2 Do you think you could impliment a faster DrawScaledBlock function with powers of 2? Ex drawing a 320x240 chunk to a 640x480 buffer. This is useful for making pixel perfect low res work. Testing with FRAPS the big hit seems to be here, not in the drawing functions, which are really quite good.
Here is my small little test...
' test for Zawran's screen buffer library
SuperStrict
Include "Framework.bmx"
'' first draw everything to a small block, then draw the small block to a larger screen block
Global base_buffer:zImageBuffer = zImageBuffer.make(160, 120)
Global scaled_buffer:zImageBuffer = zImageBuffer.make(640, 480)
Graphics(640, 480, 32)
' base rotation angle please
Global rotz:Float = 0
'radius for the star please
Global radius:Float = 64
While Not KeyDown(KEY_ESCAPE)
base_buffer.clear()
scaled_buffer.clear()
If rotz < 360
rotz:+1
Else
rotz:-360
End If
'draw some fun horizontal lines please
For Local y:Float = 0 To 239
'blend 255,0,255 to 255,127,0
Local p:Float = Cos((y * 4) + (rotz * 4))
Local r:Float = 255
Local g:Float = interpolate(0, 127, p)
Local b:Float = interpolate(255, 0, p)
base_buffer.line(0, y, 160, y, r, g, b)
Next
' draw a rotating star please
Local scale:Float = Sin(rotz)
For Local I:Int = 0 To 4
Local r:Byte = Rand(0, 255)
' draw arm one
Local max_radius:Float = radius * scale + 5
Local x1:Float = 80 + Cos(i * 144 + rotz) * max_radius
Local x2:Float = 80 + Cos((i + 1) * 144 + rotz) * max_radius
Local y1:Float = 60 + Sin(i * 144 + rotz) * max_radius * scale
Local y2:Float = 60 + Sin((i + 1) * 144 + rotz) * max_radius * scale
base_buffer.line(x1, y1, x2, y2, 0, 0, 255)
Next
'flip and resize please
base_buffer.drawScaledBlock(scaled_buffer, 0, 0, 4.0, 4.0)
scaled_buffer.blitToScreen()
Flip
Wend
' yes please
Function interpolate:Float(num_1:Float, num_2:Float, p:Float)
Local dif:Float = num_2 - num_1
Return num_2 - (dif * p)
End Function
-
I dont understan d how the line function is doing that, unless when zawran converted it into the framework something got changed slightly, I cant say its ever done that in all the stuff I used it in.
Due to the way pixmaps are stored in ram all the drawing to them is done via cpu into system ram but the final drawing of the pixmap to the screen is done by bmax, now zawran has set the pixmaps it creates to RGBA format so if you dont set bmax to use opengl for drawing it will use directx7 and that means that the pixmap has to be converted to BGRA before it gets draw, which is bloody slow for some reason. I think you will find a speed improvement by doing that as long as you dont get a bottleneck from gldrawpixels which is how bmax handles drawing the pixmaps then.
I will have a look see if I can spot any error in the line draw.
Cheers
Jon
-
I am not sure why but zawran changed a couple things I did in the line code but even if I swap in my original code it misses pixels in the lib but doesnt seem to in my basic lib, which has me confused ???
And WoW that code is very slow to draw. I have seen it throw some pretty complex stuff around the screen but you managed to kill it somehow, is that code overwriting alot of areas for the background multiple times or something, I am sure it could do a plasma faster than that. [EDIT] NOTE TO SELF TURN OF DEBUG :-[
-
Yes it sure does look like there is a problem with the line method. I will try and figure out whats up. The scaling will always be somewhat slow compaired to other pixel manipulations, expecially when doing large scales. But for power 2 scaling I might be able to speed it up somewhat by only doing the pixel scaling horizontally, and then using memcopy to scale it vertically. But if what you want as an end result is a pixmap scaled to the size of the screen, then you are better of letting the hardware do the scaling:
Local tmpImg:TImage = LoadImage(base_buffer.buffer,0) ' load the pixmap into an image and tell it not to smooth when scaling
SetScale(4.0,4.0) ' set the general scaling
DrawImage(tmpImg,0,0) ' draw the scaled image to the screen
the code above would replace the following lines from your code:
base_buffer.drawScaledBlock(scaled_buffer, 0, 0, 4.0, 4.0)
scaled_buffer.blitToScreen()
I will see if I cannot make the scaling methods faster though, but I cannot say when it will be done.
[edit] With the help of Jon, we figured out what might be wrong with the line method. Please replace it with the following and see if that doesn't work better:
Method line(x1:Float,y1:Float,x2:Float,y2:Float,r:Byte,g:Byte,b:Byte)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local steps:Float,xI:Float,i:Int
x2 :- x1
y2 :- y1
If Abs(x2) > Abs(y2) Then
steps = Abs(x2)
Else
steps = Abs(y2)
End If
xI = Float(x2 / steps)
y2 = Float(y2 / steps)
For x2 = 0 To steps
If Int(y1) > -1 And Int(y1) < Self.height Then
If Int(x1) > -1 And Int(x1) < Self.width Then
Local offset:Int = Int(y1)*Self.pitch+Int(x1)*Self.bcount
pixPtr[offset] = r
pixPtr[offset+1] = g
pixPtr[offset+2] = b
If Self.bcount = 4 Then pixPtr[offset+3] = 255
End If
End If
x1 :+ xI
y1 :+ y2
Next
End Method
-
The lines are looking good now. :)
MUCH faster method of drawing/scaling too. I think I can get a good game out of this with the current speed.
-
I think I can get a good game out of this with the current speed
Glad to hear it, and looking forward to trying the game when done :)
-
I decided to check this forum out and saw this. Great work Zawran :clap:. I took the liberty of adding a couple of functions to your library: circle and elipse(not really an elipse its really an oval). I hope you don't mind. It draws them filled and hollow. Also, I added alpha to the line function to keep consistency with the pixel and my functions. I hope this is usefull if not just ignore it.
-
The following functions might be useful...
-Optimized line drawing (Might be useful for gradient filling)
line_horizontal ( optimized horizontal line)
line_verticle (optimized verticle line)
-Pattern filiing/tiling (This would be cool for drawing interesting filled shapes also pseudo screen door transparency)
pattern_draw_rectangle(x,y,width,height, pattern:zImageBuffer) ...ect
I've been playing with this and it can easly render 100 of my little 16x16 sprites in my classic resolution of 360x240! I'm working on a dynamic flower/growth shooting arcade style game now. Basically flowers grow and tangle at the top of the screen and you must destroy the ever budding heads from their stems. Using the 16 color palette from windows.
Works wonders and forces that classic resolution. This is starting to look very retro, in a completely valid sense.
-
@JumpMan, This is just the reason I keep this framework open, so that everyone can add to it, thanks for taking the time to work on this.
@Pixel_Outlaw, Its a good idea about the pattern filling, and I think I have piece of code I can work into the framework that will do just that. I also thought that a gradient line would be useful, so I will look at adding that as well. The optional lines, horizontal/vertical I think can be done fairly easily, so I will try and get those done as well.
-
I'll mention the framework and you for sure when I finish. :goodpost:
-
I have added two new drawing methods. drawTile and drawScaledToSize, both available as block and masked versions. The first one drawTile will draw an image buffer into another image buffer tiled between x,y,width,height and at an offset so scrolling is possible. The second one drawScaledToSize is an alternative to the drawScaled and the difference is that drawScaled uses floats to decide how the image is scaled, where the drawScaledToSize will draw an image scaled between x,y,width,height. I have also added the circle and oval that JumpMan wrote. A couple of methods has changed names to make things more uniform, but that should be easy to find out.
' The following code is public domain, use it as you like, in parts or as it is. Feel free to modify as you see fit.
' No warranties are provided for the code.
' All code written by Johnny Bremer (aka. Zawran), unless otherwise mentioned
Type zImageBuffer
Global zImageBufferList:TList = CreateList()
Field link:TLink ' link pointer
Field buffer:TPixmap ' buffer where pixmap is stored
Field width:Int ' width of image
Field height:Int ' height of image
Field pitch:Int ' pitch of image
Field bcount:Int ' byte count of each pixel 3/4 depending on alpha
Field mask:Int[3] ' masking color array
' create a new image buffer
Function make:zImageBuffer(width:Int,height:Int,maskrgb:Int=$000000,fillrgb:Int=$FF000000)
Local tmp:zImageBuffer = New zImageBuffer
tmp.buffer = CreatePixmap(width,height,PF_RGBA8888)
tmp.width = tmp.buffer.width
tmp.height = tmp.buffer.height
tmp.pitch = PixmapPitch(tmp.buffer)
tmp.bcount = tmp.pitch / tmp.width
tmp.mask[0] = (maskrgb Shr 16) & 255
tmp.mask[1] = (maskrgb Shr 8) & 255
tmp.mask[2] = maskrgb & 255
ClearPixels(tmp.buffer,fillrgb)
tmp.link = ListAddLast(zImageBufferList,tmp)
Return tmp
End Function
' load an image and create an image buffer from that
Function loadFile:zImageBuffer(path:String,maskrgb:Int=$000000)
Local tmp:zImageBuffer = New zImageBuffer
tmp.buffer = LoadPixmap(path)
tmp.width = tmp.buffer.width
tmp.height = tmp.buffer.height
tmp.pitch = PixmapPitch(tmp.buffer)
tmp.bcount = tmp.pitch / tmp.width
tmp.mask[0] = (maskrgb Shr 16) & 255
tmp.mask[1] = (maskrgb Shr 8) & 255
tmp.mask[2] = maskrgb & 255
tmp.link = ListAddLast(zImageBufferList,tmp)
Return tmp
End Function
' copy an already existing image buffer
Function copy:zImageBuffer(from:zImageBuffer)
Local tmp:zImageBuffer = New zImageBuffer
tmp.buffer = CopyPixmap(from.buffer)
tmp.width = tmp.buffer.width
tmp.height = tmp.buffer.height
tmp.pitch = PixmapPitch(tmp.buffer)
tmp.bcount = tmp.pitch / tmp.width
tmp.mask = from.mask
tmp.link = ListAddLast(zImageBufferList,tmp)
Return tmp
End Function
' make a copy of the image buffer which is rotated at an angle
Function rotateCopy:zImageBuffer(from:zImageBuffer,angle:Int)
Local tmp:zImagebuffer = zImageBuffer.copy(from)
tmp.clear()
Local r:Byte,g:Byte,b:Byte,a:Byte
Local wh:Int = (tmp.width/2)-1
Local hh:Int = (tmp.height/2)-1
For Local y:Int = 0 Until tmp.height
For Local x:Int = 0 Until tmp.width
Local tx:Int = (x-wh) * Cos(angle) - (y-hh) * Sin(angle) + wh
Local ty:Int = (y-hh) * Cos(angle) + (x-wh) * Sin(angle) + hh
If tx > -1 And tx < tmp.width And ty > -1 And ty < tmp.height Then
from.getpixel(tx,ty,r,g,b,a)
tmp.setpixel(x,y,r,g,b,a)
End If
Next
Next
Return tmp
End Function
' clear an image buffer with a set RGBA color
Method clear(rgba:Int=$FF000000)
ClearPixels(Self.buffer,rgba)
End Method
' save an image buffer to file
Method saveImage(path:String="image.png",comp:Int=5)
SavePixmapPNG(Self.buffer,path,comp)
End Method
' draw an image buffer to screen
Method drawToScreen(x:Int=0,y:Int=0)
DrawPixmap(Self.buffer,x,y)
End Method
' remove an image buffer
Method remove()
Self.buffer = Null
RemoveLink(Self.link)
End Method
' set the masking color of the image buffer
Method setMask(maskrgb:Int=$000000)
Self.mask[0] = (maskrgb Shr 16) & 255
Self.mask[1] = (maskrgb Shr 8) & 255
Self.mask[2] = maskrgb & 255
End Method
' set the color of a pixel in the image buffer
Method setPixel(x:Int,y:Int,r:Byte,g:Byte,b:Byte,a:Byte=255)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
If x > -1 And x < Self.width And y > -1 And y < Self.height Then
Local offset:Int = y*Self.pitch+x*Self.bcount
pixPtr[offset] = r
pixPtr[offset+1] = g
pixPtr[offset+2] = b
If Self.bcount = 4 Then pixPtr[offset+3] = a
End If
End Method
' get the color of a pixel in the image buffer
Method getPixel(x:Int,y:Int,r:Byte Var,g:Byte Var,b:Byte Var,a:Byte Var)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
If x > -1 And x < Self.width And y > -1 And y < Self.height Then
Local offset:Int = y*Self.pitch+x*Self.bcount
r = pixPtr[offset]
g = pixPtr[offset+1]
b = pixPtr[offset+2]
If Self.bcount = 4 Then a = pixPtr[offset+3]
End If
End Method
' draw a line into the image buffer using a specific color
Method line(x1:Float,y1:Float,x2:Float,y2:Float,r:Byte,g:Byte,b:Byte,a:Byte=255)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local steps:Float,xI:Float,i:Int
x2 :- x1
y2 :- y1
If Abs(x2) > Abs(y2) Then
steps = Abs(x2)
Else
steps = Abs(y2)
End If
xI = Float(x2 / steps)
y2 = Float(y2 / steps)
For x2 = 0 To steps
If Int(y1) > -1 And Int(y1) < Self.height Then
If Int(x1) > -1 And Int(x1) < Self.width Then
Local offset:Int = Int(y1)*Self.pitch+Int(x1)*Self.bcount
pixPtr[offset] = r
pixPtr[offset+1] = g
pixPtr[offset+2] = b
If Self.bcount = 4 Then pixPtr[offset+3] = a
End If
End If
x1 :+ xI
y1 :+ y2
Next
End Method
' draw a rect into the image buffer using a specific color
Method rect(x1:Int,y1:Int,x2:Int,y2:Int,r:Byte,g:Byte,b:Byte)
For Local i:Int = y1 To y2
Self.line(x1,i,x2,i,r,g,b)
Next
End Method
' draw an oval into the image buffer using a specific color (written by Jumpman from the DBF-Interactive forum)
Method oval(cx:Int,cy:Int,width:Int,height:Int,r:Byte,g:Byte,b:Byte,a:Byte=255,filled:Int = True)
Local Rx:Int,Ry:Int
Local p:Int,px:Int,py:Int,x:Int,y:Int
Local Rx2:Int,Ry2:Int,twoRx2:Int,twoRy2:Int
Local pFloat:Float
Rx=Abs(width Shr 1)
Ry=Abs(height Shr 1)
cx :+rx
cy :+ry
Rx2=Rx*Rx
Ry2=Ry*Ry
twoRx2=Rx2 Shl 1
twoRy2=Ry2 Shl 1
'Region 1
x=0
y=Ry
If filled Then
line cx+x,cy+y,cx-x,cy+y,r,g,b,a
line cx+x,cy-y,cx-x,cy-y,r,g,b,a
Else
setpixel cx+x,cy+y,r,g,b,a
setpixel cx-x,cy+y,r,g,b,a
setpixel cx+x,cy-y,r,g,b,a
setpixel cx-x,cy-y,r,g,b,a
EndIf
pFloat=(Ry2-(Rx2*Ry))+(0.25*Rx2)
p=Int(pFloat)
If pFloat Mod 1.0>=0.5 Then p:+1
px=0
py=twoRx2*y
While px<py
x:+1
px:+twoRy2
If p>=0
y:-1
py:-twoRx2
EndIf
If p<0 Then p:+Ry2+px Else p:+Ry2+px-py
If filled Then
Self.line cx+x,cy+y,cx-x,cy+y,r,g,b,a
Self.line cx+x,cy-y,cx-x,cy-y,r,g,b,a
Else
Self.setPixel cx+x,cy+y,r,g,b,a
Self.setPixel cx-x,cy+y,r,g,b,a
Self.setPixel cx+x,cy-y,r,g,b,a
Self.setPixel cx-x,cy-y,r,g,b,a
EndIf
Wend
'Region 2
pFloat=(Ry2*(x+0.5)*(x+0.5))+(Rx2*(y-1.0)*(y-1.0))-(Rx2*(Float(Ry2)))
p=Int(pFloat)
If pFloat Mod 1.0>=0.5 Then p:+1
While y>0
y:-1
py:-twoRx2
If p<=0
x:+1
px:+twoRy2
EndIf
If p>0 Then p:+Rx2-py Else p:+Rx2-py+px
If filled Then
Self.line cx+x,cy+y,cx-x,cy+y,r,g,b,a
Self.line cx+x,cy-y,cx-x,cy-y,r,g,b,a
Else
Self.setPixel cx+x,cy+y,r,g,b,a
Self.setPixel cx-x,cy+y,r,g,b,a
Self.setPixel cx+x,cy-y,r,g,b,a
Self.setPixel cx-x,cy-y,r,g,b,a
EndIf
Wend
End Method
' draw a circle into the image buffer with a specific color (written by Jumpman from the DBF-Interactive forum)
Method circle(cx:Int,cy:Int,radius:Int,r:Byte,g:Byte,b:Byte,a:Byte=255,filled:Int = True)
If (cx-radius) > Self.width Return
If (cy-radius) > Self.height Return
If (cx+radius) < 0 Then Return
If (cy+radius) < 0 Then Return
Local x:Int = 0
Local d:Int = (2*Radius)
Local y:Int=Radius
While x<y
If d < 0 Then
d = d + (4 * x) + 6
Else
d = d + 4 * (x - y) + 10
y = y - 1
End If
If filled Then
Self.line cx + X, cy + Y,cx + X, cy - Y,r,g,b,a
Self.line cx - X, cy + Y,cx - X, cy - Y,r,g,b,a
Self.line cx + Y, cy + X,cx + Y, cy - X,r,g,b,a
Self.line cx - Y, cy + X,cx - Y, cy - X,r,g,b,a
Else
Self.setPixel cx + X, cy + Y,r,g,b,a
Self.setPixel cx + X, cy - Y,r,g,b,a
Self.setPixel cx - X, cy + Y,r,g,b,a
Self.setPixel cx - X, cy - Y,r,g,b,a
Self.setPixel cx + Y, cy + X,r,g,b,a
Self.setPixel cx + Y, cy - X,r,g,b,a
Self.setPixel cx - Y, cy + X,r,g,b,a
Self.setPixel cx - Y, cy - X,r,g,b,a
EndIf
x=x+1
Wend
End Method
' draw an entire image buffer into another image buffer tiled at an offset and within a set rectangle ignoring masking
Method drawTileBlock(toImg:zImageBuffer,x:Int,y:Int,w:Int,h:Int,offsetX:Int=0,offsetY:Int=0)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
If offsetX < 0 Or offsetX => Self.width Then offsetX = 0 ' if offset is outside image, then use default
If offsetY < 0 Or offsetY => Self.height Then offsetY = 0 ' if offset is outside image, then use default
Local dx:Int = offsetX
Local dy:Int = offsetY
For Local ty:Int = 0 Until h
dx = offsetX
For Local tx:Int = 0 Until w
If tx+x > -1 And tx+x < toImg.width And ty+y > -1 And ty+y < toImg.height Then
Local offsetS:Int = (dy*Self.pitch) + (dx*Self.bcount)
Local offsetD:Int = (ty+y)*toImg.pitch+(tx+x)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
dx :+ 1
If dx = Self.width Then dx = 0
Next
dy :+ 1
If dy = Self.height Then dy = 0
Next
End Method
' draw an entire image buffer into another image buffer tiled at an offset and within a set rectangle using masking
Method drawTileMasked(toImg:zImageBuffer,x:Int,y:Int,w:Int,h:Int,offsetX:Int=0,offsetY:Int=0)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
If offsetX < 0 Or offsetX => Self.width Then offsetX = 0 ' if offset is outside image, then use default
If offsetY < 0 Or offsetY => Self.height Then offsetY = 0 ' if offset is outside image, then use default
Local dx:Int = offsetX
Local dy:Int = offsetY
For Local ty:Int = 0 Until h
dx = offsetX
For Local tx:Int = 0 Until w
If tx+x > -1 And tx+x < toImg.width And ty+y > -1 And ty+y < toImg.height Then
If pixSourcePtr[dy*Self.pitch+dx*Self.bcount] <> Self.mask[0] Or pixSourcePtr[dy*Self.pitch+dx*Self.bcount+1] <> Self.mask[1] Or pixSourcePtr[dy*Self.pitch+dx*Self.bcount+2] <> Self.mask[2] Then
Local offsetS:Int = (dy*Self.pitch) + (dx*Self.bcount)
Local offsetD:Int = (ty+y)*toImg.pitch+(tx+x)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
End If
dx :+ 1
If dx = Self.width Then dx = 0
Next
dy :+ 1
If dy = Self.height Then dy = 0
Next
End Method
' draw the entire image buffer into another image buffer ignoring masking
Method drawBlock(toImg:zImagebuffer,x:Int,y:Int)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
For Local ty:Int = 0 Until Self.height
For Local tx:Int = 0 Until Self.width
If tx+x > -1 And tx+x < toImg.width And ty+y > -1 And ty+y < toImg.height Then
Local offsetS:Int = ty*Self.pitch+tx*Self.bcount
Local offsetD:Int = (ty+y)*toImg.pitch+(tx+x)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
Next
Next
End Method
' draw the entire image buffer into another image buffer using the masking color
Method drawMasked(toImg:zImageBuffer,x:Int,y:Int)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
For Local ty:Int = 0 Until Self.height
For Local tx:Int = 0 Until Self.width
If tx+x > -1 And tx+x < toImg.width And ty+y > -1 And ty+y < toImg.height Then
If pixSourcePtr[ty*Self.pitch+tx*Self.bcount] <> Self.mask[0] Or pixSourcePtr[ty*Self.pitch+tx*Self.bcount+1] <> Self.mask[1] Or pixSourcePtr[ty*Self.pitch+tx*Self.bcount+2] <> Self.mask[2] Then
Local offsetS:Int = ty*Self.pitch+tx*Self.bcount
Local offsetD:Int = (ty+y)*toImg.pitch+(tx+x)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
End If
Next
Next
End Method
' draw the entire image buffer into another image buffer with additive effect
Method drawAdditiveMasked(toImg:zImageBuffer,x:Int,y:Int)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
Local r:Byte,g:Byte,b:Byte,a:Byte
For Local ty:Int = 0 Until Self.height
For Local tx:Int = 0 Until Self.width
If tx+x > -1 And tx+x < toImg.width And ty+y > -1 And ty+y < toImg.height Then
If pixSourcePtr[ty*Self.pitch+tx*Self.bcount] <> Self.mask[0] Or pixSourcePtr[ty*Self.pitch+tx*Self.bcount+1] <> Self.mask[1] Or pixSourcePtr[ty*Self.pitch+tx*Self.bcount+2] <> Self.mask[2] Then
Local offsetS:Int = ty*Self.pitch+tx*Self.bcount
Local offsetD:Int = (ty+y)*toImg.pitch+(tx+x)*toImg.bcount
r = pixSourcePtr[offsetS]
g = pixSourcePtr[offsetS+1]
b = pixSourcePtr[offsetS+2]
If Self.bcount = 4 Then a = pixSourcePtr[offsetS+3]
If r + pixDestPtr[offsetD] > 255 Then
r = 255
Else
r :+ pixDestPtr[offsetD]
End If
If g + pixDestPtr[offsetD+1] > 255 Then
g = 255
Else
g :+ pixDestPtr[offsetD+1]
End If
If b + pixDestPtr[offsetD+2] > 255 Then
b = 255
Else
b :+ pixDestPtr[offsetD+2]
End If
If Self.bcount = 4 Then
If a + pixDestPtr[offsetD+3] > 255 Then
a = 255
Else
a :+ pixDestPtr[offsetD+3]
End If
Else
a = 255
End If
pixDestPtr[offsetD] = r
pixDestPtr[offsetD+1] = g
pixDestPtr[offsetD+2] = b
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = a
End If
End If
Next
Next
End Method
' draw the entire image buffer into another image buffer scaled but ignoring masking
Method drawScaledBlock(toImg:zImageBuffer,x:Int,y:Int,scaleX:Float,scaleY:Float)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
Local sw:Int = Self.width * scaleX
Local sh:Int = Self.height * scaleY
Local deltaX:Float = Self.width / Float(sw)
Local deltaY:Float = Self.height / Float(sh)
Local cx:Float=0.0,cy:Float=0.0
For Local ty:Int = 0 Until sh
cx = 0.0
For Local tx:Int = 0 Until sw
If tx+x > -1 And tx+x < toImg.width And ty+y > -1 And ty+y < toImg.height Then
Local offsetS:Int = Int(cy)*Self.pitch+Int(cx)*Self.bcount
Local offsetD:Int = (ty+y)*toImg.pitch+(tx+x)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
cx :+ deltaX
Next
cy :+ deltaY
Next
End Method
' draw the entire image buffer into another image buffer scaled using masking
Method drawScaledMasked(toImg:zImageBuffer,x:Int,y:Int,scaleX:Float,scaleY:Float)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
Local sw:Int = Self.width * scaleX
Local sh:Int = Self.height * scaleY
Local deltaX:Float = Self.width / Float(sw)
Local deltaY:Float = Self.height / Float(sh)
Local cx:Float=0.0,cy:Float=0.0
For Local ty:Int = 0 Until sh
cx = 0.0
For Local tx:Int = 0 Until sw
If tx+x > -1 And tx+x < toImg.width And ty+y > -1 And ty+y < toImg.height Then
If pixSourcePtr[Int(cy)*Self.pitch+Int(cx)*Self.bcount] <> Self.mask[0] Or pixSourcePtr[Int(cy)*Self.pitch+Int(cx)*Self.bcount+1] <> Self.mask[1] Or pixSourcePtr[Int(cy)*Self.pitch+Int(cx)*Self.bcount+2] <> Self.mask[2] Then
Local offsetS:Int = Int(cy)*Self.pitch+Int(cx)*Self.bcount
Local offsetD:Int = (ty+y)*toImg.pitch+(tx+x)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
End If
cx :+ deltaX
Next
cy :+ deltaY
Next
End Method
' draw the entire image buffer into another image buffer scaled to size based on x/y coordinate set but ignoring masking
Method drawScaledToSizeBlock(toImg:zImageBuffer,x1:Int,y1:Int,x2:Int,y2:Int)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
Local sw:Int = x2-x1
Local sh:Int = y2-y1+1
Local deltaX:Float = Self.width / Float(sw)
Local deltaY:Float = Self.height / Float(sh)
Local cx:Float=0.0,cy:Float=0.0
For Local ty:Int = 0 Until sh
cx = 0.0
For Local tx:Int = 0 Until sw
If tx+x1 > -1 And tx+x1 < toImg.width And ty+y1 > -1 And ty+y1 < toImg.height Then
Local offsetS:Int = Int(cy)*Self.pitch+Int(cx)*Self.bcount
Local offsetD:Int = (ty+y1)*toImg.pitch+(tx+x1)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
cx :+ deltaX
Next
cy :+ deltaY
Next
End Method
' draw the entire image buffer into another image buffer scaled to size based on x/y coordinate set using masking
Method drawScaledToSizeMasked(toImg:zImageBuffer,x1:Int,y1:Int,x2:Int,y2:Int)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixDestPtr:Byte Ptr = PixmapPixelPtr(toImg.buffer)
Local sw:Int = x2-x1
Local sh:Int = y2-y1+1
Local deltaX:Float = Self.width / Float(sw)
Local deltaY:Float = Self.height / Float(sh)
Local cx:Float=0.0,cy:Float=0.0
For Local ty:Int = 0 Until sh
cx = 0.0
For Local tx:Int = 0 Until sw
If tx+x1 > -1 And tx+x1 < toImg.width And ty+y1 > -1 And ty+y1 < toImg.height Then
If pixSourcePtr[Int(cy)*Self.pitch+Int(cx)*Self.bcount] <> Self.mask[0] Or pixSourcePtr[Int(cy)*Self.pitch+Int(cx)*Self.bcount+1] <> Self.mask[1] Or pixSourcePtr[Int(cy)*Self.pitch+Int(cx)*Self.bcount+2] <> Self.mask[2] Then
Local offsetS:Int = Int(cy)*Self.pitch+Int(cx)*Self.bcount
Local offsetD:Int = (ty+y1)*toImg.pitch+(tx+x1)*toImg.bcount
pixDestPtr[offsetD] = pixSourcePtr[offsetS]
pixDestPtr[offsetD+1] = pixSourcePtr[offsetS+1]
pixDestPtr[offsetD+2] = pixSourcePtr[offsetS+2]
If Self.bcount = 4 And toImg.bcount = 4 Then pixDestPtr[offsetD+3] = pixSourcePtr[offsetS+3]
End If
End If
cx :+ deltaX
Next
cy :+ deltaY
Next
End Method
' copy a rectangle from one image buffer to another as a block ignoring masking
Method copyRectBlock(from:zImageBuffer,x:Int,y:Int,w:Int,h:Int,tx:Int,ty:Int)
Local pixTargetPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(from.buffer)
For Local yy:Int = 0 Until h
For Local xx:Int = 0 Until w
' is target pixel within the target buffer
If tx+xx > -1 And ty+yy > -1 And tx+xx < Self.width And ty+yy < Self.height Then
' is source pixel within the source buffer
If x+xx > -1 And y+yy > -1 And x+xx < from.width And y+yy < from.height Then
Local offsetT:Int = (ty+yy)*Self.pitch+(tx+xx)*Self.bcount
Local offsetS:Int = (y+yy)*from.pitch+(x+xx)*from.bcount
pixTargetPtr[offsetT] = pixSourcePtr[offsetS]
pixTargetPtr[offsetT+1] = pixSourcePtr[offsetS+1]
pixTargetPtr[offsetT+2] = pixSourcePtr[offsetS+2]
' check to see if both images include alpha value that needs to be copied as well
If Self.bcount = 4 And from.bcount = 4 Then pixTargetPtr[offsetT+3] = pixSourcePtr[offsetS+3]
End If
End If
Next
Next
End Method
' copy a rectangle from one image buffer to another using masking
Method copyRect(from:zImageBuffer,x:Int,y:Int,w:Int,h:Int,tx:Int,ty:Int)
Local pixTargetPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local pixSourcePtr:Byte Ptr = PixmapPixelPtr(from.buffer)
For Local yy:Int = 0 Until h
For Local xx:Int = 0 Until w
' is target pixel within the target buffer
If tx+xx > -1 And ty+yy > -1 And tx+xx < Self.width And ty+yy < Self.height Then
' is source pixel within the source buffer
If x+xx > -1 And y+yy > -1 And x+xx < from.width And y+yy < from.height Then
If pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount] <> from.mask[0] Or pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+1] <> from.mask[1] Or pixSourcePtr[(y+yy)*from.pitch+(x+xx)*from.bcount+2] <> from.mask[2] Then
Local offsetT:Int = (ty+yy)*Self.pitch+(tx+xx)*Self.bcount
Local offsetS:Int = (y+yy)*from.pitch+(x+xx)*from.bcount
pixTargetPtr[offsetT] = pixSourcePtr[offsetS]
pixTargetPtr[offsetT+1] = pixSourcePtr[offsetS+1]
pixTargetPtr[offsetT+2] = pixSourcePtr[offsetS+2]
' check to see if both images include alpha value that needs to be copied as well
If Self.bcount = 4 And from.bcount = 4 Then pixTargetPtr[offsetT+3] = pixSourcePtr[offsetS+3]
End If
End If
End If
Next
Next
End Method
' add noise effect to a rectangular area inside the image buffer
Method noise(x:Int,y:Int,width:Int,height:Int,noiselevel:Int)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
For Local ty:Int = y To y+height-1
For Local tx:Int = x To x+width-1
If tx > -1 And tx < Self.width And ty > -1 And ty < Self.height Then
Local offset:Int = ty*Self.pitch+tx*Self.bcount
Local n:Int = Rnd(-noiselevel,noiselevel)
If pixPtr[offset] + n < 0 Then
pixPtr[offset] = 0
Else
If pixPtr[offset] + n > 255 Then
pixPtr[offset] = 255
Else
pixPtr[offset] :+ n
End If
End If
If pixPtr[offset+1] + n < 0 Then
pixPtr[offset+1] = 0
Else
If pixPtr[offset+1] + n > 255 Then
pixPtr[offset+1] = 255
Else
pixPtr[offset+1] :+ n
End If
End If
If pixPtr[offset+2] + n < 0 Then
pixPtr[offset+2] = 0
Else
If pixPtr[offset+2] + n > 255 Then
pixPtr[offset+2] = 255
Else
pixPtr[offset+2] :+ n
End If
End If
End If
Next
Next
End Method
' subtract color value from the pixels of a rectangular area inside the image buffer
Method colorSubtract(x:Int,y:Int,width:Int,height:Int,r:Byte,g:Byte,b:Byte,a:Byte)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
For Local ty:Int = y To y+height-1
For Local tx:Int = x To x+width-1
If tx > -1 And tx < Self.width And ty > -1 And ty < Self.height Then
Local offset:Int = ty*Self.pitch+tx*Self.bcount
If pixPtr[offset] - r < 0 Then
pixPtr[offset] = 0
Else
pixPtr[offset] = pixPtr[offset]-r
End If
If pixPtr[offset+1] - g < 0 Then
pixPtr[offset+1] = 0
Else
pixPtr[offset+1] = pixPtr[offset+1]-g
End If
If pixPtr[offset+2] - b < 0 Then
pixPtr[offset+2] = 0
Else
pixPtr[offset+2] = pixPtr[offset+2]-b
End If
If Self.bcount = 4 Then
If pixPtr[offset+3] - a < 0 Then
pixPtr[offset+3] = 0
Else
pixPtr[offset+3] = pixPtr[offset+3]-a
End If
End If
End If
Next
Next
End Method
' add color value to the pixels of a rectangular area inside the image buffer
Method colorAddition(x:Int,y:Int,width:Int,height:Int,r:Byte,g:Byte,b:Byte,a:Byte)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
For Local ty:Int = y To y+height-1
For Local tx:Int = x To x+width-1
If tx > -1 And tx < Self.width And ty > -1 And ty < Self.height Then
Local offset:Int = ty*Self.pitch+tx*Self.bcount
If r + pixPtr[offset] > 255 Then
pixPtr[offset] = 255
Else
pixPtr[offset] = pixPtr[offset]+r
End If
If g + pixPtr[offset+1] > 255 Then
pixPtr[offset+1] = 255
Else
pixPtr[offset+1] = pixPtr[offset+1]+g
End If
If b + pixPtr[offset+2] > 255 Then
pixPtr[offset+2] = 255
Else
pixPtr[offset+2] = pixPtr[offset+2]+b
End If
If Self.bcount = 4 Then
If a + pixPtr[offset+3] > 255 Then
pixPtr[offset+3] = 255
Else
pixPtr[offset+3] = pixPtr[offset+3]+a
End If
End If
End If
Next
Next
End Method
' mirror the entire image buffer in various directions
Method mirror(direction:Int)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Local tmp:Byte,x:Int,y:Int
Select direction
Case 0 ' horizontal
For x = 0 To (Self.width/2)-1
For y = 0 Until Self.height
tmp = pixPtr[y*Self.pitch+x*Self.bcount]
pixPtr[y*Self.pitch+x*Self.bcount] = pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount]
pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount] = tmp
tmp = pixPtr[y*Self.pitch+x*Self.bcount+1]
pixPtr[y*Self.pitch+x*Self.bcount+1] = pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+1]
pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+1] = tmp
tmp = pixPtr[y*Self.pitch+x*Self.bcount+2]
pixPtr[y*Self.pitch+x*Self.bcount+2] = pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+2]
pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+2] = tmp
If Self.bcount = 4 Then
tmp = pixPtr[y*Self.pitch+x*Self.bcount+3]
pixPtr[y*Self.pitch+x*Self.bcount+3] = pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+3]
pixPtr[y*Self.pitch+(Self.width-x-1)*Self.bcount+3] = tmp
End If
Next
Next
Case 1 ' vertical
For y = 0 To (Self.height/2)-1
For x = 0 Until Self.width
tmp = pixPtr[y*Self.pitch+x*Self.bcount]
pixPtr[y*Self.pitch+x*Self.bcount] = pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount]
pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount] = tmp
tmp = pixPtr[y*Self.pitch+x*Self.bcount+1]
pixPtr[y*Self.pitch+x*Self.bcount+1] = pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+1]
pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+1] = tmp
tmp = pixPtr[y*Self.pitch+x*Self.bcount+2]
pixPtr[y*Self.pitch+x*Self.bcount+2] = pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+2]
pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+2] = tmp
If Self.bcount = 4 Then
tmp = pixPtr[y*Self.pitch+x*Self.bcount+3]
pixPtr[y*Self.pitch+x*Self.bcount+3] = pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+3]
pixPtr[(Self.height-y-1)*Self.pitch+x*Self.bcount+3] = tmp
End If
Next
Next
Case 2 ' horizontal + vertical
Self.mirror(0)
Self.mirror(1)
End Select
End Method
' shift the entire content of an image buffer in one of four directions: 0=left, 1=right, 2=up, 3=down
Method shift(direction:Int,pixels:Int)
Local pixPtr:Byte Ptr = PixmapPixelPtr(Self.buffer)
Select direction
Case 0 ' left
Local moveSize:Int = (Self.width-pixels)*Self.bcount
Local offset:Int = pixels*Self.bcount
For Local y:Int = 0 Until Self.height
MemCopy(pixPtr,pixPtr+offset,moveSize)
pixPtr :+ pitch
Next
Case 1 ' right
Local pixSize:Int = pitch/Self.width
Local moveSize:Int = (Self.width-pixels)*Self.bcount
Local offset:Int = pixels*Self.bcount
For Local y:Int = 0 Until Self.height
MemCopy(pixPtr+offset,pixPtr,moveSize)
pixPtr :+ pitch
Next
Case 2 ' up
Local pixSize:Int = pitch/Self.width
For Local y:Int = 0 To Self.height-1-pixels
MemCopy(pixPtr,pixPtr+pitch*pixels,pitch)
pixPtr :+ pitch
Next
Case 3 ' down
Local pixSize:Int = pitch/Self.width
pixPtr :+ ((Self.width*Self.bcount)*(Self.height-1))
For Local y:Int = 0 To Self.height-1-pixels
MemCopy(pixPtr,pixPtr-(pitch*pixels),pitch)
pixPtr :- pitch
Next
End Select
End Method
End Type
Type zSpriteSheet
Global zSpriteSheetList:TList = CreateList()
Field link:TLink ' link pointer
Field image:zImageBuffer ' image buffer where sprites pixmap is stored
Field spriteCount:Int ' number of sprites on sheet
Field spriteX:Int[] ' x position of top left pixel in sprite
Field spriteY:Int[] ' y position of top left pixel in sprite
Field spriteW:Int[] ' width of sprite
Field spriteH:Int[] ' height of sprite
' load spritesheet
Function loadsheet:zSpriteSheet(path:String,maskrgb:Int=$000000)
Local tmp:zSpriteSheet = New zSpriteSheet
tmp.image = zImageBuffer.loadfile(path,maskrgb)
Local f:String = Left(path,Len(path)-3)+"zss"
Local filein:TStream = OpenFile(f)
tmp.spriteCount = ReadInt(filein)
tmp.spriteX = New Int[tmp.spriteCount]
tmp.spriteY = New Int[tmp.spriteCount]
tmp.spriteW = New Int[tmp.spriteCount]
tmp.spriteH = New Int[tmp.spriteCount]
For Local i:Int = 0 Until tmp.spriteCount
tmp.spriteX[i] = ReadInt(filein)
tmp.spriteY[i] = ReadInt(filein)
tmp.spriteW[i] = ReadInt(filein)
tmp.spriteH[i] = ReadInt(filein)
Next
tmp.link = ListAddLast(zSpriteSheetList,tmp)
Return tmp
End Function
' create a new image buffer
Function loadfontsheet:zSpriteSheet(path:String,maskrgb:Int=$000000)
Local tmp:zSpriteSheet = New zSpriteSheet
tmp.image = zImageBuffer.loadfile(path,maskrgb)
Local f:String = Left(path,Len(path)-3)+"fxb"
Local filein:TStream = ReadStream(f)
Local t:Int = ReadInt(filein) ' font height not used
tmp.spriteCount = ReadInt(filein)
tmp.spriteX = New Int[tmp.spriteCount]
tmp.spriteY = New Int[tmp.spriteCount]
tmp.spriteW = New Int[tmp.spriteCount]
tmp.spriteH = New Int[tmp.spriteCount]
For Local i:Int = 0 Until tmp.spriteCount
t = ReadInt(filein) ' ascii code not used
tmp.spriteX[i] = ReadInt(filein)
tmp.spriteY[i] = ReadInt(filein)
tmp.spriteW[i] = ReadInt(filein)
tmp.spriteH[i] = ReadInt(filein)
Next
tmp.link = ListAddLast(zSpriteSheetList,tmp)
Return tmp
End Function
Method draw(toImg:zImageBuffer,x:Int,y:Int,sprite:Int)
toImg.copyrect(Self.image,Self.spriteX[sprite],Self.spriteY[sprite],Self.spriteW[sprite],Self.spriteH[sprite],x,y)
End Method
' remove sprite sheet
Method remove()
Self.image.remove() ' remove sprite image
RemoveLink(Self.link) ' remove sprite sheet
End Method
End Type
I will still be adding the optional line methods and I will be testing to see if using memcopy can speed up the drawing of the methods that does not use masking.
-
Im wondering if your line code has a conversion bug, from converting an int to a float, in that it doesnt round properlly. it's a common occurence, FreeBASIC has it too.
I havent looked at your code, this could be a possiblity.
-
The line code has been fixed, its mentioned about 8 posts up where Pixel_Outlaw confirms it to be working.
-
Im wondering if your line code has a conversion bug, from converting an int to a float, in that it doesnt round properlly. it's a common occurence, FreeBASIC has it too.
It's not a bug. In C, floats are truncated towards 0. In FB they are rounded to nearest integer.
In C:
float a=5.9;
int b=a;
//b is now = 5
In FB:
Dim as single a=5.9
Dim as integer b
b=a
print b
//b is now 6
b=int(a)
print b
//b is now 5
Jim
-
Just some thoughts from scrolling through your source...
Even if you don't need an alpha-channel it's much faster to work with rgba (32bit) images because you can read/write a whole pixel as integer.
If you really need 24bit images you shouldn't distinguish these two cases on a per-pixel basis.
Try to remove the range-checks for every pixel, eg clip a line to a valid range before starting to draw (see here (http://en.wikipedia.org/wiki/Line_clipping)).
That said the code in "drawScaled..." is particularly terrible ;)
You're testing for every pixel if the current scanline is out of range.
Do it once a scanline or skip all invisible scanlines in advance.
The same applies for the horizontal range-test:
In case x1<0 you can simply chop the left side of the image and make cx start at -x1*deltax.
In case x1+sw>=toImg.width you can just skip the pixels on the right by decreasing sw in the x-loop.
cy doesn't change across a scanline, so "Int(cy)*Self.pitch" doesn't either (floating-point casts are relatively expensive).
Finally you can keep cx and deltax in fixed-point to remove all floating-point conversion from the inner loop.
You'll rarely scale to an arbitrary size using point-sampling so most of the time you'll use an integer scale factor - you might want to add an special case for that.
-
Thanks for the input, its appreciated, and its true that there are a few places where optimizations could be done. The entire idea with this framework is that people who has no idea about how to get started with this stuff now have a reference point to start from. Its ment to be optimizable, otherwise the idea of people getting together to write code and help each other improve on it goes away.
I'm using ints for rbga colors in a drawing tool I'm working on, and yes I know its faster, I've been using ints for colors in just about everything I have released, but decided on purpose that this framework would be based on having the colors seperate because I felt it would be easier for people new to coding to understand and work with. Not everyone thinks in hex and so having a seperate color value range of 0-255 I thought was the way go about it. But I could be wrong ofcause, and since all the code is free to use and modify, and available, anyone is free to make any changes they like.
I will implement changes you mention to speed things up a bit, but to be honest I was never planing on being the one to write everything. I was counting on people pitching in and make it their own. Its not really a framework that I will make heavy use of, and will probably only use it for a few small fun 1-2 days projects, like the small intro I wrote using it to showcase that it has its uses. My biggest motivation for even working on it, was to help people new to blitzmax and new to demo effect programming at least get some code to look at, which isn't too difficult to follow.
Sorry about the long text, but I thought it was about time I wrote a little bit about why the code was even written and made available.
-
I actually think the framework is pretty cool, it shows how to use a fair few bmax features, such as the types with methods and pixmap access. It also provides some nice ready made methods to help people get started on image and pixel manipulation. While some of the code may not be the fastest method it is fairly easy for people to follow and if they need more speed they can edit the code :)
There are lots of ways this could be expanded but I think zawrans idea was to give people a base to start with that they can expand and post there changes here or even there problems rather than everyone rely on him to code it all for them, I am not sure how many bmax users we have on the forums but I suspect they all could find uses for this as is as well ;)