182 lines
5.1 KiB
C#
182 lines
5.1 KiB
C#
#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<byte> data = new List<byte>();
|
|
|
|
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");
|
|
}
|
|
}
|
|
}
|