need to rotate a pixmap with smooth filtering

BlitzMax Forums/BlitzMax Programming/need to rotate a pixmap with smooth filtering

The forum is locked. These are the original blitzbasic.com forums, kept so they can still be read. No new topics, replies or edits can be made here. To join in, head over to the Blitz3D+ forums.

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