以编程方式创建的C#位图;作物代码开始给出;GDI+;中发生一般性错误&引用;
我使用以下方法将文本渲染为图像。它生成一个比所需的位图更大的位图,绘制文本,然后查找位图中的空白并将其裁剪掉。在保存图像时,它抛出错误“GDI+中发生了一般错误”。这段代码一直在我开发的同一台机器上运行,尽管它已经很长时间没有运行了,所以自上次运行以来,可能已经发生了相当数量的windows更新。据我所知,没有任何其他改变,比如解决方案/.net framework等等-我只是打开了解决方案,在调试中运行了它(像往常一样),它产生了错误以编程方式创建的C#位图;作物代码开始给出;GDI+;中发生一般性错误&引用;,c#,bitmap,gdi+,C#,Bitmap,Gdi+,我使用以下方法将文本渲染为图像。它生成一个比所需的位图更大的位图,绘制文本,然后查找位图中的空白并将其裁剪掉。在保存图像时,它抛出错误“GDI+中发生了一般错误”。这段代码一直在我开发的同一台机器上运行,尽管它已经很长时间没有运行了,所以自上次运行以来,可能已经发生了相当数量的windows更新。据我所知,没有任何其他改变,比如解决方案/.net framework等等-我只是打开了解决方案,在调试中运行了它(像往常一样),它产生了错误 private void CreateImageFromT
private void CreateImageFromText(string text, string filename){
// Set global stage dimensions
int stageWidth = (int)(text.Length * 3 * _fontSizeNumericUpDown.Value);
int stageHeight = (int)(3 * _fontSizeNumericUpDown.Value);
// Create Bitmap placeholder for new image
Bitmap createdImage = new Bitmap(stageWidth, stageHeight);
Color blankPixel = createdImage.GetPixel(0, 0);
// Draw new blank image
Graphics imageCanvas = Graphics.FromImage(createdImage);
imageCanvas.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
imageCanvas.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
// Add text
if (!string.IsNullOrEmpty(text))
{
Font font = new Font("Arial", (int)_fontSizeNumericUpDown.Value);
Font bigFont = new Font("Arial", (int)(_fontSizeNumericUpDown.Value * (decimal)1.25));
Font veryBigFont = new Font("Arial", (int)(_fontSizeNumericUpDown.Value * (decimal)3));
if(text.StartsWith("tick:"))
imageCanvas.DrawString("✔", bigFont, Brushes.Green, 0, 0);
else if (text.StartsWith("cross:"))
imageCanvas.DrawString("X", bigFont, Brushes.Red, 0, 0);
else if (text.StartsWith("highlight:"))
imageCanvas.DrawString("•", veryBigFont, Brushes.Magenta, 0, 0);
else
imageCanvas.DrawString(text, font, Brushes.Black, 0, 0);
}
//clip to only part containing text
Rectangle r = ImageUtils.GetBoundsThatContainData(
createdImage,
blankPixel,
searchArea: (text.StartsWith("highlight:") ? new Rectangle?(new Rectangle(10, 20, createdImage.Width - 10, createdImage.Height - 20)) : null)
);
// Save cropped
var img = createdImage.Clone(r, createdImage.PixelFormat);
img.Save(filename, System.Drawing.Imaging.ImageFormat.Png);
imageCanvas.Dispose();
createdImage.Dispose();
}
搜索完全空白像素行的辅助方法是:
公共静态矩形GetBoundstHatContainerData(位图createdImage,Color blankPixel,int borderSizePixels=5,矩形?searchArea=null)
{
矩形sa=新矩形(0,0,createdImage.Width,createdImage.Height)
if(searchArea.HasValue)
{
if(searchArea.Value.X>sa.X)
sa.X=searchArea.Value.X;
如果(searchArea.Value.Y>sa.Y)
sa.Y=searchArea.Value.Y;
if(searchArea.Value.Width=sa.Y;i--)
{
如果(!AllPixelsOnHorizontalLineMatch(blankPixel,i,sa,createdImage))
{
高度=(i-sa.Y)+1+边界大小像素;
打破
}
}
如果(sa.Y+sa.Height>createdImage.Height)
sa.Height=createdImage.Height-sa.Y;
//寻找水平线
对于(inti=(sa.X+sa.Width)-1;i>=sa.X;i--)
{
如果(!AllPixelsOnVerticalLineMatch(blankPixel,i,sa,createdImage))
{
宽度=(i-sa.X)+1+边界大小像素;
打破
}
}
如果(sa.X+sa.Width>createdImage.Width)
sa.Width=createdImage.Width-sa.X;
返回sa;
}
helper功能正常,返回我期望的一个rect
是否有其他人能够在他们的机器上重新设置GDI错误(我这里没有另一台机器作为比较来测试它是否只影响我的机器)?有没有关于如何诊断原因的建议?读到很多此类错误都与关闭位图所在的流有关,但在本例中没有流;位图不是从任何地方加载的-它完全是在代码中创建的。当
图形
对象存在时,图像对象被视为处于编辑状态。图像仅在图形对象被处理后才被视为“完成”。您试图在处理图形
对象之前保存图像,这可能会导致问题。使用块向代码中添加适当的,可以完全解决此问题
除了,也就是说,如果真正的问题是AllPixelsOnHorizontalLineMatch
或AllPixelsOnVerticalLineMatch
工具,您的问题中没有包括这些工具。如果他们做了一些可能会弄乱GDI+对象的事情,那么这可能会影响以后的保存
无论如何,下面是使用适当的块重写的函数:
public static void CreateImageFromText(String text, String filename, Int32 fontSize)
{
// Set global stage dimensions
Int32 stageWidth = (Int32)(text.Length * 3 * fontSize);
Int32 stageHeight = (Int32)(3 * fontSize);
using (Bitmap createdImage = new Bitmap(stageWidth, stageHeight))
{
Color blankPixel = createdImage.GetPixel(0, 0);
// Draw new blank image
using (Graphics imageCanvas = Graphics.FromImage(createdImage))
{
imageCanvas.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
imageCanvas.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
// Add text
if (!string.IsNullOrEmpty(text))
{
if (text.StartsWith("tick:"))
using (Font bigFont = new Font("Arial", (Int32)(fontSize * (decimal)1.25)))
imageCanvas.DrawString("✔", bigFont, Brushes.Green, 0, 0);
else if (text.StartsWith("cross:"))
using (Font bigFont = new Font("Arial", (Int32)(fontSize * (decimal)1.25)))
imageCanvas.DrawString("X", bigFont, Brushes.Red, 0, 0);
else if (text.StartsWith("highlight:"))
using (Font veryBigFont = new Font("Arial", (Int32)(fontSize * (decimal)3)))
imageCanvas.DrawString("•", veryBigFont, Brushes.Magenta, 0, 0);
else
using (Font font = new Font("Arial", (Int32)fontSize))
imageCanvas.DrawString(text, font, Brushes.Black, 0, 0);
}
}
// Honestly not sure what the point of this is, especially given the complete inaccuracy of the original image size calculation.
Rectangle? searchArea = text.StartsWith("highlight:") ? new Rectangle(10, 20, createdImage.Width - 10, createdImage.Height - 20) : (Rectangle?)null;
Rectangle r = ImageUtils.GetCropBounds(createdImage, blankPixel, searchArea: searchArea);
// Save cropped
using (Image img = createdImage.Clone(r, createdImage.PixelFormat))
img.Save(filename, ImageFormat.Png);
}
}
我不想重写这些丢失的工具函数,因为在整个过程中使用字节并将它们传递给这些工具函数会更加高效,所以我最终只编写了自己的裁剪函数。我不确定它的功能是否与您的完全相同,但受约束的搜索区域和边界的功能似乎很有效,因此,这里提供了它,以供参考:
public static Rectangle GetCropBounds(Bitmap image, Color blankPixel, Int32 borderSizePixels = 5, Rectangle? searchArea = null)
{
// Not too worried about the other boundaries; the "for" loops will exclude those anyway.
Int32 yStart = searchArea.HasValue ? Math.Max(0, searchArea.Value.Y) : 0;
Int32 yEnd = searchArea.HasValue ? Math.Min(image.Height, searchArea.Value.Y + searchArea.Value.Height) : image.Height;
Int32 xStart = searchArea.HasValue ? Math.Max(0, searchArea.Value.X) : 0;
Int32 xEnd = searchArea.HasValue ? Math.Min(image.Width, searchArea.Value.X + searchArea.Value.Width) : image.Width;
// Values to calculate
Int32 top;
Int32 bottom;
Int32 left;
Int32 right;
// Convert to 32bppARGB and get bytes and stride out.
Byte[] data;
Int32 stride;
using (Bitmap bm = new Bitmap(image))
{
BitmapData sourceData = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadOnly, bm.PixelFormat);
stride = sourceData.Stride;
data = new Byte[stride*bm.Height];
Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
bm.UnlockBits(sourceData);
}
// ============= Y =============
// Top = first found row which contains data
for (top = yStart; top < yEnd; top++)
{
Int32 index = top * stride;
if (!RowClear(data, index, 4, xStart, xEnd, blankPixel))
break;
}
// Sanity check: no data on image. Abort.
if (top == yEnd)
return new Rectangle(xStart, yStart, 0, 0);
// Bottom = last found row which contains data
for (bottom = yEnd - 1; bottom > top; bottom--)
{
Int32 index = bottom * stride;
if (!RowClear(data, index, 4, xStart, xEnd, blankPixel))
break;
}
// Make bottom the first actually clear row.
bottom++;
// ============= X =============
// Left = first found column which contains data
for (left = xStart; left < xEnd; left++)
{
Int32 index = left * 4;
if (!ColClear(data, index, stride, yStart, yEnd, blankPixel))
break;
}
// Right = last found row which contains data
for (right = xEnd - 1; right > left; right--)
{
Int32 index = right * 4;
if (!ColClear(data, index, stride, yStart, yEnd, blankPixel))
break;
}
// Make right the first actually clear column
right++;
// Calculate final rectangle values, including border.
Int32 rectX = Math.Max(0, left - borderSizePixels);
Int32 rectY = Math.Max(0, top - borderSizePixels);
Int32 rectW = Math.Min(image.Width, right + borderSizePixels) - rectX;
Int32 rectH = Math.Min(image.Height, bottom + borderSizePixels) - rectY;
return new Rectangle(rectX, rectY, rectW, rectH);
}
public static Boolean RowClear(Byte[] data, Int32 index, Int32 pixelWidth, Int32 xStart, Int32 xEnd, Color blankPixel)
{
Boolean rowOk = true;
Int32 start = index + pixelWidth * xStart;
Int32 end = index + pixelWidth * xEnd;
for (Int32 x = start; x < end; x += pixelWidth)
{
if (blankPixel.A != data[x + 3]) rowOk = false;
else if (blankPixel.R != data[x + 2]) rowOk = false;
else if (blankPixel.G != data[x + 1]) rowOk = false;
else if (blankPixel.B != data[x + 0]) rowOk = false;
if (!rowOk)
return false;
}
return true;
}
public static Boolean ColClear(Byte[] data, Int32 index, Int32 stride, Int32 yStart, Int32 yEnd, Color blankPixel)
{
Boolean colOk = true;
Int32 start = index + stride * yStart;
Int32 end = index + stride * yEnd;
for (Int32 y = start; y < end; y += stride)
{
if (blankPixel.A != data[y + 3]) colOk = false;
else if (blankPixel.R != data[y + 2]) colOk = false;
else if (blankPixel.G != data[y + 1]) colOk = false;
else if (blankPixel.B != data[y + 0]) colOk = false;
if (!colOk)
return false;
}
return true;
}
hdi错误。。。。停止创建甚至不使用的字体实例…在尝试保存图像之前,您需要正确处理图形对象。使用
块而不是手动处理,使用更多。Image.Save()不会生成良好的异常。首先使用File.writealText(文件名“”)来获得更好的文件。现在,您将有一个很好的机会来诊断由于此代码不具有线程安全性和忘记处理img而导致的共享冲突。这是gold,感谢您为我的一个问题提供了我所见过的最好、最全面的答案之一!很高兴我能帮忙:)
public static Rectangle GetCropBounds(Bitmap image, Color blankPixel, Int32 borderSizePixels = 5, Rectangle? searchArea = null)
{
// Not too worried about the other boundaries; the "for" loops will exclude those anyway.
Int32 yStart = searchArea.HasValue ? Math.Max(0, searchArea.Value.Y) : 0;
Int32 yEnd = searchArea.HasValue ? Math.Min(image.Height, searchArea.Value.Y + searchArea.Value.Height) : image.Height;
Int32 xStart = searchArea.HasValue ? Math.Max(0, searchArea.Value.X) : 0;
Int32 xEnd = searchArea.HasValue ? Math.Min(image.Width, searchArea.Value.X + searchArea.Value.Width) : image.Width;
// Values to calculate
Int32 top;
Int32 bottom;
Int32 left;
Int32 right;
// Convert to 32bppARGB and get bytes and stride out.
Byte[] data;
Int32 stride;
using (Bitmap bm = new Bitmap(image))
{
BitmapData sourceData = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadOnly, bm.PixelFormat);
stride = sourceData.Stride;
data = new Byte[stride*bm.Height];
Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
bm.UnlockBits(sourceData);
}
// ============= Y =============
// Top = first found row which contains data
for (top = yStart; top < yEnd; top++)
{
Int32 index = top * stride;
if (!RowClear(data, index, 4, xStart, xEnd, blankPixel))
break;
}
// Sanity check: no data on image. Abort.
if (top == yEnd)
return new Rectangle(xStart, yStart, 0, 0);
// Bottom = last found row which contains data
for (bottom = yEnd - 1; bottom > top; bottom--)
{
Int32 index = bottom * stride;
if (!RowClear(data, index, 4, xStart, xEnd, blankPixel))
break;
}
// Make bottom the first actually clear row.
bottom++;
// ============= X =============
// Left = first found column which contains data
for (left = xStart; left < xEnd; left++)
{
Int32 index = left * 4;
if (!ColClear(data, index, stride, yStart, yEnd, blankPixel))
break;
}
// Right = last found row which contains data
for (right = xEnd - 1; right > left; right--)
{
Int32 index = right * 4;
if (!ColClear(data, index, stride, yStart, yEnd, blankPixel))
break;
}
// Make right the first actually clear column
right++;
// Calculate final rectangle values, including border.
Int32 rectX = Math.Max(0, left - borderSizePixels);
Int32 rectY = Math.Max(0, top - borderSizePixels);
Int32 rectW = Math.Min(image.Width, right + borderSizePixels) - rectX;
Int32 rectH = Math.Min(image.Height, bottom + borderSizePixels) - rectY;
return new Rectangle(rectX, rectY, rectW, rectH);
}
public static Boolean RowClear(Byte[] data, Int32 index, Int32 pixelWidth, Int32 xStart, Int32 xEnd, Color blankPixel)
{
Boolean rowOk = true;
Int32 start = index + pixelWidth * xStart;
Int32 end = index + pixelWidth * xEnd;
for (Int32 x = start; x < end; x += pixelWidth)
{
if (blankPixel.A != data[x + 3]) rowOk = false;
else if (blankPixel.R != data[x + 2]) rowOk = false;
else if (blankPixel.G != data[x + 1]) rowOk = false;
else if (blankPixel.B != data[x + 0]) rowOk = false;
if (!rowOk)
return false;
}
return true;
}
public static Boolean ColClear(Byte[] data, Int32 index, Int32 stride, Int32 yStart, Int32 yEnd, Color blankPixel)
{
Boolean colOk = true;
Int32 start = index + stride * yStart;
Int32 end = index + stride * yEnd;
for (Int32 y = start; y < end; y += stride)
{
if (blankPixel.A != data[y + 3]) colOk = false;
else if (blankPixel.R != data[y + 2]) colOk = false;
else if (blankPixel.G != data[y + 1]) colOk = false;
else if (blankPixel.B != data[y + 0]) colOk = false;
if (!colOk)
return false;
}
return true;
}
public static void CreateImageFromText(String text, String filename, Int32 fontSize, Int32 padding)
{
if (text == null)
text = String.Empty;
Boolean prefixTick = text.StartsWith("tick:");
Boolean prefixCross = !prefixTick && text.StartsWith("cross:");
Boolean highlight = !prefixTick && !prefixCross && text.StartsWith("highlight:");
const String symbTick = "✔";
const String symbCross = "X";
const String symbBullet = "•";
// Cut off the prefix part
if (prefixTick || prefixCross || highlight)
text = text.Substring(text.IndexOf(":", StringComparison.Ordinal) + 1).TrimStart();
using (Font font = new Font("Arial", fontSize))
using (Font prefixFont = new Font("Arial", fontSize * (highlight ? 3f : 1.25f), highlight ? FontStyle.Bold : FontStyle.Regular))
{
// Calculate accurate dimensions of required image.
Single textWidth;
Single prefixWidth = 0;
Single requiredHeight = 0;
Single textHeight;
Single prefixHeight = 0;
// Dummy image will have the same dpi as the final one.
using (Bitmap dummy = new Bitmap(1, 1))
using (Graphics g = Graphics.FromImage(dummy))
{
if (prefixTick)
{
SizeF tickSize = g.MeasureString(symbTick, prefixFont);
requiredHeight = Math.Max(tickSize.Height, requiredHeight);
prefixWidth = tickSize.Width;
}
else if (prefixCross)
{
SizeF crossSize = g.MeasureString(symbCross, prefixFont);
requiredHeight = Math.Max(crossSize.Height, requiredHeight);
prefixWidth = crossSize.Width;
}
else if (highlight)
{
SizeF bulletSize = g.MeasureString(symbBullet, prefixFont);
requiredHeight = Math.Max(bulletSize.Height, requiredHeight);
prefixWidth = bulletSize.Width;
}
prefixHeight = requiredHeight;
SizeF textSize = g.MeasureString(text.Length == 0 ? " " : text, font);
textWidth = text.Length == 0 ? 0 : textSize.Width;
textHeight= textSize.Height;
requiredHeight = Math.Max(textSize.Height, requiredHeight);
}
if (!prefixTick && !prefixCross && !highlight && text.Length == 0)
{
Int32 width = padding*2;
Int32 height = (Int32)Math.Round(textHeight + padding*2, MidpointRounding.AwayFromZero);
if (width == 0)
width = 1;
// Creates an image of the expected height for the font, and a width consisting of only the padding, or 1 for no padding.
using (Image img = new Bitmap(width, height))
img.Save(filename, ImageFormat.Png);
return;
}
Single prefixX = 5;
Single prefixY = 5 + padding + prefixWidth > 0 && requiredHeight > prefixHeight ? (requiredHeight - prefixHeight) / 2 : 0;
Single textX = 5 + prefixWidth;
Single textY = 5 + padding + requiredHeight > textHeight ? (requiredHeight - textHeight) / 2 : 0;
// Set global stage dimensions. Add 10 Pixels to each to allow for 5-pixel border.
Int32 stageWidth = (Int32)Math.Round(prefixWidth + textWidth, MidpointRounding.AwayFromZero) + 10 + padding * 2;
Int32 stageHeight = (Int32)Math.Round(requiredHeight, MidpointRounding.AwayFromZero) + 10 + padding * 2;
// Create Bitmap placeholder for new image
using (Bitmap createdImage = new Bitmap(stageWidth, stageHeight))
{
Color blankPixel = createdImage.GetPixel(0, 0);
// Draw new blank image
using (Graphics imageCanvas = Graphics.FromImage(createdImage))
{
imageCanvas.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
imageCanvas.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
// Add text
if (prefixTick)
imageCanvas.DrawString(symbTick, prefixFont, Brushes.Green, prefixX, prefixY);
else if (prefixCross)
imageCanvas.DrawString(symbCross, prefixFont, Brushes.Red, prefixX, prefixY);
else if (highlight)
imageCanvas.DrawString(symbBullet, prefixFont, Brushes.Magenta, prefixX, prefixY);
if (text.Length > 0)
imageCanvas.DrawString(text, font, Brushes.Black, textX, textY);
}
//clip to only part containing text.
Rectangle r = ImageUtils.GetCropBounds(createdImage, blankPixel, padding);
if (r.Width <= 0 || r.Height <= 0)
return; // Possibly throw exception; image formats can't handle 0x0.
// Save cropped
createdImage.Save(Path.Combine(Path.GetDirectoryName(filename), Path.GetFileNameWithoutExtension(filename)) + "_orig" + Path.GetExtension(filename), ImageFormat.Png);
using (Image img = createdImage.Clone(r, createdImage.PixelFormat))
img.Save(filename, ImageFormat.Png);
}
}
}