zoukankan      html  css  js  c++  java
  • Rotate bitmap from TImage to any angle

    http://www.codeguru.com/cpp/g-m/bitmap/specialeffects/article.php/c1725/Invert-mirror-a-bitmap.htm

    Invert (mirror) a bitmap

    http://www.efg2.com/Lab/ImageProcessing/RotateScanline.htm

    Discussion
    If one attempts to rotate a bitmap in the "forward" direction by selection Pixel (i, j) and rotating it into new Pixel (i',j'),
    you will discover "holes" in the rotated image due to the discrete space and integer math.  
    To avoid this problem, use a "reverse" method. 
    Consider each Pixel (i, j) in the rotated image, and lookup where this pixel was in the original image.  
    This technique avoid any "holes" in the rotated image.

    This version of the Rotate Scanline program has three methods of rotation that can be selected via a Combobox: 
    Simple, Center of Pixel 1, and Center of Pixel 2.
    These are implemented in the code in Functions RotateBitmapMethodN.

    The "Simple" method implements the math nearly exactly as described above in the Mathematical Background.   
    Here's the relevant code:

    CONST
      MaxPixelCount = 32768;
    
    TYPE
      TRGBTripleArray = ARRAY[0..MaxPixelCount-1] OF TRGBTriple;
      pRGBTripleArray = ^TRGBTripleArray;
    ...
    
    // "Simple" approach. For pixel (i,j), use "reverse" rotation to find
    // where the rotated pixel must have been before the rotation.
    // Don't bother with center of pixel adjustment.
    // Assumes input BitmapOriginal has PixelFormat = pf24bit.
    FUNCTION RotateBitmapMethod1 (CONST BitmapOriginal: TBitmap;
      CONST iRotationAxis, jRotationAxis: INTEGER;
      CONST AngleOfRotation: DOUBLE  {radians} ): TBitmap;
    
      VAR
        cosTheta   : EXTENDED;
        i          : INTEGER;
        iOriginal  : INTEGER;
        iPrime     : INTEGER;
        j          : INTEGER;
        jOriginal  : INTEGER;
        jPrime     : INTEGER;
        RowOriginal: pRGBTripleArray;
        RowRotated : pRGBTRipleArray;
        sinTheta   : EXTENDED;   
    BEGIN
      // The size of BitmapRotated is the same as BitmapOriginal. PixelFormat
      // must also match since 24-bit GBR triplets are assumed in ScanLine.
      RESULT := TBitmap.Create;
      RESULT.Width  := BitmapOriginal.Width;
      RESULT.Height := BitmapOriginal.Height;
      RESULT.PixelFormat := pf24bit; // Force this
    
      // Get SIN and COS in single call from math library
      sincos(AngleOfRotation, sinTheta, cosTheta);
    
      // If no math library, then use this:
      // sinTheta := SIN(AngleOfRotation);
      // cosTheta := COS(AngleOfRotation);
    
      // Step through each row of rotated image.
      FOR j := RESULT.Height-1 DOWNTO 0 DO
      BEGIN
        RowRotated := RESULT.Scanline[j];
        jPrime := j - jRotationAxis;
    
        FOR i := RESULT.Width-1 DOWNTO 0 DO
        BEGIN
          iPrime := i - iRotationAxis;
          iOriginal := iRotationAxis + ROUND(iPrime * CosTheta - jPrime * sinTheta);
          jOriginal := jRotationAxis + ROUND(iPrime * sinTheta + jPrime * cosTheta);
    
          // Make sure (iOriginal, jOriginal) is in BitmapOriginal. If not,
          // assign blue color to corner points.
          IF (iOriginal >= 0) AND (iOriginal <= BitmapOriginal.Width-1) AND
              (jOriginal >= 0) AND (jOriginal <= BitmapOriginal.Height-1)
          THEN BEGIN
            // Assign pixel from rotated space to current pixel in BitmapRotated
            RowOriginal := BitmapOriginal.Scanline[jOriginal];
            RowRotated[i] := RowOriginal[iOriginal]
          END
          ELSE BEGIN
            RowRotated[i].rgbtBlue := 255; // assign "corner" color
            RowRotated[i].rgbtGreen := 0;
            RowRotated[i].rgbtRed := 0
         END
    
        END
      END
    END {RotateBitmapMethod1};

    In the original version of this program, I went to great length to use the center of the pixel as the "location" of the pixel. 
    Method 2 uses location (i + 0.5, j + 0.5) for pixel (i, j) and then does the "reverse" lookup as described above. 
    Method 3 was like Method 2 but attempted to used more integer math. 

    If you multiply (i + 0.5, j + 0.5) by 2, you can work in space (2i + 1, 2j + 1). 
    Method 3 effectively is a more complicated way to implement Method 2 -- unfortunately Method 3 was the way Rotate ScanLine was implemented. 
    This confused several people, so I restructured this Lab to show the "Simple" method.  
    The timing differences between Methods 1-3 doesn't seem to be that significant.

    Aliasing effects of rotating a 24-bit color bitmap usually aren't noticed for a "real world" image but may be noticed
    if rotating a bitmap with lines or sharp edges. To reduce such aliasing, a bilinear interpolation would be an improvement
    of the existing algorithm that just picks the closest pixel (but it'll be slower).

    For even a better way to rotate a bitmap (but it'll be somewhat slower), see "High Accuracy Rotation of Images,"
    in Computer Vision, Graphics and Image Processing, Vol. 54, No. 4, July 1992, pp. 340-344.

    http://www.efg2.com/Lab/Library/Delphi/Graphics/JackSudarevRotateBitmap.txt

    From: "Jack Sudarev" <jack.sudarev@practel.com.au>
    To: <EarlGlynn>
    Subject: Rotate bitmap
    Date: Thursday, June 22, 2000 10:23 PM
    
    Hello Earl,
    
    I`d like to say thank you for your wonderful site
    and make sort of contribution. A modified "Rotate bitmap
    using scanline" function which performs antialiasing is in
    attachment. Probably not the fastest one, but it`s working.
    
    Regards,
    
    Jack Sudarev
    
    =======================================================================
    
    //Rotate bitmap from TImage to any angle
    procedure RotateBitmap(ImageToChange: TImage; ReferenceImage:
      TImage; Angle: SmallInt; const CameraNumber: Integer; AntiAliasing: Boolean);
    var
      cosTheta, sinTheta: Double;
      i, j: Integer;
      iRotationAxis, jRotationAxis: Integer;
      iOriginal, jOriginal: Integer;
      iPrime, jPrime: Integer;
      iPrimeRotated, jPrimeRotated: Integer;
      iPrimeRotatedA, jPrimeRotatedA: Double;
      iOriginalA, jOriginalA: Double;
      RowOriginal: pRGBArray;
      RowRotated: pRGBArray;
      Theta: Double; // angle in radians
      RotatedBitmap, OriginalBitmap: TBitmap;
    begin
      if Assigned(ImageToChange) then begin
        RotatedBitmap := TBitmap.Create;
        OriginalBitmap := ReferenceImage.Picture.Bitmap;
        if OriginalBitmap.PixelFormat <> pf24bit then
          OriginalBitmap.PixelFormat := pf24bit; // force to 24 bits
        try
          // The size of BitmapRotated is the same as BitmapOriginal.  PixelFormat
          // must also match since 24-bit GBR triplets are assumed in ScanLine.
          RotatedBitmap.Width := OriginalBitmap.Width;
          RotatedBitmap.Height := OriginalBitmap.Height;
          RotatedBitmap.PixelFormat := pf24bit;
    
          // Axis of rotation is normally center of image
          iRotationAxis := RotatedBitmap.Width div 2;
          jRotationAxis := RotatedBitmap.Height div 2;
    
          // Convert degrees to radians.  Use minus sign to force clockwise rotation.
          Theta := (- Angle) * Pi/180;
          sinTheta := Sin(Theta);
          cosTheta := Cos(Theta);
    
          // Step through each row of rotated image.
          for j := RotatedBitmap.Height - 1 downto 0 do begin
            RowRotated := RotatedBitmap.Scanline[j];
            {
              Assume the bitmap has an even number of pixels in both dimensions and
              the axis of rotation is to be the exact middle of the image -- so this
              axis of rotation is not at the middle of any pixel.
    
              The transformation (i,j) to (iPrime, jPrime) puts the center of each
              pixel at odd-numbered coordinates.  The left and right sides of each
              pixel (as well as the top and bottom) then have even-numbered coordinates.
    
              The point (iRotationAxis, jRotationAxis) identifies the axis of rotation.
    
              For a 640 x 480 pixel image, the center point is (320, 240).  Pixels
              numbered (index i) 0..319 are left of this point along the "X" axis and
              pixels numbered 320..639 are right of this point.  Likewise, vertically
              pixels are numbered (index j) 0..239 above the axis of rotation and
              240..479 below the axis of rotation.
    
              The subtraction (i, j) - (iRotationAxis, jRotationAxis) moves the axis of
              rotation from (i, j) to (iRotationAxis, jRotationAxis), which is the
              center of the bitmap in this implementation.
            }
    
            jPrime := 2*(j - jRotationAxis) + 1;
    
            for i := RotatedBitmap.Width - 1 downto 0 do begin
    
              iPrime := 2*(i - iRotationAxis) + 1;
    
              // Rotate (iPrime, jPrime) to location of desired pixel
              // Note:  There is negligible difference between floating point and
              // scaled integer arithmetic here, so keep the math simple (and readable).
              if not AntiAliasing then begin
                iPrimeRotated := Round(iPrime * CosTheta - jPrime * sinTheta);
                jPrimeRotated := Round(iPrime * sinTheta + jPrime * cosTheta);
    
                // Transform back to pixel coordinates of image, including translation
                // of origin from axis of rotation to origin of image.
                iOriginal := (iPrimeRotated - 1) div 2 + iRotationAxis;
                jOriginal := (jPrimeRotated - 1) div 2 + jRotationAxis;
    
                // Make sure (iOriginal, jOriginal) is in BitmapOriginal.  If not,
                // assign white color to corner points.
                if (iOriginal >= 0) and (iOriginal <= OriginalBitmap.Width - 1) and
                (jOriginal >= 0) and (jOriginal <= OriginalBitmap.Height - 1) then begin
                  // Assign pixel from rotated space to current pixel in BitmapRotated
                  RowOriginal := OriginalBitmap.Scanline[jOriginal];
                  RowRotated[i] := RowOriginal[iOriginal]
                end
                else begin
                  RowRotated[i].rgbtBlue := 255; // assign "corner" color
                  RowRotated[i].rgbtGreen := 255;
                  RowRotated[i].rgbtRed := 255;
                end;
              end
              else begin
                //Antialiasing is On
    
                iPrimeRotatedA := iPrime * CosTheta - jPrime * sinTheta;
                jPrimeRotatedA := iPrime * sinTheta + jPrime * cosTheta;
    
                // Transform back to pixel coordinates of image, including translation
                // of origin from axis of rotation to origin of image.
                iOriginalA := (iPrimeRotatedA - 1)/2 + iRotationAxis;
                jOriginalA := (jPrimeRotatedA - 1)/2 + jRotationAxis;
    
                // Make sure (iOriginal, jOriginal) is in BitmapOriginal.  If not,
                // assign white color to corner points.
                if (iOriginalA >= 0) and (iOriginalA <= OriginalBitmap.Width - 1) and
                (jOriginalA >= 0) and (jOriginalA <= OriginalBitmap.Height - 1) then begin
                  // Assign pixel from rotated space to current pixel in BitmapRotated
                  RowRotated[i] := GetSmoothColor(iOriginalA, jOriginalA, OriginalBitmap);
                end
                else begin
                  RowRotated[i].rgbtRed := 255;
                  RowRotated[i].rgbtGreen := 255;
                  RowRotated[i].rgbtBlue := 255; // assign "corner" color
                end;
              end;
            end;//for i
          end;//for j
          AddCameraNumber(RotatedBitmap, CameraNumber);
          ImageToChange.Picture.Graphic := RotatedBitmap;
        finally
          RotatedBitmap.Free;
        end;
      end;//valid input data
    end;
    
    
    //Add camera number to camera image
    procedure AddCameraNumber(RotatedBitmap: TBitmap; const CameraNumber: Integer);
    var
      CameraString: string;
      OldFont: TFont;
      StringSize: TSize;
      TextStart: TPoint;
    begin
      if Assigned(RotatedBitmap) then begin
        //Save canvas font
        OldFont := RotatedBitmap.Canvas.Font;
        CameraString := Format('%.3d', [CameraNumber]);
        //Switch to small font
        RotatedBitmap.Canvas.Font.Name := 'Small Fonts';
        RotatedBitmap.Canvas.Font.Size := 7;
        //Get the sting size
        StringSize := RotatedBitmap.Canvas.TextExtent(CameraString);
        //Define the point from which start to paint the text
        TextStart.x := 16 - StringSize.cx div 2;
        if TextStart.x < 0 then
          TextStart.x := 0;
        TextStart.y := 16 - StringSize.cy div 2;
        if TextStart.y < 0 then
          TextStart.y := 0;
        RotatedBitmap.Canvas.TextOut(TextStart.x, TextStart.y, CameraString);
        //Restore the font
        RotatedBitmap.Canvas.Font := OldFont;
      end;
    end;
    
    //Bilinear interpolation
    function GetSmoothColor(iOriginal, jOriginal: Double;
      OriginalBitmap: TBitmap): TRGBTriple;
    var
      f0, f1, f2, f3: Double;
      P0, P1, P2, P3: TRGBTriple;
      P : pRGBArray;
    begin
      if Assigned(OriginalBitmap) then begin
        //Get fractional parts
        f0 := (1 - Frac(iOriginal))*(1 - Frac(jOriginal));
        f1 := Frac(iOriginal)*(1 - Frac(jOriginal));
        f2 := Frac(iOriginal)*Frac(jOriginal);
        f3 := (1 - Frac(iOriginal))*Frac(jOriginal);
        //Get surrounding points
        P := OriginalBitmap.ScanLine[Trunc(jOriginal)];
        P0 := P[Trunc(iOriginal)];
        P1 := P[Ceil(iOriginal)];
        P := OriginalBitmap.ScanLine[Ceil(jOriginal)];
        P2 := P[Trunc(iOriginal)];
        P3 := P[Ceil(iOriginal)];
        //Calculate result color
        Result.rgbtRed := Round(P0.rgbtRed*f0 + P1.rgbtRed*f1 + P2.rgbtRed*f2 + P3.rgbtRed*f3);
        Result.rgbtGreen := Round(P0.rgbtGreen*f0 + P1.rgbtGreen*f1 + P2.rgbtGreen*f2 + P3.rgbtGreen*f3);
        Result.rgbtBlue := Round(P0.rgbtBlue*f0 + P1.rgbtBlue*f1 + P2.rgbtBlue*f2 + P3.rgbtBlue*f3);
      end;
    end;

    http://www.codeguru.com/cpp/g-m/bitmap/specialeffects/article.php/c1743/Rotate-a-bitmap-image.htm

    Rotate a bitmap image

    Posted by Zafir Anjum on August 5th, 1998

    If you are targeting your application for Windows NT alone, then you have access to API functions to help you rotate the bitmap.
    You can either use world transformation and BitBlt() or use PlgBlt() for rotating the bitmap.
    A function using the first combination is shown below.

    If you are targeting multiple platforms, then your task becomes tougher. You could either rotate each pixel in the source bitmap one
    at a time or directly manipulate the DIB bits to get the rotated bitmap.

    The first method is very slow to the point of being useless and the second is more complex but is fast enough.
    We cover both these methods below. Note that all the functions shown below create a new bitmap ( in one case it creates a DIB ).
    If instead of a new bitmap you want the rotated image to be drawn directly, you will need to modify the functions.

    Function 1: GetRotatedBitmapNT() This function is specific to NT and will not work on Windows95.
    It's the simplest and probably the fastest of the three functions listed in this topic.
    The other two functions share some of the same initial code.

    All the three functions expect the angle of rotation to be in radians.
    If you have degrees to begin with you can convert from degrees to radians by using the formula

    radian = (2*pi *degree)/360

    Here are the steps that we take to rotate the bitmap.

      1. Create a couple of device contexts compatible with the display.
        One of them will be used to hold the source bitmap and the other will hold the destination bitmap (for the rotated image).
      2. Precompute the cosine and the sine of the angle. This will help save a few microseconds in subsequent computations.
      3. Compute the bounding rectangle of the rotated image. We use the formula 
        newx = x.cos(angle) + y.sin(angle) 
        newy = y.cos(angle) - x.sin(angle)
         
        We assume one of the corners (0,0) to be the center of rotation and therefore need to calculate the new co-ordinates of
        the other three corners. Based on this we can determine the widht and height of the new bitmap.
      4. Since the rotated image will not occupy the entire area of the new bitmap, we fill the destination bitmap
        with the background color specified through the function argument.
      5. Since we will use NTs support for linear transformation we set the graphic mode of the destination DC to support this.
      6. We set up the XFORM struction and call SetWorldTransform() to activate the transformation.
        The SetWorldTransform() function sets up the stage for BitBlt(), which is when the transformation takes place.
        The algorithm used for the linear transformation is 
        newx = x * eM11 + y * eM21 + eDx 
        newy = x * eM12 + y * eM22 + eDy
         
        For rotation eM11 and eM22 should be the cosine of the rotation angle,
        eM12 should be the sine of the rotation angle and eM21 should be -eM12.
        In the DC we are using, since the +ve y direction is downwards, we reverse the signs of eM12 and eM21.
        We also set the translation components (eDx & eDy) so that rotated image fits inside the new bitmap
        without part of it getting clipped.
      7. We finally call BitBlt() to do the actual rotation.
        This call in itself looks like it will simply copy the image.
        However, the previous call to SetWorldTransform() causes the image to be rotated.
    // GetRotatedBitmapNT    - Create a new bitmap with rotated image
    // Returns        - Returns new bitmap with rotated image
    // hBitmap        - Bitmap to rotate
    // radians        - Angle of rotation in radians
    // clrBack        - Color of pixels in the resulting bitmap that do
    //              not get covered by source pixels
    HBITMAP GetRotatedBitmapNT( HBITMAP hBitmap, float radians, COLORREF clrBack )
    {
        // Create a memory DC compatible with the display
        CDC sourceDC, destDC;
        sourceDC.CreateCompatibleDC( NULL );
        destDC.CreateCompatibleDC( NULL );
     
        // Get logical coordinates
        BITMAP bm;
        ::GetObject( hBitmap, sizeof( bm ), &bm );
     
        float cosine = (float)cos(radians);
        float sine = (float)sin(radians);
     
        // Compute dimensions of the resulting bitmap
        // First get the coordinates of the 3 corners other than origin
        int x1 = (int)(bm.bmHeight * sine);
        int y1 = (int)(bm.bmHeight * cosine);
        int x2 = (int)(bm.bmWidth * cosine + bm.bmHeight * sine);
        int y2 = (int)(bm.bmHeight * cosine - bm.bmWidth * sine);
        int x3 = (int)(bm.bmWidth * cosine);
        int y3 = (int)(-bm.bmWidth * sine);
     
        int minx = min(0,min(x1, min(x2,x3)));
        int miny = min(0,min(y1, min(y2,y3)));
        int maxx = max(0,max(x1, max(x2,x3)));
        int maxy = max(0,max(y1, max(y2,y3)));
     
        int w = maxx - minx;
        int h = maxy - miny;
     
        // Create a bitmap to hold the result
        HBITMAP hbmResult = ::CreateCompatibleBitmap(CClientDC(NULL), w, h);
     
        HBITMAP hbmOldSource = (HBITMAP)::SelectObject( sourceDC.m_hDC, hBitmap );
        HBITMAP hbmOldDest = (HBITMAP)::SelectObject( destDC.m_hDC, hbmResult );
     
        // Draw the background color before we change mapping mode
        HBRUSH hbrBack = CreateSolidBrush( clrBack );
        HBRUSH hbrOld = (HBRUSH)::SelectObject( destDC.m_hDC, hbrBack );
        destDC.PatBlt( 0, 0, w, h, PATCOPY );
        ::DeleteObject( ::SelectObject( destDC.m_hDC, hbrOld ) );
     
        // We will use world transform to rotate the bitmap
        SetGraphicsMode(destDC.m_hDC, GM_ADVANCED);
        XFORM xform;
        xform.eM11 = cosine;
        xform.eM12 = -sine;
        xform.eM21 = sine;
        xform.eM22 = cosine;
        xform.eDx = (float)-minx;
        xform.eDy = (float)-miny;
     
        SetWorldTransform( destDC.m_hDC, &xform );
     
        // Now do the actual rotating - a pixel at a time
        destDC.BitBlt(0,0,bm.bmWidth, bm.bmHeight, &sourceDC, 0, 0, SRCCOPY );
     
        // Restore DCs
        ::SelectObject( sourceDC.m_hDC, hbmOldSource );
        ::SelectObject( destDC.m_hDC, hbmOldDest );
     
        return hbmResult;
    }

    Function 2: GetRotatedBitmap() using GetPixel & SetPixel

    This function is more for education purpose. It does work but it takes a few seconds to finish.
    It might even be useful for very small bitmaps.

    Here's what the function does:

    1. Create a couple of device contexts to work with.
    2. Computes the bounding rectangle of the rotated image and fills it with the background color.
    3. We set the mapping mode in both the device contexts so that the +ve direction of the y-axis goes up.
      We also set the origin in the destination DC to the point that would coincide with the point (0,0) in the source DC.
      This is simply to make our computation somewhat simpler.
    4. We traverse through each pixel in the destination bitmap and compute which pixel, if any, from the source bitmap corresponds to it.
      This is somewhat unusual. You would expect to traverse the source pixels and copy it to its proper place in the destination bitmap. Although, this would be a more straight forward approach, it doesn't work right.
      The problem is that it leaves a few gaps in the destination bitmap. Reversing the transformation fixes this problem.
    // GetRotatedBitmap    - Create a new bitmap with rotated image
    // Returns        - Returns new bitmap with rotated image
    // hBitmap        - Bitmap to rotate
    // radians        - Angle of rotation in radians
    // clrBack        - Color of pixels in the resulting bitmap that do
    //              not get covered by source pixels
    // Note            - If the bitmap uses colors not in the system palette 
    //              then the result is unexpected. You can fix this by
    //              adding an argument for the logical palette.
    HBITMAP GetRotatedBitmap( HBITMAP hBitmap, float radians, COLORREF clrBack )
    {
        // Create a memory DC compatible with the display
        CDC sourceDC, destDC;
        sourceDC.CreateCompatibleDC( NULL );
        destDC.CreateCompatibleDC( NULL );
     
        // Get logical coordinates
        BITMAP bm;
        ::GetObject( hBitmap, sizeof( bm ), &bm );
     
        float cosine = (float)cos(radians);
        float sine = (float)sin(radians);
     
        // Compute dimensions of the resulting bitmap
        // First get the coordinates of the 3 corners other than origin
        int x1 = (int)(-bm.bmHeight * sine);
        int y1 = (int)(bm.bmHeight * cosine);
        int x2 = (int)(bm.bmWidth * cosine - bm.bmHeight * sine);
        int y2 = (int)(bm.bmHeight * cosine + bm.bmWidth * sine);
        int x3 = (int)(bm.bmWidth * cosine);
        int y3 = (int)(bm.bmWidth * sine);
     
        int minx = min(0,min(x1, min(x2,x3)));
        int miny = min(0,min(y1, min(y2,y3)));
        int maxx = max(x1, max(x2,x3));
        int maxy = max(y1, max(y2,y3));
     
        int w = maxx - minx;
        int h = maxy - miny;
     
     
        // Create a bitmap to hold the result
        HBITMAP hbmResult = ::CreateCompatibleBitmap(CClientDC(NULL), w, h);
     
        HBITMAP hbmOldSource = (HBITMAP)::SelectObject( sourceDC.m_hDC, hBitmap );
        HBITMAP hbmOldDest = (HBITMAP)::SelectObject( destDC.m_hDC, hbmResult );
     
        // Draw the background color before we change mapping mode
        HBRUSH hbrBack = CreateSolidBrush( clrBack );
        HBRUSH hbrOld = (HBRUSH)::SelectObject( destDC.m_hDC, hbrBack );
        destDC.PatBlt( 0, 0, w, h, PATCOPY );
        ::DeleteObject( ::SelectObject( destDC.m_hDC, hbrOld ) );
     
        // Set mapping mode so that +ve y axis is upwords
        sourceDC.SetMapMode(MM_ISOTROPIC);
        sourceDC.SetWindowExt(1,1);
        sourceDC.SetViewportExt(1,-1);
        sourceDC.SetViewportOrg(0, bm.bmHeight-1);
     
        destDC.SetMapMode(MM_ISOTROPIC);
        destDC.SetWindowExt(1,1);
        destDC.SetViewportExt(1,-1);
        destDC.SetWindowOrg(minx, maxy);
     
        // Now do the actual rotating - a pixel at a time
        // Computing the destination point for each source point
        // will leave a few pixels that do not get covered
        // So we use a reverse transform - e.i. compute the source point
        // for each destination point
     
        for( int y = miny; y < maxy; y++ )
        {
            for( int x = minx; x < maxx; x++ )
            {
                int sourcex = (int)(x*cosine + y*sine);
                int sourcey = (int)(y*cosine - x*sine);
                if( sourcex >= 0 && sourcex < bm.bmWidth && sourcey >= 0 
                        && sourcey < bm.bmHeight )
                    destDC.SetPixel(x,y,sourceDC.GetPixel(sourcex,sourcey));
            }
        }
     
        // Restore DCs
        ::SelectObject( sourceDC.m_hDC, hbmOldSource );
        ::SelectObject( destDC.m_hDC, hbmOldDest );
     
        return hbmResult;
    }

    Function 3: GetRotatedBitmap() using DIB

    This function works on the same principle as the previous function.
    Unlike the previous function this function is usable and works directly on the bits of the device-independent bitmap.
    Most of the complexity of this function is due to the fact that DIBs are organized differently depending on the number of colors it uses.

    There are two main attributes of a DIB that affect the flow of control in this function.
    The first is the number of bits used to specify a pixel. This affects how the color information is read and written.
    The second attribute is the compression. This function does not support run length encoded DIBs.
    The two compression flags handled are the BI_RGB and BI_BITFIELD. These flags affect how we compute the start of the bitmap bits.
    The BI_BITFIELD is valid only when the bits per pixel (BPP) is more than 8.
    This indicates that immediately after the BITMAPINFOHEADER are three double word values which represent a bitmask
    for the red, green and blue colors. The first of the double word mask is for red.

    After computing the size of the new DIB we allocate space for a new bitmap and initialize it with values from the source DIB.

    The width and height of course is set to new values. We then set the background color for the new DIB to the specified value.
    Actually we set the color only when the BPP is 8 or less, otherwise we simply compute a color value specific to the DIB
    and save it for later use. If the bits per pixel is 4 or 8, we scan through the color table to find a match
    for the supplied background color. If we find the color, we set this index for all the pixels in the bitmap
    otherwise we let the color remain black.

    For bitmaps with 16 or more bits per pixel we create a 16, 24 or 32 bit value that will represent the color.
    We only use those color masks that is supported by both Windows 95 and NT.

    The actual rotation involves using a reverse transform. That is, for each destination pixel we determine the source pixel
    that should be copied there. As I've noted while discussing the previous function, the reason for this is that
    the straight transform will leave small blank spots. The main stuff here is getting and setting the color information.

    When dealing with *monochrome bitmaps*, each bit represents a pixel. The most significant bit in a byte is the left most pixel.
    This explains the expression (0x80 >> x%8) which gives us the bit position of the given pixel.
    Since we are dealing with bits, we have to first clear out the relevant bit in the destination bitmap before using the OR operation
    to set it to the new value.
    Note that the bit position in the source and the bit position in the destination bitmaps are likely to be different.

    For bitmaps with 4 bits per pixel, we again have to deal with bitmasks.
    In this case the 4 most significant bits specify the pixel on the left.

    When the bits per pixel is 8, 16, 24 or 32 we copy 1,2,3 and 4 bytes respectively.
    Also, when the bits per pixel is more than 8 and the destination pixel does not correspond to any of the source pixel,
    then we set it to the background color.

    // GetRotatedBitmap    - Create a new bitmap with rotated image
    // Returns        - Returns new bitmap with rotated image
    // hDIB            - Device-independent bitmap to rotate
    // radians        - Angle of rotation in radians
    // clrBack        - Color of pixels in the resulting bitmap that do
    //              not get covered by source pixels
    HANDLE GetRotatedBitmap( HANDLE hDIB, float radians, COLORREF clrBack )
    {
        // Get source bitmap info
        BITMAPINFO &bmInfo = *(LPBITMAPINFO)hDIB ;
        int bpp = bmInfo.bmiHeader.biBitCount;        // Bits per pixel
        
        int nColors = bmInfo.bmiHeader.biClrUsed ? bmInfo.bmiHeader.biClrUsed : 
                        1 << bpp;
        int nWidth = bmInfo.bmiHeader.biWidth;
        int nHeight = bmInfo.bmiHeader.biHeight;
        int nRowBytes = ((((nWidth * bpp) + 31) & ~31) / 8);
     
        // Make sure height is positive and biCompression is BI_RGB or BI_BITFIELDS
        DWORD &compression = bmInfo.bmiHeader.biCompression;
        if( nHeight < 0 || (compression!=BI_RGB && compression!=BI_BITFIELDS))
            return NULL;
     
        LPVOID lpDIBBits;
        if( bmInfo.bmiHeader.biBitCount > 8 )
            lpDIBBits = (LPVOID)((LPDWORD)(bmInfo.bmiColors +
                bmInfo.bmiHeader.biClrUsed) + 
                ((compression == BI_BITFIELDS) ? 3 : 0));
        else
            lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors);
     
        
        // Compute the cosine and sine only once
        float cosine = (float)cos(radians);
        float sine = (float)sin(radians);
     
        // Compute dimensions of the resulting bitmap
        // First get the coordinates of the 3 corners other than origin
        int x1 = (int)(-nHeight * sine);
        int y1 = (int)(nHeight * cosine);
        int x2 = (int)(nWidth * cosine - nHeight * sine);
        int y2 = (int)(nHeight * cosine + nWidth * sine);
        int x3 = (int)(nWidth * cosine);
        int y3 = (int)(nWidth * sine);
     
        int minx = min(0,min(x1, min(x2,x3)));
        int miny = min(0,min(y1, min(y2,y3)));
        int maxx = max(x1, max(x2,x3));
        int maxy = max(y1, max(y2,y3));
     
        int w = maxx - minx;
        int h = maxy - miny;
     
     
        // Create a DIB to hold the result
        int nResultRowBytes = ((((w * bpp) + 31) & ~31) / 8);
        long len = nResultRowBytes * h;
        int nHeaderSize = ((LPBYTE)lpDIBBits-(LPBYTE)hDIB) ;
        HANDLE hDIBResult = GlobalAlloc(GMEM_FIXED,len+nHeaderSize);
        // Initialize the header information
        memcpy( (void*)hDIBResult, (void*)hDIB, nHeaderSize);
        BITMAPINFO &bmInfoResult = *(LPBITMAPINFO)hDIBResult ;
        bmInfoResult.bmiHeader.biWidth = w;
        bmInfoResult.bmiHeader.biHeight = h;
        bmInfoResult.bmiHeader.biSizeImage = len;
     
        LPVOID lpDIBBitsResult = (LPVOID)((LPBYTE)hDIBResult + nHeaderSize);
     
        // Get the back color value (index)
        ZeroMemory( lpDIBBitsResult, len );
        DWORD dwBackColor;
        switch(bpp)
        {
        case 1:    //Monochrome
            if( clrBack == RGB(255,255,255) )
                memset( lpDIBBitsResult, 0xff, len );
            break;
        case 4:
        case 8:    //Search the color table
            int i;
            for(i = 0; i < nColors; i++ )
            {
                if( bmInfo.bmiColors[i].rgbBlue ==  GetBValue(clrBack)
                    && bmInfo.bmiColors[i].rgbGreen ==  GetGValue(clrBack)
                    && bmInfo.bmiColors[i].rgbRed ==  GetRValue(clrBack) )
                {
                    if(bpp==4) i = i | i<<4;
                    memset( lpDIBBitsResult, i, len );
                    break;
                }
            }
            // If not match found the color remains black
            break;
        case 16:
            // Windows95 supports 5 bits each for all colors or 5 bits for red & blue
            // and 6 bits for green - Check the color mask for RGB555 or RGB565
            if( *((DWORD*)bmInfo.bmiColors) == 0x7c00 )
            {
                // Bitmap is RGB555
                dwBackColor = ((GetRValue(clrBack)>>3) << 10) + 
                        ((GetRValue(clrBack)>>3) << 5) +
                        (GetBValue(clrBack)>>3) ;
            }
            else
            {
                // Bitmap is RGB565
                dwBackColor = ((GetRValue(clrBack)>>3) << 11) + 
                        ((GetRValue(clrBack)>>2) << 5) +
                        (GetBValue(clrBack)>>3) ;
            }
            break;
        case 24:
        case 32:
            dwBackColor = (((DWORD)GetRValue(clrBack)) << 16) | 
                    (((DWORD)GetGValue(clrBack)) << 8) |
                    (((DWORD)GetBValue(clrBack)));
            break;
        }
     
     
        // Now do the actual rotating - a pixel at a time
        // Computing the destination point for each source point
        // will leave a few pixels that do not get covered
        // So we use a reverse transform - e.i. compute the source point
        // for each destination point
     
        for( int y = 0; y < h; y++ )
        {
            for( int x = 0; x < w; x++ )
            {
                int sourcex = (int)((x+minx)*cosine + (y+miny)*sine);
                int sourcey = (int)((y+miny)*cosine - (x+minx)*sine);
                if( sourcex >= 0 && sourcex < nWidth && sourcey >= 0 
                    && sourcey < nHeight )
                {
                    // Set the destination pixel
                    switch(bpp)
                    {
                        BYTE mask;
                    case 1:        //Monochrome
                        mask = *((LPBYTE)lpDIBBits + nRowBytes*sourcey + 
                            sourcex/8) & (0x80 >> sourcex%8);
                        //Adjust mask for destination bitmap
                        mask = mask ? (0x80 >> x%8) : 0;
                        *((LPBYTE)lpDIBBitsResult + nResultRowBytes*(y) + 
                                    (x/8)) &= ~(0x80 >> x%8);
                        *((LPBYTE)lpDIBBitsResult + nResultRowBytes*(y) + 
                                    (x/8)) |= mask;
                        break;
                    case 4:
                        mask = *((LPBYTE)lpDIBBits + nRowBytes*sourcey + 
                            sourcex/2) & ((sourcex&1) ? 0x0f : 0xf0);
                        //Adjust mask for destination bitmap
                        if( (sourcex&1) != (x&1) )
                            mask = (mask&0xf0) ? (mask>>4) : (mask<<4);
                        *((LPBYTE)lpDIBBitsResult + nResultRowBytes*(y) + 
                                (x/2)) &= ~((x&1) ? 0x0f : 0xf0);
                        *((LPBYTE)lpDIBBitsResult + nResultRowBytes*(y) + 
                                (x/2)) |= mask;
                        break;
                    case 8:
                        BYTE pixel ;
                        pixel = *((LPBYTE)lpDIBBits + nRowBytes*sourcey + 
                                sourcex);
                        *((LPBYTE)lpDIBBitsResult + nResultRowBytes*(y) + 
                                (x)) = pixel;
                        break;
                    case 16:
                        DWORD dwPixel;
                        dwPixel = *((LPWORD)((LPBYTE)lpDIBBits + 
                                nRowBytes*sourcey + sourcex*2));
                        *((LPWORD)((LPBYTE)lpDIBBitsResult + 
                            nResultRowBytes*y + x*2)) = (WORD)dwPixel;
                        break;
                    case 24:
                        dwPixel = *((LPDWORD)((LPBYTE)lpDIBBits + 
                            nRowBytes*sourcey + sourcex*3)) & 0xffffff;
                        *((LPDWORD)((LPBYTE)lpDIBBitsResult + 
                            nResultRowBytes*y + x*3)) |= dwPixel;
                        break;
                    case 32:
                        dwPixel = *((LPDWORD)((LPBYTE)lpDIBBits + 
                            nRowBytes*sourcey + sourcex*4));
                        *((LPDWORD)((LPBYTE)lpDIBBitsResult + 
                            nResultRowBytes*y + x*4)) = dwPixel;
                    }
                }
                else 
                {
                    // Draw the background color. The background color
                    // has already been drawn for 8 bits per pixel and less
                    switch(bpp)
                    {
                    case 16:
                        *((LPWORD)((LPBYTE)lpDIBBitsResult + 
                            nResultRowBytes*y + x*2)) = 
                            (WORD)dwBackColor;
                        break;
                    case 24:
                        *((LPDWORD)((LPBYTE)lpDIBBitsResult + 
                            nResultRowBytes*y + x*3)) |= dwBackColor;
                        break;
                    case 32:
                        *((LPDWORD)((LPBYTE)lpDIBBitsResult + 
                            nResultRowBytes*y + x*4)) = dwBackColor;
                        break;
                    }
                }
            }
        }
     
        return hDIBResult;
    }

  • 相关阅读:
    点击listview 的列头对其item进行自动排序
    将选择的图片显示在listview中,并显示filename,path和type
    【翻译】8 个可以节省时间的 C# 开发相关工具
    【原创】关于乘法运算的新思路
    【翻译】为什么我们要用抽象类?
    【翻译】如何使用 C# 的 BackgroundWorker
    【汉化】DevExpress插件中RichEdit控件的自定义汉化方法
    关于C#日期格式化问题
    C#获取(大陆)身份证基本信息的算法
    C#关于精确年龄的算法(精确到天)
  • 原文地址:https://www.cnblogs.com/shangdawei/p/3070128.html
Copyright © 2011-2022 走看看