如何将多次 translation/scaling/rotating 操作产生的变换应用于图像?

How do I apply to an Image a transform resulting from several translation/scaling/rotating operations?

目标是让用户能够使用 translate/scale/rotate 图像并具有以下规格的控件:

  1. 可撤销的操作
  2. 缩放和旋转应该会创建一个新图像 (outputImage)(所以我不能简单地在 OnPaint 上设置 Graphics.Transform);翻译不应该避免不必要的重绘
  3. outputImage应该从原始图像开始计算,以避免质量下降(如果图像先缩小然后再放大,质量应该和开始时一样)

至此,我得到了如下代码:

public partial class ImageControl : UserControl
{
    Image image, outputImage;
    PointF offset = PointF.Empty;
    Stack<Matrix> transformStack = new Stack<Matrix>();
    public ImageControl() { InitializeComponent(); }
    public Image Image { get { return image; } set { image = outputImage = value; Restore(); } }
    public void Translate(float dx, float dy)
    {
        transformStack.Push(new Matrix(1, 0, 0, 1, dx, dy));
        ApplyTransform(true);
    }
    public void Scale(float scaleX, float scaleY)
    {
        // as an example we scale at the top-left corner
        Matrix m = new Matrix(scaleX, 0, 0, scaleY, offset.X - scaleX * offset.X, offset.Y - scaleY * offset.Y);
        transformStack.Push(m);
        ApplyTransform();
    }
    public void Rotate(float angleDegrees)
    {
        Matrix m = new Matrix();
        // as an example we rotate around the centre of the image
        Point[] pts = new Point[] { new Point(0, 0), new Point(image.Width, 0), new Point(image.Width, image.Height), new Point(0, image.Height) };
        GetTransform().VectorTransformPoints(pts);
        var centre = PolygonCentroid(pts);
        m.RotateAt(angleDegrees, new PointF(offset.X + centre.X, offset.Y + centre.Y));

        transformStack.Push(m);
        ApplyTransform();
    }
    public void Restore()
    {
        offset = PointF.Empty;
        transformStack = new Stack<Matrix>();
        ApplyTransform();
    }
    public void Undo()
    {
        if(transformStack.Count != 0)
            transformStack.Pop();
        ApplyTransform();
    }
    Matrix GetTransform()
    {
        Matrix m = new Matrix();
        foreach (var item in transformStack.Reverse())
            m.Multiply(item, MatrixOrder.Append);
        return m;
    }
    void ApplyTransform(bool onlyTranslation = false)
    {
        Matrix transform = GetTransform();
        if (!onlyTranslation) // we do not need to redraw the image if transformation is pure translation
        {
            // transform the 4 vertices to know the output size
            PointF[] pts = new PointF[] { new PointF(0, 0), new PointF(image.Width, 0), new PointF(0, image.Height), new PointF(image.Width, image.Height) };
            transform.TransformPoints(pts);
            float minX = pts.Min(p => p.X);
            float maxX = pts.Max(p => p.X);
            float minY = pts.Min(p => p.Y);
            float maxY = pts.Max(p => p.Y);
            Bitmap bmpDest = new Bitmap(Convert.ToInt32(maxX - minX), Convert.ToInt32(maxY - minY));
            //bmpDest.SetResolution(image.HorizontalResolution, image.VerticalResolution);

            // remove the offset from the points defining the destination for the image (we need only 3 vertices)
            PointF[] destPts = new PointF[] { new PointF(pts[0].X - minX, pts[0].Y - minY), new PointF(pts[1].X - minX, pts[1].Y - minY), new PointF(pts[2].X - minX, pts[2].Y - minY) };
            using (Graphics gDest = Graphics.FromImage(bmpDest))
                gDest.DrawImage(image, destPts);
            outputImage = bmpDest; 
        }
        // keep the offset
        offset = new PointF(transform.OffsetX, transform.OffsetY);
        Invalidate();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        if (image == null) return;

        e.Graphics.TranslateTransform(offset.X, offset.Y);
        e.Graphics.DrawImage(outputImage, 0, 0);
        e.Graphics.DrawRectangle(Pens.DeepSkyBlue, 0, 0, outputImage.Width, outputImage.Height);
    }
}

其中(至少)有旋转问题:我无法围绕图像的真实中心旋转。函数 PolygonCentroid 取自 here,应该没问题。我猜这个错误与旋转后计算的新偏移量有关,为此我可能应该引入一些补偿。有什么想法吗?

我对应用旋转的效果有一个误解,它会产生位移。有兴趣者:

 public void Rotate(float angleDegrees)
    {
        Matrix m = new Matrix();
        // as an example we rotate around the centre of the image
        Point[] pts = new Point[] { new Point(0, 0), new Point(image.Width, 0), new Point(image.Width, image.Height), new Point(0, image.Height) };
        GetTransform().TransformPoints(pts);
        var centre = PolygonCentroid(pts);
        m.RotateAt(angleDegrees, new PointF(centre.X, centre.Y));

        transformStack.Push(m);
        ApplyTransform();
    }

并且在绘制 outputImage 之前用于平移 Graphics 的偏移量应该是新的 PointF(minX, minY)