Below is a BlitzBasic 2 (Amiga) sourcecode to do rotation using my old Mildred graphics library for reading and writing pixels. You might be able to adapt it. Note that most of it is stuff you can cut out, the actual rotate itself is fairly small. Also notice how much more complicated BlitzBasic USED to be ;-D
Commands starting with M are 3rd party Mildred library commands - let me know if you have questions as to what they do. It shouldn't mess up with what you want to do.
".q" types were `quick` types, a 16:16 fixed point format for decimal numbers. You could just use Floats. I'm not sure if BlitzMax has `Pi` .. it's just Pi.. you know... 3.14 etc.
MPlot plots a pixel (like Plot or WritePixel). MPoint reads a pixel (like ... oh, Max doesn't have that, lol ... ReadPixel()?)
This routine is the type where it draws all pixels within a rectangle of destination screen space, regardless of whether some parts of the rotated image are off-screen or outside the rectangle. I think that is what you'll want to use considering you're rotating within a pixmap. If not put a blank border around your source image and make your pixmap bigger to accomodate.
This is also a rotozoomer - you can zoom in and out as well as rotate. That said, to do antialiasing/smoothing you should probably draw to a rather large pixmap while scaling up quite large, and then scale down the results using a simple filtered resizing technique - filtering, smoothing, averaging, whatever.
To handle alpha just read the alpha value of each pixel that you are reading from (the MPoint() calls), divide it by 256.0 to get in the range 0-1.0, and then multiply your pixel's R G and B components by that amount to scale it.
Good luck.
I still think it would be faster and easier to use:
LoadImage
SetScale
SetAlpha
SetRotation
DrawImage
GrabPixmap
SavePixmapPNG
Plus it'll do the filtering for you with high accuracy and speed - except maybe the edges wouldn't be antialiased?
I commend you on your efforts to do software rendering, something close to my heart having written Mildred back in the day to do that very thing (256-colors chunky routines for BlitzBasic 2 on the Amiga). I never did get around to implementing rotation in the library itself but the below routine was what I was working towards. ... My problem with implementing it on the Amiga? .... trying to do it in assembler, I ran out of registers. ;-)
buf.b=0
its.l=0
cnt.b=0
ang.q=0
degrad.q=Pi/180
mag.q=1
ResetTimer
While Joyb(0)<>3 AND Joyb(1)=0
If Joyb(0)=1 Then mag+0.01
If Joyb(0)=2 Then mag-0.01
ang+1
If ang>=360 Then ang=-360
If ang<0 Then ang+360
y_uadd.q=Cos((ang+90)*degrad)*mag
y_vadd.q=Sin((ang+90)*degrad)*mag
x_uadd.q=Cos(ang*degrad)*mag
x_vadd.q=Sin(ang*degrad)*mag
xcenter=SMouseX
ycenter=SMouseY
y_u.q=-(y_uadd*(opheight/2))-(x_uadd*(opwidth/2))
y_v.q=-(y_vadd*(opheight/2))-(x_vadd*(opwidth/2))
For y=0 To opheight-1
x_u.q=y_u
x_v.q=y_v
For x=0 To opwidth-1
MPlot x,y,MPoint(xcenter+x_u,ycenter+x_v,1)
x_u+x_uadd
x_v+x_vadd
Next x
y_u+y_uadd
y_v+y_vadd
Next y
Taken from this original demo program...
.Demo
WBStartup
DEFTYPE.w
MCPU Processor
Mc2pCPUmode Processor
*ScrVP._ViewPort=0
IsAGA.b=True ; Defaults to AGA
Dim PlanarBuf.l(2) ; Base address of planar memory to output c2p to (allowed up to triple buffers)
PrefDisplayID.l=$0 ; Default ModeID (Pal:LowRes, or promotes to DoublePal:LowRes)
PrefDisplayWidth.w=320 ; Default Width
PrefDisplayHeight.w=240 ; Default Height
PrefDisplayBuffering.b=3 ; 1..3. 1=Singlebuffered, 2=Doublebuffered, 3=Triplebuffered
PrefDisplayMethod.b=2 ; 0=WritePixelArray8/WriteChunkyPixels, 1=MBlockScroll, 2=WritePixelArray(CGFX)
PrefCGFXLock.b=True ; Wether when Method=1, CGFX LockBitmap will be attempted for safety and to get base address
OSVersion.w=ExecVersion
CGFXAvail.b=False ; Default
#DTAG_DISP=$80000000
#DTAG_DIMS=$80001000
#DTAG_MNTR=$80002000
#DTAG_NAME=$80003000
#LBMI_BASEADDRESS=$84001007
#DIPF_IS_FOREIGN=$80000000
#DIPF_IS_ECS=$00000010
#DIPF_IS_AGA=$00010000
If Joyb(0)=0 AND Joyb(1)=0 Then Goto SkipSMR
NEWTYPE.SMode
DID.l
DWidth.l
DHeight.l
DDepth.w
DType.w
End NEWTYPE
DEFTYPE.Hook myhook ; The hook for ASL tag as &myhook
myhook\h_Entry=?hook
MOVE.l a5,globalbase
funcret.l=0
Dim SMRtags.TagItem(17)
SMRtags(0)\ti_Tag=#ASLSM_InitialLeftEdge,160 ; X coord of requester
SMRtags(1)\ti_Tag=#ASLSM_InitialTopEdge,0 ; Y coord of requester
SMRtags(2)\ti_Tag=#ASLSM_InitialWidth,300 ; Width of requester
SMRtags(3)\ti_Tag=#ASLSM_InitialHeight,400 ; Height of requester
SMRtags(4)\ti_Tag=#ASLSM_InitialDisplayID,$21000 ; Default ModeID (Pal:LowRes)
SMRtags(5)\ti_Tag=#ASLSM_InitialDisplayDepth,8 ; Default depth (8-bit usually)
SMRtags(6)\ti_Tag=#ASLSM_InitialDisplayWidth,PrefDisplayWidth
SMRtags(7)\ti_Tag=#ASLSM_InitialDisplayHeight,PrefDisplayHeight
SMRtags(8)\ti_Tag=#ASLSM_InitialOverscanType,1 ; Default overscan type (Text)
SMRtags(9)\ti_Tag=#ASLSM_InitialInfoOpened,1 ; Info window?
SMRtags(10)\ti_Tag=#ASLSM_InitialInfoLeftEdge,350 ; X coord of info window
SMRtags(11)\ti_Tag=#ASLSM_InitialInfoTopEdge,50 ; Y coord of info window
SMRtags(12)\ti_Tag=#ASLSM_DoDepth,0 ; Depth gadget? (Generally NO for chunky 8-bit)
SMRtags(13)\ti_Tag=#ASLSM_DoOverscanType,0 ; Overscan gadget?
SMRtags(14)\ti_Tag=#ASLSM_DoWidth,0 ; Width gadget?
SMRtags(15)\ti_Tag=#ASLSM_DoHeight,0 ; Height gadget?
SMRtags(16)\ti_Tag=#ASLSM_FilterFunc,&myhook ; Address of callback hook routine
SMRtags(17)\ti_Tag=#TAG_DONE,0
*sreq.SMode=0
*sreq=AllocAslRequest_(2,&SMRtags(0)\ti_Tag)
ok.b=AslRequest_(*sreq,&SMRtags(0)\ti_Tag)
If ok<>0
PrefDisplayID.l=*sreq\DID
PrefDisplayWidth.w=*sreq\DWidth
PrefDisplayHeight.w=*sreq\DHeight
EndIf
If (*sreq) Then FreeAslRequest_(*sreq)
Goto SkipSMR
;*************************************************************************
; This is the statement that the hook will call. Put the label before
; the statement you want to jump to.
Runerrsoff
hook_jump:
Statement hook{*dahook.Hook, modeID.l, *smr.ScreenModeRequester}
; We're inside the hook, and supposedly we should be able to do whatever
; we want.
; Filter modeID's here
SHARED funcret.l
DEFTYPE.DisplayInfo DisInfoBuf
DEFTYPE.DimensionInfo DimInfoBuf
DEFTYPE.MonitorInfo MonInfoBuf
DEFTYPE.NameInfo NamInfoBuf
;Refer to Includes/Graphics/DisplayInfo.h or view newtypes
IDhandle.l=FindDisplayInfo_(modeID)
GetDisplayInfoData_ IDhandle,&DisInfoBuf,SizeOf.DisplayInfo,#DTAG_DISP,0
GetDisplayInfoData_ IDhandle,&DimInfoBuf,SizeOf.DimensionInfo,#DTAG_DIMS,0
GetDisplayInfoData_ IDhandle,&MonInfoBuf,SizeOf.MonitorInfo,#DTAG_MNTR,0
GetDisplayInfoData_ IDhandle,&NamInfoBuf,SizeOf.NameInfo,#DTAG_NAME,0
;Do tests. True=Mode is valid, False=mode is invalid.
;See newtypes for DisplayInfo,DimensionInfo,MonitorInfo and NameInfo for things to further test
If DimInfoBuf\MaxDepth<>8
;No true-colour modes, only 8-bit
funcret=False
Else
funcret=True
EndIf
End Statement
;**********************
; Hook
Macro goto_hook
JSR `1+6
End Macro
globalbase: Dc.l 0
hook: ;This hook is called by the filter hook callback from screenmode requester, per item
; Store registers
MOVEM.l d1-d7/a0-a6,-(a7) ; Not d0!
; Put parameters into dregs ready for a statement
MOVE.l a0,d0
MOVE.l a1,d1
MOVE.l a2,d2
; Get global variable base
MOVE.l globalbase,a5
; Goto hook statement
!goto_hook{hook_jump}
GetReg d0,funcret ; return accept/discard
; Restore registers
MOVEM.l (a7)+,d1-d7/a0-a6 ; Not d0!
RTS
;**********************
Runerrson
.SkipSMR
Function.b CheckLib{Lib$,LibVer}
;Returns wether a specific library is available or not
*lib.l=OpenLibrary_(&Lib$,LibVer)
If *lib
CloseLibrary_ *lib
Function Return True
Else
Function Return False
EndIf
End Function
Function.b InitDisplay{Title$}
;Creates a display for AGA or Graphics-Card output
;Title$=The screen title (not displayed)
SHARED PrefDisplayWidth,PrefDisplayHeight,PrefDisplayBuffering
SHARED *ScrVP,PrefDisplayID,IsAGA,PlanarBuf(),CGFXAvail
;Setup a test screen
Dim ScrTags.TagItem(13)
Rect.Rectangle\MinX=0,0,320,240 ; For test
ScrTags(0)\ti_Tag=#SA_Width,320 ; For test
ScrTags(1)\ti_Tag=#SA_Height,240; For test
ScrTags(2)\ti_Tag=#SA_Depth,8
ScrTags(3)\ti_Tag=#SA_DisplayID,PrefDisplayID
ScrTags(4)\ti_Tag=#SA_Type,$F
ScrTags(5)\ti_Tag=#SA_Quiet,True
ScrTags(6)\ti_Tag=#SA_ShowTitle,False
ScrTags(7)\ti_Tag=#SA_Behind,True
ScrTags(8)\ti_Tag=#SA_DClip,&Rect ; For test
ScrTags(9)\ti_Tag=#SA_Exclusive,False
ScrTags(10)\ti_Tag=#SA_Draggable,False
ScrTags(11)\ti_Tag=#SA_AutoScroll,False
ScrTags(12)\ti_Tag=#TAG_DONE,0
ScrTags(13)\ti_Tag=#TAG_DONE,0
If CGFXAvail
IsAGA=1-(IsCyberModeID_(PrefDisplayID))
Else
; Need to do a test
UsedChip.l=320*240 ; With test params (depth 8)
FreeChip.l=AvailMem_(#MEMF_CHIP)
Forbid_
If ScreenTags(0,Title$,&ScrTags(0))
NowChip.l=AvailMem_(#MEMF_CHIP)
Permit_
If FreeChip-NowChip<UsedChip
IsAGA=False
Else
IsAGA=True
EndIf
VWait 5
Free Screen 0
VWait 5
Else
; Failed to open, so resort to fixed AGA LowRes
Permit_
IsAGA=True
PrefDisplayID=0
PrefDisplayWidth=320
PrefDisplayHeight=240
EndIf
EndIf
If IsAGA
PrefDisplayWidth AND $FFC0 ; Multiples of 64 for AGA
Else
PrefDisplayWidth AND $FFF0 ; Multiples of 16 for graphics card
EndIf
ScrTags(0)\ti_Tag=#SA_Width,PrefDisplayWidth
Rect.Rectangle\MinX=0,0,PrefDisplayWidth,PrefDisplayHeight
ScrTags(8)\ti_Tag=#SA_DClip,&Rect
If IsAGA
; AGA display
ScrTags(1)\ti_Tag=#SA_Height,PrefDisplayHeight ; Seperate buffers
ScrTags(3)\ti_Tag=#SA_DisplayID,PrefDisplayID
Forbid_
For Loop.w=0 To PrefDisplayBuffering-1
If Loop=0 Then WFlags.l=$1900 Else WFlags.l=$800
If AvailMem_(#MEMF_CHIP)>=(PrefDisplayWidth*PrefDisplayHeight)+16
Memory.l=AllocMem((PrefDisplayWidth*PrefDisplayHeight)+16,$10002) ; Chipram bitmap
Memory=(Memory+16) AND $FFFFFFF0 ; Align for move16's
If Memory
CludgeBitMap Loop,PrefDisplayWidth,PrefDisplayHeight,8,Memory ; Depth 8
If Loop=0
ScrTags(12)\ti_Tag=#SA_BitMap,Addr BitMap(0)
If ScreenTags(0,Title$,&ScrTags(0))=0
Permit_
Function Return False
EndIf
EndIf
If Window(Loop,0,0,PrefDisplayWidth,PrefDisplayHeight,WFlags,"",0,0)=0 Then Function Return False
Menus Off
Else
Permit_
Function Return False
EndIf
Else
Permit_
Function Return False
EndIf
PlanarBuf(Loop)=Memory
Next Loop
Permit_
Else
; Graphics-card display
ScrTags(1)\ti_Tag=#SA_Height,PrefDisplayHeight*PrefDisplayBuffering
If ScreenTags(0,Title$,&ScrTags(0))
For Loop.w=0 To PrefDisplayBuffering-1
If Loop=0 Then WFlags.l=$1900 Else WFlags.l=$800
If Window(Loop,0,PrefDisplayHeight*Loop,PrefDisplayWidth,PrefDisplayHeight,WFlags,"",0,0)=0 Then Function Return False
Menus Off
ScreensBitMap 0,Loop
*TmpBmp.bitmap=Addr BitMap(Loop)
Offset.l=*TmpBmp\_ebwidth*(PrefDisplayHeight*Loop)
For DLoop.w=0 To 8-1 ; Depth of 8
*TmpBmp\_data[DLoop]=*TmpBmp\_data[DLoop]+Offset
Next DLoop
Next Loop
Else
Function Return False
EndIf
EndIf
If Peek.l(Addr Screen(0))
DEFTYPE.DimensionInfo DimInfoBuf
GetDisplayInfoData_ FindDisplayInfo_(PrefDisplayID) AND $FFFFFFFF,&DimInfoBuf,SizeOf.DimensionInfo,#DTAG_DIMS,0
PrefDisplayLeft.w=((DimInfoBuf\TxtOScan\MaxX)-PrefDisplayWidth)/2
PrefDisplayTop.w=((DimInfoBuf\TxtOScan\MaxY)-PrefDisplayHeight)/2
*Scr._Screen=Peek.l(Addr Screen(0))
*ScrVP=ViewPort(0)
*ScrVP\DxOffset=PrefDisplayLeft,PrefDisplayTop
ScrollVPort_ *ScrVP
RethinkDisplay_
Menus Off
If *ScrVP\DHeight<>PrefDisplayHeight
Forbid_
*Scr\Height=PrefDisplayHeight ; Enforce y clipping
Permit_
EndIf
ScreenToFront_ *Scr
Function Return True
Else
Function Return False
EndIf
End Function
.Main
CGFXAvail.b=CheckLib{"cybergraphics.library",0}
If CGFXAvail=False AND PrefDisplayMethod=2 Then PrefDisplayMethod=0
InitPalette 0,256
If InitDisplay{"Game"}=False Then Goto Finish
If PrefDisplayMethod=0 AND OSVersion<40
MBitmap 5,PrefDisplayWidth,PrefDisplayHeight ; Temporary bitmap to allow WPA8 instead of WPL8's
EndIf
MBitmap 0,PrefDisplayWidth,PrefDisplayHeight
MBitmap 1,PrefDisplayWidth,PrefDisplayHeight
MUseBitmap 0
If IsAGA Then Mc2pWindow 0,PrefDisplayWidth,PrefDisplayHeight
InitBank 0,320*240,$10000 ; Get some free CHIP memory.
CludgeBitMap 3,320,240,8,Bank(0) ; And make it a planar bitmap.
LoadBitMap 3,"RotatePic2.IFF",0 ; Get a pic
MPlanar16ToBitmap 1,Bank(0),320,240,320,240 ; Make it chunky
MBlockScroll 0,0,PrefDisplayWidth,PrefDisplayHeight,0,0,1
LoadRGB32_ *ScrVP,Peek.l(Addr Palette(0))
opwidth.w=96 ; multiple of 32
opheight.w=96
If IsAGA
Mc2p MBitmapPtr(0),PlanarBuf(0)
If PrefDisplayBuffering>=2 Then Mc2p MBitmapPtr(0),PlanarBuf(1)
If PrefDisplayBuffering=3 Then Mc2p MBitmapPtr(0),PlanarBuf(2)
Mc2pWindow 0,opwidth/32*32,opheight,PrefDisplayWidth,PrefDisplayWidth,PrefDisplayHeight
EndIf
.Loop
buf.b=0
its.l=0
cnt.b=0
ang.q=0
degrad.q=Pi/180
mag.q=1
ResetTimer
While Joyb(0)<>3 AND Joyb(1)=0
If Joyb(0)=1 Then mag+0.01
If Joyb(0)=2 Then mag-0.01
ang+1
If ang>=360 Then ang=-360
If ang<0 Then ang+360
y_uadd.q=Cos((ang+90)*degrad)*mag
y_vadd.q=Sin((ang+90)*degrad)*mag
x_uadd.q=Cos(ang*degrad)*mag
x_vadd.q=Sin(ang*degrad)*mag
xcenter=SMouseX
ycenter=SMouseY
y_u.q=-(y_uadd*(opheight/2))-(x_uadd*(opwidth/2))
y_v.q=-(y_vadd*(opheight/2))-(x_vadd*(opwidth/2))
For y=0 To opheight-1
x_u.q=y_u
x_v.q=y_v
For x=0 To opwidth-1
MPlot x,y,MPoint(xcenter+x_u,ycenter+x_v,1)
x_u+x_uadd
x_v+x_vadd
Next x
y_u+y_uadd
y_v+y_vadd
Next y
;Display
If IsAGA
Mc2p MBitmapPtr(0),PlanarBuf(buf)
If PrefDisplayBuffering>1
ShowBitMap buf
buf+1
If buf=PrefDisplayBuffering Then buf=0
EndIf
Else
*RP0._RastPort=RastPort(0)
Select PrefDisplayMethod
Case 0 ; WritePixelArray8
If PrefDisplayBuffering>1
*RP1._RastPort=RastPort(Min(PrefDisplayBuffering-1,cnt+1))
If OSVersion<40
MUseBitmap 5
MBlockScroll 0,0,PrefDisplayWidth,PrefDisplayHeight,0,0,0 ; From window in modulo bitmap, to nonmodulo bitmap
MUseBitmap 0
WritePixelArray8_ *RP1,0,0,PrefDisplayWidth-1,PrefDisplayHeight-1,MBitmapPtr(5),0
Else
WriteChunkyPixels_ *RP1,0,0,PrefDisplayWidth-1,PrefDisplayHeight-1,MBitmapPtr(0),MBitmapWidth(0)
EndIf
ClipBlit_ *RP1,0,0,*RP0,0,0,PrefDisplayWidth,PrefDisplayHeight,$C0
If PrefDisplayBuffering=3 Then cnt=1-cnt ; Toggle output buffer
Else
If OSVersion<40
MUseBitmap5
MBlockScroll 0,0,PrefDisplayWidth,PrefDisplayHeight,0,0,0 ; From window in modulo bitmap, to nonmodulo bitmap
MUseBitmap 0
WritePixelArray8_ *RP0,0,0,PrefDisplayWidth-1,PrefDisplayHeight-1,MBitmapPtr(5),0
Else
WriteChunkyPixels_ *RP0,0,0,PrefDisplayWidth-1,PrefDisplayHeight-1,MBitmapPtr(0),MBitmapWidth(0)
EndIf
EndIf
Case 1 ; MBlockScroll
If CGFXAvail AND PrefCGFXLock
Dim CGFXTags.TagItem(1)
CGFXData.l=0
CGFXTags(0)\ti_Tag=#LBMI_BASEADDRESS,&CGFXData
CGFXTags(1)\ti_Tag=#TAG_DONE,0
LockHandle.l=LockBitMapTagList_(*RP0\_BitMap,&CGFXTags(0))
MCludgeBitmap 4,PrefDisplayWidth,PrefDisplayHeight*PrefDisplayBuffering,CGFXData
Else
MCludgeBitmap 4,PrefDisplayWidth,PrefDisplayHeight*PrefDisplayBuffering,*RP0\_BitMap\Planes
EndIf
If PrefDisplayBuffering>1
*RP1._RastPort=RastPort(Min(PrefDisplayBuffering-1,cnt+1))
MBlockScroll 0,0,PrefDisplayWidth,PrefDisplayHeight,0,PrefDisplayHeight+(cnt*PrefDisplayHeight),0 ; From modulo bitmap
ClipBlit_ *RP1,0,0,*RP0,0,0,PrefDisplayWidth,PrefDisplayHeight,$C0
If PrefDisplayBuffering=3 Then cnt=1-cnt ; Toggle output buffer
Else
MBlockScroll 0,0,PrefDisplayWidth,PrefDisplayHeight,0,0,0 ; From modulo bitmap
EndIf
MUseBitmap 0
If CGFXAvail AND (LockHandle<>0) AND PrefCGFXLock Then UnLockBitMap_ LockHandle
Case 2 ; CGFXWriteChunkyPixels
If PrefDisplayBuffering>1
*RP1._RastPort=RastPort(Min(PrefDisplayBuffering-1,cnt+1))
WritePixelArray_ MBitmapPtr(0),0,0,MBitmapWidth(0),*RP1,0,0,PrefDisplayWidth,PrefDisplayHeight,#RECTFMT_LUT8
ClipBlit_ *RP1,0,0,*RP0,0,0,PrefDisplayWidth,PrefDisplayHeight,$C0
If PrefDisplayBuffering=3 Then cnt=1-cnt ; Toggle output buffer
Else
WritePixelArray_ MBitmapPtr(0),0,0,MBitmapWidth(0),*RP0,0,0,PrefDisplayWidth,PrefDisplayHeight,#RECTFMT_LUT8
EndIf
End Select
EndIf
its+1
Wend
;Report
t=Timer
t=Max(t,1)
its=Max(its,1)
a.q=50.0/(t/its)
WBenchToFront_
WbToScreen 1
Window 2,16,16,300,40,0,"Test results",1,0
WindowOutput 2
NPrint a," frames per second"
NPrint " "
NPrint "Press mouse/joy button..."
VWait 20
Repeat
Until Joyb(0)<>0 OR Joyb(1)<>0
Finish:
End