diff --git a/OpenRA.FileFormats/Graphics/PngLoader.cs b/OpenRA.FileFormats/Graphics/PngLoader.cs new file mode 100644 index 0000000000..f977d9f5f7 --- /dev/null +++ b/OpenRA.FileFormats/Graphics/PngLoader.cs @@ -0,0 +1,181 @@ +#region Copyright & License Information +/* + * Copyright 2007-2010 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.IO.Compression; +using System.Net; +using System.Runtime.InteropServices; +using System.Text; + +namespace OpenRA.FileFormats.Graphics +{ + public static class PngLoader + { + public static Bitmap Load(string filename) + { + return Load(File.OpenRead(filename)); + } + + public static Bitmap Load(Stream s) + { + using (var br = new BinaryReader(s)) + { + var signature = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 }; + foreach (var b in signature) + if (br.ReadByte() != b) + throw new InvalidDataException("PNG Signature is bogus"); + + Bitmap bitmap = null; + Color[] palette = null; + List data = new List(); + + for (; ; ) + { + var length = IPAddress.NetworkToHostOrder(br.ReadInt32()); + var type = Encoding.UTF8.GetString(br.ReadBytes(4)); + var content = br.ReadBytes(length); + var crc = br.ReadInt32(); + + using (var ms = new MemoryStream(content)) + using (var cr = new BinaryReader(ms)) + switch (type) + { + case "IHDR": + { + + var width = IPAddress.NetworkToHostOrder(cr.ReadInt32()); + var height = IPAddress.NetworkToHostOrder(cr.ReadInt32()); + var bitDepth = cr.ReadByte(); + var colorType = (PngColorType)cr.ReadByte(); + var compression = cr.ReadByte(); + var filter = cr.ReadByte(); + var interlace = cr.ReadByte(); + + if (compression != 0) throw new InvalidDataException("Compression method not supported"); + if (interlace != 0) throw new InvalidDataException("Interlacing not supported"); + + bitmap = new Bitmap(width, height, MakePixelFormat(bitDepth, colorType)); + + } + break; + + case "PLTE": + { + palette = new Color[256]; + for (var i = 0; i < 256; i++) + { + var r = cr.ReadByte(); var g = cr.ReadByte(); var b = cr.ReadByte(); + palette[i] = Color.FromArgb(r, g, b); + } + } + break; + + case "tRNS": + { + for (var i = 0; i < length; i++) + palette[i] = Color.FromArgb(cr.ReadByte(), palette[i]); + } + break; + + case "IDAT": + { + data.AddRange(content); + } + break; + + case "IEND": + { + var bits = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), + ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); + + using (var ns = new MemoryStream(data.ToArray())) + { + // 'zlib' flags bytes; confuses the DeflateStream. + var flags = (byte)ns.ReadByte(); + var moreFlags = (byte)ns.ReadByte(); + + using (var ds = new DeflateStream(ns, CompressionMode.Decompress)) + using (var dr = new BinaryReader(ds)) + { + var prevLine = new byte[bitmap.Width]; // all zero + for (var y = 0; y < bitmap.Height; y++) + { + var filter = (PngFilter)dr.ReadByte(); + var line = dr.ReadBytes(bitmap.Width); + + for (var i = 0; i < bitmap.Width; i++) + line[i] = i > 0 + ? UnapplyFilter(filter, line[i], line[i - 1], prevLine[i], prevLine[i - 1]) + : UnapplyFilter(filter, line[i], 0, prevLine[i], 0); + + Marshal.Copy(line, 0, new IntPtr(bits.Scan0.ToInt64() + y * bits.Stride), line.Length); + prevLine = line; + } + } + } + + bitmap.UnlockBits(bits); + + using (var temp = new Bitmap(1, 1, PixelFormat.Format8bppIndexed)) + { + var cp = temp.Palette; + for (var i = 0; i < 256; i++) + cp.Entries[i] = palette[i]; // finalize the palette. + bitmap.Palette = cp; + return bitmap; + } + } + } + } + } + } + + static byte UnapplyFilter(PngFilter f, byte x, byte a, byte b, byte c) + { + switch (f) + { + case PngFilter.None: return x; + case PngFilter.Sub: return (byte)(x + a); + case PngFilter.Up: return (byte)(x + b); + case PngFilter.Average: return (byte)(x + (a + b) / 2); + case PngFilter.Paeth: return (byte)(x + Paeth(a, b, c)); + default: + throw new InvalidOperationException("Unsupported Filter"); + } + } + + static byte Paeth(byte a, byte b, byte c) + { + var p = a + b - c; + var pa = Math.Abs(p - a); + var pb = Math.Abs(p - b); + var pc = Math.Abs(p - c); + + return (pa <= pb && pa <= pc) ? a : + (pb <= pc) ? b : c; + } + + [Flags] + enum PngColorType { Indexed = 1, Color = 2, Alpha = 4 }; + enum PngFilter { None, Sub, Up, Average, Paeth }; + + static PixelFormat MakePixelFormat(byte bitDepth, PngColorType colorType) + { + if (bitDepth == 8 && colorType == (PngColorType.Indexed | PngColorType.Color)) + return PixelFormat.Format8bppIndexed; + + throw new InvalidDataException("Unknown pixelformat"); + } + } +} diff --git a/OpenRA.FileFormats/OpenRA.FileFormats.csproj b/OpenRA.FileFormats/OpenRA.FileFormats.csproj index 1c8bdeb88a..2fad3ee4c2 100644 --- a/OpenRA.FileFormats/OpenRA.FileFormats.csproj +++ b/OpenRA.FileFormats/OpenRA.FileFormats.csproj @@ -66,6 +66,7 @@ +