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

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

有没有办法避免它,这样:

  1. 我不需要使用第三方库,而且
  2. 我不需要检查PNG字节以了解我是否需要“修复”某些东西?

重现步骤

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

问题是什么?

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

什么时候发生?

这取决于PNG文件的大小。 如果生成的PNG文件大小(以字节为单位)为0x1001C + n * 0x10000且n为0,1,2,3,4且可能更大,则只会出现此问题。

它是可重复的

可以调整步骤2以产生特定的PNG文件大小(例如,在其他空的BitMap中为不同数量的像素着色)。 当尺寸如上所述时,错误一直发生。

代码重现

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

另外:第二个PNG保存为“constructReExport.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 (wxh) = {_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)); } } } 

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

广告内容时,您的意思是绘制到图像或添加文件,检查添加的文件是不是问题。