C# 在UserControl中渲染大型画布
这几天来,我一直很难实现这一点。我已经搜索了很多关于我想做什么的类似问题,但是我没有遇到一个能直接帮助我解决问题的问题 基本上,我是在我的C# 在UserControl中渲染大型画布,c#,gdi+,doublebuffered,C#,Gdi+,Doublebuffered,这几天来,我一直很难实现这一点。我已经搜索了很多关于我想做什么的类似问题,但是我没有遇到一个能直接帮助我解决问题的问题 基本上,我是在我的UserControl类中将平铺渲染到网格上。这是我正在开发的基于平铺引擎的世界编辑器。这是一个开放世界文档的屏幕截图和一些涂刷过的瓷砖 最初,我打算在我的控件中使用一个位图,这将是世界的预览画布。例如,使用画笔工具,当您移动鼠标并按下左键时,它会将光标下方最近的瓷砖设置为画笔的瓷砖,并将其绘制在层位图上。控件的OnPaint方法将被重写,以相对于绘制事件的
UserControl
类中将平铺渲染到网格上。这是我正在开发的基于平铺引擎的世界编辑器。这是一个开放世界文档的屏幕截图和一些涂刷过的瓷砖
最初,我打算在我的控件中使用一个位图
,这将是世界的预览画布。例如,使用画笔工具,当您移动鼠标并按下左键时,它会将光标下方最近的瓷砖设置为画笔的瓷砖,并将其绘制在层
位图上。控件的OnPaint
方法将被重写,以相对于绘制事件的剪切矩形绘制层
位图
这种方法的问题是,当处理大世界时,位图将非常大。我需要这个应用程序能够适应世界大小,而且很明显,每次在控件上渲染大型位图无效时,都会出现性能问题
目前,我正在控件的重写OnPaint
事件中直接将平铺绘制到控件上。这很好,因为它不需要很多内存。例如,一个(10001000)
世界在(20,20)
每个磁贴(总画布大小为(20000,20000)
)上运行,整个应用程序的内存约为18mb。虽然不是内存密集型,但它相当占用处理器,因为每次控件失效时,它都会遍历视口中的每个分片。这会产生非常恼人的闪烁
我想完成的是一种在内存使用和性能方面达到中间的方法。本质上是双重缓冲世界,这样当控件被重画时就不会闪烁(窗体大小调整、焦点和模糊、滚动等)。以Photoshop为例,当打开的文档溢出容器视口时,它如何呈现该文档
作为参考,这里是我的控件的OnPaint
覆盖,它使用上面提到的直接绘制方法
getRenderBounds
返回一个相对于PaintEventArgs.ClipRectangle
的矩形,用于渲染可见的分幅,而不是在世界上的所有分幅中循环并检查它是否可见
protected override void OnPaint(PaintEventArgs e)
{
WorldSettings settings = worldSettings();
Rectangle bounds = getRenderBounds(e.ClipRectangle),
drawLocation = new Rectangle(Point.Empty, settings.TileSize);
e.Graphics.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
e.Graphics.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.None;
e.Graphics.PixelOffsetMode =
System.Drawing.Drawing2D.PixelOffsetMode.None;
e.Graphics.CompositingQuality =
System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
for (int x = bounds.X; x < bounds.Width; x++)
{
for (int y = bounds.Y; y < bounds.Height; y++)
{
if (!inWorld(x, y))
continue;
Tile tile = getTile(x, y);
if (tile == null)
continue;
drawLocation.X = x * settings.TileSize.Width;
drawLocation.Y = y * settings.TileSize.Height;
e.Graphics.DrawImage(img,
drawLocation,
tileRectangle,
GraphicsUnit.Pixel);
}
}
}
protected override void OnPaint(PaintEventArgs e)
{
WorldSettings=WorldSettings();
矩形边界=getRenderBounds(例如ClipRectangle),
drawLocation=新矩形(Point.Empty,settings.TileSize);
e、 Graphics.InterpolationMode=
System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
e、 Graphics.SmoothingMode=
System.Drawing.Drawing2D.SmoothingMode.None;
e、 Graphics.PixelOffsetMode=
System.Drawing.Drawing2D.PixelOffsetMode.None;
e、 Graphics.CompositingQuality=
系统。绘图。绘图2D。合成质量。高速;
for(int x=bounds.x;x
如果您需要我的代码中的更多上下文,只需进行注释。诀窍是根本不使用大位图。您只需要覆盖可见区域的位图。然后你画任何可见的东西
protected override void OnPaint(PaintEventArgs e)
{
WorldSettings settings = worldSettings();
Rectangle bounds = getRenderBounds(e.ClipRectangle),
drawLocation = new Rectangle(Point.Empty, settings.TileSize);
e.Graphics.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
e.Graphics.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.None;
e.Graphics.PixelOffsetMode =
System.Drawing.Drawing2D.PixelOffsetMode.None;
e.Graphics.CompositingQuality =
System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
for (int x = bounds.X; x < bounds.Width; x++)
{
for (int y = bounds.Y; y < bounds.Height; y++)
{
if (!inWorld(x, y))
continue;
Tile tile = getTile(x, y);
if (tile == null)
continue;
drawLocation.X = x * settings.TileSize.Width;
drawLocation.Y = y * settings.TileSize.Height;
e.Graphics.DrawImage(img,
drawLocation,
tileRectangle,
GraphicsUnit.Pixel);
}
}
}
要实现这一点,需要将数据与位图分开进行维护。这可以是一个简单的数组,也可以是一个带有简单类的数组/列表,其中包含每个块的信息,例如世界位置
当块位于可见区域内时,可以绘制它。您可能需要迭代整个数组,也可能不需要迭代整个数组,但这并不是一个真正的问题(您还可以在单独的线程上计算可见数组)。还可以通过创建区域索引使函数更加智能,这样就不会迭代所有块
若要将新块添加到阵列,请将其画布位置计算为世界坐标,添加它,然后再次渲染阵列(或绘制块的区域)
这也是系统绘制具有可滚动区域的控件的方式
启用双缓冲将使其保持清晰且闪烁较少
在本例中,我还将使用一个带有单独滚动条的面板,并计算滚动条的相对位置。诀窍是根本不使用大位图。您只需要覆盖可见区域的位图。然后你画任何可见的东西
protected override void OnPaint(PaintEventArgs e)
{
WorldSettings settings = worldSettings();
Rectangle bounds = getRenderBounds(e.ClipRectangle),
drawLocation = new Rectangle(Point.Empty, settings.TileSize);
e.Graphics.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
e.Graphics.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.None;
e.Graphics.PixelOffsetMode =
System.Drawing.Drawing2D.PixelOffsetMode.None;
e.Graphics.CompositingQuality =
System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
for (int x = bounds.X; x < bounds.Width; x++)
{
for (int y = bounds.Y; y < bounds.Height; y++)
{
if (!inWorld(x, y))
continue;
Tile tile = getTile(x, y);
if (tile == null)
continue;
drawLocation.X = x * settings.TileSize.Width;
drawLocation.Y = y * settings.TileSize.Height;
e.Graphics.DrawImage(img,
drawLocation,
tileRectangle,
GraphicsUnit.Pixel);
}
}
}
要实现这一点,需要将数据与位图分开进行维护。这可以是一个简单的数组,也可以是一个带有简单类的数组/列表,其中包含每个块的信息,例如世界位置
当块位于可见区域内时,可以绘制它。您可能需要迭代整个数组,也可能不需要迭代整个数组,但这并不是一个真正的问题(您还可以在单独的线程上计算可见数组)。还可以通过创建区域索引使函数更加智能,这样就不会迭代所有块
若要将新块添加到阵列,请将其画布位置计算为世界坐标,添加它,然后再次渲染阵列(或绘制块的区域)
这也是系统绘制具有可滚动区域的控件的方式
启用双缓冲将使其保持清晰且闪烁较少
在本例中,我还将使用带有单独滚动条的面板并计算滚动条