Login | Register   
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


Tip of the Day
Language: VB5,VB6
Expertise: Advanced
Oct 20, 2001

RotatePicture - Rotate a 256-color bitmap by any angle (super-optimized version)

' This structure holds Bitmap information
Private Type BITMAP
    bmType As Long
    bmWidth As Long
    bmHeight As Long
    bmWidthBytes As Long
    bmPlanes As Integer
    bmBitsPixel As Integer
    bmBits As Long
End Type

' This structure holds SAFEARRAY info
Private Type SafeArray2
    cDims As Integer
    fFeatures As Integer
    cbElements As Long
    cLocks As Long
    pvData As Long
    cElements1 As Long
    lLbound1 As Long
    cElements2 As Long
    lLbound2 As Long
End Type

' API declares
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As _
    Any, pSrc As Any, ByVal ByteLen As Long)
Private Declare Function GetObjectAPI Lib "gdi32" Alias "GetObjectA" (ByVal _
    hObject As Long, ByVal nCount As Long, lpObject As Any) As Long

' Rotate a 256-color bitmap by any angle:
'   sourcePB is the source PictureBox control (may be hidden)
'   destPB is the destination PictureBox control
'   XC, YC are the coordinates of the rotation center
'   ANGLE is the rotation angle in degrees

' this improved version scans only a portion of the image, and builds
' remaining points using simmetry. This algorithm is particularly efficient 
' when the
' center of the rotation is inside the bitmap, the best performances are 
' achieved
' when it is near to the center of the bitmap. Moreover, this code saves some
' CPU time by using pre-calculated values for SIN, COS, and SQR functions.

' IMPORTANT: the source and destination PictureBox control must initially
' contain the *same* bitmap, to ensure that size and color palette
' are correctly initialized.

' Example:
'    'Load the same image in both source (hidden) and destination controls
'    Picture1.Picture = LoadPicture("d:\winnt\gone fishing.bmp")
'    Picture2.Picture = LoadPicture("d:\winnt\gone fishing.bmp")
'    ' Rotate by 360°
'    Dim a As Single
'    For a = 0 To 360 Step 5
'        RotatePicture2 Picture1, Picture2, 50, 50, a
'    Next

Private Sub RotatePicture2(sourcePB As PictureBox, destPB As PictureBox, _
    xc As Long, yc As Long, degrees As Single)

    ' all angles are expressed in 1/10000ths of radians
    Const PI As Long = 31416
    Const HALFPI As Long = 15707
    Const DOUBLEPI As Long = 62831
    
    Const SQRTABLE_MAX As Long = 40000
    
    Static sinTable() As Single
    Static atnTable() As Long
    Static sqrTable() As Single
    Static initialized As Boolean
    
    ' these are used to address the pixel using matrices
    Dim pict1() As Byte
    Dim pict2() As Byte
    Dim p1 As SafeArray2, p2 As SafeArray2
    Dim bmp1 As BITMAP, bmp2 As BITMAP
    ' these are used by the rotating algorithm
    Dim radians As Long
    Dim angle As Long
    Dim angle0 As Long
    Dim distance As Single
    Dim distanceSquared As Long
    Dim deltaX As Long, deltaY As Long
    Dim deltaXSquared As Single, deltaX10000 As Long
    Dim x As Long, y As Long
    Dim dx As Long, dy As Long
    Dim x0 As Long, y0 As Long
    Dim xx As Long, yy As Long
    Dim xStart As Long, xEnd As Long
    Dim yStart As Long, yEnd As Long
    Dim bmWidth1 As Long
    Dim bmHeight1 As Long
    
    ' Initialize sin,cos,sqr tables
    If Not initialized Then
        initialized = True
        
        Dim i As Long
        ' evaluate a table of sin for 360+90 degrees
        ' with a precision of 1/10000 of a radian
        ' this permits to reuse the same table for cosine, too
        ' since COX(x) = SIN(x + 90°)
        ReDim sinTable(0 To 62831 + 15709) As Single
        For i = 0 To UBound(sinTable)
            sinTable(i) = Sin(i / 10000)
        Next
        
        ' evaluate a table for Atn(x)*10000 for x=[0,1], with steps of 0,0001
        ReDim atnTable(0 To 10000) As Long
        For i = LBound(atnTable) To UBound(atnTable)
            atnTable(i) = Atn(i / 10000) * 10000#
        Next
        
        ' evaluate a table for Sqr(i)
        ReDim sqrTable(SQRTABLE_MAX) As Single
        For i = 0 To SQRTABLE_MAX
            sqrTable(i) = Sqr(i)
        Next
    End If
    
    ' get bitmap info
    GetObjectAPI sourcePB.Picture, Len(bmp1), bmp1
    GetObjectAPI destPB.Picture, Len(bmp2), bmp2

    If bmp1.bmPlanes <> 1 Or bmp1.bmBitsPixel <> 8 Or bmp2.bmPlanes <> 1 Or _
        bmp2.bmBitsPixel <> 8 Then
        MsgBox "This routine supports 256-color bitmaps only", vbCritical
        Exit Sub
    End If
    
    ' have the local matrices point to bitmap pixels
    With p1
        .cbElements = 1
        .cDims = 2
        .lLbound1 = 0
        .cElements1 = bmp1.bmHeight
        .lLbound2 = 0
        .cElements2 = bmp1.bmWidthBytes
        .pvData = bmp1.bmBits
    End With
    CopyMemory ByVal VarPtrArray(pict1), VarPtr(p1), 4
    
    With p2
        .cbElements = 1
        .cDims = 2
        .lLbound1 = 0
        .cElements1 = bmp2.bmHeight
        .lLbound2 = 0
        .cElements2 = bmp2.bmWidthBytes
        .pvData = bmp2.bmBits
    End With
    CopyMemory ByVal VarPtrArray(pict2), VarPtr(p2), 4
    
    ' convert the angle into 1/10000ths of radians
    ' subtracting 628310000 ensure that when radians is used in the
    ' subtraction in the loop, it produces a positive number
    radians = degrees / (180 / 3.14159) * 10000& - 628310000
    
    ' we have several cases, depending on where XC falls
    ' compared to the center of the image
    If xc < bmp2.bmWidth \ 2 Then
        xStart = xc
        xEnd = bmp2.bmWidth - 1
    Else
        xStart = 0
        xEnd = xc
    End If

    If yc < bmp2.bmHeight \ 2 Then
        yStart = yc
        yEnd = bmp2.bmWidth - 1
    Else
        yStart = 0
        yEnd = yc
    End If
    
    ' the main loop of this routine scans a squared portion
    ' of the image whose corners falls on the rotation center
    ' Of the four squares that touch the rotation center, here
    ' we choose the one with the highest number of pixels
    ' withing the image
    
    If xEnd - xStart > yEnd - yStart Then
        If yStart = 0 Then
            yStart = yEnd - (xEnd - xStart)
        Else
            yEnd = yStart + (xEnd - xStart)
        End If
    Else
        If xStart = 0 Then
            xStart = xEnd - (yEnd - yStart)
        Else
            xEnd = xStart + (yEnd - yStart)
        End If
    End If

    bmWidth1 = bmp1.bmWidth
    bmHeight1 = bmp1.bmHeight
    
    ' rotate the picture
    
    For x = xStart To xEnd
        ' these values are loop invariant for the following For-Next
        deltaX = x - xc
        deltaXSquared = deltaX * deltaX
        deltaX10000 = deltaX * 10000

        For y = yStart To yEnd
            deltaY = y - yc
            
            ' evaluate the arc-tangent of (deltaY/deltaX)
            ' many IFs are required, since the atnTable() array only
            ' covers the range [0,1] - if (deltaY/deltaX) is > 1 we
            ' must use its reciprocal deltaX/deltaY
            If deltaX > 0 Then
                If deltaY >= 0 Then
                    If deltaY < deltaX Then
                        angle = atnTable((deltaY * 10000) \ deltaX)
                    Else
                        angle = HALFPI - atnTable(deltaX10000 \ deltaY)
                    End If
                Else
                    If -deltaY < deltaX Then
                        angle = -atnTable((deltaY * -10000) \ deltaX)
                    Else
                        angle = -HALFPI + atnTable(-deltaX10000 \ deltaY)
                    End If
                End If
            ElseIf deltaX < 0 Then
                If deltaY > 0 Then
                    If deltaY < -deltaX Then
                        angle = PI - atnTable((deltaY * -10000) \ deltaX)
                    Else
                        angle = HALFPI + atnTable(-deltaX10000 \ deltaY)
                    End If
                Else
                    If deltaY > deltaX Then
                        angle = PI + atnTable((deltaY * 10000) \ deltaX)
                    Else
                        angle = -HALFPI - atnTable(deltaX10000 \ deltaY)
                    End If
                End If
            Else
                If deltaY >= 0 Then
                    angle = HALFPI
                Else
                    angle = -HALFPI
                End If
            End If
            ' --- end of arc-tangent evaluation
                
            ' "angle" is the angle of the segment that goes from
            ' the center to (x,y) - since we wish to evaluate the
            ' color of this point, we must check the point in the
            ' original bitmap that has the same distance from the
            ' center but with a different angle
                
            ' evaluate the distance of (x,y) from the rotation
            ' center, using if possible the value already stored
            ' in sqrTable()
            distanceSquared = deltaXSquared + deltaY * deltaY
            If distanceSquared <= SQRTABLE_MAX Then
                distance = sqrTable(distanceSquared)
            Else
                distance = Sqr(distanceSquared)
            End If
            
            ' the old point in the original bitmap has same
            ' distance but a different angle
            angle0 = (angle - radians) Mod DOUBLEPI
            
            ' evaluate the x,y offset of the old point from
            ' the rotation center
            dx = distance * sinTable(angle0 + HALFPI)  ' really cosine
            dy = distance * sinTable(angle0)
            
            ' if (x,y) falls within the image
            If x >= 0 And x < bmWidth1 And y >= 0 And y < bmHeight1 Then
                ' (x0,y0) is the corresponding point in the original bitmap
                x0 = xc + dx
                y0 = yc + dy
                ' if (x0,y0) falls within the bitmap boundaries, copy the pixel
                ' else, set the (x,y) pixel to zero (background color)
                If x0 >= 0 And x0 < bmWidth1 And y0 >= 0 And y0 < bmHeight1 Then
                    pict2(x, y) = pict1(x0, y0)
                Else
                    pict2(x, y) = 0
                End If
            
                ' this is the point simmetrical to the rotation center - this
                ' block is within the outer If clause because the simmetrical
                ' point can be within the bitmap only if (x,y) was within the
                ' bitmap too
                xx = xc - deltaX
                yy = yc - deltaY
                If xx >= 0 And xx < bmWidth1 And yy >= 0 And yy < bmHeight1 Then
                    x0 = xc - dx
                    y0 = yc - dy
                    If x0 >= 0 And x0 < bmWidth1 And y0 >= 0 And y0 < bmHeight1 _
                        Then
                        pict2(xx, yy) = pict1(x0, y0)
                    Else
                        pict2(xx, yy) = 0
                    End If
                End If
            
            End If
            
            ' now deal with the pixel 90° ahead of the one in (x,y)
            xx = xc + deltaY
            yy = yc - deltaX
            If xx >= 0 And xx < bmWidth1 And yy >= 0 And yy < bmHeight1 Then
                x0 = xc + dy
                y0 = yc - dx
                If x0 >= 0 And x0 < bmWidth1 And y0 >= 0 And y0 < bmHeight1 Then
                    pict2(xx, yy) = pict1(x0, y0)
                Else
                    pict2(xx, yy) = 0
                End If
            End If

            ' now deal with the pixel 270° ahead of the one in (x,y)
            xx = xc - deltaY
            yy = yc + deltaX
            If xx >= 0 And xx < bmWidth1 And yy >= 0 And yy < bmHeight1 Then
                x0 = xc - dy
                y0 = yc + dx
                If x0 >= 0 And x0 < bmWidth1 And y0 >= 0 And y0 < bmHeight1 Then
                    pict2(xx, yy) = pict1(x0, y0)
                Else
                    pict2(xx, yy) = 0
                End If
            End If
        Next
    Next
    
    ' release arrays
    CopyMemory ByVal VarPtrArray(pict1), 0&, 4
    CopyMemory ByVal VarPtrArray(pict2), 0&, 4
    
    ' show the rotated bitmap
    destPB.Refresh
    
End Sub

' Support routine

Private Function VarPtrArray(arr As Variant) As Long
    CopyMemory VarPtrArray, ByVal VarPtr(arr) + 8, 4
End Function
Francesco Balena
 
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap