Re: Image Rotation routines for 24/32 bit with optional AA
Posted: Fri Sep 11, 2009 4:27 pm
Badly, it's not my own routine, even if I optimised it, and I really have no time by now... But yours "does the job", and is clean 

http://www.purebasic.com
https://www.purebasic.fr/english/
djes wrote:you could use lines routines as x and y have a linear progression. Then you only have to compute the four source corners (not the dest).
Code: Select all
ProcedureDLL.l RotateImageEx2(ImageID, Angle.f, Mode.l) ; Rotation d'une image d'un angle en °
Protected bmi.BITMAPINFO, bmi2.BITMAPINFO, hdc.l, NewImageID, Mem, n, nn, bm.BITMAP
Angle = Angle * #PI / 180 ; On convertit en radian
Cos.f = Cos(Angle)
Sin.f = Sin(Angle)
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
bmi2\bmiHeader\biSize = SizeOf(BITMAPINFOHEADER)
Select Mode
Case 1
bmi2\bmiHeader\biWidth = bm\bmWidth
bmi2\bmiHeader\biHeight = bm\bmHeight
Case 2
bmi2\bmiHeader\biWidth = Round(Sqr(bm\bmWidth * bm\bmWidth + bm\bmHeight * bm\bmHeight), 1)
bmi2\bmiHeader\biHeight = bmi2\bmiHeader\biWidth
Default
bmi2\bmiHeader\biWidth = Round(bm\bmWidth * Abs(Cos) + bm\bmHeight * Abs(Sin), 1)
bmi2\bmiHeader\biHeight = Round(bm\bmHeight * Abs(Cos) + bm\bmWidth * Abs(Sin), 1)
EndSelect
bmi2\bmiHeader\biPlanes = 1
bmi2\bmiHeader\biBitCount = 32
Mem = AllocateMemory(bm\bmWidth * bm\bmHeight * 4)
If Mem
Mem2 = AllocateMemory(bmi2\bmiHeader\biWidth * bmi2\bmiHeader\biHeight * 4)
If Mem2
hdc = CreateCompatibleDC_(GetDC_(ImageID))
If hdc
GetDIBits_(hdc, ImageID, 0, bm\bmHeight, Mem, @bmi, #DIB_RGB_COLORS) ; on envoie la liste dans l'image
DeleteDC_(hdc)
EndIf
CX1 = bm\bmWidth - 1
CY1 = bm\bmHeight - 1
CX2 = bmi2\bmiHeader\biWidth - 1
CY2 = bmi2\bmiHeader\biHeight - 1
Mem01 = Mem + bm\bmWidth * 4
Mem10 = Mem + 4
Mem11 = Mem01 + 4
Mem2Temp = Mem2
For nn = 0 To CY2
y1b.l = nn * 2 - CY2
Temp1.f = CX1 - y1b * Sin
Temp2.f = CY1 + y1b * Cos
For n = 0 To CX2
x1b.l = n * 2 - CX2
x1.f = (Temp1 + x1b * Cos) / 2
y1.f = (Temp2 + x1b * Sin) / 2
x2.l = x1
y2.l = y1
If x1 < x2
DEC x2
EndIf
If y1 < y2
DEC y2
EndIf
x2b = x2 + 1
y2b = y2 + 1
If x2b >= 0 And x2 <= CX1 And y2b >= 0 And y2 <= CY1 ; On filtre si on est completement en dehors de l'image
fx.f = x1 - x2
fy.f = y1 - y2
f00.f = (1 - fx) * (1 - fy)
f01.f = (1 - fx) * fy
f10.f = fx * (1 - fy)
f11.f = fx * fy
MemTemp = (x2 + y2 * bm\bmWidth) * 4
If x2 >= 0 And x2 <= CX1
If y2 >= 0 And y2 <= CY1
c00 = PeekL(Mem + MemTemp)
Else
c00 = 0
EndIf
If y2b >= 0 And y2b <= CY1
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
c10 = PeekL(Mem10 + MemTemp)
Else
c10 = 0
EndIf
If y2b >= 0 And y2b <= CY1
c11 = PeekL(Mem11 + MemTemp)
Else
c11 = 0
EndIf
Else
c10 = 0
c11 = 0
EndIf
Channel00 = c00 >> 24 & $FF
Channel01 = c01 >> 24 & $FF
Channel10 = c10 >> 24 & $FF
Channel11 = c11 >> 24 & $FF
Alpha = Channel00 * f00 + Channel01 * f01 + Channel10 * f10 + Channel11 * f11
Channel00 = c00 >> 16 & $FF
Channel01 = c01 >> 16 & $FF
Channel10 = c10 >> 16 & $FF
Channel11 = c11 >> 16 & $FF
Rouge = Channel00 * f00 + Channel01 * f01 + Channel10 * f10 + Channel11 * f11
Channel00 = c00 >> 8 & $FF
Channel01 = c01 >> 8 & $FF
Channel10 = c10 >> 8 & $FF
Channel11 = c11 >> 8 & $FF
Vert = Channel00 * f00 + Channel01 * f01 + Channel10 * f10 + Channel11 * f11
Channel00 = c00 & $FF
Channel01 = c01 & $FF
Channel10 = c10 & $FF
Channel11 = c11 & $FF
Bleu = Channel00 * f00 + Channel01 * f01 + Channel10 * f10 + Channel11 * f11
PokeL(Mem2Temp, Rouge | Vert << 8 | Bleu << 16 | Alpha << 24)
Else
PokeL(Mem2Temp, 0)
EndIf
Mem2Temp + 4
Next
Next
; On crée la nouvelle image
NewImageID = CreateImage(#PB_Any, bmi2\bmiHeader\biWidth, bmi2\bmiHeader\biHeight, 32)
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
DeleteDC_(hdc)
EndIf
FreeMemory(Mem2)
EndIf
FreeMemory(Mem)
EndIf
ProcedureReturn NewImageID
EndProcedure
;- ----------------------------------------
;- Test d'affichage d'image avec rotation
; On dessine une image
ImageNormale = CreateImage(#PB_Any, 80, 100, 32)
StartDrawing(ImageOutput(ImageNormale))
DrawingMode(#PB_2DDrawing_AlphaChannel)
Box(0, 0, 80, 100, $00000000)
DrawingMode(#PB_2DDrawing_Gradient | #PB_2DDrawing_AlphaBlend)
LinearGradient(0, 0, 80, 100)
GradientColor(0.0, $00000000)
GradientColor(1.0, $FF000000)
Box(0, 0, 80, 100)
DrawingMode(#PB_2DDrawing_AlphaBlend)
Box(5, 5, 35, 45, $B00000FF)
Box(40, 5, 35, 45, $FF00FF00)
Box(5, 50, 35, 45, $FFFF0000)
Box(40, 50, 35, 45, $80FFFFFF)
StopDrawing()
; Création de la fenêtre et de la GadgetList
If OpenWindow(0, 0, 0, 350, 400, "Effect - Rotation d'image", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_MinimizeGadget) = 0
End
EndIf
Angle.f = 30
Mode = 0
ImageRotation = RotateImageEx2(ImageID(ImageNormale), Angle, Mode)
TextGadget(#PB_Any, 10, 10, 100, 15, "Image normale")
ImageGadget(#PB_Any, 10, 25, 0, 0, ImageID(ImageNormale))
RotationTexte = TextGadget(#PB_Any, 10, 200, 100, 15, "Rotation de 30°")
Rotation = ImageGadget(#PB_Any, 10, 215, 0, 0, ImageID(ImageRotation))
RotationTemps = TextGadget(#PB_Any, 180, 270, 170, 15, "")
TextGadget(#PB_Any, 180, 200, 100, 15, "Mode :")
OptionGadget(0, 180, 215, 170, 15, "normal")
OptionGadget(1, 180, 230, 170, 15, "sans redimensionnement")
OptionGadget(2, 180, 245, 170, 15, "avec redimensionnement fixe")
SetGadgetState(0, 1)
AddWindowTimer(0, 1, 200)
Repeat
Event = WaitWindowEvent()
If Event = #PB_Event_Timer
If EventTimer() = 1
Angle + 1
If Angle = 360
Angle = 0
EndIf
Temps1 = ElapsedMilliseconds()
For n = 1 To 100
FreeImage(ImageRotation)
ImageRotation = RotateImageEx2(ImageID(ImageNormale), Angle, Mode)
Next
Temps2 = ElapsedMilliseconds()
SetGadgetState(Rotation, ImageID(ImageRotation))
SetGadgetText(RotationTexte, "Rotation de " + Str(Angle) + "°")
SetGadgetText(RotationTemps, StrF((Temps2 - Temps1) / 100, 2) + "ms")
EndIf
ElseIf Event = #PB_Event_Gadget
Select EventGadget()
Case 0
Mode = 0
Case 1
Mode = 1
Case 2
Mode = 2
EndSelect
EndIf
Until Event = #PB_Event_CloseWindow
End
Well, if with "non rectangle rotation" you mean "free angle rotation" (I'm not sure) you can see there are a couple of routines included to do 90 degrees rotations and flipping/mirroring too. They are also obviously faster than the generic one. If you need only a subset of what this one does you can trim it down (that's the good thing of having the source !).c4s wrote:The code I'm using right now has about 100 lines and fits into one procedure.
But I think I have to accept this for AA, alpha support and non rectangle rotation.
Sorry, I meant the rectangular rotation (90, 180, 270).luis wrote:Well, if with "non rectangle rotation" you mean "free angle rotation" (I'm not sure) you can see there are a couple of routines included to do 90 degrees rotations and flipping/mirroring too. They are also obviously faster than the generic one. If you need only a subset of what this one does you can trim it down (that's the good thing of having the source !).
Thanks for the thanks, they are always appreciated!
Yes, you are right. Seems to work. But I forgot that PureBasic can't save in the "correct" bitrate, right?luis wrote:The 24 bit routines should work for 16 bits also. I should try but I'm reasonably sure.
Bitdepths for loading, saving and catchimage are not particulary clear, at least to me.c4s wrote: Yes, you are right. Seems to work. But I forgot that PureBasic can't save in the "correct" bitrate, right?
It's by design. You get a new image rotated, but the original is not deleted. You could have still some very good use for it for what I know.c4s wrote: Note, I just found a nasty memory leak or at least this behavior wasn't clear for me: The source image still exists after the modification!
No problemc4s wrote: Anyway, thanks again for your great code and help.