如何将多次 translation/scaling/rotating 操作产生的变换应用于图像?
How do I apply to an Image a transform resulting from several translation/scaling/rotating operations?
目标是让用户能够使用 translate/scale/rotate 图像并具有以下规格的控件:
- 可撤销的操作
- 缩放和旋转应该会创建一个新图像 (
outputImage
)(所以我不能简单地在 OnPaint
上设置 Graphics.Transform
);翻译不应该避免不必要的重绘
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)
。
目标是让用户能够使用 translate/scale/rotate 图像并具有以下规格的控件:
- 可撤销的操作
- 缩放和旋转应该会创建一个新图像 (
outputImage
)(所以我不能简单地在OnPaint
上设置Graphics.Transform
);翻译不应该避免不必要的重绘 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)
。