用C#旋转量规针

时间:2015-05-09 17:09:50

标签: c# wpf graphics .net-4.5

我无法在中心周围正确旋转针头而不发生剪裁。我发现的旋转不会导致削波的代码以错误的方式旋转针。我在stackoverflow的某个地方找到了代码示例。

Pic1:指向南方的正确位置

Pic2:RotateBitmap4(),在错误的位置旋转,没有剪裁

Pic3:RotateBitmap5(),正确旋转点但剪裁

Pic4:RotateBitmap5(),正确旋转点但剪裁

Correct position Code snippet 1, rotation at wrong position, no clipping Code snippet 2, correct rotation point but clipping Code snippet 3, correct rotation point but clipping

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows;
using System.Windows.Media.Imaging;
using Point = System.Drawing.Point;

namespace Stackoverflow
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
public partial class MainWindow : Window
{
    private float _angle = 0;

    public MainWindow()
    {
        InitializeComponent();
    }

    private void ButtonTestImage_OnClick(object sender, RoutedEventArgs e)
    {
        var backgroundImage = new Bitmap(@"");//Compass circle
        var foregroundImage = new Bitmap(@"");//Compass needle
        foregroundImage = ResizeBitmap(foregroundImage, 13, 65);

        //Wrong rotation, no clipping
        //foregroundImage = RotateBitmap4(foregroundImage, _angle);

        //Correct rotation, clipping!
        foregroundImage = RotateBitmap5(foregroundImage, _angle);
        var finalImage = new Bitmap(320, 240);
        using (var graphics = Graphics.FromImage(finalImage))
        {
            //set background color
            graphics.Clear(System.Drawing.Color.Black);

            graphics.DrawImage(backgroundImage, new System.Drawing.Rectangle(0, 0, backgroundImage.Width, backgroundImage.Height));
            //graphics.DrawImage(foregroundImage, new System.Drawing.Rectangle(int.Parse(TextBoxXOffset.Text), int.Parse(TextBoxYOffset.Text), foregroundImage.Width, foregroundImage.Height));
            graphics.DrawImage(foregroundImage, new System.Drawing.Rectangle(44, 18, foregroundImage.Width, foregroundImage.Height));
        }
        var image = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(finalImage.GetHbitmap(), IntPtr.Zero, System.Windows.Int32Rect.Empty, BitmapSizeOptions.FromWidthAndHeight(320, 240));
        ImageTest.Source = image;

        _angle += 20;
        if (_angle >= 360)
        {
            _angle = 0;
        }
    }

    private static Bitmap ResizeBitmap(Bitmap sourceBitmap, int width, int height)
    {
        var result = new Bitmap(width, height);
        using (var graphics = Graphics.FromImage(result))
        {
            graphics.DrawImage(sourceBitmap, 0, 0, width, height);
        }
        return result;
    }

    private Bitmap RotateBitmap5(Bitmap b, float angle)
    {
        //Create a new empty bitmap to hold rotated image.
        //Bitmap returnBitmap = new Bitmap(b.Width, b.Height);
        Bitmap returnBitmap = new Bitmap(b.Height + 500, b.Height + 500);
        //Make a graphics object from the empty bitmap.
        Graphics g = Graphics.FromImage(returnBitmap);
        //move rotation point to center of image.
        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
        g.TranslateTransform((float)b.Width / 2, (float)b.Height / 2);
        //Rotate.        
        g.RotateTransform(angle);
        //Move image back.
        g.TranslateTransform(-(float)b.Width / 2, -(float)b.Height / 2);
        //Draw passed in image onto graphics object.
        g.DrawImage(b, new Point(0, 0));
        return returnBitmap;
    }

    public Bitmap RotateBitmap4(Bitmap b, float angle)
    {
        if (angle > 0)
        {
            int l = b.Width;
            int h = b.Height;
            double an = angle * Math.PI / 180;
            double cos = Math.Abs(Math.Cos(an));
            double sin = Math.Abs(Math.Sin(an));
            int nl = (int)(l * cos + h * sin);
            int nh = (int)(l * sin + h * cos);
            Bitmap returnBitmap = new Bitmap(nl, nh);
            Graphics g = Graphics.FromImage(returnBitmap);
            g.TranslateTransform((float)(nl - l) / 2, (float)(nh - h) / 2);
            g.TranslateTransform((float)b.Width / 2, (float)b.Height / 2);
            g.RotateTransform(angle);
            g.TranslateTransform(-(float)b.Width / 2, -(float)b.Height / 2);
            g.DrawImage(b, new Point(0, 0));
            return returnBitmap;
        }
        else return b;
    }
}
}

XAML是:

<Window x:Class="Stackoverflow.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="432" Width="782">
<Grid>
    <Image Name="ImageTest" HorizontalAlignment="Left" Height="260" Margin="22,170,0,0" VerticalAlignment="Top" Width="348"/>
    <Button Name="ButtonTestImage" Content="Test Image" HorizontalAlignment="Left" Margin="490,132,0,0" VerticalAlignment="Top" Width="75" Click="ButtonTestImage_OnClick"/>
</Grid>
</Window>

Compass circle Compass needle

1 个答案:

答案 0 :(得分:3)

感谢非常有用的代码示例。它使您更容易理解您的问题。

那么,答案是什么?

好吧,我有几点建议。首先,通过使用主要用于支持Winforms的System.Drawing命名空间,你真的会以错误的方式处理整个位图。因此调用CreateBitmapSourceFromHBitmap()将所有Winforms代码的结果映射到WPF兼容的位图。

其次,现有代码中的基本问题是,如果要创建一个旋转了叠加层的新位图,则必须确保将旋转后的图像放置在该位图中,以使其完全适合。< / p>

在您发布的代码中,您只是在旋转后将箭头位图重新定位回其原始位置,而不是将其移动到足够远的距离以便为新的旋转后足迹设置足够的余量,因此当然它不是垂直定向的,任何被绘制到左边原始宽度一半以上的部分都是在位图的边缘之外。这就是造成裁剪的原因。

现在,您可以修复代码以解决该问题,同时仍保留基本概念。但恕我直言,实施只是浪费。如果您要绘制旋转的箭头图像,您可以在将其与原始图像合成的同时执行此操作。无需为旋转创建另一个中间位图。

所以,如果你觉得你必须使用Winforms / GDI +的东西,那么恕我直言这个版本的代码会好得多:

public partial class MainWindow : Window
{
    private float _angle = 0;

    public MainWindow()
    {
        InitializeComponent();
    }

    private void ButtonTestImage_OnClick(object sender, RoutedEventArgs e)
    {
        var backgroundImage = new Bitmap(@"Assets\dial.png");//Compass circle
        System.Drawing.Size finalSize = backgroundImage.Size;
        var foregroundImage = new Bitmap(@"Assets\arrow.png");//Compass needle

        foregroundImage = new Bitmap(foregroundImage, 13, 65);

        var finalImage = new Bitmap(finalSize.Width, finalSize.Height);
        using (var graphics = Graphics.FromImage(finalImage))
        {
            graphics.DrawImage(backgroundImage, 0, 0, backgroundImage.Width, backgroundImage.Height);

            graphics.TranslateTransform(backgroundImage.Width / 2f, backgroundImage.Height / 2f);
            graphics.RotateTransform(_angle);
            graphics.TranslateTransform(foregroundImage.Width / -2f, foregroundImage.Height / -2f);

            graphics.DrawImage(foregroundImage, Point.Empty);
        }
        var image = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(finalImage.GetHbitmap(), IntPtr.Zero, System.Windows.Int32Rect.Empty,
            BitmapSizeOptions.FromWidthAndHeight(finalSize.Width, finalSize.Height));
        ImageTest.Source = image;

        _angle += 20;
        if (_angle >= 360)
        {
            _angle = 0;
        }
    }
}

(为了我自己的测试目的,我删除了整体调整大小的逻辑。我相信,鉴于上面的例子,你可以调整实际大小以满足你的需要。)


正如我在第一个建议中所提到的,如果你只是做了“WPF方式”会更好。以下是一个示例:

<强> XAML:

<Window x:Class="TestSO30142795RotateBitmapWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
  <Window.Resources>
    <BitmapImage x:Key="backgroundImage" UriSource="Assets\dial.png"/>
    <BitmapImage x:Key="foregroundImage" UriSource="Assets\arrow.png"/>
  </Window.Resources>
  <Grid>
    <Grid Margin="22,170,0,0" HorizontalAlignment="Left" VerticalAlignment="Top">
      <Image Name="ImageTestBackground" Source="{StaticResource backgroundImage}"
           HorizontalAlignment="Left" VerticalAlignment="Top" Stretch="None"/>
      <Image Name="ImageTestForeground" Source="{StaticResource foregroundImage}"
           HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="None"
           RenderTransformOrigin="0.5,0.5">
        <Image.RenderTransform>
          <TransformGroup>
            <ScaleTransform ScaleX="1" ScaleY=".8"/>
            <RotateTransform Angle="{Binding Angle}"/>
          </TransformGroup>
        </Image.RenderTransform>
      </Image>
    </Grid>
    <Button Name="ButtonTestImage" Content="Test Image"
            HorizontalAlignment="Left" VerticalAlignment="Top"
            Margin="490,132,0,0" Width="75"
            Click="ButtonTestImage_OnClick"/>
  </Grid>
</Window>

<强> C#:

public partial class MainWindow : Window
{
    public static readonly DependencyProperty AngleProperty =
        DependencyProperty.Register("Angle", typeof(double), typeof(MainWindow));

    public double Angle
    {
        get { return (double)GetValue(AngleProperty); }
        set { SetValue(AngleProperty, value); }
    }

    public MainWindow()
    {
        InitializeComponent();

        DataContext = this;
    }

    private void ButtonTestImage_OnClick(object sender, RoutedEventArgs e)
    {
        double angle = Angle;

        angle += 20;
        if (angle >= 360)
        {
            angle = 0;
        }

        Angle = angle;
    }
}

请注意,在此示例中,大部分逻辑实际上都在XAML中。 C#代码隐藏非常简单,只是暴露了XAML可以绑定的依赖项属性,然后是实际更新该值的按钮单击处理程序。

XAML本身实际上也很简单。当然,您可能希望包含一些额外的大小/格式标记,以使图像具有正确的大小,位置等。但是正如您所看到的,就基本布局而言,您让WPF完成所有工作,包括弄清楚如何旋转位图,并将其缩放以适应底层的拨号图形。

合成位图时,我更喜欢利用alpha通道,因此在上面的第二个示例中,我编辑了原始位图,以便删除黑色区域,使该区域透明。该位图看起来像这样:
arrow with transparency
这样可确保无论背景如何,只有黄色箭头遮挡图像。

请注意,对于像箭头这样的简单图形,在WPF中甚至不需要使用位图。您可以定义Path对象来表示形状。这样做的主要优点是图形可以任意缩放而不需要任何像素化,就像位图一样。

就此而言,您甚至可以使用线条形状和文本的组合,并使用适当的变换将它们正确放置,作为背景图像。然后背景图像也可以任意缩放而不会有任何质量损失。