As a function - it's not that much different...(untested)
Function RotatePixmap(SourcePixmap:TPixmap,DestPixmap:TPixmap,Angle:Float=0,Magnification:Float=1.0,XCenter:Float,YCenter:Float)
'Draw a pixmap into another pixmap rotated and zoomed around a center with alpha blending
'SourcePixmap is the source TPixmap to read from
'DestPixmap is the dest TPixmap to write to
'XCenter,YCenter is the center within the destination, usually half of its width and height
'Source and destination pixmaps MUST be RGBA 32-bit
If PixmapFormat(SourcePixmap)<>PF_RGBA8888 Then SourcePixmap=ConvertPixmap(SourcePixmap,PF_RGBA8888)
If PixmapFormat(DestPixmap)<>PF_RGBA8888 then DeskPixmap=COnvertPixmap(DestPixmap,PF_RGBA8888)
'What sizes are we working with?
Local SourcePixelsPerRow=PixmapPitch(SourcePixmap)/4
Local DestPixelsPerRow=PixmapPitch(DestPixmap)/4
Local OpWidth:Int=PixmapWidth(SourcePixmap) 'Width of operation
Local OpHeight:Int=PixmapHeight(SourcePixmap) 'Height of operation
Local OpWidth2:Int=OpWidth/2
Local OpHeight2:Int=OpHeight/2
'Local XCenter:Int'=OpWidth/2
'Local YCenter:Int'=OpHeight/2
'Set up variables
Local Y_UAdd:Float,Y_VAdd:Float,X_UAdd:Float,X_VAdd:Float
Local Y_U:Float,Y_V:Float,X_U:Float,X_V:Float
Local SourceBase:Int Ptr=Int Ptr(PixmapPixelPtr(SourcePixmap,0,0))
Local DestBase:Int Ptr
Local SourcePixel:Int,DestPixel:Int
Local Xpos:Int,YPos:Int
Local Red:Int,Green:Int,Blue:Int
Local Alpha:Float
Local AlphaMask:Int=$000000FF
If Angle>=360 Then Angle:-360
If Angle<0 Then Angle:+360
'Setup
Y_UAdd=Cos(Angle+90)*Magnification
Y_VAdd=Sin(Angle+90)*Magnification
X_UAdd=Cos(Angle)*Magnification
X_VAdd=Sin(Angle)*Magnification
Y_U=-(Y_UAdd*OpHeight2)-(X_UAdd*OpWidth2)
Y_V=-(Y_VAdd*OpHeight2)-(X_VAdd*OpWidth2)
DestBase=Int Ptr(PixmapPixelPtr(DestPixmap,0,0)) 'reset top left
For Local Y:Int=0 To OpHeight-1
X_U=Y_U
X_V=Y_V
For Local X:Int=0 To OpWidth-1
XPos=XCenter+Int(X_U)
YPos=YCenter+Int(X_V)
If XPos>=0 And XPos<OpWidth 'clip x
If YPos>0 And YPos<OpHeight 'clip y
'SourcePixel=( SourceBase + XPos + (YPos*SourcePixelsPerRow) )[0] 'Read full pixel, clipped
SourcePixel=ReadPixel(SourcePixmap,Xpos,Ypos)
SourcePixel=(SourcePixel Shl 8) | (SourcePixel Shr 24) 'Convert ARGB to RGBA if using ReadPixel()
Alpha=(SourcePixel & AlphaMask)/256.0
Red=(SourcePixel Shr 24) * Alpha
Green=((SourcePixel Shl 8) Shr 24) * Alpha
Blue=((SourcePixel Shl 16) Shr 24) * Alpha
Alpha=1.0-Alpha
DestPixel=DestBase[X] 'Read full pixel
Red:+((DestPixel Shr 24) * Alpha)
Green:+(((DestPixel Shl 8) Shr 24) * Alpha)
Blue:+(((DestPixel Shl 16) Shr 24) * Alpha)
DestBase[X]=(Red Shl 24) | (Green Shl 16) | (Blue Shl 8) 'Plot RGBA
EndIf
EndIf
X_U:+X_UAdd
X_V:+X_VAdd
Next
DestBase:+DestPixelsPerRow 'next row
Y_U:+Y_UAdd
Y_V:+Y_VAdd
Next
End Function
I think this should work. Actually ReadPixel() is not really supposed to be needed because I am trying to use pointers all the way through, but I recall having some difficulties with it so had to revert to reading using ReadPixel, which is not optimium speed. You can see the pointer-based read line is commented out. Maybe we can figure out how to make it work, then it should be even faster.
This routine only has to calculate the `angle` using trigonometry once outside the loop so isn't doing it for every pixel like that other routine you saw from someone else.
Also you need to be aware that there are two different ways to do rotation and they have different properties. Basically to rotate something, either the source or the destination has to be `aligned` to the axis, ie non rotated, while the other has to be aligned to the rotation angle. So there are two choices for how to move through the pixels. Either:
a) You go through all the pixels in the source pixmap horizontally and vertically as rows and colums, and then calculate what position those pixels would be when rotated to the destination. Pixels are then drawn into the destination at their rotated positions. With this method, you only read pixels that are in the source image and don't have to do any clipping of the source, but you have to be careful to clip the rendering into the destination because the corners might overlap the edges of the pixmap. This routine also will only draw to the destination those pixels which are a part of the source pixmap, and will ignore the areas outside of the rotated shape. The disadvantage of this method is that you get some `bittiness` in the quality of the rotation due to the way the coordinates are rounded and it's possible to get small holes in the drawn image. This is (I think) solved in the second method:
b) You go through all the pixels in the destination pixmap horizontally and vertically in rows and columns, and then calculate what rotated position to *read from* in the source pixmap. So as you go through the destination rows you rotate the coordinates around and then instead of reading the source one row at a time you are literally reading from a rotated rectangle within the source, and you read from the source diagonally. Pixels are drawn to the destination a row at a time and all pixels in the destination are drawn. The care then needs to be taken that you don't *read* pixels in the source image which are outside of the source pixmap, so you have to kind of clip it in reverse. When the rotated coords are outside of the source image, the dest pixel is not drawn. This type of rotation is used when you want to think of the destination pixmap as a kind of `viewport`, whereas the first method sees the source pixmap as the viewport.
These are two ways to do the rotation. Zooming is done much the same in both - the steps that are used to move throught the data are just scaled.
The function above uses the second method. Based on this code, since there is no detection of what size the destination pixmap needs to be to fit the rotated rectangle inside perfectly, you need to create a destination pixmap before calling. Whatever size you choose for the dest pixmap determines whether parts of the rectangle will be clipped off the edges.
Also with regard to defining the center position, if I remember rightly, normally the center should be half the size of the width and height of the destination pixmap. This results in the source pixmap being put into the middle of the destination image. If you move the center, it still rotates around the middle of the destination image but it reads from a different position in the source. So it might seem to be the `inverse` of where you actually place the center. So you might want to play with that and maybe define your center as CenterX=WidthOfDestPixmap-CenterX and CenterY=HeightOfDestPixmap-CenterY so that it actually appears to rotate around the coordinates you're defining. The object will still rotate around the center of the dest pixmap I think, I don't remember for sure.
With this routine you can zoom right into the source pixmap so that one pixel covers the entire destination, or vice-versa, zoom out so that the whole source image only fills one pixel in the destination.
Filtering that you see when you zoom in close is basically nearest-neighbor, which is the same as using a normal image (not FILTEREDIMAGE) - there's no bilinear filtering. Boundaries between pixel rows/columns are not antialiased. Render zoomed to a big pixmap and use a simple filtering algorithm to reduce it down to the size you want with an averaging effect to simulate `multitampling` antialiasing.
Let me know if you have questions.