Ошибка в Windows / .NET System.Drawing.Save (Stream, ImageFormat). Произведен коррумпированный PNG - PullRequest
0 голосов
/ 30 августа 2018

В некоторых специфических ситуациях System.Drawing.Save (Stream, Imageformat) создает поврежденные изображения PNG.

Есть ли способ избежать этого, такой что:

  1. Мне не нужно использовать сторонние библиотеки, а
  2. Мне не нужно проверять байты PNG, чтобы узнать, нужно ли мне что-то «ремонтировать»?

Шаги для воспроизведения

  1. Создание System.Drawing.BitMap
  2. Добавление содержимого к изображению таким образом, чтобы оно приводило к очень конкретному размеру файла PNG («Когда это происходит»)
  3. Call 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 с точным указанным размером и сохранить его как «built.png».

В качестве отступления: второй PNG-файл сохраняется "construReExport.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 голосов
/ 30 августа 2018

Я сталкивался с проблемами при манипулировании некоторыми файлами, созданными продуктами Adobe, в разных Adobe-продуктах используются разные движки, и иногда это нарушает GDI + с аналогичным обходным приемом для резервирования файла. Лучше всего использовать сторонний инструмент для работы с изображениями, который не использует GDI +, например ImageMagic (или оболочку .net Magic.Net)

Когда вы рекламируете контент, подразумеваете ли вы рисование на изображении или добавление файла, убедитесь, что добавленный файл не является проблемой.

...