zoukankan      html  css  js  c++  java
  • Rotate bitmap by real angle

    tl;dr; Use GDI+

    SetWorldTransform

    With WinAPI's SetWorldTransform you can transform the space of device context: rotate, shear, offset, and scale. This is done by setting the members of a transform matrix of type XFORM. Fill its members according the documentation.

    procedure RotateBitmap(Bmp: TBitmap; Rads: Single; AdjustSize: Boolean;
      BkColor: TColor = clNone);
    var
      C: Single;
      S: Single;
      XForm: tagXFORM;
      Tmp: TBitmap;
    begin
      C := Cos(Rads);
      S := Sin(Rads);
      XForm.eM11 := C;
      XForm.eM12 := S;
      XForm.eM21 := -S;
      XForm.eM22 := C;
      Tmp := TBitmap.Create;
      try
        Tmp.TransparentColor := Bmp.TransparentColor;
        Tmp.TransparentMode := Bmp.TransparentMode;
        Tmp.Transparent := Bmp.Transparent;
        Tmp.Canvas.Brush.Color := BkColor;
        if AdjustSize then
        begin
          Tmp.Width := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S));
          Tmp.Height := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C));
          XForm.eDx := (Tmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
          XForm.eDy := (Tmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
        end
        else
        begin
          Tmp.Width := Bmp.Width;
          Tmp.Height := Bmp.Height;
          XForm.eDx := (Bmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
          XForm.eDy := (Bmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
        end;
        SetGraphicsMode(Tmp.Canvas.Handle, GM_ADVANCED);
        SetWorldTransform(Tmp.Canvas.Handle, XForm);
        BitBlt(Tmp.Canvas.Handle, 0, 0, Tmp.Width, Tmp.Height, Bmp.Canvas.Handle,
          0, 0, SRCCOPY);
        Bmp.Assign(Tmp);
      finally
        Tmp.Free;
      end;
    end;
    
    PlgBlt

    The PlgBlt function performs a bit-block transfer from the specified rectangle in the source device context to the specified parallelogram in the destination device context. Map the corner points of the source image via the lpPoint parameter.

    procedure RotateBitmap(Bmp: TBitmap; Rads: Single; AdjustSize: Boolean;
      BkColor: TColor = clNone);
    var
      C: Single;
      S: Single;
      Tmp: TBitmap;
      OffsetX: Single;
      OffsetY: Single;
      Points: array[0..2] of TPoint;
    begin
      C := Cos(Rads);
      S := Sin(Rads);
      Tmp := TBitmap.Create;
      try
        Tmp.TransparentColor := Bmp.TransparentColor;
        Tmp.TransparentMode := Bmp.TransparentMode;
        Tmp.Transparent := Bmp.Transparent;
        Tmp.Canvas.Brush.Color := BkColor;
        if AdjustSize then
        begin
          Tmp.Width := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S));
          Tmp.Height := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C));
          OffsetX := (Tmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
          OffsetY := (Tmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
        end
        else
        begin
          Tmp.Width := Bmp.Width;
          Tmp.Height := Bmp.Height;
          OffsetX := (Bmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
          OffsetY := (Bmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
        end;
        Points[0].X := Round(OffsetX);
        Points[0].Y := Round(OffsetY);
        Points[1].X := Round(OffsetX + Bmp.Width * C);
        Points[1].Y := Round(OffsetY + Bmp.Width * S);
        Points[2].X := Round(OffsetX - Bmp.Height * S);
        Points[2].Y := Round(OffsetY + Bmp.Height * C);
        PlgBlt(Tmp.Canvas.Handle, Points, Bmp.Canvas.Handle, 0, 0, Bmp.Width,
          Bmp.Height, 0, 0, 0);
        Bmp.Assign(Tmp);
      finally
        Tmp.Free;
      end;
    end;
    
    Graphics32

    Graphics32 is a library especially designed for fast bitmap handling. It requires some experience to grasp its full potential, but the documentation as well as the provided examples should get you started.

    A rotation of a TBitmap32 image is done by transforming it by one of the many available transformation classes. The TAffineTransformation class is needed here. First, shift the image half its size to the upper left, then rotate, and shift the result back to the lower right, possibly using the new image dimensions.

    uses
      GR32, GR32_Transforms;
    
    procedure RotateBitmap(Bmp: TBitmap32; Degs: Integer; AdjustSize: Boolean;
      BkColor: TColor = clNone; Transparent: Boolean = False); overload;
    var
      Tmp: TBitmap32;
      Transformation: TAffineTransformation;
    begin
      Tmp := TBitmap32.Create;
      Transformation := TAffineTransformation.Create;
      try
        Transformation.BeginUpdate;
        Transformation.SrcRect := FloatRect(0, 0, Bmp.Width, Bmp.Height);
        Transformation.Translate(-0.5 * Bmp.Width, -0.5 * Bmp.Height);
        Transformation.Rotate(0, 0, -Degs);
        if AdjustSize then
          with Transformation.GetTransformedBounds do
            Tmp.SetSize(Round(Right - Left), Round(Bottom - Top))
        else
          Tmp.SetSize(Bmp.Width, Bmp.Height);
        Transformation.Translate(0.5 * Tmp.Width, 0.5 * Tmp.Height);
        Transformation.EndUpdate;
        Tmp.Clear(Color32(BkColor));
        if not Transparent then
          Bmp.DrawMode := dmTransparent;
        Transform(Tmp, Bmp, Transformation);
        Bmp.Assign(Tmp);
        Bmp.OuterColor := Color32(BkColor);
        if Transparent then
          Bmp.DrawMode := dmTransparent;
      finally
        Transformation.Free;
        Tmp.Free;
      end;
    end;
    
    procedure RotateBitmap(Bmp: TBitmap; Degs: Integer; AdjustSize: Boolean;
      BkColor: TColor = clNone); overload;
    var
      Tmp: TBitmap32;
      Transparent: Boolean;
    begin
      Tmp := TBitmap32.Create;
      try
        Transparent := Bmp.Transparent;
        Tmp.Assign(Bmp);
        RotateBitmapGR32(Tmp, Degs, AdjustSize, BkColor, Transparent);
        Bmp.Assign(Tmp);
        if Transparent then
          Bmp.Transparent := True;
      finally
        Tmp.Free;
      end;
    end;
    
    GDI+

    Introduced in Windows XP, Microsoft's GDI+ API is more efficient then the default GDI API. For Delphi 2009 and up, the library is available from here. For older Delphi versions, the library is available from here.

    In GDI+ the rotation is also done by a transformation matrix. Drawing works quite differently though. Create a TGPGraphics object and attach it to a device context with its constructor. Subsequently, drawing operations on the object are translated by the API and will be output to the destination context.

    uses
      GDIPOBJ, GDIPAPI; // < D2009
      GdiPlus;          // >= D2009
    
    procedure RotateBitmap(Bmp: TBitmap; Degs: Integer; AdjustSize: Boolean;
      BkColor: TColor = clNone);
    var
      Tmp: TGPBitmap;
      Matrix: TGPMatrix;
      C: Single;
      S: Single;
      NewSize: TSize;
      Graphs: TGPGraphics;
      P: TGPPointF;
    begin
      Tmp := TGPBitmap.Create(Bmp.Handle, Bmp.Palette);
      Matrix := TGPMatrix.Create;
      try
        Matrix.RotateAt(Degs, MakePoint(0.5 * Bmp.Width, 0.5 * Bmp.Height));
        if AdjustSize then
        begin
          C := Cos(DegToRad(Degs));
          S := Sin(DegToRad(Degs));
          NewSize.cx := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S));
          NewSize.cy := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C));
          Bmp.Width := NewSize.cx;
          Bmp.Height := NewSize.cy;
        end;
        Graphs := TGPGraphics.Create(Bmp.Canvas.Handle);
        try
          Graphs.Clear(ColorRefToARGB(ColorToRGB(BkColor)));
          Graphs.SetTransform(Matrix);
          Graphs.DrawImage(Tmp, (Cardinal(Bmp.Width) - Tmp.GetWidth) div 2,
            (Cardinal(Bmp.Height) - Tmp.GetHeight) div 2);
        finally
          Graphs.Free;
        end;
      finally
        Matrix.Free;
        Tmp.Free;
      end;
    end;
    
    Handling transparency

    The routines above preserve the transparent settings of the fead bitmap, with the exception of the Graphics32 solution which requires an additional Transparent parameter.

    Performance and image quality

    I wrote a test application (see full code below) to tune the performance of the various methods and to compare the resulting image quality.

    The first and most important conclusion is that GDI+ uses anti-aliasing where the others do not, resulting in the best image quality. (I unsuccessfully tried to prevent anti-aliasing by setting CompositingQuality, InterpolationMode, SmoothingMode, and PixelOffsetMode, so when anti-aliasing is not preferred, do not use GDI+.)

    Furthermore, the GDI+ solution is also the fastest method, by far.

    Test results

    unit RotateTestForm;
    
    interface
    
    uses
      Windows, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls,
      JPEG, Math, GR32, GR32_Transforms, GDIPOBJ, GDIPAPI {, GdiPlus};
    
    type
      TTestForm = class(TForm)
      private
        FImage: TImage;
        FOpenDialog: TOpenDialog;
        procedure FormPaint(Sender: TObject);
      public
        constructor Create(AOwner: TComponent); override;
      end;
    
    var
      TestForm: TTestForm;
    
    implementation
    
    {$R *.dfm}
    
    procedure RotateBitmapSWT(Bmp: TBitmap; Rads: Single; AdjustSize: Boolean;
      BkColor: TColor = clNone);
    var
      C: Single;
      S: Single;
      XForm: TXForm;
      Tmp: TBitmap;
    begin
      C := Cos(Rads);
      S := Sin(Rads);
      XForm.eM11 := C;
      XForm.eM12 := S;
      XForm.eM21 := -S;
      XForm.eM22 := C;
      Tmp := TBitmap.Create;
      try
        Tmp.TransparentColor := Bmp.TransparentColor;
        Tmp.TransparentMode := Bmp.TransparentMode;
        Tmp.Transparent := Bmp.Transparent;
        Tmp.Canvas.Brush.Color := BkColor;
        if AdjustSize then
        begin
          Tmp.Width := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S));
          Tmp.Height := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C));
          XForm.eDx := (Tmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
          XForm.eDy := (Tmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
        end
        else
        begin
          Tmp.Width := Bmp.Width;
          Tmp.Height := Bmp.Height;
          XForm.eDx := (Bmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
          XForm.eDy := (Bmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
        end;
        SetGraphicsMode(Tmp.Canvas.Handle, GM_ADVANCED);
        SetWorldTransform(Tmp.Canvas.Handle, XForm);
        BitBlt(Tmp.Canvas.Handle, 0, 0, Tmp.Width, Tmp.Height, Bmp.Canvas.Handle,
          0, 0, SRCCOPY);
        Bmp.Assign(Tmp);
      finally
        Tmp.Free;
      end;
    end;
    
    procedure RotateBitmapPLG(Bmp: TBitmap; Rads: Single; AdjustSize: Boolean;
      BkColor: TColor = clNone);
    var
      C: Single;
      S: Single;
      Tmp: TBitmap;
      OffsetX: Single;
      OffsetY: Single;
      Points: array[0..2] of TPoint;
    begin
      C := Cos(Rads);
      S := Sin(Rads);
      Tmp := TBitmap.Create;
      try
        Tmp.TransparentColor := Bmp.TransparentColor;
        Tmp.TransparentMode := Bmp.TransparentMode;
        Tmp.Transparent := Bmp.Transparent;
        Tmp.Canvas.Brush.Color := BkColor;
        if AdjustSize then
        begin
          Tmp.Width := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S));
          Tmp.Height := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C));
          OffsetX := (Tmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
          OffsetY := (Tmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
        end
        else
        begin
          Tmp.Width := Bmp.Width;
          Tmp.Height := Bmp.Height;
          OffsetX := (Bmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
          OffsetY := (Bmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
        end;
        Points[0].X := Round(OffsetX);
        Points[0].Y := Round(OffsetY);
        Points[1].X := Round(OffsetX + Bmp.Width * C);
        Points[1].Y := Round(OffsetY + Bmp.Width * S);
        Points[2].X := Round(OffsetX - Bmp.Height * S);
        Points[2].Y := Round(OffsetY + Bmp.Height * C);
        PlgBlt(Tmp.Canvas.Handle, Points, Bmp.Canvas.Handle, 0, 0, Bmp.Width,
          Bmp.Height, 0, 0, 0);
        Bmp.Assign(Tmp);
      finally
        Tmp.Free;
      end;
    end;
    
    procedure RotateBitmapGR32(Bmp: TBitmap32; Degs: Integer; AdjustSize: Boolean;
      BkColor: TColor = clNone; Transparent: Boolean = False); overload;
    var
      Tmp: TBitmap32;
      Transformation: TAffineTransformation;
    begin
      Tmp := TBitmap32.Create;
      Transformation := TAffineTransformation.Create;
      try
        Transformation.BeginUpdate;
        Transformation.SrcRect := FloatRect(0, 0, Bmp.Width, Bmp.Height);
        Transformation.Translate(-0.5 * Bmp.Width, -0.5 * Bmp.Height);
        Transformation.Rotate(0, 0, -Degs);
        if AdjustSize then
          with Transformation.GetTransformedBounds do
            Tmp.SetSize(Round(Right - Left), Round(Bottom - Top))
        else
          Tmp.SetSize(Bmp.Width, Bmp.Height);
        Transformation.Translate(0.5 * Tmp.Width, 0.5 * Tmp.Height);
        Transformation.EndUpdate;
        Tmp.Clear(Color32(BkColor));
        if not Transparent then
          Bmp.DrawMode := dmTransparent;
        Transform(Tmp, Bmp, Transformation);
        Bmp.Assign(Tmp);
        Bmp.OuterColor := Color32(BkColor);
        if Transparent then
          Bmp.DrawMode := dmTransparent;
      finally
        Transformation.Free;
        Tmp.Free;
      end;
    end;
    
    procedure RotateBitmapGR32(Bmp: TBitmap; Degs: Integer; AdjustSize: Boolean;
      BkColor: TColor = clNone); overload;
    var
      Tmp: TBitmap32;
      Transparent: Boolean;
    begin
      Tmp := TBitmap32.Create;
      try
        Transparent := Bmp.Transparent;
        Tmp.Assign(Bmp);
        RotateBitmapGR32(Tmp, Degs, AdjustSize, BkColor, Transparent);
        Bmp.Assign(Tmp);
        if Transparent then
          Bmp.Transparent := True;
      finally
        Tmp.Free;
      end;
    end;
    
    procedure RotateBitmapGDIP(Bmp: TBitmap; Degs: Integer; AdjustSize: Boolean;
      BkColor: TColor = clNone);
    var
      Tmp: TGPBitmap;
      Matrix: TGPMatrix;
      C: Single;
      S: Single;
      NewSize: TSize;
      Graphs: TGPGraphics;
      P: TGPPointF;
    begin
      Tmp := TGPBitmap.Create(Bmp.Handle, Bmp.Palette);
      Matrix := TGPMatrix.Create;
      try
        Matrix.RotateAt(Degs, MakePoint(0.5 * Bmp.Width, 0.5 * Bmp.Height));
        if AdjustSize then
        begin
          C := Cos(DegToRad(Degs));
          S := Sin(DegToRad(Degs));
          NewSize.cx := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S));
          NewSize.cy := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C));
          Bmp.Width := NewSize.cx;
          Bmp.Height := NewSize.cy;
        end;
        Graphs := TGPGraphics.Create(Bmp.Canvas.Handle);
        try
          Graphs.Clear(ColorRefToARGB(ColorToRGB(BkColor)));
          Graphs.SetTransform(Matrix);
          Graphs.DrawImage(Tmp, (Cardinal(Bmp.Width) - Tmp.GetWidth) div 2,
            (Cardinal(Bmp.Height) - Tmp.GetHeight) div 2);
        finally
          Graphs.Free;
        end;
      finally
        Matrix.Free;
        Tmp.Free;
      end;
    end;
    
    { TTestForm }
    
    constructor TTestForm.Create(AOwner: TComponent);
    begin
      inherited Create(AOwner);
      Font.Name := 'Tahoma';
      Top := 0;
      ClientWidth := 560;
      ClientHeight := 915;
      Show;
      FImage := TImage.Create(Self);
      FOpenDialog := TOpenDialog.Create(Self);
      FOpenDialog.Title := 'Select an small sized image (min. 100 x 100)';
      FOpenDialog.Options := FOpenDialog.Options + [ofFileMustExist];
      FOpenDialog.Filter := 'JPEG|*.JPG|BMP|*.BMP';
      if FOpenDialog.Execute then
      begin
        FImage.Picture.LoadFromFile(FOpenDialog.FileName);
        OnPaint := FormPaint;
        Invalidate;
      end
      else
        Application.Terminate;
    end;
    
    procedure TTestForm.FormPaint(Sender: TObject);
    var
      Img: TBitmap;
      Bmp: TBitmap;
      Bmp32: TBitmap32;
      BkColor: TColor;
      AdjustSize: Boolean;
      Degs: Integer;
      Rads: Single;
      RotCount: Integer;
      I: Integer;
      Tick: Cardinal;
    begin
      Img := TBitmap.Create;
      Bmp := TBitmap.Create;
      Bmp32 := TBitmap32.Create;
      try
        BkColor := clBtnFace;
        Img.Canvas.Brush.Color := BkColor;
        Img.Width := 100;
        Img.Height := 100;
        Img.Canvas.Draw(0, 0, FImage.Picture.Graphic);
        AdjustSize := False;
        Degs := 45;
        Rads := DegToRad(Degs);
        RotCount := 1000;
    
        Canvas.TextOut(10, 10, 'Original:');
        Canvas.Draw(10, 30, Img);
        Canvas.TextOut(10, 140, Format('Size = %d x %d', [Img.Width, Img.Height]));
        Canvas.TextOut(10, 160, Format('Angle = %d°', [Degs]));
        Canvas.TextOut(10, 250, Format('%d rotations:', [RotCount]));
    
        Canvas.TextOut(120, 10, 'SetWorldTransform:');
        Bmp.Assign(Img);
        RotateBitmapSWT(Bmp, Rads, AdjustSize, BkColor);
        Canvas.Draw(120, 30, Bmp);
        if not AdjustSize then
        begin
          Tick := GetTickCount;
          for I := 0 to RotCount - 2 do
            RotateBitmapSWT(Bmp, Rads, AdjustSize, BkColor);
          Canvas.TextOut(120, 250, Format('%d msec', [GetTickCount - Tick]));
          Canvas.Draw(120, 140, Bmp);
        end;
    
        Canvas.TextOut(230, 10, 'PlgBlt:');
        Bmp.Assign(Img);
        RotateBitmapPLG(Bmp, Rads, AdjustSize, BkColor);
        Canvas.Draw(230, 30, Bmp);
        if not AdjustSize then
        begin
          Tick := GetTickCount;
          for I := 0 to RotCount - 2 do
            RotateBitmapPLG(Bmp, Rads, AdjustSize, BkColor);
          Canvas.TextOut(230, 250, Format('%d msec', [GetTickCount - Tick]));
          Canvas.Draw(230, 140, Bmp);
        end;
    
        Canvas.TextOut(340, 10, 'Graphics32:');
        Bmp.Assign(Img);
        RotateBitmapGR32(Bmp, Degs, AdjustSize, BkColor);
        Canvas.Draw(340, 30, Bmp);
        if not AdjustSize then
        begin
          Tick := GetTickCount;
          for I := 0 to RotCount - 2 do
            RotateBitmapGR32(Bmp, Degs, AdjustSize, BkColor);
          Canvas.TextOut(340, 250, Format('%d msec', [GetTickCount - Tick]));
          Canvas.Draw(340, 140, Bmp);
    
          // Without in between conversion to TBitmap:
          Bmp32.Assign(Img);
          Tick := GetTickCount;
          for I := 0 to RotCount - 1 do
            RotateBitmapGR32(Bmp32, Degs, AdjustSize, BkColor, False);
          Canvas.TextOut(340, 270, Format('%d msec (optimized)',
            [GetTickCount - Tick]));
        end;
    
        Canvas.TextOut(450, 10, 'GDI+ :');
        Bmp.Assign(Img);
        RotateBitmapGDIP(Bmp, Degs, AdjustSize, BkColor);
        Canvas.Draw(450, 30, Bmp);
        if not AdjustSize then
        begin
          Tick := GetTickCount;
          for I := 0 to RotCount - 2 do
            RotateBitmapGDIP(Bmp, Degs, AdjustSize, BkColor);
          Canvas.TextOut(450, 250, Format('%d msec', [GetTickCount - Tick]));
          Canvas.Draw(450, 140, Bmp);
        end;
      finally
        Bmp32.Free;
        Bmp.Free;
        Img.Free;
        OnPaint := nil;
      end;
    end;
    
    end.
    
  • 相关阅读:
    LINUX安装NGINX
    CentOS 设置mysql的远程访问
    centos6 mysql 安装与配置
    php读取用友u8采购入库单列表及详细
    php读取用友u8客户档案
    深度linux没有ll等命令的解决办法
    CentOS7下FTP的安装与配置
    虚拟机CentOS6.5搭建samba服务器实现文件共享
    linux 查找php.ini在那个文件夹
    CBE引擎概览
  • 原文地址:https://www.cnblogs.com/h2zZhou/p/4988635.html
Copyright © 2011-2022 走看看