位图克隆上的 C# WinForms 内存不足异常
本文关键字:WinForms 内存不足 异常 位图 | 更新日期: 2023-09-27 18:33:00
我编写了一个小型的"水印"程序来为图像添加自定义水印。有两个水印,一个白色的和一个黑色的。水印始终放置在图像的左下角。我克隆图像的该区域以确定应根据该点放置哪个水印(浅色区域的黑色水印和深色区域的白色水印(。
当我在我的机器上使用应用程序(调试或正常(时 - 没问题。处理所有图像,并在正确的位置添加水印。
但是,在客户端计算机上,程序会在克隆部件上引发内存不足异常的所有映像上中断。
我知道,通常,当我指定一个区域时,也会抛出 OutOfMemory 异常,但由于该函数在我的机器上像魅力一样工作,我无法想象是这种情况。除此之外,该程序在几次尝试后不会中断,它会在所有克隆尝试中中断。
是否有文本(DrawString 方法(并不重要。它在克隆上中断。
正在处理的图像很大,但不是"大"(最多 6016 x 4000 像素(,但即使图像较小(3264 x 2448 像素(,客户端也会中断。
变量:
bmOriginal
:原始位图
processImage
:原始图像(图片框( - bmOriginal
是此图像的位图副本
watermarkText
:水印下方额外信息的文本框
black
和white
:包含水印图像的图片框
watermarkCombo
:用于选择自动,白色或黑色的组合框(自动失败(
法典:
using (Graphics gWatermark = Graphics.FromImage(bmOriginal))
{
gWatermark.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
System.Drawing.SolidBrush drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Black);
// position watermark - watermark should be 10% of the image height
int watermarkHeight = (int)(processImage.Image.Height * 0.1);
int watermarkPadding = (int)(watermarkHeight * 0.1); // not completely true, but asume watermark is square
Rectangle watermarkArea = new Rectangle(watermarkPadding, processImage.Image.Height - (watermarkPadding + (watermarkText.Text.Length == 0 ? 0 : watermarkPadding) + watermarkHeight), watermarkHeight, watermarkHeight);
// determine color watermark
bmWatermark = (Bitmap)black.Image;
if (watermarkCombo.SelectedIndex == 0)
{
using (Bitmap watermarkClone = bmOriginal.Clone(watermarkArea, bmOriginal.PixelFormat))
{
var pixels = Pixels(watermarkClone);
if (pixels.Average((Func<Color, decimal>)Intensity) < 110) // human eye adoption; normal threshold should be 128
{
bmWatermark = (Bitmap)white.Image;
drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.White);
}
}
}
else if (watermarkCombo.SelectedIndex == 1)
{
bmWatermark = (Bitmap)white.Image;
drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.White);
}
// draw the watermark
gWatermark.DrawImage(bmWatermark, watermarkArea.X, watermarkArea.Y, watermarkArea.Width, watermarkArea.Height);
// draw the text (if needed)
if (watermarkText.Text.Length > 0)
{
System.Drawing.Font drawFont = new System.Drawing.Font("Tahoma", (float)watermarkPadding);
gWatermark.DrawString(watermarkText.Text, drawFont, drawBrush, watermarkPadding, bmOriginal.Height - (watermarkPadding * 2));
}
}
bmOriginal.Save(System.IO.Path.Combine(diWatermarked.FullName, fileName), System.Drawing.Imaging.ImageFormat.Jpeg);
错误行:using (Bitmap watermarkClone = bmOriginal.Clone(watermarkArea, bmOriginal.PixelFormat))
现在最大的问题是:我如何摆脱 OutOfMemory 异常......有人有想法吗?
编辑 当我选择不自动确定水印的颜色而只是添加水印(假设是白色水印(时,程序正常运行。我已经在错误日志中看到堆栈跟踪(在函数的捕获上,我输出异常和 - 如果有的话 - 内部异常(。
我知道很多使用 Clone 函数的 OOM 异常发生在您指定一个区域时;但这里的情况并非如此。
当我在调试模式下使用应用程序时查看我的内存时,我从 5.36 Gb 程序启动和标准化的 5.39 Gb(最大峰值为 5.42 Gb(开始运行我提到的执行,它不会像疯了一样消耗内存。
我使用的代码确定平均"颜色"(它来自 StackOverflow 上的某人 - 我只是从其他答案中复制了它,但找不到链接(;
// functions used to determine watermark color
private static decimal ComponentAverage(decimal a, decimal b)
{
return Math.Min(a, b) + Math.Abs(a - b) / 2M;
}
private static decimal Intensity(Color color)
{
decimal result = color.A;
result = ComponentAverage(result, color.R);
result = ComponentAverage(result, color.G);
result = ComponentAverage(result, color.B);
return result;
}
private static IEnumerable<Color> Pixels(Bitmap bitmap)
{
for (int x = 0; x < bitmap.Width; x++)
for (int y = 0; y < bitmap.Height; y++)
yield return bitmap.GetPixel(x, y);
}
来源 这里有一个测试项目:http://hotpepper.nu/oomtestapp.zip
Leon,我已经更改了您上传的代码,以不锁定任何资源。我注意到,如果我保持您的应用程序打开状态,我将无法删除输出文件夹,因为某些文件正在使用中。这通常意味着您没有释放所有文件句柄,基本上它始终是最后一个文件。
在更改之前和之后,我无法在计算机上重现内存不足问题,似乎是文件非常大的问题?
好吧,无论如何,我发现您使用ImageBox加载白色和黑色资源并从光盘加载图像。这根本不需要,直接使用资源
Bitmap white = OomTestApp.Properties.Resources.white;
Bitmap black = OomTestApp.Properties.Resources.black;
然后要从光盘加载映像,只需使用Bitmap.FromFile
我添加了一些行以正确释放您的资源.Dispose
没有使用using
块的地方。
我还删除了Clone()
调用,因为我认为绝对不需要它,因为您只是在计算原始图像的像素,并且您不会在该图像中绘制一些东西。那么克隆映像的必要性是什么呢?
这是完整的代码(从创建文件夹后开始(
if (errors == 0)
{
this.Height = 323;
goButton.Enabled = false;
stopButton.Enabled = true;
Bitmap white = OomTestApp.Properties.Resources.white;
Bitmap black = OomTestApp.Properties.Resources.black;
Bitmap bmWatermark = black;
Bitmap processImage = null;
progressBar1.Maximum = filesToProcess.Count;
foreach (string handleFile in filesToProcess)
{
string fileName = System.IO.Path.GetFileName(handleFile);
fileNameLabel.Text = "File: " + System.IO.Path.GetFileName(handleFile);
try
{
// create backup if checked
if (diOriginal != null)
{
System.IO.File.Move(handleFile, System.IO.Path.Combine(diOriginal.FullName, fileName));
processImage = (Bitmap)Bitmap.FromFile(System.IO.Path.Combine(diOriginal.FullName, fileName));
}
else
{
processImage = (Bitmap)Bitmap.FromFile(handleFile);
}
double aspectRatio = (double)processImage.Width / (double)processImage.Height;
using (Graphics gWatermark = Graphics.FromImage(processImage))
{
gWatermark.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
System.Drawing.SolidBrush drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Black);
// position watermark - watermark should be 10% of the image height
int watermarkHeight = (int)(processImage.Height * 0.1);
int watermarkPadding = (int)(watermarkHeight * 0.1); // not completely true, but asume watermark is square
// calculate rectangle. if there is extra text, add extra padding below
Rectangle watermarkArea = new Rectangle(watermarkPadding, processImage.Height - (watermarkPadding + (watermarkText.Text.Length == 0 ? 0 : watermarkPadding) + watermarkHeight), watermarkHeight, watermarkHeight);
// determine color watermark
bmWatermark = black;
if (watermarkCombo.SelectedIndex == 0)
{
/*using (Bitmap watermarkClone = processImage.Clone(watermarkArea, processImage.PixelFormat))
{*/
var pixels = Pixels(processImage);
if (pixels.Average((Func<Color, decimal>)Intensity) < 110) // human eye adoption; normal threshold should be 128
{
bmWatermark = white;
drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.White);
}
//}
}
else if (watermarkCombo.SelectedIndex == 1)
{
bmWatermark = white;
drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.White);
}
// draw the watermark
gWatermark.DrawImage(bmWatermark, watermarkArea.X, watermarkArea.Y, watermarkArea.Width, watermarkArea.Height);
// draw the text (if needed)
if (watermarkText.Text.Length > 0)
{
System.Drawing.Font drawFont = new System.Drawing.Font("Tahoma", (float)watermarkPadding);
gWatermark.DrawString(watermarkText.Text, drawFont, drawBrush, watermarkPadding, processImage.Height - (watermarkPadding * 2));
drawFont.Dispose();
}
// disposing resources
drawBrush.Dispose();
}
// save the watermarked file
processImage.Save(System.IO.Path.Combine(diWatermarked.FullName, fileName), System.Drawing.Imaging.ImageFormat.Jpeg);
// stop button pressed?
Application.DoEvents();
if (stopProcess) break;
// update exection progress
progressBar1.Value++;
percentLabel.Text = ((int)((progressBar1.Value * 100) / filesToProcess.Count)).ToString() + "%";
fileCountLabel.Text = "File " + progressBar1.Value.ToString() + "/" + filesToProcess.Count.ToString();
}
catch (Exception ex)
{
try
{
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(System.IO.Path.Combine(folderText.Text, "errorlog.txt"), true))
{
sw.WriteLine("File: " + fileName);
while (ex != null)
{
sw.WriteLine("Message: " + ex.Message);
sw.WriteLine(ex.StackTrace);
sw.WriteLine(ex.Source);
ex = ex.InnerException;
}
sw.WriteLine();
}
}
catch
{
// nothing to do - it already failed
}
errors++;
}
finally
{
if (processImage != null) processImage.Dispose();
}
}
// dispose resources
white.Dispose();
black.Dispose();
bmWatermark.Dispose();
if (!stopProcess)
{
// set status to complete
fileCountLabel.Text = "File " + filesToProcess.Count.ToString() + "/" + filesToProcess.Count.ToString();
percentLabel.Text = "100%";
fileNameLabel.Text = "Completed...";
}
else
{
fileNameLabel.Text = "Aborted...";
}
fileNameLabel.Text += errors.ToString() + " error(s) encountered";
// defaults to screen
progressBar1.Value = progressBar1.Maximum;
stopProcess = false;
goButton.Enabled = true;
stopButton.Enabled = false;
好吧,我没有阅读页面上的所有内容,但我想知道是否有人提到位图的"步伐"?基本上,位图必须是 4 字节的倍数。我在拆开网格制作瓷砖时遇到了这个问题。最后一个磁贴会出错,因为位图不能被 4 整除。
步幅是单行像素(扫描线(的宽度, 向上舍入为四字节边界。如果步幅为正,则 位图是自上而下的。如果步幅为负,则位图为 自下而上。 https://softwarebydefault.com/2013/04/11/bitmap-color-balance/