保存图像时,在大型位图上绘制的线条不可见
Lines drawn on a large Bitmap are not visible when the image is saved
我创建了一个程序来在选定的图像上绘制方形网格。它适用于分辨率较小的图像,但不适用于大图像。
图像保存为文件时,所有网格线都看不到。
我正在测试的图像分辨率为 3600x4320
,可以在 link 中显示。
我该如何解决这个问题?
我的代码:
Image drawGrid(int n, string imgPath)
{
Image img = Image.FromFile(imgPath);
Graphics grp = Graphics.FromImage(img);
grp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
float m = img.Width * 1f / n;
for (int i = 1; i < n; i++)
{
var x = new PointF(i * m, 0);
var y = new PointF(i * m, img.Height);
grp.DrawLine(Pens.Red, x, y);
}
for (int i = 1; i <= (int)(this.Height / m); i++)
{
var x = new PointF(0, i * m);
var y = new PointF(img.Width, i * m);
grp.DrawLine(new Pen(Color.Red, 5f), x, y);
}
return img;
}
void BtnExportClick(object sender, EventArgs e)
{
if(saveFileDialog1.ShowDialog() == DialogResult.OK)
{
int n = (int)numericUpDown1.Value;
drawGrid(n, txtImagePath.Text).Save(saveFileDialog1.FileName, System.Drawing.Imaging.ImageFormat.Jpeg);
MessageBox.Show("Done");
}
}
结果图片如下(分辨率降低上传)
网格线显示不正确。
主要问题出在这一行:
for (int i = 1; i <= (int)(this.Height / m); i++)
▶ this.Height
明显不是你想写的,我们换成 Image.Height
▶ grp.DrawLine(Pens.Red, x, y);
和 grp.DrawLine(new Pen(Color.Red, 5f), x, y);
将绘制不同大小的线条(1 和 5 像素)。在示例代码中,这两个方法接受定义笔颜色和大小的 Color
和 float
参数。
▶ grp.SmoothingMode
:我们这里不需要任何平滑模式,不需要绘制直线,它会添加清晰可见的抗锯齿,特别是在将图像保存为 JPEG 格式时(它会消除锯齿 - 有点,它实际上会破坏颜色 - 这些线条本身)。
▶ 您没有处理您创建的任何 Graphics 对象。这对于频繁的图形操作和处理大型位图都非常重要。
需要处理 Pen 和 Graphics 对象。
因为如果你想生成一个在两个维度上具有相同数量的线的网格并不完全清楚 - 因此,一个具有宽度不等于高度的单元格的网格,很可能 - 或者带有方形单元格的网格(这是示例图像似乎显示的内容,而不是代码),我发布了两种绘制两种网格类型的方法:
第一种方法,宽度和高度的行数相同:
var gridSizeX = (float)image.Width / lines;
var gridSizeY = (float)image.Height / lines;
private Image DrawGridLines(int lines, string imgPath, Color penColor, float penSize)
{
var image = Image.FromStream(new MemoryStream(File.ReadAllBytes(imgPath)), true);
using (var g = Graphics.FromImage(image)) {
g.PixelOffsetMode = PixelOffsetMode.Half;
var gridSizeX = (float)image.Width / lines;
var gridSizeY = (float)image.Height / lines;
for (int i = 1; i < lines; i++) {
var pointX1 = new PointF(0, i * gridSizeY);
var pointX2 = new PointF(image.Width, i * gridSizeY);
var pointY1 = new PointF(i * gridSizeX, 0);
var pointY2 = new PointF(i * gridSizeX, image.Height);
using (var pen = new Pen(penColor, penSize)) {
g.DrawLine(pen, pointX1, pointX2);
g.DrawLine(pen, pointY1, pointY2);
}
}
return image;
}
}
第二种方法,画方格。整数值,gridSection
,用于根据Bitmap的最小尺寸定义网格Cell。
然后使用此维度来确定在另一个维度中绘制多少条线。
网格大小按最小维度计算:
var gridSize = (float)Math.Min(image.Width, image.Height) / gridSection;
并且 Cell 被确定为结果:
var gridStepMin = Math.Min(image.Width, image.Height) / gridSize;
var gridStepMax = Math.Max(image.Width, image.Height) / gridSize;
private Image DrawSquaredGrid(int gridSection, string imgPath, Color penColor, float penSize)
{
var image = Image.FromStream(new MemoryStream(File.ReadAllBytes(imgPath)), true);
using (var g = Graphics.FromImage(image)) {
g.PixelOffsetMode = PixelOffsetMode.Half;
var gridSize = (float)Math.Min(image.Width, image.Height) / gridSection;
var gridStepMin = Math.Min(image.Width, image.Height) / gridSize;
var gridStepMax = Math.Max(image.Width, image.Height) / gridSize;
for (int i = 1; i < gridStepMin; i++) {
var pointY1 = new PointF(i * gridSize, 0);
var pointY2 = new PointF(i * gridSize, image.Height);
using (var pen = new Pen(penColor, penSize)) {
g.DrawLine(pen, pointY1, pointY2);
}
}
for (int i = 1; i < gridStepMax; i++) {
var pointX1 = new PointF(0, i * gridSize);
var pointX2 = new PointF(image.Width, i * gridSize);
using (var pen = new Pen(penColor, penSize)) {
g.DrawLine(pen, pointX1, pointX2);
}
}
return image;
}
}
重构 SaveFileDialog 以允许多种图像格式。并调用其中一种基于selection的绘图方法(在示例代码中,使用了CheckBox(chkSquared
)select一个网格类型)。
您可以添加更多格式,ImageFormatFromFileName()
方法 select 是基于 SaveFileDialog.FielName
扩展名的 ImageFormat
类型。
private void BtnExportClick(object sender, EventArgs e)
{
string imagePath = [Some Path];
using (var sfd = new SaveFileDialog()) {
sfd.Filter = "PNG Image (*.png)|*.png|TIFF Image (*.tif)|*.tif|JPEG Image (*.jpg)|*.jpg";
sfd.RestoreDirectory = true;
sfd.AddExtension = true;
if (sfd.ShowDialog() == DialogResult.OK) {
Image image = null;
if (chkSquared.Checked) {
image = DrawSquaredGrid((int)numericUpDown1.Value, imagePath, Color.Red, 5.0f);
}
else {
image = DrawGridLines((int)numericUpDown1.Value, imagePath, Color.Red, 5.0f);
}
image.Save(sfd.FileName, ImageFormatFromFileName(sfd.FileName));
MessageBox.Show("Done");
image.Dispose();
}
}
}
private ImageFormat ImageFormatFromFileName(string fileName)
{
string fileType = Path.GetExtension(fileName).Remove(0, 1);
if (fileType.Equals("tif")) fileType = "tiff";
if (fileType.Equals("jpg")) fileType = "jpeg";
return (ImageFormat)new ImageFormatConverter().ConvertFromString(fileType);
}
我创建了一个程序来在选定的图像上绘制方形网格。它适用于分辨率较小的图像,但不适用于大图像。
图像保存为文件时,所有网格线都看不到。
我正在测试的图像分辨率为 3600x4320
,可以在 link 中显示。
我该如何解决这个问题?
我的代码:
Image drawGrid(int n, string imgPath)
{
Image img = Image.FromFile(imgPath);
Graphics grp = Graphics.FromImage(img);
grp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
float m = img.Width * 1f / n;
for (int i = 1; i < n; i++)
{
var x = new PointF(i * m, 0);
var y = new PointF(i * m, img.Height);
grp.DrawLine(Pens.Red, x, y);
}
for (int i = 1; i <= (int)(this.Height / m); i++)
{
var x = new PointF(0, i * m);
var y = new PointF(img.Width, i * m);
grp.DrawLine(new Pen(Color.Red, 5f), x, y);
}
return img;
}
void BtnExportClick(object sender, EventArgs e)
{
if(saveFileDialog1.ShowDialog() == DialogResult.OK)
{
int n = (int)numericUpDown1.Value;
drawGrid(n, txtImagePath.Text).Save(saveFileDialog1.FileName, System.Drawing.Imaging.ImageFormat.Jpeg);
MessageBox.Show("Done");
}
}
结果图片如下(分辨率降低上传)
网格线显示不正确。
主要问题出在这一行:
for (int i = 1; i <= (int)(this.Height / m); i++)
▶ this.Height
明显不是你想写的,我们换成 Image.Height
▶ grp.DrawLine(Pens.Red, x, y);
和 grp.DrawLine(new Pen(Color.Red, 5f), x, y);
将绘制不同大小的线条(1 和 5 像素)。在示例代码中,这两个方法接受定义笔颜色和大小的 Color
和 float
参数。
▶ grp.SmoothingMode
:我们这里不需要任何平滑模式,不需要绘制直线,它会添加清晰可见的抗锯齿,特别是在将图像保存为 JPEG 格式时(它会消除锯齿 - 有点,它实际上会破坏颜色 - 这些线条本身)。
▶ 您没有处理您创建的任何 Graphics 对象。这对于频繁的图形操作和处理大型位图都非常重要。
需要处理 Pen 和 Graphics 对象。
因为如果你想生成一个在两个维度上具有相同数量的线的网格并不完全清楚 - 因此,一个具有宽度不等于高度的单元格的网格,很可能 - 或者带有方形单元格的网格(这是示例图像似乎显示的内容,而不是代码),我发布了两种绘制两种网格类型的方法:
第一种方法,宽度和高度的行数相同:
var gridSizeX = (float)image.Width / lines;
var gridSizeY = (float)image.Height / lines;
private Image DrawGridLines(int lines, string imgPath, Color penColor, float penSize)
{
var image = Image.FromStream(new MemoryStream(File.ReadAllBytes(imgPath)), true);
using (var g = Graphics.FromImage(image)) {
g.PixelOffsetMode = PixelOffsetMode.Half;
var gridSizeX = (float)image.Width / lines;
var gridSizeY = (float)image.Height / lines;
for (int i = 1; i < lines; i++) {
var pointX1 = new PointF(0, i * gridSizeY);
var pointX2 = new PointF(image.Width, i * gridSizeY);
var pointY1 = new PointF(i * gridSizeX, 0);
var pointY2 = new PointF(i * gridSizeX, image.Height);
using (var pen = new Pen(penColor, penSize)) {
g.DrawLine(pen, pointX1, pointX2);
g.DrawLine(pen, pointY1, pointY2);
}
}
return image;
}
}
第二种方法,画方格。整数值,gridSection
,用于根据Bitmap的最小尺寸定义网格Cell。
然后使用此维度来确定在另一个维度中绘制多少条线。
网格大小按最小维度计算:
var gridSize = (float)Math.Min(image.Width, image.Height) / gridSection;
并且 Cell 被确定为结果:
var gridStepMin = Math.Min(image.Width, image.Height) / gridSize;
var gridStepMax = Math.Max(image.Width, image.Height) / gridSize;
private Image DrawSquaredGrid(int gridSection, string imgPath, Color penColor, float penSize)
{
var image = Image.FromStream(new MemoryStream(File.ReadAllBytes(imgPath)), true);
using (var g = Graphics.FromImage(image)) {
g.PixelOffsetMode = PixelOffsetMode.Half;
var gridSize = (float)Math.Min(image.Width, image.Height) / gridSection;
var gridStepMin = Math.Min(image.Width, image.Height) / gridSize;
var gridStepMax = Math.Max(image.Width, image.Height) / gridSize;
for (int i = 1; i < gridStepMin; i++) {
var pointY1 = new PointF(i * gridSize, 0);
var pointY2 = new PointF(i * gridSize, image.Height);
using (var pen = new Pen(penColor, penSize)) {
g.DrawLine(pen, pointY1, pointY2);
}
}
for (int i = 1; i < gridStepMax; i++) {
var pointX1 = new PointF(0, i * gridSize);
var pointX2 = new PointF(image.Width, i * gridSize);
using (var pen = new Pen(penColor, penSize)) {
g.DrawLine(pen, pointX1, pointX2);
}
}
return image;
}
}
重构 SaveFileDialog 以允许多种图像格式。并调用其中一种基于selection的绘图方法(在示例代码中,使用了CheckBox(chkSquared
)select一个网格类型)。
您可以添加更多格式,ImageFormatFromFileName()
方法 select 是基于 SaveFileDialog.FielName
扩展名的 ImageFormat
类型。
private void BtnExportClick(object sender, EventArgs e)
{
string imagePath = [Some Path];
using (var sfd = new SaveFileDialog()) {
sfd.Filter = "PNG Image (*.png)|*.png|TIFF Image (*.tif)|*.tif|JPEG Image (*.jpg)|*.jpg";
sfd.RestoreDirectory = true;
sfd.AddExtension = true;
if (sfd.ShowDialog() == DialogResult.OK) {
Image image = null;
if (chkSquared.Checked) {
image = DrawSquaredGrid((int)numericUpDown1.Value, imagePath, Color.Red, 5.0f);
}
else {
image = DrawGridLines((int)numericUpDown1.Value, imagePath, Color.Red, 5.0f);
}
image.Save(sfd.FileName, ImageFormatFromFileName(sfd.FileName));
MessageBox.Show("Done");
image.Dispose();
}
}
}
private ImageFormat ImageFormatFromFileName(string fileName)
{
string fileType = Path.GetExtension(fileName).Remove(0, 1);
if (fileType.Equals("tif")) fileType = "tiff";
if (fileType.Equals("jpg")) fileType = "jpeg";
return (ImageFormat)new ImageFormatConverter().ConvertFromString(fileType);
}