2

我编写了一个小型“水印”程序来为图像添加自定义水印。有两个水印,一个是白色的,一个是黑色的。水印始终位于图像的左下角。我克隆图像的该区域以确定应根据该点放置哪个水印(亮区域上的黑色水印和暗区域上的白色水印)。

当我在我的机器上使用应用程序(调试或正常)时 - 没问题。所有图像都经过处理,并在正确的位置添加水印。

但是,在客户端计算机上,程序会中断所有图像,并在克隆部分引发 OutOfMemory 异常。

我知道,通常情况下,当我指定一个超出范围的区域时,也会抛出 OutOfMemory 异常,但由于该函数在我的机器上就像一个魅力一样工作,我无法想象会出现这种情况。除此之外,该程序在几次尝试后不会中断,它会在所有尝试克隆时中断。

是否有文本(DrawString 方法)无关紧要。它在克隆上中断。

正在处理的图像很大,但不是“巨大”(最多 6016 x 4000 像素),但即使是较小的图像(3264 x 2448 像素),客户端也会中断。

变量:

bmOriginal: 原始位图

processImage: 原始图像 (pictureBox) -bmOriginal是这个的位图副本

watermarkText: 水印下方额外信息的文本框

blackwhite:包含水印图像的图片框

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

SOURCE这里有一个测试项目:http: //hotpepper.nu/oomtestapp.zip

4

2 回答 2

1

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;
于 2013-09-27T11:35:44.760 回答
-1

好吧,我没有阅读页面上的所有内容,但我想知道是否有人提到了位图的“步幅”?基本上,您的位图必须是 4 个字节的倍数。我有这个问题拆开网格来制作瓷砖。最后一个图块会出错,因为位图不能被 4 整除。

步幅是单行像素(扫描线)的宽度,四舍五入到四字节边界。如果步幅为正,则位图是自上而下的。如果步幅为负,则位图是自下而上的。 https://softwarebydefault.com/2013/04/11/bitmap-color-balance/

于 2017-03-16T21:21:30.703 回答