ImageBufferオブジェクトをPNG形式で保存するエンコーダーです。
SimplePngEncoder.cs
using System.IO;
using System.IO.Compression;
using System.Text;
using ImageBuffer = Maywork.WPF.Helpers.ImageBufferHelper.ImageBuffer;
namespace Maywork.WPF.Helpers;
public static class SimplePngEncoder
{
public static void Save(this ImageBuffer src, string path)
{
using var fs = File.Create(path);
Save(src, fs);
}
public static void Save(this ImageBuffer src, Stream stream)
{
if (src.Width <= 0 || src.Height <= 0)
throw new ArgumentException("Invalid image size.");
if (src.Channels is not (1 or 3 or 4))
throw new NotSupportedException($"Unsupported channels:{src.Channels}");
using var bw = new BinaryWriter(stream, Encoding.ASCII, leaveOpen: true);
// =========================
// PNGシグネチャ
// =========================
bw.Write(new byte[]
{
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A
});
// =========================
// IHDR
// =========================
byte colorType = src.Channels switch
{
1 => (byte)0, // Gray
3 => (byte)2, // RGB
4 => (byte)6, // RGBA
_ => throw new NotSupportedException()
};
byte[] ihdr = new byte[13];
WriteInt32BE(ihdr, 0, src.Width);
WriteInt32BE(ihdr, 4, src.Height);
ihdr[8] = 8; // bit depth
ihdr[9] = colorType; // color type
ihdr[10] = 0; // compression method
ihdr[11] = 0; // filter method
ihdr[12] = 0; // interlace method
WriteChunk(bw, "IHDR", ihdr);
// =========================
// 画像データ作成
// 各行の先頭に FilterType=0 を付ける
// =========================
byte[] rawImage = BuildRawImage(src);
// =========================
// zlib圧縮
// =========================
byte[] compressed;
using (var ms = new MemoryStream())
{
using (var z = new ZLibStream(ms, CompressionLevel.Optimal, leaveOpen: true))
{
z.Write(rawImage, 0, rawImage.Length);
}
compressed = ms.ToArray();
}
WriteChunk(bw, "IDAT", compressed);
// =========================
// IEND
// =========================
WriteChunk(bw, "IEND", Array.Empty<byte>());
}
static byte[] BuildRawImage(ImageBuffer src)
{
int width = src.Width;
int height = src.Height;
int ch = src.Channels;
int srcStride = src.Stride;
int dstStride = 1 + (width * ch); // filter byte + row pixels
byte[] dst = new byte[height * dstStride];
int di = 0;
for (int y = 0; y < height; y++)
{
// Filter Type = 0 (None)
dst[di++] = 0;
int si = y * srcStride;
if (ch == 1)
{
// Gray はそのまま
Buffer.BlockCopy(src.Pixels, si, dst, di, width);
di += width;
}
else if (ch == 3)
{
// ImageBuffer: BGR -> PNG: RGB
for (int x = 0; x < width; x++)
{
byte b = src.Pixels[si++];
byte g = src.Pixels[si++];
byte r = src.Pixels[si++];
dst[di++] = r;
dst[di++] = g;
dst[di++] = b;
}
}
else if (ch == 4)
{
// ImageBuffer: BGRA -> PNG: RGBA
for (int x = 0; x < width; x++)
{
byte b = src.Pixels[si++];
byte g = src.Pixels[si++];
byte r = src.Pixels[si++];
byte a = src.Pixels[si++];
dst[di++] = r;
dst[di++] = g;
dst[di++] = b;
dst[di++] = a;
}
}
else
{
throw new NotSupportedException($"Unsupported channels:{ch}");
}
}
return dst;
}
static void WriteChunk(BinaryWriter bw, string type, byte[] data)
{
byte[] typeBytes = Encoding.ASCII.GetBytes(type);
if (typeBytes.Length != 4)
throw new ArgumentException("Chunk type must be 4 chars.", nameof(type));
WriteInt32BE(bw, data.Length);
bw.Write(typeBytes);
bw.Write(data);
uint crc = Crc32(typeBytes, data);
WriteUInt32BE(bw, crc);
}
static void WriteInt32BE(BinaryWriter bw, int value)
{
bw.Write(new byte[]
{
(byte)((value >> 24) & 0xFF),
(byte)((value >> 16) & 0xFF),
(byte)((value >> 8) & 0xFF),
(byte)(value & 0xFF),
});
}
static void WriteUInt32BE(BinaryWriter bw, uint value)
{
bw.Write(new byte[]
{
(byte)((value >> 24) & 0xFF),
(byte)((value >> 16) & 0xFF),
(byte)((value >> 8) & 0xFF),
(byte)(value & 0xFF),
});
}
static void WriteInt32BE(byte[] buffer, int offset, int value)
{
buffer[offset + 0] = (byte)((value >> 24) & 0xFF);
buffer[offset + 1] = (byte)((value >> 16) & 0xFF);
buffer[offset + 2] = (byte)((value >> 8) & 0xFF);
buffer[offset + 3] = (byte)(value & 0xFF);
}
static uint Crc32(byte[] typeBytes, byte[] data)
{
uint crc = 0xFFFFFFFF;
for (int i = 0; i < typeBytes.Length; i++)
crc = UpdateCrc32(crc, typeBytes[i]);
for (int i = 0; i < data.Length; i++)
crc = UpdateCrc32(crc, data[i]);
return ~crc;
}
static uint UpdateCrc32(uint crc, byte b)
{
crc ^= b;
for (int k = 0; k < 8; k++)
{
if ((crc & 1) != 0)
crc = (crc >> 1) ^ 0xEDB88320u;
else
crc >>= 1;
}
return crc;
}
}
/*
// 使い方
var img = SimplePngDecoder.Load("input.png");
SimplePngEncoder.Save("output.png", img);
*/
Download


コメント