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

问题描述 投票:4回答:1

在某些非常特殊的情况下,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 (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));

        }
    }
}
c# .net windows gdi+
1个回答
0
投票

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

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

© www.soinside.com 2019 - 2024. All rights reserved.