为巨大的图像添加透明度C#

问题描述 投票:0回答:2

我有A0格式(600dpi)png(19860px x 28080px),只包含黑白像素(每像素文件一位仅约3MB)。我想要的是将此文件保存为png,其中白色像素将被透明颜色替换。

bitmap.MakeTransparent(color)不起作用,因为文件太大了。使用ColorMap的问题相同

有什么想法如何在合理的时间内替换所有这些白色像素?

c# bitmap transparency system.drawing
2个回答
0
投票

我不是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
    }

}

0
投票

你根本不需要System.Drawing这个操作。

请参阅,PNG格式的每像素一位黑白图像将采用灰度格式或调色板。

PNG由具有以下格式的块组成:

  • 内部块长度的四个字节(big-endian)
  • 块标识符的四个ASCII字符
  • 块内容(具有第一部分中指定的长度)
  • 一个四字节的CRC哈希作为一致性检查。

现在这里是有趣的部分:PNG具有相当模糊的特性,它支持添加的tRNS块,用于设置灰度和调色板格式的alpha。对于灰度,这个块包含一个双字节值,指示哪个灰度值应该是透明的(我假设每个像素一位应该是'1',因为那是白色),而对于调色板格式,这包含每个的灰度值。它的调色板颜色(虽然它不必与调色板一样长;任何未包含的索引都默认为不透明)。而且由于PNG没有对其块进行整体索引,因此您可以直接添加它并完成它。

用于读取和写入PNG块的代码在此处的早期答案中发布:

Reading chunks

Writing chunks

您需要阅读IHDR块并检查标题中的颜色类型以查看需要添加的透明块类型。可以在here找到标题格式和所有颜色类型可能性的概述。基本上,颜色类型0是灰度,颜色类型3是调色板,因此您的图像应该是这两个中的一个。

对于调色板透明度,tRNS块应该添加在PLTE块后面。对于灰度透明度,我相信它应该在第一个IDAT块之前。

如果它是调色板,您需要检查调色板中的白色是第一种还是第二种颜色,因此您可以将正确的颜色设置为透明。

所以一旦你得到了它,创建一个与你的图像一样大的新字节数组加上添加的块(12个字节的块头和页脚,可能还有2个字节的数据),然后将文件的第一部分复制到应将段添加到其中的点,然后是新段,然后是文件的其余部分。将bytes数组保存到文件中,就完成了。

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