Windows /.NET的System.Drawing.Save(Stream,ImageFormat)中的错误。产生了错误的PNG

时间:2018-08-30 15:32:08

标签: c# .net windows gdi+

在某些特定情况下,System.Drawing.Save(Stream,Imageformat)创建损坏的PNG图像。

有办法避免这种情况吗?

  1. 我不需要使用第三方库,并且
  2. 我不需要检查PNG字节来知道是否需要“修复”某些东西?

复制步骤

  1. 创建一个System.Drawing.BitMap
  2. 向图像添加内容,以使其具有非常特定的PNG文件大小(“何时发生”)
  3. 调用Save(Stream,Imageformat)-选择PNG格式

出了什么问题?

问题是最后一个图像数据之后的IDAT块不正确。它不包含任何数据,但长度字节为00 00 ff f4。 可以使用https://github.com/jsummers/tweakpng进行检测。我们注意到Linux上的图像库(不确定哪个)无法处理此类错误。据我们所知,在Windows中此错误将被忽略,您将不会发现任何问题。

何时发生?

这取决于PNG文件的大小。仅当生成的PNG文件大小(字节)为 0x1001C + n * 0x10000,其中n为0、1、2、3、4,并且可能更大。

它是可复制的

可以对第二步进行调整,以生成特定的PNG文件大小(例如,在原本为空的BitMap中为不同数量的像素着色)。当大小如上所述时,始终出现该错误。

要复制的代码

在干净的控制台应用程序中替换Program.cs的内容。运行该程序时,它将尝试创建具有指定大小的PNG,并将其另存为“ constructed.png”。

顺便说一句:第二个PNG被保存为“ constructedReExport.png”:这是通过加载第一个并再次保存来创建的。我想我记得这是一个可能的解决方法,但是当我现在运行它时,第二个文件包含相同的错误...

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace CreateCorruptPng
{
    class Program
    {
        static void Main(string[] args)
        {
            // Choose 0x1001C + 0x10000 * n; with n = 0, 1, 2, 3, 4 to get corrupt PNG
            int targetOutputSizeBytes = 0x5001C;

            // You may need to fiddle with these parameters to 
            // successfully create an image of the exact size.
            int widthPixels = 2000;
            int height = 1200;

            var creator = new PngCreator(widthPixels, height);

            string outputPath = ".";
            creator.TryCreateWithSize(targetOutputSizeBytes);
            creator.SaveCurrentImage(Path.Combine(outputPath, "constructed.png"));
            creator.SaveAfterSecondExport(Path.Combine(outputPath, "constructedReExport.png"));
        }
    }

    public class PngCreator
    {
        Bitmap _img;
        int _width;
        int _height;
        int _maxPixcount;

        public PngCreator(int w, int h)
        {
            _width = w;
            _height = h;
            _maxPixcount = w * h;
        }

        public void TryCreateWithSize(int requiredByteCount)
        {
            Console.WriteLine($"Attempting to create png file of exactly {requiredByteCount} bytes.");
            Console.WriteLine($"Image size (w x h) = {_width} x {_height}.");

            int lowerBound = 0;
            int upperBound = _maxPixcount;

            bool success = false;
            while (upperBound > lowerBound + 1)
            {
                InitImage();
                int pixelCount = (upperBound + lowerBound) / 2;
                AddPixels(pixelCount);

                int currentSize = GetPngByteCount();
                if (currentSize == requiredByteCount)
                {
                    success = true;
                    break;
                }

                if (currentSize < requiredByteCount)
                    lowerBound = pixelCount;
                else
                    upperBound = pixelCount;
            }
            Console.WriteLine("Search stopped.");

            if (success)
                Console.WriteLine($"SUCCESS.\n   Created PNG with exact file size {requiredByteCount} bytes.");
            else
                Console.WriteLine($"WARNING.\n" +
                    $"   Could not produce PNG with file size {requiredByteCount} bytes.\n" +
                    "   Try to run again with different resolution.\n" +
                    "   If the file size in the last iteration is too small, try larger resolution.");
        }

        private void InitImage()
        {
            _img?.Dispose();
            _img = new Bitmap(_width, _height, PixelFormat.Format16bppArgb1555);
        }

        private void AddPixels(int n)
        {
            Console.WriteLine($"Coloring {n} pixels...");
            for (int i = 0; i < n; i++)
            {
                int x = i % _width;
                int y = i / _width;
                _img.SetPixel(x, y, Color.FromArgb((i / 2) % 255, 0, 0));
            }
        }

        private int GetPngByteCount()
        {
            using (MemoryStream s = new MemoryStream())
            {
                _img.Save(s, ImageFormat.Png);

                byte[] imgBytes = s.ToArray();
                Console.WriteLine($"Png file size {imgBytes.Length}");
                return imgBytes.Length;
            }
        }

        public void SaveCurrentImage(string path)
        {
            SaveImage(path, _img);
        }

        public void SaveAfterSecondExport(string path)
        {
            using (Bitmap imgReimported = ToPngBytesAndLoadAgain(_img))
            {
                SaveImage(path, imgReimported);
            }
        }

        private Bitmap ToPngBytesAndLoadAgain(Bitmap img)
        {
            return new Bitmap(new MemoryStream(ToPngBytes(img)));
        }

        private static byte[] ToPngBytes(Bitmap img)
        {
            using (MemoryStream s = new MemoryStream())
            {
                img.Save(s, ImageFormat.Png);
                return s.ToArray();
            }
        }

        private static void SaveImage(string path, Bitmap img)
        {
            Console.WriteLine($"Saving file to {path}");
            File.WriteAllBytes(path, ToPngBytes(img));

        }
    }
}

1 个答案:

答案 0 :(得分:0)

在处理由Adobe产品创建的某些文件时,我看到了问题,不同的Adobe产品具有不同的引擎,有时这会破坏GDI +,并采用类似的解决方法来保留文件。使用不使用GDI +的第三方图像处理工具可能是最好的选择,例如ImageMagic(或.net包装器Magic.Net)

在广告内容时,是指绘制图像还是添加文件,请确保添加的文件不是问题。