我的A0格式(600dpi)png(19860px x 28080px)仅包含黑白像素(每个像素文件一位大约只有3MB)。我想要的就是将此文件另存为png,其中白色像素将由透明颜色替换。
bitmap.MakeTransparent(color)
不起作用,因为文件太大。使用ColorMap
有什么想法如何在合理的时间内替换所有这些白色像素?
答案 0 :(得分: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
}
}
答案 1 :(得分:0)
此操作完全不需要System.Drawing
。
请参阅,PNG格式每像素一位的黑白图像将为灰度格式或调色板。
PNG由具有以下格式的块组成:
现在这是有趣的部分:PNG具有相当模糊的功能,它支持添加一个tRNS
块,该块将
用于读取和写入PNG块的代码已发布在此处的早期答案中:
您将需要阅读IHDR
块并检查标题中的颜色类型,以查看需要添加哪种类型的透明度块。 here可以找到标题格式和所有颜色类型的概述。基本上,颜色类型0是灰度的,颜色类型3是调色板的,因此您的图像应该是这两种颜色之一。
对于调色板透明性,应该在tRNS
块后面添加PLTE
块。对于灰度透明,我认为它应该在第一个IDAT
块之前。
如果使用调色板,则需要检查白色是调色板中的第一还是第二种颜色,因此可以将正确的颜色设置为透明。
因此,一旦获得了这个,就创建一个新的字节数组,其大小与图像以及添加的块(块头和页脚的12个字节以及其中的2个字节的数据)一样大,然后复制文件,直到应该添加细分的位置,然后是新细分,然后是文件的其余部分。将bytes数组保存到文件中,就可以完成。