Author Topic: [Bmax] Software render framework  (Read 21631 times)

0 Members and 1 Guest are viewing this topic.

Offline zawran

  • Sponsor
  • Pentium
  • *******
  • Posts: 909
  • Karma: 67
    • View Profile
Re: [Bmax] Software render framework
« Reply #40 on: August 10, 2009 »
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.

Code: [Select]
' 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.

Offline Clyde

  • A Little Fuzzy Wuzzy
  • DBF Aficionado
  • ******
  • Posts: 7271
  • Karma: 71
    • View Profile
Re: [Bmax] Software render framework
« Reply #41 on: August 10, 2009 »
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.
Still Putting The IT Into Gravy
If Only I Knew Then What I Know Now.

Challenge Trophies Won:

Offline zawran

  • Sponsor
  • Pentium
  • *******
  • Posts: 909
  • Karma: 67
    • View Profile
Re: [Bmax] Software render framework
« Reply #42 on: August 11, 2009 »
The line code has been fixed, its mentioned about 8 posts up where Pixel_Outlaw confirms it to be working.

Offline Jim

  • Founder Member
  • DBF Aficionado
  • ********
  • Posts: 5301
  • Karma: 402
    • View Profile
Re: [Bmax] Software render framework
« Reply #43 on: August 11, 2009 »
Quote
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.
Code: [Select]
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
Challenge Trophies Won:

Offline hellfire

  • Sponsor
  • Pentium
  • *******
  • Posts: 1294
  • Karma: 466
    • View Profile
    • my stuff
Re: [Bmax] Software render framework
« Reply #44 on: August 11, 2009 »
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).

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.

Challenge Trophies Won:

Offline zawran

  • Sponsor
  • Pentium
  • *******
  • Posts: 909
  • Karma: 67
    • View Profile
Re: [Bmax] Software render framework
« Reply #45 on: August 11, 2009 »
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.

Offline TinDragon

  • Pentium
  • *****
  • Posts: 644
  • Karma: 24
    • View Profile
    • J2K's blog
Re: [Bmax] Software render framework
« Reply #46 on: August 12, 2009 »
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  ;)