Добавьте прозрачность к огромному изображению C # - PullRequest
0 голосов
/ 19 ноября 2018

У меня есть формат A0 (600 dpi) png (19860px x 28080px), который содержит только черные и белые пиксели (один бит на файл пикселя составляет всего около 3 МБ). Все, что я хочу, это сохранить этот файл в формате png, где пиксели белого цвета будут заменены прозрачным цветом.

bitmap.MakeTransparent(color) не работает, потому что файл слишком большой. Та же проблема с использованием ColorMap

Есть идеи, как заменить все эти белые пиксели в разумные сроки?

Ответы [ 2 ]

0 голосов
/ 20 ноября 2018

Вам вообще не нужно System.Drawing для этой операции.

См. Черно-белые изображения размером один бит на пиксель в формате PNG будут либо в оттенках серого, либо в виде палитры.

PNG состоит из кусков, которые имеют следующий формат:

  • Четыре байта внутренней длины фрагмента (big-endian)
  • Четыре символа ASCII идентификатора куска
  • Содержимое чанка (с длиной, указанной в первой части)
  • Четырехбайтовый хэш CRC в качестве проверки согласованности.

Теперь вот интересная часть: PNG имеет довольно неясную особенность, заключающуюся в том, что он поддерживает добавляемый блок tRNS, который устанавливает alpha для оттенков серого и палитрового формата. Для градаций серого этот блок содержит двухбайтовое значение, которое указывает, какое значение градаций серого должно быть прозрачным (я предполагаю, что для одного бита на пиксель должно быть '1', поскольку оно белое), а для формата палитры это содержит альфа для каждого из цвета его палитры (хотя он не должен быть таким же длинным, как палитра; все индексы, не включенные в него, по умолчанию непрозрачны). А поскольку PNG не имеет общей индексации своих фрагментов, вы можете буквально просто добавить это и покончить с этим.

Код для чтения и записи фрагментов PNG был опубликован в предыдущих ответах здесь:

Чтение фрагментов

Написание фрагментов

Вам нужно будет прочитать блок IHDR и проверить тип цвета в заголовке, чтобы увидеть, какой тип блока прозрачности вам нужно добавить. Обзор формата заголовка и всех возможностей типа цвета можно найти здесь . В основном, цветовой тип 0 - это оттенки серого, а цветовой тип 3 - палитра, поэтому ваше изображение должно быть одним из этих двух.

Для прозрачности палитры, чанк tRNS должен быть добавлен сразу после чанка PLTE. Я полагаю, что для прозрачности в оттенках серого это должно быть перед первым IDAT фрагментом.

Если это палитра, вам нужно проверить, является ли белый цвет первым или вторым цветом в палитре, чтобы вы могли установить правильный прозрачный.

Итак, как только вы это получите, создайте новый байтовый массив размером с ваше изображение плюс добавленный фрагмент (12 байтов заголовка и нижнего колонтитула блока и, вероятно, 2 байта данных внутри него), а затем скопируйте первую часть файл до точки, где сегмент должен быть добавлен в него, затем ваш новый сегмент, а затем остальная часть файла. Сохраните массив байтов в файл, и все готово.

0 голосов
/ 19 ноября 2018

Я не специалист по PNG-файлам, но я читаю документацию. Я считаю, что если у вас есть файл GRAEY SCALE только с однобайтовыми данными, тогда все, что вам нужно сделать, это добавить заголовок прозрачности перед первым блоком IDATA. Заголовок прозрачности содержит два байта, которые представляют собой цветовую шкалу от 0 до (2 ^ bitdepth - 1)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace PNG_Tool
{
    class Program
    {
        const string READ_FILENAME = @"c:\temp\untitled.png";
        const string WRITE_FILENAME = @"c:\temp\untitled1.png";
        static void Main(string[] args)
        {
            PNG png = new PNG(READ_FILENAME, WRITE_FILENAME);

        }
    }
    class PNG
    {
        byte[] header;
        byte[] ident = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
        byte[] TNS = { 0x74, 0x52, 0x4E, 0x53 }; //"tRNS"

        public PNG(string inFilename, string outFilename)
        {
            Stream inStream = File.OpenRead(inFilename);
            BinaryReader reader = new BinaryReader(inStream);
            Stream outStream = File.Open(outFilename, FileMode.Create);
            BinaryWriter writer = new BinaryWriter(outStream);

            Boolean foundIDAT = false;


            header = reader.ReadBytes(8);

            if ((header.Length != ident.Length) || !(header.Select((x,i) => (x == ident[i])).All(x => x)))
            {
                Console.WriteLine("File is not PNG");
                return;
            }
            writer.Write(header);


            while (inStream.Position < inStream.Length)
            {
                byte[] byteLength = reader.ReadBytes(4);
                if (byteLength.Length < 4)
                {
                    Console.WriteLine("Unexpected End Of File");
                    return;
                }
                UInt32 length = (UInt32)((byteLength[0] << 24) | (byteLength[1] << 16) | (byteLength[2] << 8) | byteLength[3]);


                byte[] chunkType = reader.ReadBytes(4);
                if (chunkType.Length < 4)
                {
                    Console.WriteLine("Unexpected End Of File");
                    return;
                }
                string chunkName = Encoding.ASCII.GetString(chunkType);
                byte[] data = reader.ReadBytes((int)length);

                if (data.Length < length)
                {
                    Console.WriteLine("Unexpected End Of File");
                    return;
                }

                byte[] CRC = reader.ReadBytes(4);

                if (CRC.Length < 4)
                {
                    Console.WriteLine("Unexpected End Of File");
                    return;
                }
                uint crc = GetCRC(chunkType, data);

                UInt32 ExpectedCRC = (UInt32)((CRC[0] << 24) | (CRC[1] << 16) | (CRC[2] << 8) | CRC[3]);
                if (crc != ExpectedCRC)
                {
                    Console.WriteLine("Bad CRC");
                }

                switch (chunkName)
                {
                    case "IHDR" :
                        writer.Write(byteLength);
                        writer.Write(chunkType);
                        writer.Write(data);


                        Header chunkHeader = new Header(data);
                        chunkHeader.PrintImageHeader();
                        break;

                    case "IDAT" :
                        if (!foundIDAT)
                        {
                            //add transparency header before first IDAT header
                            byte[] tnsHeader = CreateTransparencyHeader();
                            writer.Write(tnsHeader);
                            foundIDAT = true;

                        }
                        writer.Write(byteLength);
                        writer.Write(chunkType);
                        writer.Write(data);

                        break;

                    default :
                        writer.Write(byteLength);
                        writer.Write(chunkType);
                        writer.Write(data);

                        break;
                }

                writer.Write(CRC);

            }
            reader.Close();
            writer.Flush();
            writer.Close();

        }
        public byte[] CreateTransparencyHeader()
        {
            byte[] white = { 0, 0 };

            List<byte> header = new List<byte>();
            byte[] length = { 0, 0, 0, 2 };  //length is just two bytes
            header.AddRange(length);
            header.AddRange(TNS);
            header.AddRange(white);

            UInt32 crc = GetCRC(TNS, white);
            byte[] crcBytes = { (byte)((crc >> 24) & 0xFF), (byte)((crc >> 16) & 0xFF), (byte)((crc >> 8) & 0xFF), (byte)(crc & 0xFF) };
            header.AddRange(crcBytes);

            return header.ToArray();
        }

        public uint GetCRC(byte[] type, byte[] bytes)
        {
            uint crc = 0xffffffff; /* CRC value is 32bit */
            //crc = CRC32(byteLength, crc);
            crc = CRC32(type, crc);
            crc = CRC32(bytes, crc);

            crc = Reflect(crc, 32);
            crc ^= 0xFFFFFFFF;

            return crc;

        }
        public uint CRC32(byte[] bytes, uint crc)
        {
            const uint polynomial = 0x04C11DB7; /* divisor is 32bit */

            foreach (byte b in bytes)
            {
                crc ^= (uint)(Reflect(b, 8) << 24); /* move byte into MSB of 32bit CRC */

                for (int i = 0; i < 8; i++)
                {
                    if ((crc & 0x80000000) != 0) /* test for MSB = bit 31 */
                    {
                        crc = (uint)((crc << 1) ^ polynomial);
                    }
                    else
                    {
                        crc <<= 1;
                    }
                }
            }
            return crc;
        }
        static public UInt32 Reflect(UInt32 data, int size)
        {
            UInt32 output = 0;
            for (int i = 0; i < size; i++)
            {
                UInt32 lsb = data & 0x01;
                output = (UInt32)((output << 1) | lsb);
                data >>= 1;
            }
            return output;
        }
    }
    public class Header
    {
        public UInt32 width { get; set; }
        public UInt32 height { get; set; }

        byte[] widthBytes { get; set; }
        byte[] heightBytes { get; set; }

        public byte depth { get; set; }
        public byte colourType { get; set; }
        public byte compressionMethod { get; set; }
        public byte filterMethod { get; set; }
        public byte interlaceMethod { get; set; }

        public Header(byte[] bytes)
        {

            UInt32 width = (UInt32)((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]);
            UInt32 height = (UInt32)((bytes[4] << 24) | (bytes[5] << 16) | (bytes[6] << 8) | bytes[7]);

            widthBytes = new byte[4];
            Array.Copy(bytes, widthBytes, 4);

            heightBytes = new byte[4];
            Array.Copy(bytes, 4, heightBytes, 0, 4);

            depth = bytes[8];
            colourType = bytes[9];
            compressionMethod = bytes[10];
            filterMethod = bytes[11];
            interlaceMethod = bytes[12];
        }
        public void PrintImageHeader()
        {

           Console.WriteLine("Width = '{0}', Height = '{1}', Bit Depth = '{2}', Colour Type = '{3}', Compression Method = '{4}', Filter Medthod = '{5}', Interlace Method = '{6}'",
               width.ToString(),
               height.ToString(),
               depth.ToString(),
               ((COLOUR_TYPE)colourType).ToString(),
               compressionMethod.ToString(),
               filterMethod.ToString(),
               interlaceMethod.ToString()
                   );

         }
        public byte[] GetHeader()
        {
            List<byte> header = new List<byte>();
            header.AddRange(widthBytes);
            header.AddRange(heightBytes);

            header.Add(depth);
            header.Add(colourType);
            header.Add(compressionMethod);
            header.Add(filterMethod);
            header.Add(interlaceMethod);


            return header.ToArray();
        }
    }
    public enum COLOUR_TYPE
    {
        GRAY_SCALE = 0,
        TRUE_COLOUR = 2,
        INDEXED_COLOUR = 3,
        GREY_SCALE_ALPHA = 4,
        TRUE_COLOUR_ALPHA = 6
    }

}
...