Page 1 of 1

Rotate images +/- 90 deg. using native code only

Posted: Thu Feb 06, 2014 10:31 pm
by BasicallyPure
I wanted to rotate an image left or right by 90 degrees.
There are other examples here on the forum that will do that but they were either too complex for my liking or not cross platform.

I have written a procedure that uses only native PureBasic code.
The code is simple and reasonably fast.
The image is rotated in place once it is copied into a square temporary image.
After rotation if the original image was not square there will be empty space either on top or along the left side of the temporary image.
GrabImage() is used to recover the relevant portion of the rotated image.

2.17.2014
edit: now supports 32 bit as well as 24 bit images.

Code: Select all

; Rotate image +/- 90 degrees
; by BasicallyPure
; 2.18.2014
; supports 24 and 32 bit depth
; cross platform

EnableExplicit

Procedure ROTATE_90(image, dir)
   ; rotate image +/- 90 deg.
   ; 'image' is the number of the image to rotate
   ; if 'dir' <> 0 then rotate right else rotate left
   
   Protected a,b,c,e,f,h,s,w,x,y,ym,xm,tempImg,depth
   
   If IsImage(image) = 0 : ProcedureReturn 0 : EndIf
   
   StartDrawing(ImageOutput(image))
      w = OutputWidth()
      h = OutputHeight()
      f = DrawingBufferPixelFormat() & $7F
   StopDrawing()
      
      If f = #PB_PixelFormat_32Bits_RGB Or f = #PB_PixelFormat_32Bits_BGR
         depth = 32
      ElseIf f = #PB_PixelFormat_24Bits_RGB Or f = #PB_PixelFormat_24Bits_BGR
         depth = 24
      Else
         ProcedureReturn 0
      EndIf

   If w > h : s = w : Else : s = h : EndIf ; find the largest dimension
   
   tempImg = CreateImage(#PB_Any,s,s,depth) ; make a square working area
   
   StartDrawing(ImageOutput(tempImg))
      If depth = 32 : DrawingMode(#PB_2DDrawing_AllChannels) : EndIf
      
      DrawImage(ImageID(image),0,0)
      
      ym = s/2-1 ; max y loop value
      xm = s/2-(s!1&1) ; max x value, subtract 1 if 's' is even
      s-1
      
      If dir <> 0 ; rotate right
         For y = 0 To ym
            For x = 0 To xm
               e = Point(x,y)
               a = s-x : Plot(x,y,Point(y,a))
               b = s-y : Plot(y,a,Point(a,b))
               c = s-a : Plot(a,b,Point(b,c))
               Plot(b,c,e)
            Next x
         Next y
      Else ; rotate left
         For y = 0 To ym
            For x = 0 To xm
               e = Point(x,y)
               a = s-y : Plot(x,y,Point(a,x))
               b = s-x : Plot(a,x,Point(b,a))
               c = s-a : Plot(b,a,Point(c,b))
               Plot(c,b,e)
            Next x
         Next y
      EndIf
      
   StopDrawing()
   
   If dir <> 0
      GrabImage(tempImg,image,s-h+1,0,h,w) ; right
   Else
      GrabImage(tempImg,image,0,s-w+1,h,w) ; left
   EndIf
   
   FreeImage(tempImg)
   ProcedureReturn 1
EndProcedure


; Demo
UseJPEGImageDecoder() : UsePNGImageDecoder()

If OpenWindow(0,0,0,800,600,"",#PB_Window_MaximizeGadget|#PB_Window_ScreenCentered)
   
   Define Pattern$ = "image (*.png, *.jpg, *.bmp)|*.png;*.jpg;*.bmp|image *.*|*.*"
   Define F$, T$, Path$ = GetHomeDirectory() + "My Pictures\"
   
   CompilerIf #PB_Compiler_OS = #PB_OS_Linux
      Path$ = GetHomeDirectory() + "Pictures/"
   CompilerEndIf
   
   CreateStatusBar(0,WindowID(0))
   AddStatusBarField(#PB_Ignore)
   ImageGadget(0,110,10,1,1,0)
   ButtonGadget(1,10,010,90,25,"Load Image")
   ButtonGadget(2,10,070,90,25,"Rotate Right")
   ButtonGadget(3,10,100,90,25,"Rotate left")
   ButtonGadget(4,10,150,90,25,"Quit")
   
   Repeat
      Select WaitWindowEvent()
         Case #PB_Event_CloseWindow : Break
         Case #PB_Event_Gadget
            Select EventGadget()
               Case 1 : F$ = OpenFileRequester("Select image", Path$, Pattern$, 0)
                  If F$ : LoadImage(0,F$)
                     If IsImage(0)
                        T$ = F$ + "  |  (" + ImageWidth(0)
                        T$ + " x " + ImageHeight(0) + ")"
                        StatusBarText(0, 0, T$)
                        SetGadgetState(0,ImageID(0))
                     EndIf
                  EndIf
               Case 2 : If ROTATE_90(0,1) : SetGadgetState(0,ImageID(0)) : EndIf
               Case 3 : If ROTATE_90(0,0) : SetGadgetState(0,ImageID(0)) : EndIf
               Case 4 : Break
            EndSelect
      EndSelect
   ForEver
   
EndIf

Re: Rotate images +/- 90 deg. using native code only

Posted: Thu Feb 06, 2014 11:07 pm
by davido
@BasicallyPure

Tried it with some scanned text documents.

Works perfectly.
Thank you for sharing. :D

Re: Rotate images +/- 90 deg. using native code only

Posted: Thu Feb 06, 2014 11:55 pm
by majikeyric
Thanks! It's fast even with big images.

Re: Rotate images +/- 90 deg. using native code only

Posted: Mon Feb 10, 2014 10:19 am
by Kwai chang caine
Works great, quick and very usefull
Thanks for sharing 8)

Re: Rotate images +/- 90 deg. using native code only

Posted: Wed Feb 12, 2014 10:37 am
by RichardL
Good morning,

I dug this out of my archives, it should be much faster than using Point() and Plot() which are traditionally very slow functions and not good news when used intensively to remap images.

(It does a flip as well... but the method can be modified to produce any combination of rotate +-90 and flip XY)

RichardL

Code: Select all

; Simple image rotate by 90 degrees using direct access to
; image buffers. Should be cross platform... no API tricks.

; Define libraries
UseJPEGImageDecoder()
UseJPEGImageEncoder()
UsePNGImageDecoder()

SourceFile$ = "c:\temp\90DegreeClamp.jpg"
DestFile$   = "c:\temp\DestFile.jpg"

If LoadImage(1,SourceFile$) ; Load Image
  Iw = ImageWidth(1) : Ih = ImageHeight(1) ; Get image size
  Debug File$+"  W="+Str(Iw)+"  H="+Str(Ih)
  
  If StartDrawing(ImageOutput(1))
      
      ; Get information for source image
      *SBuf = DrawingBuffer()
      SPitch = DrawingBufferPitch()
      Select DrawingBufferPixelFormat() & $7FFF
        Case #PB_PixelFormat_24Bits_RGB : SBpp = 3 : Depth = 24 ; 3 Bytes per pixel (RRGGBB)
        Case #PB_PixelFormat_24Bits_BGR : SBpp = 3 : Depth = 24 ; 3 Bytes per pixel (BBGGRR)
        Case #PB_PixelFormat_32Bits_RGB : SBpp = 4 : Depth = 32 ; 4 Bytes per pixel (RRGGBB)
        Case #PB_PixelFormat_32Bits_BGR : SBpp = 4 : Depth = 32 ; 4 Bytes per pixel (BBGGRR)
        Default : MessageRequester("Sorry...","24 and 32 bit depths only")  : End
      EndSelect
    StopDrawing()
    
    ; Create new image with axes swapped and get it's info
    CreateImage(2,Ih,Iw,8*SBpp)
    If StartDrawing(ImageOutput(2))
        *DBuf = DrawingBuffer()
        DPitch = DrawingBufferPitch()
      StopDrawing()
      
      ; Copy image 1 to image 2 with rotation
      For Y = 0 To Ih - 2
        *S = *SBuf + (Y * SPitch)
        *D = *DBuf + (Y * SBpp)
        For X = 0 To Iw -1
          Pix = PeekL(*S)
          PokeL(*D,Pix)
          *D + DPitch
          *S + SBpp
        Next
      Next Y
      
    EndIf
  EndIf
  
  SaveImage(2,DestFile$)
  
  ; VERY crude demo of result
  h = Ih : If Iw>Ih : h = Iw : EndIf
  OpenWindow(1,100,100,Iw+Ih,h,"Test")
  StartDrawing(WindowOutput(1))
    DrawImage(ImageID(1),0,0)
    DrawImage(ImageID(2),Iw,0)
  StopDrawing()
  
  FreeImage(1)
  FreeImage(2)
  
  While WaitWindowEvent() <> #PB_Event_CloseWindow
  Wend    
   
EndIf

Re: Rotate images +/- 90 deg. using native code only

Posted: Wed Feb 12, 2014 12:56 pm
by davido
@RichardL
Another nice example.

Thank you for sharing.

Re: Rotate images +/- 90 deg. using native code only

Posted: Thu Feb 13, 2014 10:28 am
by Kwai chang caine
@RichardL
Yes !!! nice too :D
Thanks !!! 8)

Re: Rotate images +/- 90 deg. using native code only

Posted: Sat Feb 15, 2014 12:53 am
by em_uk
Yummy, direct to drawbuffer :)

why is using direct access much quicker than point?

Re: Rotate images +/- 90 deg. using native code only

Posted: Sat Feb 15, 2014 1:09 am
by BasicallyPure
@RichardL
Is there any way to prevent your code from doing a flip (mirror) as the rotation is the only effect I would like?

I spent quite a bit of time trying to do a counter-clockwise rotation with your code with only partial success.
After I made a better GUI that allows consecutive rotations I noticed the mirror problem with the original code.

I have modified your code to allow both CW and CCW rotation.
My testing has shown a problem with my CCW rotation on some images.
It perform the rotation ok but the colors are wrong on some images.
All of my test images have been 24 bit BGR as far as I can tell.

The code below has a variable named 'padding' that can fix the color problem
on the images that show wrong colors when rotated CCW if it set to equal 1.
On other images that do not show the wrong color problem there will be an IMA error if 'padding' is equal to 1.
I don't know how to determine when this variable needs to be set to 1.

I suspect but do not know if this is caused by the 'padding' that is mentioned in the help entry for DrawingBufferPitch().
I really don't know what is going on here so a solution escapes me.
If anyone knows what this 'padding' is and could explain it, that would be great.

After I noticed the mirror problem with the original code I decided to stop further work on this and I hope someone can provide some help.

What I do here for the CCW rotation is to set *DBuff to point to the end of the buffer, not the beginning.
Then the loop iterates backward thru the buffer.

Here is the code that I have so far. (Warning! This code is buggy)

Edit: I have solved the color problem. Now all images maintain the proper color when rotated.
Unfortunately I have discovered another problem with one particular image I have.
I have determined the problem (an IMA error) only occurs with an image that is 1024 x 768 in size.

(Warning! This code is still buggy but not as much. :shock: )

Code: Select all

; Simple image rotate by 90 degrees using direct access to
; image buffers. Should be cross platform... no API tricks.

EnableExplicit

; Define libraries
UseJPEGImageDecoder()
UseJPEGImageEncoder()
UsePNGImageDecoder()

Procedure ROTATE(image, flag = 0)
   ;if flag <> 0 image rotates left else image rotates right
   
   Protected Iw, Ih, SBpp, DBpp, Depth, SPitch, DPitch, X, Y
   Protected result, padding, imgTemp, *SBuf, *DBuf, *D, *S
   
   If IsImage(image) = 0 : ProcedureReturn result : EndIf
   
   StartDrawing(ImageOutput(image))
      
      ; Get information for source image
      Iw = OutputWidth() : Ih = OutputHeight() ; Get image size
      *SBuf = DrawingBuffer()
      SPitch = DrawingBufferPitch()
      Select DrawingBufferPixelFormat() & $7FFF
         Case #PB_PixelFormat_24Bits_RGB : SBpp = 3 : Depth = 24 ; 3 Bytes per pixel (RRGGBB)
         Case #PB_PixelFormat_24Bits_BGR : SBpp = 3 : Depth = 24 ; 3 Bytes per pixel (BBGGRR)
         Case #PB_PixelFormat_32Bits_RGB : SBpp = 4 : Depth = 32 ; 4 Bytes per pixel (RRGGBB)
         Case #PB_PixelFormat_32Bits_BGR : SBpp = 4 : Depth = 32 ; 4 Bytes per pixel (BBGGRR)
         Default
            MessageRequester("Sorry...","24 and 32 bit depths only")
            StopDrawing()
            ProcedureReturn result
      EndSelect
   StopDrawing()
   
   ; Create temporary image with axes swapped and get it's info
   imgTemp = CreateImage(#PB_Any, Ih, Iw, 8*SBpp)
   
   If imgTemp
      StartDrawing(ImageOutput(imgTemp))
         *DBuf = DrawingBuffer()
         DPitch = DrawingBufferPitch()
         
         If flag ; rotate left
            ; DPitch is padded to multiples of 4 bytes.
            ; (Ih * SBpp) is the actual ammount of data per row.
            ; so the adjustment is as follows:
            padding = DPitch - (Ih * SBpp)
            
            ; set pointer to the last pixel data
            *DBuf = *DBuf + (Iw * DPitch) - (SBpp + padding)
            
            ; reverse the direction of data iteration
            DPitch = -DPitch
            DBpp = -SBpp
         Else
            DBpp = SBpp
         EndIf
         
         Iw - 1 : Ih - 1
         
         ; Copy image to imgTemp with rotation
         For Y = 0 To Ih
            ;fixes IMA error when image is 1024 x 768
            ;If y = Ih : Iw-1 : EndIf
            
            *S = *SBuf + (Y * SPitch)
            *D = *DBuf + (Y * DBpp)
            For X = 0 To Iw
               PokeL(*D, PeekL(*S))
               *D + DPitch
               *S + SBpp
            Next
         Next Y
      StopDrawing()
      
      CopyImage(imgTemp,image)
      FreeImage(imgTemp)
      result = 1
   EndIf
   
   ProcedureReturn result
EndProcedure

; demo
Define Path$ = GetHomeDirectory() + "My Pictures\"
Define Pattern$ = "image (*.png, *.jpg, *.bmp)|*.png;*.jpg;*.bmp|image *.*|*.*"
Define F$

OpenWindow(0, 0, 0, 910, 610, "Test",#PB_Window_MaximizeGadget)
CreateImage(1,800,600,24)
ImageGadget(0,100,5,800,600,ImageID(1))
ButtonGadget(1,5,5,90,25,"Load")
ButtonGadget(2,5,35,90,25,"Right")
ButtonGadget(3,5,65,90,25,"Left")
ButtonGadget(4,5,95,90,25,"Quit")

Repeat
   Select WaitWindowEvent()
      Case #PB_Event_CloseWindow : Break
      Case #PB_Event_Gadget
         Select EventGadget()
            Case 1 : F$ = OpenFileRequester("Select image", Path$, Pattern$, 0)
               If F$ : LoadImage(1,F$)
                  If IsImage(1) : SetGadgetState(0,ImageID(1)) : EndIf
               EndIf
            Case 2 : ROTATE(1,0) : SetGadgetState(0,ImageID(1))
            Case 3 : ROTATE(1,1) : SetGadgetState(0,ImageID(1))
            Case 4 : Break
         EndSelect
   EndSelect
ForEver

Re: Rotate images +/- 90 deg. using native code only

Posted: Sat Feb 15, 2014 7:46 am
by wilbert
You might be careful with this latest approach.
As far as I know there's no guarantee the returned value from DrawingBuffer() is still valid after StopDrawing() is called.
It seems to work fine but in theory it's possible to have only a temporary buffer that is not accessible anymore after StopDrawing().
If the return value from DrawingBuffer() is guaranteed to be available even after StopDrawing() is called, it would be nice if Fred could add that to the documentation since it would be a nice feature.

Re: Rotate images +/- 90 deg. using native code only

Posted: Sat Feb 15, 2014 9:26 am
by BasicallyPure
You make a good point.

Thanks,
BP

Re: Rotate images +/- 90 deg. using native code only

Posted: Tue Feb 18, 2014 1:01 am
by BasicallyPure
The code from the first post has been updated to support 32 bit as well as 24 bit images.

I now have fixed all of the known problems with the direct buffer access method.
Keep in mind Wilbert's warning as stated above.
Now that I have two different working methods I performed some speed test comparisons.

The code from the first post I will refer to as method 1.
The code in this post I will refer to as method 2.

Here are the results using a 24 bit test image of dimensions 3008 x 2000.
all times are in milliseconds. Each rotation was perform 4 times then averaged.

Method 1 : uses Plot() and Point() to move pixels around.
rotate right, 330, 333, 340, 344, average = 337
rotate left, 329, 333, 332, 337, average = 333

Method 2: uses Poke() and Peek() with direct buffer access.
rotate right, 233, 234, 241, 236, average = 236
rotate left, 228, 229, 233, 237, average 232

Method 2 is about 30% faster.

This has been tested with Windows and Linux.

Code: Select all

; rotate image by 90 degrees using direct access to image buffers.
; Should be cross platform... no API tricks.
; 2.17.2014
; PureBasic 5.21
; supports 24 and 32 bit images

EnableExplicit

Procedure ROTATE(image, flag)
   ;if flag <> 0 image rotates left else image rotates right
   
   Protected A, B, result, padding, imgTemp, format
   Protected *SBuf  ; pointer to first memory byte of source buffer
   Protected *DBuf  ; pointer to first memory byte of destination buffer
   Protected *FPix  ; points to where first pixel is peeked in source buffer
   Protected *S,*D  ; source and destination pointers
   Protected Iw     ; image width
   Protected Ih     ; image height
   Protected Depth  ; image color depth, 24 or 32
   Protected SBpp   ; SourceBytesPerPixel
   Protected DBpp   ; DestinationBytesPerPixel
   Protected SPitch ; source image number of bytes per row (x)
   Protected DPitch ; destination image number of bytes per row (x)
   
   Macro Copy3Bytes(Source, Destination)
      PokeA(Destination, PeekA(Source))
      PokeW(Destination+1, PeekW(Source+1))
   EndMacro
   
   Macro Copy4Bytes(Source, Destination)
      PokeL(Destination, PeekL(Source))
   EndMacro
   
   If IsImage(image) = 0 : ProcedureReturn result : EndIf
   
   StartDrawing(ImageOutput(image))
      ; Get information for source image
      Iw = OutputWidth() : Ih = OutputHeight() ; Get image size
      *SBuf  = DrawingBuffer()
      SPitch = DrawingBufferPitch()
      format = DrawingBufferPixelFormat()
      
      Select  format & $7FFF
         Case #PB_PixelFormat_24Bits_RGB : SBpp = 3 : Depth = 24 ; 3 Bytes per pixel (RRGGBB)
         Case #PB_PixelFormat_24Bits_BGR : SBpp = 3 : Depth = 24 ; 3 Bytes per pixel (BBGGRR)
         Case #PB_PixelFormat_32Bits_RGB : SBpp = 4 : Depth = 32 ; 4 Bytes per pixel (RRGGBB)
         Case #PB_PixelFormat_32Bits_BGR : SBpp = 4 : Depth = 32 ; 4 Bytes per pixel (BBGGRR)
         Default
            MessageRequester("Sorry...","24 and 32 bit depths only")
            StopDrawing()
            ProcedureReturn result
      EndSelect
      
      If format & #PB_PixelFormat_ReversedY = 0 ; this is for Linux
         flag = flag ! 1 & 1
      EndIf
   StopDrawing()
   
   ; Create temporary image with axis swapped
   Swap Ih,Iw
   imgTemp = CreateImage(#PB_Any, Iw, Ih, 8*SBpp)
   
   ; perform the rotation
   If imgTemp
      StartDrawing(ImageOutput(imgTemp))
         *DBuf = DrawingBuffer()
         DPitch = DrawingBufferPitch()
         DBpp = SBpp
         
         If flag = 0 ; configure to rotate right
            *FPix = *SBuf + (Ih-1) * SBpp ; point to last pixel in first row
            SBpp = -SBpp ; reverse X iteration direction
         Else ; configure to rotate left
            *FPix = *SBuf + (Iw-1) * SPitch ; point to first pixel in last row
            SPitch = -SPitch ; reverse Y iteration direction
         EndIf
         
         ; time to fly
         If Depth = 24
            A = 0 : While A < Ih
               *S = *FPix + (A * SBpp)   ; set source pointer X position
               *D = *DBuf + (A * DPitch) ; set destination pointer Y position
               B = 0 : While B < Iw
                  Copy3Bytes(*S, *D)
                  *S + SPitch ; increment source pointer Y position
                  *D + DBpp   ; increment destination pointer X position
               B + 1 : Wend
            A + 1 : Wend
         ElseIf Depth = 32
            A = 0 : While A < Ih
               *S = *FPix + (A * SBpp)   ; set source pointer X position
               *D = *DBuf + (A * DPitch) ; set destination pointer Y position
               B = 0 : While B < Iw
                  Copy4Bytes(*S, *D)
                  *S + SPitch ; increment source pointer Y position
                  *D + DBpp   ; increment destination pointer X position
               B + 1 : Wend
            A + 1 : Wend
         EndIf
      
      StopDrawing()
      
      CopyImage(imgTemp,image)
      FreeImage(imgTemp)
      result = 1
   EndIf
   
   ProcedureReturn result
EndProcedure

; demo
UseJPEGImageDecoder()
UsePNGImageDecoder()

CompilerIf #PB_Compiler_OS = #PB_OS_Linux
   Define Path$ = GetHomeDirectory() + "Pictures/"
CompilerElse
   Define Path$ = GetHomeDirectory() + "My Pictures\"
CompilerEndIf

Define Pattern$ = "image (*.png, *.jpg, *.bmp)|*.png;*.jpg;*.bmp|image *.*|*.*"
Define flags = #PB_Window_MaximizeGadget | #PB_Window_ScreenCentered
Define F$

OpenWindow(0, 0, 0, 910, 610, "Test", flags)
SetWindowColor(0,$A0A0A0)
CreateImage(1,800,600,24)
ImageGadget(0,100,5,800,600,ImageID(1))
ButtonGadget(1,5,5,90,25,"Load")
ButtonGadget(2,5,60,90,25,"Right")
ButtonGadget(3,5,90,90,25,"Left")
ButtonGadget(4,5,145,90,25,"Quit")

Repeat
   Select WaitWindowEvent()
      Case #PB_Event_CloseWindow : Break
      Case #PB_Event_Gadget
         Select EventGadget()
            Case 1 : F$ = OpenFileRequester("Select image", Path$, Pattern$, 0)
               If F$ : LoadImage(1,F$)
                  If IsImage(1) : SetGadgetState(0,ImageID(1)) : EndIf
               EndIf
            Case 2 : ROTATE(1,0) : SetGadgetState(0,ImageID(1))
            Case 3 : ROTATE(1,1) : SetGadgetState(0,ImageID(1))
            Case 4 : Break
         EndSelect
   EndSelect
ForEver

Re: Rotate images +/- 90 deg. using native code only

Posted: Tue Feb 18, 2014 9:49 am
by davido
@BasicallyPure

Looks great. Some very nice improvements.

Rotating images used to be a pain. It has just got very much easier.

Thank you for sharing. :D :D

Re: Rotate images +/- 90 deg. using native code only

Posted: Wed Feb 19, 2014 12:54 am
by electrochrisso
Very nice BP, thanks for sharing. :)