C#位图以编程方式创建;作物代码开始给出“在GDI +中发生一般错误". [英] C# Bitmap created programmatically; crop code started giving "A generic error occurred in GDI+"

查看:59
本文介绍了C#位图以编程方式创建;作物代码开始给出“在GDI +中发生一般错误".的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下将文本渲染为图像的方法.它制作了比必要的位图大的图,绘制了文本,然后在位图上寻找空白并进行裁剪.在保存图像的位置,它将引发错误"GDI +中发生一般错误".这段代码一直在我开发的同一台机器上运行,尽管它已经运行很长时间了,所以自上次运行以来,可能已经发生了相当数量的Windows更新.就我所知,解决方案/.net框架等都没有改变-我只是打开解决方案,在调试中运行它(像往常一样),并且产生了错误

I have the following method that renders text into an image. It makes a larger than necessary bitmap, draws the text, then hunts the bitmap for blank space and crops it off. At the point where the image is saved, it throws the error "A generic error occurred in GDI+". This code has always worked on this same machine, that I develop on, though it hasn't been run in a long time so a reasonable amount of windows updates are likely to have occurred since the last time it worked. Nothing else has changed, to my knowledge re the solution/.net framework etc - I just opened the solution, ran it in debug (like always), and it produced the error

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();
}

搜索完全空白的像素行的辅助方法是:

The helper method that searches for completely blank rows of pixels is:

公共静态Rectangle GetBoundsThatContainData(位图createdImage,Color blankPixel,int borderSizePixels = 5,Rectangle?searchArea = null) { 矩形sa =新矩形(0,0,createdImage.Width,createdImage.Height);

public static Rectangle GetBoundsThatContainData(Bitmap createdImage, Color blankPixel, int borderSizePixels = 5, Rectangle? searchArea = null) { Rectangle sa = new Rectangle(0, 0, createdImage.Width, createdImage.Height);

  if (searchArea.HasValue)
  {
    if (searchArea.Value.X > sa.X)
      sa.X = searchArea.Value.X;

    if (searchArea.Value.Y > sa.Y)
      sa.Y = searchArea.Value.Y;

    if (searchArea.Value.Width < sa.Width)
      sa.Width = searchArea.Value.Width;

    if (searchArea.Value.Height < sa.Height)
      sa.Height = searchArea.Value.Height;
  }

  //look for vertical
  for (int i = (sa.Y + sa.Height) - 1; i >= sa.Y; i--)
  {
    if (!AllPixelsOnHorizontalLineMatch(blankPixel, i, sa, createdImage))
    {
      sa.Height = (i - sa.Y) + 1 + borderSizePixels;
      break;
    }
  }

  if (sa.Y + sa.Height > createdImage.Height)
    sa.Height = createdImage.Height - sa.Y;

  //look for the horizontal
  for (int i = (sa.X + sa.Width) - 1; i >= sa.X; i--)
  {
    if (!AllPixelsOnVerticalLineMatch(blankPixel, i, sa, createdImage))
    {
      sa.Width = (i - sa.X) + 1 + borderSizePixels;
      break;
    }
  }

  if (sa.X + sa.Width > createdImage.Width)
    sa.Width = createdImage.Width - sa.X;

  return sa;
}

助手功能正常,返回我想要的矩形.

The helper functions OK, returns me a rect I'm expecting.

还有其他人能够在他们的计算机上再现GDI错误吗(我没有另一台计算机可以进行测试,以查看它是否仅影响我的计算机)?关于如何诊断原因的任何指示?读到很多这类错误与关闭位图所基于的流有关,但是在这种情况下,没有流;位图没有从任何地方加载-完全在代码中创建.

Is anyone else able to repro the GDI error on their machine (I don't have another machine here to test as a compare to see if it's affecting just my machine)? Any pointers as to how to diagnose the cause? A read that a lot of these kinds of errors relate to closing the stream the bitmap is resting on, but in this case there is no stream; the bitmap isn't loaded from anywhere - it's created entirely in the code..

推荐答案

虽然Graphics对象存在,但是图像对象被视为处于编辑状态.放置图形对象后,才将图像视为完成".您尝试在放置Graphics对象之前 保存图像,这可能会导致问题.在代码中添加适当的using块应该可以完全解决此问题.

While the Graphics object exists, the image object is considered to be in a state of being edited. The image is only considered "done" after the graphics object is disposed. You attempt to save the image before disposing that Graphics object, and that can cause problem. Adding proper using blocks to your code should solve this problem completely.

除非是真正的问题出在AllPixelsOnHorizontalLineMatchAllPixelsOnVerticalLineMatch工具中,否则您未将其包括在问题中.如果他们做的事情可能会弄乱GDI +对象,那么这可能会影响您以后的保存.

Except, that is, if the real problem is in the AllPixelsOnHorizontalLineMatch or AllPixelsOnVerticalLineMatch tools, which you didn't include in your question. If they do something that might mess up the GDI+ object, then that can affect the saving you do later.

无论如何,这是使用正确的using块重写的函数:

Anyway, here's your function rewritten with proper using blocks:

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);
    }
}

我不想重写这些丢失的工具功能,因为从头到尾处理字节并将它们传递给这些工具功能效率更高,所以我最终只写了自己的裁剪函数.我不确定它确实能做到准确,但是受限制的搜索区域和边框似乎有效,因此在这里供参考:

I didn't feel like rewriting these missing tool functions, since it's much more efficient to work with bytes all the way through and pass those on to these tools function, so I just ended up writing my own crop function altogether. I'm not sure it does exactly what yours does, but the constrained search area and the border thing seemed to work, so here it is, for reference:

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;
}

请注意,您可能希望使用更准确的方法来确定图像所需的尺寸. .Net框架具有内置的方法.另请注意,由于您始终绘制为(0,0),因此裁剪功能所留下的5像素边框在顶部往往不起作用.考虑到原始图像尺寸估计的完全不准确,我也不知道为什么"highlight:"前缀将约束矩形(基于所说的不准确图像尺寸)赋予裁剪函数.

Note that you may want to use a more accurate way to determine the size needed for the image. The .Net framework has inbuilt methods for that. Also note that since you always paint to (0,0), the 5-pixel border that the crop function leaves tends to not work at the top. Given the complete inaccuracy of the original image size estimation, I also have no idea why the "highlight:" prefix gives that constraining rectangle (based on said inaccurate image size) to the crop function.

弄弄所有这些东西时,我有些混乱,想知道StartsWith调用是否实际上意味着符号应该被用作前缀而不是整个字符串...所以我最终以这种方式实现了它.这是最后重写的功能.它会自动将较小字体在较大字体上垂直居中.

I messed around a little when fiddling with all that stuff, and wondered if the StartsWith calls actually meant that the symbols were supposed to act as prefix rather than the whole string... so I ended up implementing it that way. Here's the final rewritten function. It automatically does vertical centering of the smaller font on the larger one.

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);
        }
    }
}

这篇关于C#位图以编程方式创建;作物代码开始给出“在GDI +中发生一般错误".的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆