Image Rotation routines for 24/32 bit with optional AA

Share your advanced PureBasic knowledge/code with the community.
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Image Rotation routines for 24/32 bit with optional AA

Post by luis »

The following posts contain the code and three samples.

Potentially cross platform. Tested on Win32/64 and Linux32.

Tested with PB 5.30

- RotateImageFree (nSrcImage, fDegRot.f, flgAntiAliasing, iFillColor)

- RotateImage (nSrcImage, iDegRot) optimized for 90/180/270 degrees rotations

- FlipImage (nSrcImage) vertical flip

- MirrorImage (nSrcImage) horizontal flip (mirror)

These routines preserve the alpha channel information (see image with partially transparent circles).


EDIT: v1.01 - Streamlined a little

EDIT: v1.02 - Optimized a little for speed (15% gain for AA/32bit)

EDIT: v1.03 (let's forget about it)

EDIT: v1.04 Better, added some tweaking to make it hopefully work better with the "orthogonal" angles too.

EDIT: v1.05 Fixed dimensioning of the destination image with arbitrary angles (1 pixel off sometimes...).

EDIT: v1.06 Now 0,90,180,270 degrees angles passed to RotateImageFree() are processed by RotateImage() instead. Should have been this way from the start.

EDIT: v1.10 Changed error handling, tested code for PB 5.73.

EDIT: v1.11 Hopefully fixed an IMA and tested code with PB 5.73.
Last edited by luis on Wed Apr 20, 2022 8:12 pm, edited 17 times in total.
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Post by luis »

MAIN CODE - "RotateImage.pb"

Code: Select all

;******************************************************************************
;*
;* Image Rotation routines for 24/32 bit with optional AA
;* by Luis, http://luis.no-ip.net
;* v1.11 for PB 5.73
;*
;* Tested under Windows 32/64 bit and Linux 32 bit with PB 4.40 B2
;*
;* These routines can deal with both 24/32 bit images and with the alpha channel.
;* The output of the routines will be an image with the same number of BPP
;* as the one passed in input to them. The source image is not freed.
;*
;* Inspired by a simpler Visual Basic code from Robert Rayment. Thank you.
;* http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=23476&lngWId=1
;*
;* ----------------------------------------------------------------------------
;*
;* RotateImageFree (nSrcImage, fDegRot.f, flgAntiAliasing, FillColor)
;*
;*  nSrcImage           The 24/32 bit PureBasic's image to rotate
;*  fDegRot             Float angle in degrees (+/-) 0.0 -> 360.0
;*  flgAntiAliasing     0 for simpler rotation, 1 for antialiased rotation
;*  FillColor           Used to fill the new areas of the resulting image
;*
;*  Return              a 24/32 bit image rotated by fDegRot
;*
;* NOTES :
;*  FillColor is not used with 32 bit images, the new areas are always transparent.
;*
;*  FillColor can be set to a unique color with 24 bit images if you want to
;*  draw the resulting image with masking using GDI functions under Windows,
;*  for example. Or maybe to simply match a certain background color.
;*
;*  The anti aliasing use 4 pixels to do the AA, this is useful especially
;*  when text is present on the image to be rotated to obtain a good quality
;*  at the expense of speed. A free-angle rotation really need AA !
;*
;* ----------------------------------------------------------------------------
;*
;* RotateImage (nSrcImage, DegRot)
;*
;*  nSrcImage           The 24/32 bit PureBasic's image to rotate
;*  DegRot              Integer angle in degrees (+/-) 90/180/270
;*
;*  Return              a 24/32 bit image rotated by DegRot
;*
;* NOTES :
;*   Use this procedure to rotate by multiples of 90 degrees instead of RotateImageFree(). 
;*   It's faster and it's not subject to rounding errors.
;*
;* ----------------------------------------------------------------------------
;*
;* FlipImage (nSrcImage)
;*
;*  nSrcImage           The 24/32 bit PureBasic's image to flip
;*
;*  Return              a 24/32 bit image flipped vertically
;*
;* ----------------------------------------------------------------------------
;*
;* MirrorImage (nSrcImage)
;*
;*  nSrcImage           The 24/32 bit PureBasic's image to mirror
;*
;*  Return              a 24/32 bit image mirrored horizontally
;*
;******************************************************************************


#EXC_ALLOC_ERR = 1

CompilerIf Defined(Macro_TRY, #PB_Constant) = 0
#Macro_TRY = 1

Global EXCEPTION

Macro TRY (exp, exc = 0)
; [DESC]
; Evaluate the expression and jump to the corresponding CATCH if it's false.
;
; [INPUT]
; exp : The expression to test.
; exc : An optional numeric code to identify the exception type for the CATCH.
;
; [NOTES]
; This is inspired to real exception handling but it isn't.
; The purpose of this TRY/CATCH pair is to avoid messy, deep error checking in structured code 
; and to wrap the use of GOTO inside something more convenient to read/follow.
; You can write the main body of the code under the assumption nothing is going wrong, 
; and concentrate the cleanup / recovery in a single place, keeping single entry and exit points.
;
; Example:
;
; Procedure.i MyProc()
; 
;  TRY (ProcA(), 1)
;  ...
;  TRY (ProcB(), 2)
;  ...
;  TRY (ProcC(), 3)
;  ...
; 
;  ProcedureReturn 1 ; success
;  
;  CATCH
;  
;  Select EXCEPTION
;   Case 1 : ; specific cleanup
;   Case 2 : ; specific cleanup
;   Case 3 : ; specific cleanup
;   Default : ; fallback
;  EndSelect
; 
;  ProcedureReturn 0 ; failure
; EndProcedure
;
  
 If Not (exp)
    EXCEPTION = exc
    Goto label_catch_exception
 Else
    EXCEPTION = 0
 EndIf
EndMacro

Macro RAISE (exc = 0)
; [DESC]
; Unconditionally jump to the corresponding CATCH.
;
; [INPUT]
; exc : An optional numeric code to identify the exception type for the CATCH.
  
 EXCEPTION = exc
 Goto label_catch_exception
 EndMacro

Macro CATCH()
; [DESC]
; Receive the control from the program when the TRY expression is false.
;
; [NOTES]
; See the command TRY

 label_catch_exception:
EndMacro

CompilerEndIf

Structure T_RGBA
 B.a
 G.a
 R.a
 A.a
EndStructure

Macro RGB_B(color)
 ((color & $FF0000) >> 16) 
EndMacro

Macro RGB_G(color)
 ((color & $FF00) >> 8) 
EndMacro

Macro RGB_R(color)
 (color & $FF) 
EndMacro 

Macro RGB_Mix (r, g, b)
 ((((b) << 8 + (g)) << 8) + (r))
EndMacro

Macro CopyPixel32 (Xs, Ys, Xd, Yd, BufferPitchSrc, BufferPitchDest, ptRGBAs, ptRGBAd, pMemSrc, pMemDest)
 ptRGBAs = pMemSrc  + (Ys) * BufferPitchSrc + (Xs) << 2
 ptRGBAd = pMemDest + (Yd) * BufferPitchDest + (Xd) << 2        
 ptRGBAd\R = ptRGBAs\R
 ptRGBAd\G = ptRGBAs\G
 ptRGBAd\B = ptRGBAs\B        
 ptRGBAd\A = ptRGBAs\A
EndMacro

Macro CopyPixel24 (Xs, Ys, Xd, Yd, BufferPitchSrc, BufferPitchDest, ptRGBAs, ptRGBAd, pMemSrc, pMemDest)
 ptRGBAs = pMemSrc  + (Ys) * BufferPitchSrc + (Xs) * 3
 ptRGBAd = pMemDest + (Yd) * BufferPitchDest + (Xd) * 3
 ptRGBAd\R = ptRGBAs\R
 ptRGBAd\G = ptRGBAs\G
 ptRGBAd\B = ptRGBAs\B        
EndMacro

Macro ReadPixel32 (X, Y, BufferPitchSrc, ptRGBA, pMemSrc) 
 ptRGBA = pMemSrc + (Y) * BufferPitchSrc + (X) << 2
EndMacro

Macro ReadPixel24 (X, Y, BufferPitchSrc, ptRGBA, pMemSrc) 
 ptRGBA = pMemSrc + (Y) * BufferPitchSrc + (X) * 3
EndMacro

Macro WritePixel32 (tPixel, X, Y, BufferPitchDest, ptRGBA, pMemDest)
 ptRGBA = pMemDest + (Y) * BufferPitchDest + (X) << 2                       
 ptRGBA\R = tPixel\R
 ptRGBA\G = tPixel\G
 ptRGBA\B = tPixel\B
 ptRGBA\A = tPixel\A
EndMacro

Macro WritePixel24 (tPixel, X, Y, BufferPitchDest, ptRGBA, pMemDest)
 ptRGBA = pMemDest + (Y) * BufferPitchDest + (X) * 3
 ptRGBA\R = tPixel\R
 ptRGBA\G = tPixel\G
 ptRGBA\B = tPixel\B
EndMacro

Procedure.i AllocateImageData (nImage, *BufferPitch.Integer, FillColor = -1)
 Protected *ImageMem, *AllocMem, BufferPitch

 StartDrawing(ImageOutput(nImage))
   
  *ImageMem = DrawingBuffer()
  BufferPitch = DrawingBufferPitch()

   If FillColor <> -1
    Select ImageDepth(nImage)
        Case 24
            Box(0, 0, ImageWidth(nImage), ImageHeight(nImage), FillColor)
        Case 32
            DrawingMode(#PB_2DDrawing_AlphaChannel)   
            Box(0, 0, ImageWidth(nImage), ImageHeight(nImage), $00) ; full transparent
    EndSelect
  EndIf

  *AllocMem = AllocateMemory(BufferPitch * ImageHeight(nImage))

  If *AllocMem
    CopyMemory(*ImageMem, *AllocMem, MemorySize(*AllocMem))
    *BufferPitch\i = BufferPitch
  Else
    *BufferPitch\i = 0
  EndIf

 StopDrawing()

 ProcedureReturn *AllocMem
EndProcedure

Procedure CopyImageData (nImage, *DestMem)
 StartDrawing(ImageOutput(nImage))
  CopyMemory(*DestMem, DrawingBuffer(), MemorySize(*DestMem))
 StopDrawing()
EndProcedure

Procedure.i RotateImage (nSrcImage, DegRot)

; Rotate 24 bit images at (+/-) 90/180/270 degrees

; Rotate 32 bit images at (+/-) 90/180/270 degrees preserving the alpha-channel
 
 Protected *tRGBAs.T_RGBA, *tRGBAd.T_RGBA, tPixel.T_RGBA, RotType
 Protected *SrcMem, *DestMem, BufferPitchSrc, BufferPitchDest
 Protected SrcWidth, SrcHeight, DestWidth, DestHeight, nDestImage
 Protected X, Y, Xs, Ys
 Protected BitPlanes
 
 ; sanity checks
 
 If IsImage(nSrcImage) = 0
    ProcedureReturn 0
 EndIf

 BitPlanes = ImageDepth(nSrcImage)

 If BitPlanes <> 24 And BitPlanes <> 32
    ProcedureReturn 0
 EndIf

 If DegRot % 90
    ProcedureReturn 0
 EndIf

 DegRot % 360

 If DegRot = 0
    ProcedureReturn CopyImage(nSrcImage, #PB_Any)
 EndIf

 CompilerIf (#PB_Compiler_OS = #PB_OS_Linux)
  DegRot = -DegRot
 CompilerEndIf

 SrcWidth = ImageWidth(nSrcImage)
 SrcHeight = ImageHeight(nSrcImage)

 Select DegRot            
    Case 90, -270
        DestWidth = SrcHeight
        DestHeight = SrcWidth
        RotType = 1
    Case 180, -180
        RotType = 2
        DestWidth = SrcWidth
        DestHeight = SrcHeight
    Case 270, -90
        RotType = 3
        DestWidth = SrcHeight
        DestHeight = SrcWidth
 EndSelect

 ; create 24/32 bit destination image
 nDestImage = CreateImage(#PB_Any, DestWidth, DestHeight, BitPlanes)
 TRY (nDestImage)

 ; copy src image to allocated memory
 *SrcMem = AllocateImageData(nSrcImage, @BufferPitchSrc)
 TRY (*SrcMem, #EXC_ALLOC_ERR)

 ; copy dest image to allocated memory
 *DestMem = AllocateImageData(nDestImage, @BufferPitchDest)
 TRY (*DestMem, #EXC_ALLOC_ERR)

 Select BitPlanes
    Case 24
        For Y = 0 To DestHeight - 1
            For X = 0 To DestWidth - 1
           
                Select RotType
                    Case 1
                        Ys = SrcHeight - X - 1
                        Xs = Y
                    Case 2
                        Ys = SrcHeight - Y - 1
                        Xs = SrcWidth - X - 1   
                    Case 3
                        Ys = X
                        Xs = SrcWidth - Y - 1
                EndSelect

                CopyPixel24 (Xs, Ys, X, Y, BufferPitchSrc, BufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)
                           
            Next
         Next   
    Case 32
        For Y = 0 To DestHeight - 1
            For X = 0 To DestWidth - 1
           
                Select RotType
                    Case 1
                        Ys = SrcHeight - X - 1
                        Xs = Y
                    Case 2
                        Ys = SrcHeight - Y - 1
                        Xs = SrcWidth - X - 1   
                    Case 3
                        Ys = X
                        Xs = SrcWidth - Y - 1
                EndSelect

                CopyPixel32 (Xs, Ys, X, Y, BufferPitchSrc, BufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)                   
                           
            Next
         Next   
 EndSelect

 CopyImageData(nDestImage, *DestMem)
   
 FreeMemory(*SrcMem)
 FreeMemory(*DestMem)

 ProcedureReturn nDestImage

 CATCH()

 If EXCEPTION = #EXC_ALLOC_ERR
    If *SrcMem <> 0 : FreeMemory(*SrcMem) : EndIf
    If *DestMem <> 0 : FreeMemory(*DestMem) : EndIf    
    FreeImage(nDestImage)
 EndIf
 
 ProcedureReturn 0
EndProcedure


Procedure.i RotateImageFree (nSrcImage, fDegRot.f, flgAntiAliasing, FillColor = $ffffff)
; Rotates 24 bit images at any angle optionally with anti-aliasing filling the new area of the resulting image with the specified color.

; Rotates 32 bit images at any angle optionally with anti-aliasing preserving the alpha-channel.
 
 Protected *tRGBAs.T_RGBA, *tRGBAd.T_RGBA, tPixel.T_RGBA
 Protected *SrcMem, *DestMem, BufferPitchSrc, BufferPitchDest
 Protected fzCos.f, fzSin.f
 Protected SrcWidth, SrcHeight, DestWidth, DestHeight, BitPlanes, nDestImage
 Protected X, Y, Xs, Ys, Xc1, Yc1, Xc2, Yc2, iColor

 ; sanity checks
 If IsImage(nSrcImage) = 0
    ProcedureReturn 0
 EndIf

 BitPlanes = ImageDepth(nSrcImage)

 If BitPlanes <> 24 And BitPlanes <> 32
    ProcedureReturn 0
 EndIf

 If fDegRot >= 360.0 ; wrap it
    fDegRot = 360.0 * (fDegRot / 360.0 - Int(fDegRot / 360.0))
 EndIf

 If fDegRot = 0.0 Or fDegRot = 90.0 Or fDegRot = 180.0 Or fDegRot = 270.0
    ProcedureReturn RotateImage(nSrcImage, fDegRot)
 EndIf
 
 CompilerIf (#PB_Compiler_OS = #PB_OS_Linux)
 fDegRot = -fDegRot
 CompilerEndIf

 fzCos = Cos(Radian(fDegRot))
 fzSin = Sin(Radian(fDegRot)) 

 SrcWidth = ImageWidth(nSrcImage)
 SrcHeight = ImageHeight(nSrcImage)

 DestWidth = Round(SrcWidth * Abs(fzCos) + SrcHeight * Abs(fzSin), #PB_Round_Up)
 DestHeight = Round(SrcHeight * Abs(fzCos) + SrcWidth * Abs(fzSin), #PB_Round_Up)

 Xc1 = SrcWidth / 2
 Yc1 = SrcHeight / 2
 Xc2 = DestWidth / 2
 Yc2 = DestHeight / 2

 ; create 24/32 bit destination image
 nDestImage = CreateImage(#PB_Any, DestWidth, DestHeight, BitPlanes)
 TRY (nDestImage)

 ; copy src image to allocated memory
 *SrcMem = AllocateImageData (nSrcImage, @BufferPitchSrc)
 TRY (*SrcMem, #EXC_ALLOC_ERR)

 ; copy dest image to allocated memory and fill with backcolor
 *DestMem = AllocateImageData(nDestImage, @BufferPitchDest, FillColor)
 TRY (*DestMem, #EXC_ALLOC_ERR)
     
 Select flgAntiAliasing

    Case #False   
   
        Select BitPlanes
            Case 24
     
                For Y = 0 To DestHeight - 1
                    For X = 0 To DestWidth - 1
                   
                        ; For each nDestImage point find rotated nSrcImage source point
                        Xs = Xc1 + (X - Xc2) * fzCos + (Y - Yc2) * fzSin 
                        Ys = Yc1 + (Y - Yc2) * fzCos - (X - Xc2) * fzSin 
                                   
                        If Xs >= 0 And Xs < SrcWidth  And Ys >= 0 And Ys < SrcHeight
                            ; Move valid rotated nSrcImage source points to nDestImage                   
                            CopyPixel24 (Xs, Ys, X, Y, BufferPitchSrc, BufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)
                        EndIf
                                   
                    Next
                Next   
           
            Case 32
           
                For Y = 0 To DestHeight - 1
                    For X = 0 To DestWidth - 1
                        ; For each nDestImage point find rotated nSrcImage source point
                        Xs = Xc1 + (X - Xc2) * fzCos + (Y - Yc2) * fzSin 
                        Ys = Yc1 + (Y - Yc2) * fzCos - (X - Xc2) * fzSin 
                                   
                        If Xs >= 0 And Xs < SrcWidth  And Ys >= 0 And Ys < SrcHeight
                            ; Move valid rotated nSrcImage source points to nDestImage                   
                            CopyPixel32 (Xs, Ys, X, Y, BufferPitchSrc, BufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)
                        EndIf
                                   
                    Next
                Next   
                                     
        EndSelect
         
    Case #True
    
        Protected Xs0, Ys0, icr, icg, icb, icr0, icg0, icb0, icr1, icg1, icb1
        Protected fXs.f, fYs.f, fXfs1.f, fYfs1.f
        Protected fXfs1less.f, fYfs1less.f

        Select BitPlanes
           
            Case 24
           
                For Y = 0 To DestHeight - 1
                    For X = 0 To DestWidth - 1
           
                        ; For each nDestImage point find rotated nSrcImage source point
                        fXs = Xc1 + (X - Xc2) * fzCos + (Y - Yc2) * fzSin 
                        fYs = Yc1 + (Y - Yc2) * fzCos - (X - Xc2) * fzSin  
             
                        ; Bottom left coords of bounding floating point rectangle on nSrcImage
                        Xs0 = Int(fXs)   
                        Ys0 = Int(fYs)
                           
                        If Xs0 >= 0 And Xs0 < SrcWidth - 1 And Ys0 >= 0 And Ys0 < SrcHeight - 1
                            fXfs1 = fXs - Int(fXs)
                            fYfs1 = fYs - Int(fYs)
       
                            fXfs1less = 1 - fXfs1 - 0.000005 : If fXfs1less < 0 : fXfs1less = 0 : EndIf
                            fYfs1less = 1 - fYfs1 - 0.000005 : If fYfs1less < 0 : fYfs1less = 0 : EndIf
                                                                     
                            ReadPixel24 (Xs0, Ys0, BufferPitchSrc, *tRGBAs, *SrcMem)                                                                                                                               
                            icr = *tRGBAs\R * fXfs1less
                            icg = *tRGBAs\G * fXfs1less
                            icb = *tRGBAs\B * fXfs1less
                               
                            ReadPixel24 (Xs0 + 1, Ys0, BufferPitchSrc, *tRGBAs, *SrcMem)
                            icr0 = *tRGBAs\R * fXfs1 + icr
                            icg0 = *tRGBAs\G * fXfs1 + icg
                            icb0 = *tRGBAs\B * fXfs1 + icb
                                 
                            ReadPixel24 (Xs0, Ys0 + 1, BufferPitchSrc, *tRGBAs, *SrcMem)
                            icr = *tRGBAs\R * fXfs1less
                            icg = *tRGBAs\G * fXfs1less
                            icb = *tRGBAs\B * fXfs1less
                                                                       
                            ReadPixel24 (Xs0 + 1, Ys0 + 1, BufferPitchSrc, *tRGBAs, *SrcMem)
                            icr1 = *tRGBAs\R * fXfs1 + icr
                            icg1 = *tRGBAs\G * fXfs1 + icg
                            icb1 = *tRGBAs\B * fXfs1 + icb
                   
                            ; Weight along axis Y
                            tPixel\R = fYfs1less * icr0 + fYfs1 * icr1
                            tPixel\G = fYfs1less * icg0 + fYfs1 * icg1
                            tPixel\B = fYfs1less * icb0 + fYfs1 * icb1                           
                           
                            WritePixel24 (tPixel, X, Y, BufferPitchDest, *tRGBAd, *DestMem)                               
                        EndIf           
                    Next
                Next           
           
            Case 32

                Protected ica, ica0, ica1
               
                For Y = 0 To DestHeight - 1
                    For X = 0 To DestWidth - 1
           
                        ; For each nDestImage point find rotated nSrcImage source point
                        fXs = Xc1 + (X - Xc2) * fzCos + (Y - Yc2) * fzSin 
                        fYs = Yc1 + (Y - Yc2) * fzCos - (X - Xc2) * fzSin 
             
                        ; Bottom left coords of bounding floating point rectangle on nSrcImage
                        Xs0 = Int(fXs)   
                        Ys0 = Int(fYs)                       

                        If Xs0 >= 0 And Xs0 < SrcWidth - 1 And Ys0 >= 0 And Ys0 < SrcHeight - 1
                       
                            fXfs1 = fXs - Int(fXs)
                            fYfs1 = fYs - Int(fYs)
       
                            fXfs1less = 1 - fXfs1 - 0.000005 : If fXfs1less < 0 : fXfs1less = 0 : EndIf
                            fYfs1less = 1 - fYfs1 - 0.000005 : If fYfs1less < 0 : fYfs1less = 0 : EndIf
                                                                     
                            ReadPixel32 (Xs0, Ys0, BufferPitchSrc, *tRGBAs, *SrcMem)                                                                                                                                                       
                            icr = *tRGBAs\R * fXfs1less
                            icg = *tRGBAs\G * fXfs1less
                            icb = *tRGBAs\B * fXfs1less
                            ica = *tRGBAs\A * fXfs1less
                                                           
                            ReadPixel32 (Xs0 + 1, Ys0, BufferPitchSrc, *tRGBAs, *SrcMem)
                            icr0 = *tRGBAs\R * fXfs1 + icr
                            icg0 = *tRGBAs\G * fXfs1 + icg
                            icb0 = *tRGBAs\B * fXfs1 + icb
                            ica0 = *tRGBAs\A * fXfs1 + ica
                                                             
                            ReadPixel32 (Xs0, Ys0 + 1, BufferPitchSrc, *tRGBAs, *SrcMem)
                            icr = *tRGBAs\R * fXfs1less
                            icg = *tRGBAs\G * fXfs1less
                            icb = *tRGBAs\B * fXfs1less
                            ica = *tRGBAs\A * fXfs1less
                                                                                                   
                            ReadPixel32 (Xs0 + 1, Ys0 + 1, BufferPitchSrc, *tRGBAs, *SrcMem)
                            icr1 = *tRGBAs\R * fXfs1 + icr
                            icg1 = *tRGBAs\G * fXfs1 + icg
                            icb1 = *tRGBAs\B * fXfs1 + icb
                            ica1 = *tRGBAs\A * fXfs1 + ica
                                               
                            ; Weight along axis Y
                            tPixel\R = fYfs1less * icr0 + fYfs1 * icr1
                            tPixel\G = fYfs1less * icg0 + fYfs1 * icg1
                            tPixel\B = fYfs1less * icb0 + fYfs1 * icb1
                            tPixel\A = fYfs1less * ica0 + fYfs1 * ica1                           
                           
                            WritePixel32 (tPixel, X, Y, BufferPitchDest, *tRGBAd, *DestMem)
                        EndIf           
                    Next
                Next           

        EndSelect
 EndSelect

 CopyImageData(nDestImage, *DestMem)
   
 FreeMemory(*SrcMem)
 FreeMemory(*DestMem)

 ProcedureReturn nDestImage

 CATCH()

 If EXCEPTION = #EXC_ALLOC_ERR
    If *SrcMem <> 0 : FreeMemory(*SrcMem) : EndIf
    If *DestMem <> 0 : FreeMemory(*DestMem) : EndIf
    FreeImage(nDestImage)
 EndIf
 
 ProcedureReturn 0
EndProcedure


Procedure.i FlipImage (nSrcImage)

; Flip vertically a 24/32 bit image preserving the alpha-channel

 Protected *tRGBAs.T_RGBA, *tRGBAd.T_RGBA, tPixel.T_RGBA, RotType
 Protected *SrcMem, *DestMem, BufferPitchSrc, BufferPitchDest
 Protected SrcWidth, SrcHeight, DestWidth, DestHeight, nDestImage
 Protected X, Y
 Protected BitPlanes
   
 ; sanity checks
 If IsImage(nSrcImage) = 0
    ProcedureReturn 0
 EndIf

 BitPlanes = ImageDepth(nSrcImage)

 If BitPlanes <> 24 And BitPlanes <> 32
    ProcedureReturn 0
 EndIf

 SrcWidth = ImageWidth(nSrcImage)
 SrcHeight = ImageHeight(nSrcImage)

 DestWidth = SrcWidth
 DestHeight = SrcHeight

 ; create 24/32 bit destination image
 nDestImage = CreateImage(#PB_Any, DestWidth, DestHeight, BitPlanes)
 TRY (nDestImage)

 ; copy src image to allocated memory
 *SrcMem = AllocateImageData(nSrcImage, @BufferPitchSrc)
 TRY (*SrcMem, #EXC_ALLOC_ERR)

 ; copy dest image to allocated memory
 *DestMem = AllocateImageData(nDestImage, @BufferPitchDest)
 TRY (*DestMem, #EXC_ALLOC_ERR)

 Select BitPlanes
    Case 24
        For Y = 0 To DestHeight - 1
            For X = 0 To DestWidth - 1           
                CopyPixel24 (X, SrcHeight - Y - 1, X, Y, BufferPitchSrc, BufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)
            Next
         Next       
    Case 32
        For Y = 0 To DestHeight - 1
            For X = 0 To DestWidth - 1           
                CopyPixel32 (X, SrcHeight - Y - 1, X, Y, BufferPitchSrc, BufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)
            Next
         Next   
 EndSelect

 CopyImageData(nDestImage, *DestMem)

 FreeMemory(*SrcMem)
 FreeMemory(*DestMem)

 ProcedureReturn nDestImage

 CATCH()

 If EXCEPTION = #EXC_ALLOC_ERR
    If *SrcMem <> 0 : FreeMemory(*SrcMem) : EndIf
    If *DestMem <> 0 : FreeMemory(*DestMem) : EndIf    
    FreeImage(nDestImage) 
 EndIf
 
 ProcedureReturn 0
EndProcedure

Procedure.i MirrorImage (nSrcImage)

; Mirror horizontally a 24/32 bit image preserving the alpha-channel

 Protected *tRGBAs.T_RGBA, *tRGBAd.T_RGBA, tPixel.T_RGBA, RotType
 Protected *SrcMem, *DestMem, BufferPitchSrc, BufferPitchDest, BitPlanes
 Protected SrcWidth, SrcHeight, DestWidth, DestHeight, nDestImage
 Protected X, Y

 ; sanity checks
 If IsImage(nSrcImage) = 0
    ProcedureReturn 0
 EndIf

 BitPlanes = ImageDepth(nSrcImage)

 If BitPlanes <> 24 And BitPlanes <> 32
    ProcedureReturn 0
 EndIf

 SrcWidth = ImageWidth(nSrcImage)
 SrcHeight = ImageHeight(nSrcImage)

 DestWidth = SrcWidth
 DestHeight = SrcHeight

 ; create 24/32 bit destination image
 nDestImage = CreateImage(#PB_Any, DestWidth, DestHeight, BitPlanes)
 TRY (nDestImage)

 ; copy src image to allocated memory
 *SrcMem = AllocateImageData(nSrcImage, @BufferPitchSrc)
 TRY (*SrcMem, #EXC_ALLOC_ERR)

 ; copy dest image to allocated memory
 *DestMem = AllocateImageData(nDestImage, @BufferPitchDest)
 TRY (*DestMem, #EXC_ALLOC_ERR)

 Select BitPlanes
    Case 24
        For Y = 0 To DestHeight - 1
            For X = 0 To DestWidth - 1
                CopyPixel24 (SrcWidth - X - 1, Y, X, Y, BufferPitchSrc, BufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)     
            Next
        Next   
    Case 32
        For Y = 0 To DestHeight - 1
            For X = 0 To DestWidth - 1
                CopyPixel32 (SrcWidth - X - 1, Y, X, Y, BufferPitchSrc, BufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)     
            Next
        Next   
 EndSelect

 CopyImageData(nDestImage, *DestMem)

 FreeMemory(*SrcMem)
 FreeMemory(*DestMem)

 ProcedureReturn nDestImage
   
 CATCH()

 If EXCEPTION = #EXC_ALLOC_ERR   
    If *SrcMem <> 0 : FreeMemory(*SrcMem) : EndIf
    If *DestMem <> 0 : FreeMemory(*DestMem) : EndIf        
    FreeImage(nDestImage) 
 EndIf

 ProcedureReturn 0
EndProcedure
Last edited by luis on Wed Apr 20, 2022 8:09 pm, edited 13 times in total.
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Post by luis »

[REMOVED]
Last edited by luis on Fri Aug 29, 2014 11:53 pm, edited 5 times in total.
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Post by luis »

TEST 1

Code: Select all

; TEST - RotateImageFree

EnableExplicit

IncludeFile "..\RotateImage.pb"

Procedure.i CreateCheckers (Width, Height)
 Protected x, y
 Protected nImage, Size = 12
   
 nImage = CreateImage(#PB_Any, Width, Height, 24)
   
 If nImage
    StartDrawing(ImageOutput(nImage))
      
     Box(0, 0, Width, Height, $FFFFFF)
      
     While y < Height + Size
         While x < Width + Size
            Box(x, y, Size, Size, $C0C0C0)
            Box(x + Size, y + Size, Size, Size, $C0C0C0)
            x + Size * 2
         Wend
         x = 0
         y + Size * 2
     Wend
      
    StopDrawing()
 EndIf
   
 ProcedureReturn nImage
EndProcedure

Procedure.i MergeImages (nImgCheck, nImgOut)
 Protected nImage
   
 nImage = CreateImage(#PB_Any, ImageWidth(nImgCheck), ImageHeight(nImgCheck), 24)
   
 If nImage
    StartDrawing(ImageOutput(nImage))
      
     DrawImage(ImageID(nImgCheck), 0, 0)
     DrawAlphaImage(ImageID(nImgOut), 0, 0)
      
    StopDrawing()
 EndIf
   
 ProcedureReturn nImage   
EndProcedure


#WIN = 1
#IMG = 1

#IW = 640
#IH = 480

Define Event, Time1, k
Define nImgSrc, nImgOut, nFont
Define nImgCheck, nImgDisp


; ****************************************************************
; try to change these vars to check out the various combinations *
; ****************************************************************

Define BitPlanes = 32                   ; try 24 or 32 bpp image
Define flgAntiAlias = 1                 ; try antialias (1) or simpler rotation (0)
Define fRot = 33                        ; angle used for rotation
Define FillColor = RGB(255,255,255)     ; useful for transparent masking using GDI or simply to fill the background

nFont = LoadFont(#PB_Any, "Arial", 12, #PB_Font_Bold)

nImgSrc = CreateImage(#PB_Any, #IW, #IH, BitPlanes)

StartDrawing(ImageOutput(nImgSrc))

For k = 0 To 100 Step 20
   Box(0 + k, 0 + k, #IW - k * 2, #IH - k * 2, RGB(k + 50, k + 100, k + 150))
Next

DrawingMode(#PB_2DDrawing_Transparent)
DrawingFont(FontID(nFont))

For k = 0 To #IH Step #IH / 10
   DrawText(20, k + 10, "TEXT - " + Str(BitPlanes) + " BPP IMAGE - TEXT", RGB(k/3, k/2, k/2))
Next

If BitPlanes = 24
   DrawingMode(#PB_2DDrawing_Default)
   Circle(#IW - 200, 100, 50, RGB(128,0,0))
   Circle(#IW - 200, #IH - 100, 50, RGB(0,128,0))   
   Circle(#IW - 80, #IH/2, 50, RGB(0,0,128))
Else
   FillColor = 0
   
   ; make 3 holes
   DrawingMode(#PB_2DDrawing_AlphaChannel)
   Circle(#IW - 200, 100, 50,  $00)
   Circle(#IW - 200, #IH - 100, 50, $00)
   Circle(#IW - 80, #IH/2, 50, $00)
   
   ; fill them with semi-transparent circles
   DrawingMode(#PB_2DDrawing_AlphaBlend)
   Circle(#IW - 200, 100, 50, RGBA(128,0,0,100))
   Circle(#IW - 200, #IH - 100, 50, RGBA(0,128,0,100))
   Circle(#IW - 80, #IH/2, 50, RGBA(0,0,128,100))
EndIf

StopDrawing()

If OpenWindow(#WIN, 0, 0, 800, 800, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)   
   Time1 = ElapsedMilliseconds()
   
   nImgOut = RotateImageFree(nImgSrc, fRot, flgAntiAlias, FillColor)
   
   Time1 = ElapsedMilliseconds() - Time1
   
   SetWindowTitle(#WIN, "Test " + Str(BitPlanes) + " bit, AA = " + Str(flgAntiAlias) + ", msec = " + Str(Time1) + ", deg = " + StrF(fRot,1))
   
   nImgCheck = CreateCheckers(ImageWidth(nImgOut), ImageHeight(nImgOut))
   nImgDisp = MergeImages(nImgCheck, nImgOut)
      
   FreeImage(nImgOut)
   FreeImage(nImgCheck)
      
  
   ImageGadget(#IMG, 0, 0, 0, 0, ImageID(nImgDisp))
   
   FreeImage(nImgDisp)
   
   While Event <> #PB_Event_CloseWindow           
      Event = WaitWindowEvent()
   Wend
      
   FreeImage(nImgSrc)
   FreeFont(nFont)
EndIf
Last edited by luis on Fri Aug 29, 2014 11:53 pm, edited 1 time in total.
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Post by luis »

TEST 2

Code: Select all

; TEST - RotateImage

EnableExplicit

IncludeFile "..\RotateImage.pb"

Procedure.i CreateCheckers (Width, Height)
 Protected x, y
 Protected nImage, Size = 12
 
 nImage = CreateImage(#PB_Any, Width, Height, 24)
 
 If nImage
    StartDrawing(ImageOutput(nImage))
    
     Box(0, 0, Width, Height, $FFFFFF)
     
     While y < Height + Size
        While x < Width + Size
            Box(x, y, Size, Size, $C0C0C0)
            Box(x + Size, y + Size, Size, Size, $C0C0C0)
            x + Size * 2
        Wend
        x = 0
        y + Size * 2
     Wend
     
    StopDrawing()
 EndIf
 
 ProcedureReturn nImage 
EndProcedure

Procedure.i MergeImages (nImgCheck, nImgOut)
 Protected nImage
 
 nImage = CreateImage(#PB_Any, ImageWidth(nImgCheck), ImageHeight(nImgCheck), 24)

 If nImage
    StartDrawing(ImageOutput(nImage))
    
     DrawImage(ImageID(nImgCheck), 0, 0)
     DrawAlphaImage(ImageID(nImgOut), 0, 0)
          
    StopDrawing()
 EndIf
 
 ProcedureReturn nImage 

EndProcedure


#WIN = 1
#IMG = 1

#IW = 640
#IH = 480

Define Event, Time1, k
Define nImgSrc, nImgOut, nFont
Define nImgCheck, nImgDisp


; ****************************************************************
; try to change these vars to check out the various combinations *
; ****************************************************************

Define BitPlanes = 32          ; test 24 or 32 bpp image
Define Rot = 90                ; angle used for rotation (+/-) 90, 180, 270


nFont = LoadFont(#PB_Any, "Arial", 14, #PB_Font_Bold)

nImgSrc = CreateImage(#PB_Any, #IW, #IH, BitPlanes)
       
StartDrawing(ImageOutput(nImgSrc))
 
 For k = 0 To 100 Step 20
    Box(0 + k, 0 + k, #IW - k * 2, #IH - k * 2, RGB(k + 50, k + 100, k + 150)) 
 Next

 DrawingMode(#PB_2DDrawing_Transparent)
 DrawingFont(FontID(nFont))
 
 For k = 0 To #IH Step #IH / 10
    DrawText(20, k + 8, "Text - " + Str(BitPlanes) + " BPP image - Text", RGB(k/3, k/2, k/2))
 Next

 If BitPlanes = 24        
    DrawingMode(#PB_2DDrawing_Default)                
    Circle(#IW - 200, 100, 50, RGB(128,0,0))
    Circle(#IW - 200, #IH - 100, 50, RGB(0,128,0))    
    Circle(#IW - 80, #IH/2, 50, RGB(0,0,128))        
 Else
    ; make 3 holes
    DrawingMode(#PB_2DDrawing_AlphaChannel)    
    Circle(#IW - 80, #IH/2, 50, $00)
    Circle(#IW - 200, 100, 50,  $00)
    Circle(#IW - 200, #IH - 100, 50, $00)
    
    ; fill them with semi-transparent circles
    DrawingMode(#PB_2DDrawing_AlphaBlend)
    Circle(#IW - 200, 100, 50, RGBA(128,0,0,100))
    Circle(#IW - 200, #IH - 100, 50, RGBA(0,128,0,100))
    Circle(#IW - 80, #IH/2, 50, RGBA(0,0,128,100))        
 EndIf
  
StopDrawing() 

    
; UsePNGImageDecoder()
; nImgSrc = LoadImage(#PB_Any, "test32.png")


If OpenWindow(#WIN, 0, 0, 600, 700, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered) 
            
    Time1 = ElapsedMilliseconds()          
    nImgOut = RotateImage (nImgSrc, Rot)
    Time1 = ElapsedMilliseconds() - Time1
    
    If nImgOut = 0 : MessageRequester("Test", "Invalid angle ? (" + Str(Rot) + ")") :End: EndIf
    
    SetWindowTitle(#WIN, "Test " + Str(BitPlanes) + " bit, msec = " + Str(Time1) + ", deg = " + Str(Rot))

    nImgCheck = CreateCheckers(ImageWidth(nImgOut), ImageHeight(nImgOut))
    nImgDisp = MergeImages(nImgCheck, nImgOut)
        
    FreeImage(nImgOut)
    FreeImage(nImgCheck)
        
    ImageGadget(#IMG, 0, 0, 0, 0, ImageID(nImgDisp), #PB_Image_Border)
    
    While Event <> #PB_Event_CloseWindow            
        Event = WaitWindowEvent()
    Wend
                
    FreeImage(nImgDisp)
    FreeImage(nImgSrc)
    FreeFont(nFont)
EndIf

Last edited by luis on Fri Aug 29, 2014 11:54 pm, edited 1 time in total.
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Post by luis »

TEST 3

Code: Select all

; TEST - Flip and Mirror

EnableExplicit

IncludeFile "..\RotateImage.pb"

Procedure.i CreateCheckers (Width, Height)
 Protected x, y
 Protected nImage, Size = 12
 
 nImage = CreateImage(#PB_Any, Width, Height, 24)
 
 If nImage
    StartDrawing(ImageOutput(nImage))
    
     Box(0, 0, Width, Height, $FFFFFF)
     
     While y < Height + Size
        While x < Width + Size
            Box(x, y, Size, Size, $C0C0C0)
            Box(x + Size, y + Size, Size, Size, $C0C0C0)
            x + Size*2           
        Wend
        x = 0
        y + Size*2
     Wend
     
    StopDrawing()
 EndIf
 
 ProcedureReturn nImage 
EndProcedure

Procedure.i MergeImages (nImgCheck, nImgOut)
 Protected nImage
 
 nImage = CreateImage(#PB_Any, ImageWidth(nImgCheck), ImageHeight(nImgCheck), 24)

 If nImage
    StartDrawing(ImageOutput(nImage))
    
     DrawImage(ImageID(nImgCheck), 0, 0)
     DrawAlphaImage(ImageID(nImgOut), 0, 0)
          
    StopDrawing()
 EndIf
 
 ProcedureReturn nImage 

EndProcedure


#WIN   = 1
#IMG_1 = 1
#IMG_2 = 2

#IW = 512
#IH = 360

Define Event, Time1, k, Title$
Define nImgSrc, nImgOut, nFont
Define nImgCheck, nImgDisp1, nImgDisp2


; ****************************************************************
; try to change these vars to check out the various combinations *
; ****************************************************************

Define BitPlanes = 32          ; test 24 or 32 bpp image
Define flgFlipOrMirror = 0     ; 1 = FlipImage(),  0 = MirrorImage()


nFont = LoadFont(#PB_Any, "Arial", 12, #PB_Font_Bold)

nImgSrc = CreateImage(#PB_Any, #IW, #IH, BitPlanes)
       
StartDrawing(ImageOutput(nImgSrc))
 
 For k = 0 To 100 Step 20
    Box(0 + k, 0 + k, #IW - k * 2, #IH - k * 2, RGB(k + 50, k + 100, k + 150)) 
 Next

 DrawingMode(#PB_2DDrawing_Transparent)
 DrawingFont(FontID(nFont))
 
 For k = 0 To #IH Step #IH / 10
    DrawText(20, k + 8, "Text - " + Str(BitPlanes) + " BPP image - Text", RGB(k/3, k/2, k/2))
 Next

 If BitPlanes = 24
    DrawingMode(#PB_2DDrawing_Default)                
    Circle(#IW - 180, 100, 50, RGB(128,0,0))
    Circle(#IW - 180, #IH - 100, 50, RGB(0,128,0))    
    Circle(#IW - 80, #IH/2, 50, RGB(0,0,128))        
 Else
    ; make 3 holes
    DrawingMode(#PB_2DDrawing_AlphaChannel)        
    Circle(#IW - 180, 100, 50,  $00)
    Circle(#IW - 180, #IH - 100, 50, $00)
    Circle(#IW - 80, #IH/2, 50, $00)
    
    ; fill them with semi-transparent circles
    DrawingMode(#PB_2DDrawing_AlphaBlend)
    Circle(#IW - 180, 100, 50, RGBA(128,0,0,100))
    Circle(#IW - 180, #IH - 100, 50, RGBA(0,128,0,100))
    Circle(#IW - 80, #IH/2, 50, RGBA(0,0,128,100))        
 EndIf
  
StopDrawing() 

    
;;UsePNGImageDecoder()    
;;nImgSrc = LoadImage(#PB_Any, "test24.bmp")
;;nImgSrc = LoadImage(#PB_Any, "test32.png")


If OpenWindow(#WIN, 0, 0, 600, 800, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered) 
            
    Time1 = ElapsedMilliseconds()          

    If flgFlipOrMirror = 1        
        nImgOut = FlipImage(nImgSrc)
    Else
        nImgOut = MirrorImage(nImgSrc)
    EndIf   
    
    Time1 = ElapsedMilliseconds() - Time1
    
    If flgFlipOrMirror = 1
        Title$ = "Test FlipImage(), " 
    Else
        Title$ = "Test MirrorImage(), " 
    EndIf   
                       
    SetWindowTitle(#WIN, Title$ + Str(BitPlanes) + " bit, msec = " + Str(Time1))

    nImgCheck = CreateCheckers(ImageWidth(nImgOut), ImageHeight(nImgOut))
    
    nImgDisp1 = MergeImages(nImgCheck, nImgSrc)
        
    nImgDisp2 = MergeImages(nImgCheck, nImgOut)
        
        
    FreeImage(nImgOut)
    FreeImage(nImgCheck)
    
    ImageGadget(#IMG_1, 0, 0, 0, 0, ImageID(nImgDisp1), #PB_Image_Border)    
    ImageGadget(#IMG_2, 0, 400, 0, 0, ImageID(nImgDisp2), #PB_Image_Border)
    
    While Event <> #PB_Event_CloseWindow            
        Event = WaitWindowEvent()
    Wend
    
    FreeImage(nImgDisp1)            
    FreeImage(nImgDisp2)
    FreeImage(nImgSrc)
    FreeFont(nFont)
EndIf

Last edited by luis on Fri Aug 29, 2014 11:55 pm, edited 2 times in total.
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Re: Image Rotation routines for 24/32 bit with optional AA

Post by Seymour Clufley »

Thanks for this, Luis. The results are very impressive!

I've been trying to make the AA code work on its own, but no success so far. Is it dependent on the image being rotated?

In the AA code, I changed these lines:

Code: Select all

For iY = 0 To iDestHeight - 1
            For iX = 0 To iDestWidth - 1
                ; For each nDestImage point find rotated nSrcImage source point
                fXs = iXc1 + (iX - iXc2) * fzCos + (iY - iYc2) * fzSin
                fYs = iYc1 + (iY - iYc2) * fzCos - (iX - iXc2) * fzSin
to

Code: Select all

For iY = 0 To iDestHeight - 1
            For iX = 0 To iDestWidth - 1
                ; For each nDestImage point find rotated nSrcImage source point
                fXs = iX
                fYs = iY
to "cancel out" any rotation, because I just want the antialiasing. However, the image comes out exactly the same as it went in!
eesau
Enthusiast
Enthusiast
Posts: 589
Joined: Fri Apr 27, 2007 12:38 pm
Location: Finland

Re: Image Rotation routines for 24/32 bit with optional AA

Post by eesau »

Seymour Clufley wrote:to "cancel out" any rotation, because I just want the antialiasing. However, the image comes out exactly the same as it went in!
That's because anti-aliasing (sort of) depends on blending adjacent pixels together. Your code blends pixels with themselves only, which results in the same output as the original. What you need to do is check the color values of the surrounding pixels of each pixel in your image and manipulate the central pixel based on those values. One way is to use a weighted average of the pixels. Or you can use PB's own AlphaBlend() function to blend the pixels.

This isn't real anti-aliasing though, it's more of a softening or smoothing filter. It would be nice to have PureBasic support matrix filters for images natively :)
eesau
Enthusiast
Enthusiast
Posts: 589
Joined: Fri Apr 27, 2007 12:38 pm
Location: Finland

Re: Image Rotation routines for 24/32 bit with optional AA

Post by eesau »

Oh and thanks to luis for the code! One note though, I think it could be optimized a lot by moving all of the select/case-blocks out of the loops, and making own loops 24/32 bits (so that you don't have to check the bits for each pixel, which is what slows it down).
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Image Rotation routines for 24/32 bit with optional AA

Post by luis »

Seymour Clufley wrote: I've been trying to make the AA code work on its own, but no success so far. Is it dependent on the image being rotated?
In a sense yes, because destination and source point must slightly differ (their position is calculated not using integers like normal pixel coordinates but using floating point accuracy).

If you modify the code as you did the iXs0 = Int(fXs) will be exactly like iXs0 = fXs, so fXfs1 will be always zero and fXfs1less always 1

Code: Select all

                ; Bottom left coords of bounding floating point rectangle on nSrcImage
                iXs0 = Int(fXs)   
                iYs0 = Int(fYs)
                   
                If iXs0 > 0 And iXs0 < iSrcWidth -1 And iYs0 > 0 And iYs0 < iSrcHeight - 1
                
                    fXfs1 = fXs - Int(fXs)
                    fYfs1 = fYs - Int(fYs)

                    fXfs1less = 1 - fXfs1
                    fYfs1less = 1 - fYfs1
so in the code that follow you can see how the RGB components are not altered and they are exactly as the input source.

The algorithm need to be slightly changed as suggested by eesau, but I don't think the result will be very pleasing. This type of AA is really useful in a otherwise distorted rotated images but not on perfectly fine images.

BTW I'm not very expert in this field !
Last edited by luis on Fri Sep 11, 2009 2:32 pm, edited 1 time in total.
"Have you tried turning it off and on again ?"
A little PureBasic review
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Image Rotation routines for 24/32 bit with optional AA

Post by luis »

eesau wrote:Oh and thanks to luis for the code! One note though, I think it could be optimized a lot by moving all of the select/case-blocks out of the loops, and making own loops 24/32 bits (so that you don't have to check the bits for each pixel, which is what slows it down).
Do you mean separating the code for 24/32 bit in two branches and removing the select-case inside the loop and in the macros ?

If you mean that the impact would be negligible I'm afraid, yes it's true we could have a lot of CMP and JUMP or something like that removed from inside the loop, but the bulk of the time I believe is spent inside the floating point calculations. I don't know, maybe you could have a 1% gain that way. Try if you like! :)
"Have you tried turning it off and on again ?"
A little PureBasic review
eesau
Enthusiast
Enthusiast
Posts: 589
Joined: Fri Apr 27, 2007 12:38 pm
Location: Finland

Re: Image Rotation routines for 24/32 bit with optional AA

Post by eesau »

luis wrote:Do you mean separating the code for 24/32 bit in two branches and removing the select-case inside the loop and in the macros ?

If you mean that the impact would be negligible I'm afraid, yes it's true we could have a lot of CMP and JUMP or something like that removed from inside the loop, but the bulk of the time I believe is spent inside the floating point calculations. I don't know, maybe you could have a 1% gain that way. Try if you like! :)
Yes, that's what I meant. I think it would make a bigger than 1% difference, but the code is already quite fast. Maybe I'll do a speed test when I have the time!

Once again, great code and surely useful! Many thanks 8)
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Re: Image Rotation routines for 24/32 bit with optional AA

Post by Seymour Clufley »

luis wrote:This type of AA is really useful in a otherwise distorted rotated images but not on perfectly fine images.
Right. I'll see if I can implement it with Einander's image distortion (corner pin) code.
BTW I'm not very expert in this field !
Maybe not, but you got the job done!

I'm surprised that normal antialiasing has not been achieved in the PB community. (I've attempted it myself but the maths involved confuse me.)

Thanks again for the code,
Seymour.
Last edited by Seymour Clufley on Fri Sep 11, 2009 3:56 pm, edited 1 time in total.
JACK WEBB: "Coding in C is like sculpting a statue using only sandpaper. You can do it, but the result wouldn't be any better. So why bother? Just use the right tools and get the job done."
User avatar
djes
Addict
Addict
Posts: 1806
Joined: Sat Feb 19, 2005 2:46 pm
Location: Pas-de-Calais, France

Re: Image Rotation routines for 24/32 bit with optional AA

Post by djes »

We did a rotation some times ago. Maybe this code can help...

Here's the code:

Code: Select all

;Fast and clean rotation 
;Original Code : LSI (le soldat inconnu) for his effects library (http://www.purearea.net/pb/download/userlibs/Effect.zip) 
;Optimisation (not fully optimised) by djes (djes@free.fr)
;Not needing assembly inline by psychopanta

;#PI.f=3.14159265 

;******************************************************************************************************
; RotateImageEx2
;>= Angle (in degrees :()
ProcedureDLL.l RotateImageEx2(ImageID, Angle.f)
  Protected bmi.BITMAPINFO, bmi2.BITMAPINFO, Hdc.l, NewImageID, Mem, n, nn, bm.BITMAP 
  
  ;radian conversion
  Debug "Angle : "+Str(angle)
  Angle*#PI/180 
  
  Protected Cos.f = Cos(Angle) 
  Protected Sin.f = Sin(Angle) 
  
  Protected CouleurFond = 0 
  
  GetObject_(ImageID, SizeOf(BITMAP), @bm.BITMAP) 
  
  bmi\bmiHeader\biSize = SizeOf(BITMAPINFOHEADER) 
  bmi\bmiHeader\biWidth = bm\bmWidth 
  bmi\bmiHeader\biHeight = bm\bmHeight 
  bmi\bmiHeader\biPlanes = 1 
  bmi\bmiHeader\biBitCount = 32 
  bmi\bmiHeader\biCompression = #BI_RGB 
  
  bmi2\bmiHeader\biSize = SizeOf(BITMAPINFOHEADER) 
  bmi2\bmiHeader\biWidth = bm\bmWidth * Abs(Cos) + bm\bmHeight * Abs(Sin) 
  bmi2\bmiHeader\biHeight = bm\bmHeight * Abs(Cos) + bm\bmWidth * Abs(Sin) 
  bmi2\bmiHeader\biPlanes = 1 
  bmi2\bmiHeader\biBitCount = 32 
  bmi2\bmiHeader\biCompression = #BI_RGB 
  
  Mem = AllocateMemory(bm\bmWidth * bm\bmHeight * 4) 
  If Mem 
    Protected Mem2 = AllocateMemory(bmi2\bmiHeader\biWidth * bmi2\bmiHeader\biHeight * 4) 
    If Mem2 
      
      ;retrieves the bits of the specified bitmap and copies them into a buffer
      Hdc = CreateCompatibleDC_(GetDC_(ImageID)) 
      If Hdc 
        GetDIBits_(Hdc, ImageID, 0, bm\bmHeight, Mem, @bmi, #DIB_RGB_COLORS)
        ReleaseDC_(0, Hdc) 
      EndIf 
      
      Protected CX1 = bm\bmWidth - 1 
      Protected CY1 = bm\bmHeight - 1 
      Protected CX2 = bmi2\bmiHeader\biWidth - 1 
      Protected CY2 = bmi2\bmiHeader\biHeight - 1 
      
      Protected Mem01 = Mem + bm\bmWidth * 4 
      Protected Mem10 = Mem + 4 
      Protected Mem11 = Mem01 + 4 
      
      Protected Mem2Temp = Mem2 
      Protected deb=-CX2/2
      Protected fin=deb+CX2;= Round(CX2/2,1) but <> (CX2*2-CX2)/2 
          
      For nn = 0 To CY2 

        Protected x1b.l
        Protected y1b.l = (nn * 2) - CY2 

        Protected Temp1.f = (CX1 - (y1b * Sin))/2
        Protected Temp2.f = (CY1 + (y1b * Cos))/2       

        Protected x1.f = Temp1 + (deb * Cos)
        Protected y1.f = Temp2 + (deb * Sin)

        For x1b = deb To fin 
          
          ;could be faster with arrays
          
          Protected x2.l = x1 
          Protected y2.l = y1 
          
          If x1 < x2 
            !dec dword[p.v_x2] 
          EndIf 
          If y1 < y2 
            !dec dword[p.v_y2] 
          EndIf 
          
          Protected x2b.l = x2 + 1 
          Protected y2b.l = y2 + 1 
          
          ;test boundaries
          If x2b >= 0 And x2 <= CX1 And y2b >= 0 And y2 <= CY1  
          
            Protected fx.f = x1 - x2 
            Protected fy.f = y1 - y2 
            Protected f00.f = 1 - fx 
            Protected f10.f = 1 - fy 
            Protected f01.f = f00 * fy 
            f00 * f10 
            f10 * fx 
            Protected f11.f = fx * fy 
          
            Protected MemTemp = (x2 + y2 * bm\bmWidth) * 4 
            Protected c00.l, c01.l, c11.l, c10.l 
            
            If x2 >= 0 And x2 <= CX1 
              If y2 >= 0 And y2 <= CY1 
                !mov eax,dword[p.v_Mem] 
                !add eax,dword[p.v_MemTemp] 
                !mov eax,dword[eax] 
                !mov dword[p.v_c00],eax 
                ;c00 = PeekL(Mem + MemTemp) 
              Else 
                c00 = 0 
              EndIf 
              If y2b >= 0 And y2b <= CY1 
                !mov eax,dword[p.v_Mem01] 
                !add eax,dword[p.v_MemTemp] 
                !mov eax,dword[eax] 
                !mov dword[p.v_c01],eax 
                ;c01 = PeekL(Mem01 + MemTemp) 
              Else 
                c01 = 0 
              EndIf 
            Else 
              c00 = 0 
              c01 = 0 
            EndIf 
            If x2b >= 0 And x2b <= CX1 
              If y2 >= 0 And y2 <= CY1 
                !mov eax,dword[p.v_Mem10] 
                !add eax,dword[p.v_MemTemp] 
                !mov eax,dword[eax] 
                !mov dword[p.v_c10],eax 
                ;c10 = PeekL(Mem10 + MemTemp) 
              Else 
                c10 = 0 
              EndIf 
              If  y2b >= 0 And y2b <= CY1 
                !mov eax,dword[p.v_Mem11] 
                !add eax,dword[p.v_MemTemp] 
                !mov eax,dword[eax] 
                !mov dword[p.v_c11],eax 
                ;c11 = PeekL(Mem11 + MemTemp) 
              Else 
                c11 = 0 
              EndIf 
            Else 
              c10 = 0 
              c11 = 0 
            EndIf 
    
            Protected r1.l,r2.l,r3.l,r4.l,g1.l,g2.l,g3.l,g4.l,b1.l,b2.l,b3.l,b4.l 

            !mov eax,dword[p.v_c00] 
            !mov ebx,eax 
            !mov ecx,eax 

            !and eax,$FF 
            !mov dword[p.v_r1],eax 
            !and ebx,$FF00 
            !mov dword[p.v_g1],ebx 
            !and ecx,$FF0000 
            !mov dword[p.v_b1],ecx 

            !mov eax,dword[p.v_c10] 
            !mov ebx,eax 
            !mov ecx,eax 

            !and eax,$FF 
            !mov dword[p.v_r2],eax 
            !and ebx,$FF00 
            !mov dword[p.v_g2],ebx 
            !and ecx,$FF0000 
            !mov dword[p.v_b2],ecx 

            !mov eax,dword[p.v_c01] 
            !mov ebx,eax 
            !mov ecx,eax 

            !and eax,$FF 
            !mov dword[p.v_r3],eax 
            !and ebx,$FF00 
            !mov dword[p.v_g3],ebx 
            !and ecx,$FF0000 
            !mov dword[p.v_b3],ecx 

            !mov eax,dword[p.v_c11] 
            !mov ebx,eax 
            !mov ecx,eax 

            !and eax,$FF 
            !mov dword[p.v_r4],eax 
            !and ebx,$FF00 
            !mov dword[p.v_g4],ebx 
            !and ecx,$FF0000 
            !mov dword[p.v_b4],ecx 
      
           ;pure knows well how to do this 
            Protected r.l = r1 * f00 + r2 * f10 + r3 * f01 + r4 * f11
            Protected g.l = g1 * f00 + g2 * f10 + g3 * f01 + g4 * f11 
            Protected b.l = b1 * f00 + b2 * f10 + b3 * f01 + b4 * f11 
          
            !mov eax,dword[p.v_r] 
            !mov ebx,dword[p.v_g] 
            !mov ecx,dword[p.v_b]
            ;one trick is to let triplets at the same place to avoid shifting
            !and eax,$FF                                      
            !and ebx,$FF00 
            !and ecx,$FF0000 
            !or eax,ebx 
            !or eax,ecx 

            !mov ebx,dword[p.v_Mem2Temp] 
            !mov dword[ebx],eax 
          
          Else 

            !mov ebx,dword[p.v_Mem2Temp] 
            !xor eax,eax 
            !mov dword[ebx],eax 

          EndIf 
          
          Mem2Temp + 4 
          x1 + cos
          y1 + sin
          
        Next 
      Next 
      
      ; On crée la nouvelle image 
      NewImageID = CreateImage(#PB_Any, bmi2\bmiHeader\biWidth, bmi2\bmiHeader\biHeight) 
      Hdc = CreateCompatibleDC_(GetDC_(ImageID(NewImageID))) 
      If Hdc 
        SetDIBits_(Hdc, ImageID(NewImageID), 0, bmi2\bmiHeader\biHeight, Mem2, @bmi2, #DIB_RGB_COLORS) ; on envoie la liste dans l'image 
        ReleaseDC_(0, Hdc) 
      EndIf 
      
      FreeMemory(Mem2) 
    EndIf 
    FreeMemory(Mem) 
  EndIf 
  
  ProcedureReturn NewImageID 
EndProcedure 

;******************************************************************************************************
;- Speed Test

SetPriorityClass_(GetCurrentProcess_(),#REALTIME_PRIORITY_CLASS) 

#NbTest = 4 

MessageRequester("Speed test",Str(#NbTest)+" rotations of a 1000x1000 image...") 

BigImage = CreateImage(#PB_Any, 1000, 1000) 
If BigImage<>0 
  StartDrawing(ImageOutput(BigImage)) 
    For n = 0 To 999 
      For nn = 0 To 999 
        Plot(n, nn, Random($FFFFFF)) 
      Next 
    Next 
  StopDrawing() 
  
  Dim Liste(ImageWidth(BigImage) - 1, ImageHeight(BigImage) - 1) 
  
  Time1 = ElapsedMilliseconds() 
  
  For n = 1 To #NbTest 
    DestImage = RotateImageEx2(ImageID(BigImage), 30) 
  Next 
  
  Time2 = ElapsedMilliseconds() 
  
  Speed = (Time2 - Time1) / #NbTest 
  NbPixels = ImageHeight(BigImage) * ImageWidth(BigImage) 
  
  MessageRequester("Results", "Average speed of "+Str(Speed) + " ms" + Chr(10) + "Image of " + Str(ImageWidth(BigImage)) + " * " + Str(ImageHeight(BigImage)) + " pixels" + Chr(10) + "Pixels nb = " + Str(NbPixels), 0) 
EndIf 

;******************************************************************************************************
;- Drawing test 

SrcImage = CreateImage(#PB_Any, 80, 100) 

If SrcImage<>0 
  StartDrawing(ImageOutput(SrcImage)) 
    Box(0, 0, 80, 100, $6F6F6F) 
    Box(5, 5, 35, 45, $FF) 
    Box(40, 5, 35, 45, $FF00) 
    Box(5, 50, 35, 45, $FF0000) 
    Box(40, 50, 35, 45, $FFFFFF) 
  StopDrawing() 
  
  RotImage = RotateImageEx2(ImageID(SrcImage), u)  

EndIf 

SetPriorityClass_(GetCurrentProcess_(),#NORMAL_PRIORITY_CLASS) 

; Création de la fenêtre et de la GadgetList 
If OpenWindow(0, 0, 0, 250, 300, "Effect - Image rotation", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_MinimizeGadget) = 0 Or CreateGadgetList(WindowID(0)) = 0 
  End 
EndIf 

TextGadget(#PB_Any, 10, 10, 100, 15, "Src Image") 
ImageGadget(#PB_Any, 10, 25, 0, 0, ImageID(SrcImage)) 

TextGadget(1, 10, 135, 100, 15, "Rotation") 
ImageGadget(2, 10, 150, 0, 0, ImageID(RotImage)) 

angle = 1

Repeat

  Event = WindowEvent() 
  Delay(20)

  RotImage1 = ImageID(RotateImageEx2(ImageID(SrcImage), angle))

  SetGadgetText(1, "Rotation : " + Str(angle))
  SetGadgetState(2, RotImage1)

  If IsImage(RotImage2) : FreeImage(RotImage2) : Delay(20) : EndIf

  angle + 1

  RotImage2 = ImageID(RotateImageEx2(ImageID(SrcImage), angle))

  SetGadgetText(1, "Rotation : " + Str(angle))
  SetGadgetState(2, RotImage2)

  If IsImage(RotImage1) : FreeImage(RotImage1) : Delay(20) : EndIf

  angle + 1
  

Until Event = #PB_Event_CloseWindow 

End
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Image Rotation routines for 24/32 bit with optional AA

Post by luis »

Yes Djes, I remember this exceptionally fast routine from the forum, it's really great.

But I wrote my own because I wanted to not use api if possible, to be usanble under 32/64 bits and to support the background color (24 bit) and transparency (32 bit).

Would be very very nice if you can add all of the above to this routine to make it plug-and-play compatible with my own.

In any case is a very good piece of software if the above points are not important.

Cheers!
"Have you tried turning it off and on again ?"
A little PureBasic review
Post Reply