如何将图像应用于由多个平移/缩放/旋转操作产生的变换?

时间:2016-05-30 16:24:28

标签: c# image rotation transform matrix-multiplication

目标是让控件为用户提供翻译/缩放/旋转图像的能力,并遵循以下规范:

  1. 操作可撤消
  2. 缩放和旋转应该会创建一个新图像(outputImage)(所以我不能简单地在Graphics.Transform上设置OnPaint);翻译不应该避免不必要的重绘
  3. 应该从原始图像开始计算outputImage,以避免质量下降(如果图像首先按比例缩小然后重新上升,则质量应该与开始时一样)
  4. 到目前为止,我来到了以下代码:

    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,应该没问题。我猜这个错误与旋转后计算的新偏移有关,为此我可能会引入一些补偿。有什么想法吗?

1 个答案:

答案 0 :(得分:0)

我对应用旋转的效果产生了误解,这就产生了位移。对于那些感兴趣的人:

 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();
    }

以及在绘制Graphics之前用于翻译outputImage的偏移量应该是新的PointF(minX, minY)