Implement a simple managed png writer.
This commit is contained in:
@@ -14,7 +14,6 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using ICSharpCode.SharpZipLib.Checksums;
|
using ICSharpCode.SharpZipLib.Checksums;
|
||||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||||
@@ -24,6 +23,8 @@ namespace OpenRA.FileFormats
|
|||||||
{
|
{
|
||||||
public class Png
|
public class Png
|
||||||
{
|
{
|
||||||
|
static readonly byte[] Signature = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
|
||||||
|
|
||||||
public int Width { get; set; }
|
public int Width { get; set; }
|
||||||
public int Height { get; set; }
|
public int Height { get; set; }
|
||||||
public Color[] Palette { get; set; }
|
public Color[] Palette { get; set; }
|
||||||
@@ -197,8 +198,7 @@ namespace OpenRA.FileFormats
|
|||||||
public static bool Verify(Stream s)
|
public static bool Verify(Stream s)
|
||||||
{
|
{
|
||||||
var pos = s.Position;
|
var pos = s.Position;
|
||||||
var signature = new[] { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
|
var isPng = Signature.Aggregate(true, (current, t) => current && s.ReadUInt8() == t);
|
||||||
var isPng = signature.Aggregate(true, (current, t) => current && s.ReadUInt8() == t);
|
|
||||||
s.Position = pos;
|
s.Position = pos;
|
||||||
return isPng;
|
return isPng;
|
||||||
}
|
}
|
||||||
@@ -246,109 +246,104 @@ namespace OpenRA.FileFormats
|
|||||||
throw new InvalidDataException("Unknown pixel format");
|
throw new InvalidDataException("Unknown pixel format");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WritePngChunk(Stream output, string type, Stream input)
|
||||||
|
{
|
||||||
|
input.Position = 0;
|
||||||
|
|
||||||
|
var typeBytes = Encoding.ASCII.GetBytes(type);
|
||||||
|
output.Write(IPAddress.HostToNetworkOrder((int)input.Length));
|
||||||
|
output.WriteArray(typeBytes);
|
||||||
|
|
||||||
|
var data = input.ReadAllBytes();
|
||||||
|
output.WriteArray(data);
|
||||||
|
|
||||||
|
var crc32 = new Crc32();
|
||||||
|
crc32.Update(typeBytes);
|
||||||
|
crc32.Update(data);
|
||||||
|
output.Write(IPAddress.NetworkToHostOrder((int)crc32.Value));
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] Save()
|
public byte[] Save()
|
||||||
{
|
{
|
||||||
var pixelFormat = Palette != null ? System.Drawing.Imaging.PixelFormat.Format8bppIndexed : System.Drawing.Imaging.PixelFormat.Format32bppArgb;
|
using (var output = new MemoryStream())
|
||||||
|
|
||||||
// Save to a memory stream that we can then parse to add the embedded data
|
|
||||||
using (var bitmapStream = new MemoryStream())
|
|
||||||
{
|
{
|
||||||
using (var bitmap = new System.Drawing.Bitmap(Width, Height, pixelFormat))
|
output.WriteArray(Signature);
|
||||||
|
using (var header = new MemoryStream())
|
||||||
{
|
{
|
||||||
if (Palette != null)
|
header.Write(IPAddress.HostToNetworkOrder(Width));
|
||||||
{
|
header.Write(IPAddress.HostToNetworkOrder(Height));
|
||||||
// Setting bitmap.Palette.Entries directly doesn't work
|
header.WriteByte(8); // Bit depth
|
||||||
var bPal = bitmap.Palette;
|
|
||||||
for (var i = 0; i < 256; i++)
|
|
||||||
bPal.Entries[i] = System.Drawing.Color.FromArgb(Palette[i].ToArgb());
|
|
||||||
|
|
||||||
bitmap.Palette = bPal;
|
var colorType = Palette != null
|
||||||
var bd = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, Width, Height), System.Drawing.Imaging.ImageLockMode.WriteOnly,
|
? PngColorType.Indexed | PngColorType.Color
|
||||||
pixelFormat);
|
: PngColorType.Color | PngColorType.Alpha;
|
||||||
for (var i = 0; i < Height; i++)
|
header.WriteByte((byte)colorType);
|
||||||
Marshal.Copy(Data, i * Width, IntPtr.Add(bd.Scan0, i * bd.Stride), Width);
|
|
||||||
|
|
||||||
bitmap.UnlockBits(bd);
|
header.WriteByte(0); // Compression
|
||||||
}
|
header.WriteByte(0); // Filter
|
||||||
else
|
header.WriteByte(0); // Interlacing
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
var bd = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, Width, Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, pixelFormat);
|
|
||||||
var colors = (int*)bd.Scan0;
|
|
||||||
for (var y = 0; y < Height; y++)
|
|
||||||
{
|
|
||||||
for (var x = 0; x < Width; x++)
|
|
||||||
{
|
|
||||||
var i = y * Width + x;
|
|
||||||
|
|
||||||
// Convert RGBA to ARGB
|
WritePngChunk(output, "IHDR", header);
|
||||||
colors[i] = Color.FromArgb(Data[4 * i + 3], Data[4 * i + 0], Data[4 * i + 1],
|
|
||||||
Data[4 * i + 2]).ToArgb();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitmap.UnlockBits(bd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitmap.Save(bitmapStream, System.Drawing.Imaging.ImageFormat.Png);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!EmbeddedData.Any())
|
bool alphaPalette = false;
|
||||||
return bitmapStream.ToArray();
|
if (Palette != null)
|
||||||
|
|
||||||
// Add embedded metadata to the end of the image
|
|
||||||
bitmapStream.Position = 0;
|
|
||||||
var outputStream = new MemoryStream();
|
|
||||||
using (var bw = new BinaryWriter(outputStream))
|
|
||||||
{
|
{
|
||||||
bw.Write(bitmapStream.ReadBytes(8));
|
using (var palette = new MemoryStream())
|
||||||
var crc32 = new Crc32();
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
{
|
||||||
var length = IPAddress.NetworkToHostOrder(bitmapStream.ReadInt32());
|
foreach (var c in Palette)
|
||||||
var type = Encoding.UTF8.GetString(bitmapStream.ReadBytes(4));
|
|
||||||
var content = bitmapStream.ReadBytes(length);
|
|
||||||
var crc = bitmapStream.ReadUInt32();
|
|
||||||
|
|
||||||
switch (type)
|
|
||||||
{
|
{
|
||||||
case "tEXt":
|
palette.WriteByte(c.R);
|
||||||
break;
|
palette.WriteByte(c.G);
|
||||||
|
palette.WriteByte(c.B);
|
||||||
case "IEND":
|
alphaPalette |= c.A > 0;
|
||||||
bitmapStream.Close();
|
|
||||||
|
|
||||||
foreach (var kv in EmbeddedData)
|
|
||||||
{
|
|
||||||
bw.Write(IPAddress.NetworkToHostOrder(kv.Key.Length + 1 + kv.Value.Length));
|
|
||||||
bw.Write("tEXt".ToCharArray());
|
|
||||||
bw.Write(kv.Key.ToCharArray());
|
|
||||||
bw.Write((byte)0x00);
|
|
||||||
bw.Write(kv.Value.ToCharArray());
|
|
||||||
crc32.Reset();
|
|
||||||
crc32.Update(Encoding.ASCII.GetBytes("tEXt"));
|
|
||||||
crc32.Update(Encoding.ASCII.GetBytes(kv.Key + (char)0x00 + kv.Value));
|
|
||||||
bw.Write((uint)IPAddress.NetworkToHostOrder((int)crc32.Value));
|
|
||||||
}
|
|
||||||
|
|
||||||
bw.Write(0);
|
|
||||||
bw.Write(type.ToCharArray());
|
|
||||||
bw.Write(crc);
|
|
||||||
|
|
||||||
return outputStream.ToArray();
|
|
||||||
|
|
||||||
default:
|
|
||||||
bw.Write(IPAddress.NetworkToHostOrder(length));
|
|
||||||
bw.Write(type.ToCharArray());
|
|
||||||
bw.Write(content);
|
|
||||||
bw.Write(crc);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WritePngChunk(output, "PLTE", palette);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (alphaPalette)
|
||||||
|
{
|
||||||
|
using (var alpha = new MemoryStream())
|
||||||
|
{
|
||||||
|
foreach (var c in Palette)
|
||||||
|
alpha.WriteByte(c.A);
|
||||||
|
|
||||||
|
WritePngChunk(output, "tRNS", alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var data = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var compressed = new DeflaterOutputStream(data))
|
||||||
|
{
|
||||||
|
var stride = Width * (Palette != null ? 1 : 4);
|
||||||
|
for (var y = 0; y < Height; y++)
|
||||||
|
{
|
||||||
|
// Write uncompressed scanlines for simplicity
|
||||||
|
compressed.WriteByte(0);
|
||||||
|
compressed.Write(Data, y * stride, stride);
|
||||||
|
}
|
||||||
|
|
||||||
|
compressed.Flush();
|
||||||
|
compressed.Finish();
|
||||||
|
|
||||||
|
WritePngChunk(output, "IDAT", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var kv in EmbeddedData)
|
||||||
|
{
|
||||||
|
using (var text = new MemoryStream())
|
||||||
|
{
|
||||||
|
text.WriteArray(Encoding.ASCII.GetBytes(kv.Key + (char)0 + kv.Value));
|
||||||
|
WritePngChunk(output, "tEXt", text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WritePngChunk(output, "IEND", new MemoryStream());
|
||||||
|
return output.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user