MAIN CODE - "RotateImage.pb"
Code:
;******************************************************************************
;*
;* Image Rotation routines for 24/32 bit with optional AA
;* by Luis, http://luis.no-ip.net
;* v1.06 for PB 4.60
;*
;* 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.
;*
;* ----------------------------------------------------------------------------
;*
;* RotateImageFree (nSrcImage, fDegRot.f, flgAntiAliasing, iFillColor)
;*
;* 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
;* iFillColor Used to fill the new areas of the resulting image
;*
;* Return a 24/32 bit image rotated by fDegRot
;*
;* NOTES :
;* iFillColor is not used with 32 bit images, the new areas are always transparent.
;*
;* iFillColor 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, iDegRot)
;*
;* nSrcImage The 24/32 bit PureBasic's image to rotate
;* iDegRot Integer angle in degrees (+/-) 90/180/270
;*
;* Return a 24/32 bit image rotated by iDegRot
;*
;* 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
;*
;******************************************************************************
XIncludeFile #PB_Compiler_FilePath + "RotateImage.pbi"
; support procedures
Macro JMP_IF_ZERO (var, label)
; If 'var' equals zero, then execute 'exec' and jump to 'label'
; Yes, I like GOTOs for error handling.
If var = 0: Goto label : EndIf
EndMacro
Procedure.i AllocateImageData (nImage, *iBufferPitch.Integer, iFillColor = -1)
Protected *ImageMem, *AllocMem, iBufferPitch
StartDrawing(ImageOutput(nImage))
*ImageMem = DrawingBuffer()
iBufferPitch = DrawingBufferPitch()
If iFillColor <> -1
Select ImageDepth(nImage)
Case 24
Box(0, 0, ImageWidth(nImage), ImageHeight(nImage), iFillColor)
Case 32
DrawingMode(#PB_2DDrawing_AlphaChannel)
Box(0, 0, ImageWidth(nImage), ImageHeight(nImage), $00) ; full transparent
EndSelect
EndIf
*AllocMem = AllocateMemory(iBufferPitch * ImageHeight(nImage))
If *AllocMem
CopyMemory(*ImageMem, *AllocMem, MemorySize(*AllocMem))
*iBufferPitch\i = iBufferPitch
Else
*iBufferPitch\i = 0
EndIf
StopDrawing()
ProcedureReturn *AllocMem
EndProcedure
Procedure CopyImageData (nImage, *DestMem)
StartDrawing(ImageOutput(nImage))
CopyMemory(*DestMem, DrawingBuffer(), MemorySize(*DestMem))
StopDrawing()
EndProcedure
; user procedures
Procedure.i RotateImage (nSrcImage, iDegRot)
; Rotate 24 bit images at (+/-) 90/180/270 degrees
; Rotate 32 bit images at (+/-) 90/180/270 degrees preserving the alpha-channel
; This one uses the DrawingBuffer() ability to work with images available in PB 4.40
Protected *tRGBAs.T_RGBA, *tRGBAd.T_RGBA, tPixel.T_RGBA, iType
Protected *SrcMem, *DestMem, iBufferPitchSrc, iBufferPitchDest
Protected iSrcWidth, iSrcHeight, iDestWidth, iDestHeight, nDestImage
Protected iX, iY, iXs, iYs
Protected iBitPlanes
; sanity checks
If IsImage(nSrcImage) = 0
ProcedureReturn 0
EndIf
iBitPlanes = ImageDepth(nSrcImage)
If iBitPlanes <> 24 And iBitPlanes <> 32
ProcedureReturn 0
EndIf
; sanity checks
If iDegRot % 90
ProcedureReturn 0
EndIf
iDegRot % 360
If iDegRot = 0
ProcedureReturn CopyImage(nSrcImage, #PB_Any)
EndIf
CompilerIf (#PB_Compiler_OS = #PB_OS_Linux)
iDegRot = -iDegRot
CompilerEndIf
iSrcWidth = ImageWidth(nSrcImage)
iSrcHeight = ImageHeight(nSrcImage)
Select iDegRot
Case 90, -270
iDestWidth = iSrcHeight
iDestHeight = iSrcWidth
iType = 1
Case 180, -180
iType = 2
iDestWidth = iSrcWidth
iDestHeight = iSrcHeight
Case 270, -90
iType = 3
iDestWidth = iSrcHeight
iDestHeight = iSrcWidth
EndSelect
; create 24/32 bit destination image
nDestImage = CreateImage(#PB_Any, iDestWidth, iDestHeight, iBitPlanes)
JMP_IF_ZERO (nDestImage, lbl_RotateImage_ERR)
; copy src image to allocated memory
*SrcMem = AllocateImageData(nSrcImage, @iBufferPitchSrc)
JMP_IF_ZERO (*SrcMem, lbl_RotateImage_Alloc_ERR)
; copy dest image to allocated memory
*DestMem = AllocateImageData(nDestImage, @iBufferPitchDest)
JMP_IF_ZERO (*DestMem, lbl_RotateImage_Alloc_ERR)
Select iBitPlanes
Case 24
For iY = 0 To iDestHeight - 1
For iX = 0 To iDestWidth - 1
Select iType
Case 1
iYs = iSrcHeight - iX - 1
iXs = iY
Case 2
iYs = iSrcHeight - iY - 1
iXs = iSrcWidth - iX - 1
Case 3
iYs = iX
iXs = iSrcWidth - iY - 1
EndSelect
CopyPixel24 (iXs, iYs, iX, iY, iBufferPitchSrc, iBufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)
Next
Next
Case 32
For iY = 0 To iDestHeight - 1
For iX = 0 To iDestWidth - 1
Select iType
Case 1
iYs = iSrcHeight - iX - 1
iXs = iY
Case 2
iYs = iSrcHeight - iY - 1
iXs = iSrcWidth - iX - 1
Case 3
iYs = iX
iXs = iSrcWidth - iY - 1
EndSelect
CopyPixel32 (iXs, iYs, iX, iY, iBufferPitchSrc, iBufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)
Next
Next
EndSelect
CopyImageData(nDestImage, *DestMem)
FreeMemory(*SrcMem)
FreeMemory(*DestMem)
ProcedureReturn nDestImage
lbl_RotateImage_Alloc_ERR:
; check if one was successfull and free it
If *SrcMem <> 0 : FreeMemory(*SrcMem) : EndIf
If *DestMem <> 0 : FreeMemory(*DestMem) : EndIf
; image was already created, free it
FreeImage(nDestImage)
lbl_RotateImage_ERR:
ProcedureReturn 0
EndProcedure
Procedure.i RotateImageFree (nSrcImage, fDegRot.f, flgAntiAliasing, iFillColor = $ffffff)
; 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
; Rotate 24 bit images at any angle optionally with anti-aliasing filling the new area
; of the resulting image with the specified color
; Rotate 32 bit images at any angle optionally with anti-aliasing preserving the alpha-channel
; This one uses the DrawingBuffer() ability to work with images available in PB 4.40
Protected *tRGBAs.T_RGBA, *tRGBAd.T_RGBA, tPixel.T_RGBA
Protected *SrcMem, *DestMem, iBufferPitchSrc, iBufferPitchDest
Protected fzCos.f, fzSin.f
Protected iSrcWidth, iSrcHeight, iDestWidth, iDestHeight, nDestImage
Protected iX, iY, iXs, iYs, iXc1, iYc1, iXc2, iYc2, iColor
Protected iBitPlanes
Protected XRoundFix, YRoundFix
; sanity checks
If IsImage(nSrcImage) = 0
ProcedureReturn 0
EndIf
iBitPlanes = ImageDepth(nSrcImage)
If iBitPlanes <> 24 And iBitPlanes <> 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
If fDegRot > 270.0
XRoundFix = -1
YRoundFix = 0
ElseIf fDegRot > 180.0
XRoundFix = -1
YRoundFix = -1
ElseIf fDegRot > 90.0
XRoundFix = 0
YRoundFix = -1
EndIf
CompilerIf (#PB_Compiler_OS = #PB_OS_Linux)
fDegRot = -fDegRot
CompilerEndIf
fzCos = Cos(Radian(fDegRot))
fzSin = Sin(Radian(fDegRot))
iSrcWidth = ImageWidth(nSrcImage)
iSrcHeight = ImageHeight(nSrcImage)
iDestWidth = Int(iSrcWidth * Abs(fzCos) + iSrcHeight * Abs(fzSin) + 0.9)
iDestHeight = Int(iSrcHeight * Abs(fzCos) + iSrcWidth * Abs(fzSin) + 0.9)
iXc1 = iSrcWidth / 2
iYc1 = iSrcHeight / 2
iXc2 = iDestWidth / 2
iYc2 = iDestHeight / 2
; create 24/32 bit destination image
nDestImage = CreateImage(#PB_Any, iDestWidth, iDestHeight, iBitPlanes)
JMP_IF_ZERO (nDestImage, lbl_RotateImageFree_ERR)
; copy src image to allocated memory
*SrcMem = AllocateImageData (nSrcImage, @iBufferPitchSrc)
JMP_IF_ZERO (*SrcMem, lbl_RotateImageFree_Alloc_ERR)
; copy dest image to allocated memory and fill with backcolor
*DestMem = AllocateImageData(nDestImage, @iBufferPitchDest, iFillColor)
JMP_IF_ZERO (*DestMem, lbl_RotateImageFree_Alloc_ERR)
Select flgAntiAliasing
Case #False
Select iBitPlanes
Case 24
For iY = 0 To iDestHeight - 1
For iX = 0 To iDestWidth - 1
; For each nDestImage point find rotated nSrcImage source point
iXs = iXc1 + (iX - iXc2) * fzCos + (iY - iYc2) * fzSin + XRoundFix
iYs = iYc1 + (iY - iYc2) * fzCos - (iX - iXc2) * fzSin + YRoundFix
If iXs >= 0 And iXs < iSrcWidth And iYs >= 0 And iYs < iSrcHeight
; Move valid rotated nSrcImage source points to nDestImage
CopyPixel24 (iXs, iYs, iX, iY, iBufferPitchSrc, iBufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)
EndIf
Next
Next
Case 32
For iY = 0 To iDestHeight - 1
For iX = 0 To iDestWidth - 1
; For each nDestImage point find rotated nSrcImage source point
iXs = iXc1 + (iX - iXc2) * fzCos + (iY - iYc2) * fzSin + XRoundFix
iYs = iYc1 + (iY - iYc2) * fzCos - (iX - iXc2) * fzSin + YRoundFix
If iXs >= 0 And iXs < iSrcWidth And iYs >= 0 And iYs < iSrcHeight
; Move valid rotated nSrcImage source points to nDestImage
CopyPixel32 (iXs, iYs, iX, iY, iBufferPitchSrc, iBufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)
EndIf
Next
Next
EndSelect
Case #True
Protected iXs0, iYs0, icr, icg, icb, icr0, icg0, icb0, icr1, icg1, icb1
Protected fXs.f, fYs.f, fXfs1.f, fYfs1.f
Protected fXfs1less.f, fYfs1less.f
Select iBitPlanes
Case 24
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 + XRoundFix
fYs = iYc1 + (iY - iYc2) * fzCos - (iX - iXc2) * fzSin + YRoundFix
; 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 - 0.000005 : If fXfs1less < 0 : fXfs1less = 0 : EndIf
fYfs1less = 1 - fYfs1 - 0.000005 : If fYfs1less < 0 : fYfs1less = 0 : EndIf
ReadPixel24 (iXs0, iYs0, iBufferPitchSrc, *tRGBAs, *SrcMem)
icr = *tRGBAs\R * fXfs1less
icg = *tRGBAs\G * fXfs1less
icb = *tRGBAs\B * fXfs1less
ReadPixel24 (iXs0 + 1, iYs0, iBufferPitchSrc, *tRGBAs, *SrcMem)
icr0 = *tRGBAs\R * fXfs1 + icr
icg0 = *tRGBAs\G * fXfs1 + icg
icb0 = *tRGBAs\B * fXfs1 + icb
ReadPixel24 (iXs0, iYs0 + 1, iBufferPitchSrc, *tRGBAs, *SrcMem)
icr = *tRGBAs\R * fXfs1less
icg = *tRGBAs\G * fXfs1less
icb = *tRGBAs\B * fXfs1less
ReadPixel24 (iXs0 + 1, iYs0 + 1, iBufferPitchSrc, *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, iX, iY, iBufferPitchDest, *tRGBAd, *DestMem)
EndIf
Next
Next
Case 32
Protected ica, ica0, ica1
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 + XRoundFix
fYs = iYc1 + (iY - iYc2) * fzCos - (iX - iXc2) * fzSin + YRoundFix
; 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 - 0.000005 : If fXfs1less < 0 : fXfs1less = 0 : EndIf
fYfs1less = 1 - fYfs1 - 0.000005 : If fYfs1less < 0 : fYfs1less = 0 : EndIf
ReadPixel32 (iXs0, iYs0, iBufferPitchSrc, *tRGBAs, *SrcMem)
icr = *tRGBAs\R * fXfs1less
icg = *tRGBAs\G * fXfs1less
icb = *tRGBAs\B * fXfs1less
ica = *tRGBAs\A * fXfs1less
ReadPixel32 (iXs0 + 1, iYs0, iBufferPitchSrc, *tRGBAs, *SrcMem)
icr0 = *tRGBAs\R * fXfs1 + icr
icg0 = *tRGBAs\G * fXfs1 + icg
icb0 = *tRGBAs\B * fXfs1 + icb
ica0 = *tRGBAs\A * fXfs1 + ica
ReadPixel32 (iXs0, iYs0 + 1, iBufferPitchSrc, *tRGBAs, *SrcMem)
icr = *tRGBAs\R * fXfs1less
icg = *tRGBAs\G * fXfs1less
icb = *tRGBAs\B * fXfs1less
ica = *tRGBAs\A * fXfs1less
ReadPixel32 (iXs0 + 1, iYs0 + 1, iBufferPitchSrc, *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, iX, iY, iBufferPitchDest, *tRGBAd, *DestMem)
EndIf
Next
Next
EndSelect
EndSelect
CopyImageData (nDestImage, *DestMem)
FreeMemory(*SrcMem)
FreeMemory(*DestMem)
ProcedureReturn nDestImage
lbl_RotateImageFree_Alloc_ERR:
; check if one was successfull and free it
If *SrcMem <> 0 : FreeMemory(*SrcMem) : EndIf
If *DestMem <> 0 : FreeMemory(*DestMem) : EndIf
; image was already created, free it
FreeImage(nDestImage)
lbl_RotateImageFree_ERR:
ProcedureReturn 0
EndProcedure
Procedure.i FlipImage (nSrcImage)
; Flip vertically a 24/32 bit image preserving the alpha-channel
; This one uses the DrawingBuffer() ability to work with images available in PB 4.40
Protected *tRGBAs.T_RGBA, *tRGBAd.T_RGBA, tPixel.T_RGBA, iType
Protected *SrcMem, *DestMem, iBufferPitchSrc, iBufferPitchDest
Protected iSrcWidth, iSrcHeight, iDestWidth, iDestHeight, nDestImage
Protected iX, iY
Protected iBitPlanes
; sanity checks
If IsImage(nSrcImage) = 0
ProcedureReturn 0
EndIf
iBitPlanes = ImageDepth(nSrcImage)
If iBitPlanes <> 24 And iBitPlanes <> 32
ProcedureReturn 0
EndIf
iSrcWidth = ImageWidth(nSrcImage)
iSrcHeight = ImageHeight(nSrcImage)
iDestWidth = iSrcWidth
iDestHeight = iSrcHeight
; create 24/32 bit destination image
nDestImage = CreateImage(#PB_Any, iDestWidth, iDestHeight, iBitPlanes)
JMP_IF_ZERO (nDestImage, lbl_FlipImage_ERR)
; copy src image to allocated memory
*SrcMem = AllocateImageData(nSrcImage, @iBufferPitchSrc)
JMP_IF_ZERO (*SrcMem, lbl_FlipImage_Alloc_ERR)
; copy dest image to allocated memory
*DestMem = AllocateImageData(nDestImage, @iBufferPitchDest)
JMP_IF_ZERO (*DestMem, lbl_FlipImage_Alloc_ERR)
Select iBitPlanes
Case 24
For iY = 0 To iDestHeight - 1
For iX = 0 To iDestWidth - 1
CopyPixel24 (iX, iSrcHeight - iY - 1, iX, iY, iBufferPitchSrc, iBufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)
Next
Next
Case 32
For iY = 0 To iDestHeight - 1
For iX = 0 To iDestWidth - 1
CopyPixel32 (iX, iSrcHeight - iY - 1, iX, iY, iBufferPitchSrc, iBufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)
Next
Next
EndSelect
CopyImageData(nDestImage, *DestMem)
FreeMemory(*SrcMem)
FreeMemory(*DestMem)
ProcedureReturn nDestImage
lbl_FlipImage_Alloc_ERR:
; check if one was successfull and free it
If *SrcMem <> 0 : FreeMemory(*SrcMem) : EndIf
If *DestMem <> 0 : FreeMemory(*DestMem) : EndIf
; image was already created, free it
FreeImage(nDestImage)
lbl_FlipImage_ERR:
ProcedureReturn 0
EndProcedure
Procedure.i MirrorImage (nSrcImage)
; Mirror horizontally a 24/32 bit image preserving the alpha-channel
; This one uses the DrawingBuffer() ability to work with images available in PB 4.40
Protected *tRGBAs.T_RGBA, *tRGBAd.T_RGBA, tPixel.T_RGBA, iType
Protected *SrcMem, *DestMem, iBufferPitchSrc, iBufferPitchDest
Protected iSrcWidth, iSrcHeight, iDestWidth, iDestHeight, nDestImage
Protected iX, iY
Protected iBitPlanes
; sanity checks
If IsImage(nSrcImage) = 0
ProcedureReturn 0
EndIf
iBitPlanes = ImageDepth(nSrcImage)
If iBitPlanes <> 24 And iBitPlanes <> 32
ProcedureReturn 0
EndIf
iSrcWidth = ImageWidth(nSrcImage)
iSrcHeight = ImageHeight(nSrcImage)
iDestWidth = iSrcWidth
iDestHeight = iSrcHeight
; create 24/32 bit destination image
nDestImage = CreateImage(#PB_Any, iDestWidth, iDestHeight, iBitPlanes)
JMP_IF_ZERO (nDestImage, lbl_MirrorImage_ERR)
; copy src image to allocated memory
*SrcMem = AllocateImageData(nSrcImage, @iBufferPitchSrc)
JMP_IF_ZERO (*SrcMem, lbl_MirrorImage_Alloc_ERR)
; copy dest image to allocated memory
*DestMem = AllocateImageData(nDestImage, @iBufferPitchDest)
JMP_IF_ZERO (*DestMem,lbl_MirrorImage_Alloc_ERR)
Select iBitPlanes
Case 24
For iY = 0 To iDestHeight - 1
For iX = 0 To iDestWidth - 1
CopyPixel24 (iSrcWidth - iX - 1, iY, iX, iY, iBufferPitchSrc, iBufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)
Next
Next
Case 32
For iY = 0 To iDestHeight - 1
For iX = 0 To iDestWidth - 1
CopyPixel32 (iSrcWidth - iX - 1, iY, iX, iY, iBufferPitchSrc, iBufferPitchDest, *tRGBAs, *tRGBAd, *SrcMem, *DestMem)
Next
Next
EndSelect
CopyImageData(nDestImage, *DestMem)
FreeMemory(*SrcMem)
FreeMemory(*DestMem)
ProcedureReturn nDestImage
lbl_MirrorImage_Alloc_ERR:
; check if one was successfull and free it
If *SrcMem <> 0 : FreeMemory(*SrcMem) : EndIf
If *DestMem <> 0 : FreeMemory(*DestMem) : EndIf
; image was already created, free it
FreeImage(nDestImage)
lbl_MirrorImage_ERR:
ProcedureReturn 0
EndProcedure