一、啰嗦两句
玩GDI,终究是绕不开图像的。或许最初的时候,我可以尝试将位图直接赋给pictureBox的Image来实现图像加载,可终究是个死物。往往加载出图像,基本都具有放大缩小的功能,还需要图形绘制,与GDI一起玩,怎能依赖控件自己加载图像呢?那样能走多远?
二、图像放大缩小
1.图像放大缩小的原理
没玩GDI之前,图像对我而言都是神奇的,图像放大缩小,应该是神们做出来的。玩了GDI之后,发现还是得相信科学,任何东西都有它的原理。就像动画片,最初也是一张一张的更新,形成视频。图像放大缩小也是不断更新图像,仅此而已。
2.GDI绘制图像与控件Image赋值
GDI中绘图使用DrawImage方法,将指定图像的指定区域绘制到画布的指定区域,无内存拷贝。
控件Image赋值,其实就是赋值,将位图对象赋给Image属性,是存在内存拷贝的。
只是一次绘图,差别不大,没什么可深究的。
如果我们要做放大缩小,意味着图像不断在变化,要做不知道次数的绘制,Image赋值的效率就不是那么好了,还得是GDI绘图啊。
二、坐标变换
绘图都需要坐标的,而图像尺寸与控件尺寸都不是一样的,必然要有坐标转换来保证图像显示比例。这里需要做的是,每次变化按控件尺寸计算出需要截取的图像尺寸。这个过程就像拿一个小窗口,放到一张图纸上,通过这个小窗口去截取图像上的区域。参考Windows照片查看器的效果,图片初始按比例在控件居中显示,放大先填满空的区域,再根据鼠标位置进行放大。基于这些特征,需要写一个专门计算坐标的类。其大致如下:
public class Coordinate
{
public double ImageWidth; //图像宽度
public double ImageHeight; //图像高度
public double ControlWidth; //控件宽度
public double ControlHeight; //控件高度
public double Scale = 1.3; //每次缩放比例
private bool IsWidthFill;
public Rect ImgRect; //截取的图像区域
public Rect conRect; //显示到控件的区域
private int N = 0; //计数
private int matchNum = 0;
public void Init(double imgWidth, double imgHeight, double controlWidth, double controlHeight)
{
ImageWidth = imgWidth;
ImageHeight = imgHeight;
ControlWidth = controlWidth;
ControlHeight = controlHeight;
IsWidthFill = true;
matchNum = (int)(Math.Log(ImageWidth * controlHeight / controlWidth / ImageHeight, 1.3)) + 1;
conRect = new Rect();
conRect.x = 0;
conRect.width = controlWidth;
conRect.height = ImageHeight / ImageWidth * controlWidth;
conRect.y = 0.5 * (controlHeight - conRect.height);
if (imgWidth / imgHeight < controlWidth / controlHeight)
{
IsWidthFill = false;
matchNum = (int)(Math.Log(ImageHeight * controlWidth / controlHeight / ImageWidth, 1.3)) + 1;
conRect.y = 0;
conRect.height = controlHeight;
conRect.width = ImageWidth / ImageHeight * controlHeight;
conRect.x = 0.5 * (controlWidth - conRect.width);
}
N = 0;
ImgRect = new Rect(0, 0, imgWidth, imgHeight);
}
/// <summary>
/// 放大
/// </summary>
public void ZoomIn(Point2d srcpos)
{
if (N > 19)
return;
Scale = 1.3;
N++;
CalculateImageRect(srcpos);
}
/// <summary>
/// 缩小
/// </summary>
public void ZoomOut(Point2d srcpos)
{
Scale = 1.0 / 1.3;
N--;
N = Math.Max(0, N);
CalculateImageRect(srcpos);
}
/// <summary>
/// 移动
/// </summary>
/// <param name="offset"></param>
public void Move(Point2d offset)
{
double resolution = ImgRect.width / conRect.width;
if (!IsWidthFill)
resolution = ImgRect.height / conRect.height;
double imgRectx = ImgRect.x - offset.x * resolution;
double imgRecty = ImgRect.y - offset.y * resolution;
if (Math.Abs(imgRectx - 0.5 * ImageWidth) <= 0.5 * ImageWidth && Math.Abs(imgRectx + ImgRect.width - 0.5 * ImageWidth) <= 0.5 * ImageWidth)
ImgRect.x = imgRectx;
if (Math.Abs(imgRecty - 0.5 * ImageHeight) <= 0.5 * ImageHeight && Math.Abs(imgRecty + ImgRect.height - 0.5 * ImageHeight) <= 0.5 * ImageHeight)
ImgRect.y = imgRecty;
}
/// <summary>
/// 基于指定点计算图片尺寸
/// </summary>
/// <param name="pos"></param>
/// <returns></returns>
private void CalculateImageRect(Point2d srcpos)
{
if (N == 0)
{
conRect.x = 0;
conRect.width = ControlWidth;
conRect.height = ImageHeight / ImageWidth * ControlWidth;
conRect.y = 0.5 * (ControlHeight - conRect.height);
if (!IsWidthFill)
{
conRect.y = 0;
conRect.height = ControlHeight;
conRect.width = ImageWidth / ImageHeight * ControlHeight;
conRect.x = 0.5 * (ControlWidth - conRect.width);
}
ImgRect = new Rect(0, 0, ImageWidth, ImageHeight);
return;
}
Point2d imgPos = ScreenToImage(srcpos);
if (N == matchNum)
{
if (IsWidthFill)
{
ImgRect.width = ImageHeight / ControlHeight * ControlWidth;
ImgRect.height = ImageHeight;
ImgRect.x = 0.5 * (ImageWidth - ImgRect.width);
ImgRect.y = 0;
}
else
{
ImgRect.width = ImageWidth;
ImgRect.height = ImageWidth / ControlWidth * ControlHeight;
ImgRect.x = 0;
ImgRect.y = 0.5 * (ImageHeight - ImgRect.height);
}
conRect = new Rect(0, 0, ControlWidth, ControlHeight);
}
if (N < matchNum)
{
if (IsWidthFill)
{
ImgRect.width /= Scale;
ImgRect.height = ImageHeight;
ImgRect.x = 0.5 * (ImageWidth - ImgRect.width);
ImgRect.y = 0;
conRect.x = 0;
conRect.width = ControlWidth;
conRect.height = ImgRect.height / ImgRect.width * ControlWidth;
conRect.y = 0.5 * (ControlHeight - conRect.height);
}
else
{
ImgRect.width = ImageWidth;
ImgRect.height /= Scale;
ImgRect.x = 0;
ImgRect.y = 0.5 * (ImageHeight - ImgRect.height);
conRect.y = 0;
conRect.height = ControlHeight;
conRect.width = ImgRect.width / ImgRect.height * ControlHeight;
conRect.x = 0.5 * (ControlWidth - conRect.width);
}
}
if (N > matchNum)
{
ImgRect.width /= Scale;
ImgRect.height /= Scale;
conRect = new Rect(0, 0, ControlWidth, ControlHeight);
ImgRect.x = imgPos.x - (srcpos.x - conRect.x) / conRect.width * ImgRect.width;
ImgRect.y = imgPos.y - (srcpos.y - conRect.y) / conRect.height * ImgRect.height;
}
ImgRect.x = Math.Min(Math.Max(0, ImgRect.x), ImageWidth - ImgRect.width);
ImgRect.y = Math.Min(Math.Max(0, ImgRect.y), ImageHeight - ImgRect.height);
}
/// <summary>
/// 画布坐标转图像坐标
/// </summary>
/// <param name="srcPos"></param>
/// <returns></returns>
public Point2d ScreenToImage(Point2d srcPos)
{
Point2d imgPos = new Point2d();
imgPos.x = (srcPos.x - conRect.x) / conRect.width * ImgRect.width + ImgRect.x;
imgPos.y = (srcPos.y - conRect.y) / conRect.height * ImgRect.height + ImgRect.y;
return imgPos;
}
/// <summary>
/// 图像坐标转画布坐标
/// </summary>
/// <param name="imgPos"></param>
/// <returns></returns>
public Point2d ImageToScreen(Point2d imgPos)
{
Point2d srcPos = new Point2d();
srcPos.x = (imgPos.x - ImgRect.x) / ImgRect.width * conRect.width + conRect.x;
srcPos.y = (imgPos.y - ImgRect.y) / ImgRect.height * conRect.height + conRect.y;
return srcPos;
}
}
三、结尾
上面坐标变换可根据需要再关联世界坐标,可应用于大多数工业自动化项目。此文仅为我个人探索笔记,如有欠缺,希望能提出,好做改进。
本文含有隐藏内容,请 开通VIP 后查看