(ded4a3e0a) v0.9.0.7
This commit is contained in:
@@ -0,0 +1,667 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace MonoGame.Utilities.Png
|
||||
{
|
||||
internal class Palette
|
||||
{
|
||||
private IList<Color> colors;
|
||||
|
||||
internal Palette()
|
||||
{
|
||||
colors = new List<Color>();
|
||||
}
|
||||
|
||||
internal Color this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return colors[index];
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddColor(Color color)
|
||||
{
|
||||
colors.Add(color);
|
||||
}
|
||||
|
||||
internal void AddAlphaToColorAtIndex(int colorIndex, byte alpha)
|
||||
{
|
||||
var oldColor = colors[colorIndex];
|
||||
|
||||
colors[colorIndex] = new Color(oldColor.R, oldColor.G, oldColor.B, alpha);
|
||||
}
|
||||
|
||||
internal void AddAlphaToColors(IList<byte> alphas)
|
||||
{
|
||||
for (int i = 0; i < alphas.Count; i++)
|
||||
{
|
||||
AddAlphaToColorAtIndex(i, alphas[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Chunks
|
||||
|
||||
internal class PngChunk
|
||||
{
|
||||
internal PngChunk()
|
||||
{
|
||||
this.Data = new byte[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Length of Data field
|
||||
/// </summary>
|
||||
internal uint Length
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal string Type
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private bool Ancillary
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private bool Private
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private bool Reserved
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private bool SafeToCopy
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal byte[] Data
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CRC of both Type and Data fields, but not Length field
|
||||
/// </summary>
|
||||
internal uint Crc
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal virtual void Decode(byte[] chunkBytes)
|
||||
{
|
||||
var chunkBytesList = chunkBytes.ToList();
|
||||
|
||||
this.Length = chunkBytesList.GetRange(0, 4).ToArray().ToUInt();
|
||||
DecodeType(chunkBytesList.GetRange(4, 4).ToArray());
|
||||
this.Data = chunkBytesList.GetRange(8, (int)this.Length).ToArray();
|
||||
this.Crc = chunkBytesList.GetRange((int)(8 + this.Length), 4).ToArray().ToUInt();
|
||||
|
||||
if (CrcCheck() == false)
|
||||
{
|
||||
throw new Exception("CRC check failed.");
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual byte[] Encode()
|
||||
{
|
||||
var result = new List<byte>();
|
||||
|
||||
uint dataLength = (uint)this.Data.Length;
|
||||
uint dataCrc = PngCrc.Calculate(InputToCrcCheck());
|
||||
|
||||
result.AddRange(dataLength.ToByteArray());
|
||||
result.AddRange(GetChunkTypeBytes(this.Type));
|
||||
result.AddRange(this.Data);
|
||||
result.AddRange(dataCrc.ToByteArray());
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private void DecodeType(byte[] typeBytes)
|
||||
{
|
||||
this.Type = PngChunk.GetChunkTypeString(typeBytes);
|
||||
|
||||
var bitArray = new BitArray(typeBytes);
|
||||
|
||||
this.Ancillary = bitArray[4];
|
||||
this.Private = bitArray[9];
|
||||
this.Reserved = bitArray[14];
|
||||
this.SafeToCopy = bitArray[19];
|
||||
}
|
||||
|
||||
private bool CrcCheck()
|
||||
{
|
||||
var crcInputBytes = InputToCrcCheck();
|
||||
|
||||
return (PngCrc.Calculate(crcInputBytes) == this.Crc);
|
||||
}
|
||||
|
||||
private byte[] InputToCrcCheck()
|
||||
{
|
||||
byte[] chunkTypeBytes = GetChunkTypeBytes(this.Type);
|
||||
return chunkTypeBytes.Concat(this.Data).ToArray();
|
||||
}
|
||||
|
||||
internal static string GetChunkTypeString(byte[] chunkTypeBytes)
|
||||
{
|
||||
return Encoding.UTF8.GetString(chunkTypeBytes, 0, chunkTypeBytes.Length);
|
||||
}
|
||||
|
||||
private static byte[] GetChunkTypeBytes(string chunkTypeString)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(chunkTypeString);
|
||||
}
|
||||
}
|
||||
|
||||
internal class HeaderChunk : PngChunk
|
||||
{
|
||||
private static byte[] pngSignature = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 };
|
||||
|
||||
internal HeaderChunk()
|
||||
{
|
||||
base.Type = "IHDR";
|
||||
}
|
||||
|
||||
internal uint Width
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal uint Height
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal byte BitDepth
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal ColorType ColorType
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal byte CompressionMethod
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal byte FilterMethod
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal byte InterlaceMethod
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal static byte[] PngSignature
|
||||
{
|
||||
get { return pngSignature; }
|
||||
}
|
||||
|
||||
internal override void Decode(byte[] chunkBytes)
|
||||
{
|
||||
base.Decode(chunkBytes);
|
||||
var chunkData = base.Data;
|
||||
|
||||
this.Width = chunkData.Take(4).ToArray().ToUInt();
|
||||
this.Height = chunkData.Skip(4).Take(4).ToArray().ToUInt();
|
||||
this.BitDepth = chunkData.Skip(8).First();
|
||||
this.ColorType = (ColorType)chunkData.Skip(9).First();
|
||||
this.CompressionMethod = chunkData.Skip(10).First();
|
||||
this.FilterMethod = chunkData.Skip(11).First();
|
||||
this.InterlaceMethod = chunkData.Skip(12).First();
|
||||
|
||||
if (this.BitDepth < 8)
|
||||
{
|
||||
throw new Exception(String.Format("Bit depth less than 8 bits per sample is unsupported. Image bit depth is {0} bits per sample.", this.BitDepth));
|
||||
}
|
||||
}
|
||||
|
||||
internal override byte[] Encode()
|
||||
{
|
||||
var chunkData = new List<byte>();
|
||||
|
||||
chunkData.AddRange(this.Width.ToByteArray().ToList());
|
||||
chunkData.AddRange(this.Height.ToByteArray().ToList());
|
||||
chunkData.Add(this.BitDepth);
|
||||
chunkData.Add((byte)this.ColorType);
|
||||
chunkData.Add(this.CompressionMethod);
|
||||
chunkData.Add(this.FilterMethod);
|
||||
chunkData.Add(this.InterlaceMethod);
|
||||
|
||||
base.Data = chunkData.ToArray();
|
||||
|
||||
return base.Encode();
|
||||
}
|
||||
}
|
||||
|
||||
internal class PaletteChunk : PngChunk
|
||||
{
|
||||
internal PaletteChunk()
|
||||
{
|
||||
base.Type = "PLTE";
|
||||
this.Palette = new Palette();
|
||||
}
|
||||
|
||||
internal Palette Palette
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal override void Decode(byte[] chunkBytes)
|
||||
{
|
||||
base.Decode(chunkBytes);
|
||||
var chunkData = base.Data;
|
||||
|
||||
if (chunkData.Length % 3 != 0)
|
||||
{
|
||||
throw new Exception("Malformed palette chunk - length not multiple of 3.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < chunkData.Length / 3; i++)
|
||||
{
|
||||
byte red = chunkData.Skip(3 * i).Take(1).First();
|
||||
byte green = chunkData.Skip((3 * i) + 1).Take(1).First();
|
||||
byte blue = chunkData.Skip((3 * i) + 2).Take(1).First();
|
||||
|
||||
this.Palette.AddColor(new Color(red, green, blue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class TransparencyChunk : PngChunk
|
||||
{
|
||||
internal TransparencyChunk()
|
||||
{
|
||||
base.Type = "tRNS";
|
||||
this.PaletteTransparencies = new List<byte>();
|
||||
}
|
||||
|
||||
internal IList<byte> PaletteTransparencies
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal override void Decode(byte[] chunkBytes)
|
||||
{
|
||||
base.Decode(chunkBytes);
|
||||
var chunkData = base.Data;
|
||||
|
||||
this.PaletteTransparencies = chunkData.ToArray();
|
||||
}
|
||||
|
||||
internal override byte[] Encode()
|
||||
{
|
||||
var chunkData = new List<byte>();
|
||||
|
||||
|
||||
base.Data = chunkData.ToArray();
|
||||
|
||||
return base.Encode();
|
||||
}
|
||||
}
|
||||
|
||||
internal class DataChunk : PngChunk
|
||||
{
|
||||
internal DataChunk()
|
||||
{
|
||||
base.Type = "IDAT";
|
||||
}
|
||||
}
|
||||
|
||||
internal class EndChunk : PngChunk
|
||||
{
|
||||
internal EndChunk()
|
||||
{
|
||||
base.Type = "IEND";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enumerations
|
||||
|
||||
internal enum ColorType
|
||||
{
|
||||
Grayscale = 0,
|
||||
Rgb = 2,
|
||||
Palette = 3,
|
||||
GrayscaleWithAlpha = 4,
|
||||
RgbWithAlpha = 6
|
||||
}
|
||||
|
||||
internal enum FilterType
|
||||
{
|
||||
None = 0,
|
||||
Sub = 1,
|
||||
Up = 2,
|
||||
Average = 3,
|
||||
Paeth = 4
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Filters
|
||||
|
||||
internal static class NoneFilter
|
||||
{
|
||||
internal static byte[] Decode(byte[] scanline)
|
||||
{
|
||||
return scanline;
|
||||
}
|
||||
|
||||
internal static byte[] Encode(byte[] scanline)
|
||||
{
|
||||
var encodedScanline = new byte[scanline.Length + 1];
|
||||
|
||||
encodedScanline[0] = (byte)FilterType.None;
|
||||
scanline.CopyTo(encodedScanline, 1);
|
||||
|
||||
return encodedScanline;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class SubFilter
|
||||
{
|
||||
internal static byte[] Decode(byte[] scanline, int bytesPerPixel)
|
||||
{
|
||||
byte[] result = new byte[scanline.Length];
|
||||
|
||||
for (int x = 1; x < scanline.Length; x++)
|
||||
{
|
||||
byte priorRawByte = (x - bytesPerPixel < 1) ? (byte)0 : result[x - bytesPerPixel];
|
||||
|
||||
result[x] = (byte)((scanline[x] + priorRawByte) % 256);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static byte[] Encode(byte[] scanline, int bytesPerPixel)
|
||||
{
|
||||
var encodedScanline = new byte[scanline.Length + 1];
|
||||
|
||||
encodedScanline[0] = (byte)FilterType.Sub;
|
||||
|
||||
for (int x = 0; x < scanline.Length; x++)
|
||||
{
|
||||
byte priorRawByte = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel];
|
||||
|
||||
encodedScanline[x + 1] = (byte)((scanline[x] - priorRawByte) % 256);
|
||||
}
|
||||
|
||||
return encodedScanline;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class UpFilter
|
||||
{
|
||||
internal static byte[] Decode(byte[] scanline, byte[] previousScanline)
|
||||
{
|
||||
byte[] result = new byte[scanline.Length];
|
||||
|
||||
for (int x = 1; x < scanline.Length; x++)
|
||||
{
|
||||
byte above = previousScanline[x];
|
||||
|
||||
result[x] = (byte)((scanline[x] + above) % 256);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static byte[] Encode(byte[] scanline, byte[] previousScanline)
|
||||
{
|
||||
var encodedScanline = new byte[scanline.Length + 1];
|
||||
|
||||
encodedScanline[0] = (byte)FilterType.Up;
|
||||
|
||||
for (int x = 0; x < scanline.Length; x++)
|
||||
{
|
||||
byte above = previousScanline[x];
|
||||
|
||||
encodedScanline[x + 1] = (byte)((scanline[x] - above) % 256);
|
||||
}
|
||||
|
||||
return encodedScanline;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class AverageFilter
|
||||
{
|
||||
internal static byte[] Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
|
||||
{
|
||||
byte[] result = new byte[scanline.Length];
|
||||
|
||||
for (int x = 1; x < scanline.Length; x++)
|
||||
{
|
||||
byte left = (x - bytesPerPixel < 1) ? (byte)0 : result[x - bytesPerPixel];
|
||||
byte above = previousScanline[x];
|
||||
|
||||
result[x] = (byte)((scanline[x] + Average(left, above)) % 256);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
|
||||
{
|
||||
var encodedScanline = new byte[scanline.Length + 1];
|
||||
|
||||
encodedScanline[0] = (byte)FilterType.Average;
|
||||
|
||||
for (int x = 0; x < scanline.Length; x++)
|
||||
{
|
||||
byte left = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel];
|
||||
byte above = previousScanline[x];
|
||||
|
||||
encodedScanline[x + 1] = (byte)((scanline[x] - Average(left, above)) % 256);
|
||||
}
|
||||
|
||||
return encodedScanline;
|
||||
}
|
||||
|
||||
private static int Average(byte left, byte above)
|
||||
{
|
||||
return Convert.ToInt32(Math.Floor((left + above) / 2.0));
|
||||
}
|
||||
}
|
||||
|
||||
internal static class PaethFilter
|
||||
{
|
||||
internal static byte[] Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
|
||||
{
|
||||
byte[] result = new byte[scanline.Length];
|
||||
|
||||
for (int x = 1; x < scanline.Length; x++)
|
||||
{
|
||||
byte left = (x - bytesPerPixel < 1) ? (byte)0 : result[x - bytesPerPixel];
|
||||
byte above = previousScanline[x];
|
||||
byte upperLeft = (x - bytesPerPixel < 1) ? (byte)0 : previousScanline[x - bytesPerPixel];
|
||||
|
||||
result[x] = (byte)((scanline[x] + PaethPredictor(left, above, upperLeft)) % 256);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
|
||||
{
|
||||
var encodedScanline = new byte[scanline.Length + 1];
|
||||
|
||||
encodedScanline[0] = (byte)FilterType.Paeth;
|
||||
|
||||
for (int x = 0; x < scanline.Length; x++)
|
||||
{
|
||||
byte left = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel];
|
||||
byte above = previousScanline[x];
|
||||
byte upperLeft = (x - bytesPerPixel < 0) ? (byte)0 : previousScanline[x - bytesPerPixel];
|
||||
|
||||
encodedScanline[x + 1] = (byte)((scanline[x] - PaethPredictor(left, above, upperLeft)) % 256);
|
||||
}
|
||||
|
||||
return encodedScanline;
|
||||
}
|
||||
|
||||
private static int PaethPredictor(int a, int b, int c)
|
||||
{
|
||||
int p = a + b - c;
|
||||
int pa = Math.Abs(p - a);
|
||||
int pb = Math.Abs(p - b);
|
||||
int pc = Math.Abs(p - c);
|
||||
|
||||
if ((pa <= pb) && (pa <= pc))
|
||||
{
|
||||
return a;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pb <= pc)
|
||||
{
|
||||
return b;
|
||||
}
|
||||
else
|
||||
{
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal static class PngCrc
|
||||
{
|
||||
// table of CRCs of all 8-bit messages
|
||||
private static uint[] crcTable = null;
|
||||
|
||||
static PngCrc()
|
||||
{
|
||||
BuildCrcTable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build CRC lookup table for performance (once-off)
|
||||
/// </summary>
|
||||
private static void BuildCrcTable()
|
||||
{
|
||||
crcTable = new uint[256];
|
||||
|
||||
uint c, n, k;
|
||||
|
||||
for (n = 0; n < 256; n++)
|
||||
{
|
||||
c = n;
|
||||
|
||||
for (k = 0; k < 8; k++)
|
||||
{
|
||||
if ((c & 1) > 0)
|
||||
{
|
||||
c = 0xedb88320 ^ (c >> 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
c = c >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
crcTable[n] = c;
|
||||
}
|
||||
}
|
||||
|
||||
internal static uint Calculate(byte[] bytes)
|
||||
{
|
||||
uint c = 0xffffffff;
|
||||
|
||||
int n;
|
||||
|
||||
if (crcTable == null)
|
||||
{
|
||||
BuildCrcTable();
|
||||
}
|
||||
|
||||
for (n = 0; n < bytes.Length; n++)
|
||||
{
|
||||
c = crcTable[(c ^ bytes[n]) & 0xff] ^ (c >> 8);
|
||||
}
|
||||
|
||||
return c ^ 0xffffffff;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class Extensions
|
||||
{
|
||||
internal static uint ToUInt(this byte[] bytes)
|
||||
{
|
||||
byte[] input;
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
input = ReverseByteArray(bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
input = bytes;
|
||||
}
|
||||
|
||||
return BitConverter.ToUInt32(input, 0);
|
||||
}
|
||||
|
||||
internal static byte[] ToByteArray(this uint integer)
|
||||
{
|
||||
byte[] output = BitConverter.GetBytes(integer);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
return ReverseByteArray(output);
|
||||
}
|
||||
else
|
||||
{
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] ReverseByteArray(byte[] byteArray)
|
||||
{
|
||||
return (byte[])byteArray.Reverse().ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MonoGame.Utilities;
|
||||
|
||||
namespace MonoGame.Utilities.Png
|
||||
{
|
||||
public class PngReader
|
||||
{
|
||||
private int width;
|
||||
private int height;
|
||||
private int bitsPerSample;
|
||||
private int bytesPerSample;
|
||||
private int bytesPerPixel;
|
||||
private int bytesPerScanline;
|
||||
private IList<PngChunk> chunks;
|
||||
private IList<PngChunk> dataChunks;
|
||||
private ColorType colorType;
|
||||
private Palette palette;
|
||||
private Texture2D texture;
|
||||
private Color[] data;
|
||||
|
||||
public PngReader()
|
||||
{
|
||||
chunks = new List<PngChunk>();
|
||||
dataChunks = new List<PngChunk>();
|
||||
}
|
||||
|
||||
public Texture2D Read(Stream inputStream, GraphicsDevice graphicsDevice)
|
||||
{
|
||||
if (IsPngImage(inputStream) == false)
|
||||
{
|
||||
throw new Exception("File does not have PNG signature.");
|
||||
}
|
||||
|
||||
inputStream.Position = 8;
|
||||
|
||||
while (inputStream.Position != inputStream.Length)
|
||||
{
|
||||
byte[] chunkDataLengthBytes = new byte[4];
|
||||
inputStream.Read(chunkDataLengthBytes, 0, 4);
|
||||
uint chunkDataLength = chunkDataLengthBytes.ToUInt();
|
||||
|
||||
inputStream.Position -= 4;
|
||||
|
||||
byte[] chunkBytes = new byte[12 + chunkDataLength];
|
||||
inputStream.Read(chunkBytes, 0, (int)(12 + chunkDataLength));
|
||||
|
||||
ProcessChunk(chunkBytes);
|
||||
}
|
||||
|
||||
UnpackDataChunks();
|
||||
|
||||
texture = new Texture2D(graphicsDevice, width, height, false, SurfaceFormat.Color);
|
||||
texture.SetData<Color>(data);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
public static bool IsPngImage(Stream stream)
|
||||
{
|
||||
stream.Position = 0;
|
||||
|
||||
byte[] signature = new byte[8];
|
||||
stream.Read(signature, 0, 8);
|
||||
|
||||
bool result = signature.SequenceEqual(HeaderChunk.PngSignature);
|
||||
|
||||
stream.Position = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ProcessChunk(byte[] chunkBytes)
|
||||
{
|
||||
string chunkType = PngChunk.GetChunkTypeString(chunkBytes.Skip(4).Take(4).ToArray());
|
||||
|
||||
switch (chunkType)
|
||||
{
|
||||
case "IHDR":
|
||||
|
||||
var headerChunk = new HeaderChunk();
|
||||
headerChunk.Decode(chunkBytes);
|
||||
width = (int)headerChunk.Width;
|
||||
height = (int)headerChunk.Height;
|
||||
bitsPerSample = (int)headerChunk.BitDepth;
|
||||
colorType = headerChunk.ColorType;
|
||||
chunks.Add(headerChunk);
|
||||
|
||||
break;
|
||||
|
||||
case "PLTE":
|
||||
|
||||
var paletteChunk = new PaletteChunk();
|
||||
paletteChunk.Decode(chunkBytes);
|
||||
palette = paletteChunk.Palette;
|
||||
chunks.Add(paletteChunk);
|
||||
|
||||
break;
|
||||
|
||||
case "tRNS":
|
||||
|
||||
var transparencyChunk = new TransparencyChunk();
|
||||
transparencyChunk.Decode(chunkBytes);
|
||||
palette.AddAlphaToColors(transparencyChunk.PaletteTransparencies);
|
||||
break;
|
||||
|
||||
case "IDAT":
|
||||
|
||||
var dataChunk = new DataChunk();
|
||||
dataChunk.Decode(chunkBytes);
|
||||
dataChunks.Add(dataChunk);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnpackDataChunks()
|
||||
{
|
||||
var dataByteList = new List<byte>();
|
||||
|
||||
foreach (var dataChunk in dataChunks)
|
||||
{
|
||||
if (dataChunk.Type == "IDAT")
|
||||
{
|
||||
dataByteList.AddRange(dataChunk.Data);
|
||||
}
|
||||
}
|
||||
|
||||
var compressedStream = new MemoryStream(dataByteList.ToArray());
|
||||
var decompressedStream = new MemoryStream();
|
||||
|
||||
try
|
||||
{
|
||||
using (var deflateStream = new ZlibStream(compressedStream, CompressionMode.Decompress))
|
||||
{
|
||||
deflateStream.CopyTo(decompressedStream);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new Exception("An error occurred during DEFLATE decompression.", exception);
|
||||
}
|
||||
|
||||
var decompressedBytes = decompressedStream.ToArray();
|
||||
var pixelData = DeserializePixelData(decompressedBytes);
|
||||
|
||||
DecodePixelData(pixelData);
|
||||
}
|
||||
|
||||
private byte[][] DeserializePixelData(byte[] pixelData)
|
||||
{
|
||||
bytesPerPixel = CalculateBytesPerPixel();
|
||||
bytesPerSample = bitsPerSample / 8;
|
||||
bytesPerScanline = (bytesPerPixel * width) + 1;
|
||||
int scanlineCount = pixelData.Length / bytesPerScanline;
|
||||
|
||||
if (pixelData.Length % bytesPerScanline != 0)
|
||||
{
|
||||
throw new Exception("Malformed pixel data - total length of pixel data not multiple of ((bytesPerPixel * width) + 1)");
|
||||
}
|
||||
|
||||
var result = new byte[scanlineCount][];
|
||||
|
||||
for (int y = 0; y < scanlineCount; y++)
|
||||
{
|
||||
result[y] = new byte[bytesPerScanline];
|
||||
|
||||
for (int x = 0; x < bytesPerScanline; x++)
|
||||
{
|
||||
result[y][x] = pixelData[y * bytesPerScanline + x];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void DecodePixelData(byte[][] pixelData)
|
||||
{
|
||||
data = new Color[width * height];
|
||||
|
||||
byte[] previousScanline = new byte[bytesPerScanline];
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
var scanline = pixelData[y];
|
||||
|
||||
FilterType filterType = (FilterType)scanline[0];
|
||||
byte[] defilteredScanline;
|
||||
|
||||
switch (filterType)
|
||||
{
|
||||
case FilterType.None:
|
||||
|
||||
defilteredScanline = NoneFilter.Decode(scanline);
|
||||
|
||||
break;
|
||||
|
||||
case FilterType.Sub:
|
||||
|
||||
defilteredScanline = SubFilter.Decode(scanline, bytesPerPixel);
|
||||
|
||||
break;
|
||||
|
||||
case FilterType.Up:
|
||||
|
||||
defilteredScanline = UpFilter.Decode(scanline, previousScanline);
|
||||
|
||||
break;
|
||||
|
||||
case FilterType.Average:
|
||||
|
||||
defilteredScanline = AverageFilter.Decode(scanline, previousScanline, bytesPerPixel);
|
||||
|
||||
break;
|
||||
|
||||
case FilterType.Paeth:
|
||||
|
||||
defilteredScanline = PaethFilter.Decode(scanline, previousScanline, bytesPerPixel);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception("Unknown filter type.");
|
||||
}
|
||||
|
||||
previousScanline = defilteredScanline;
|
||||
ProcessDefilteredScanline(defilteredScanline, y);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDefilteredScanline(byte[] defilteredScanline, int y)
|
||||
{
|
||||
switch (colorType)
|
||||
{
|
||||
case ColorType.Grayscale:
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
int offset = 1 + (x * bytesPerPixel);
|
||||
|
||||
byte intensity = defilteredScanline[offset];
|
||||
|
||||
data[(y * width) + x] = new Color(intensity, intensity, intensity);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ColorType.GrayscaleWithAlpha:
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
int offset = 1 + (x * bytesPerPixel);
|
||||
|
||||
byte intensity = defilteredScanline[offset];
|
||||
byte alpha = defilteredScanline[offset + bytesPerSample];
|
||||
|
||||
data[(y * width) + x] = new Color(intensity, intensity, intensity, alpha);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ColorType.Palette:
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
var pixelColor = palette[defilteredScanline[x + 1]];
|
||||
|
||||
data[(y * width) + x] = pixelColor;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ColorType.Rgb:
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
int offset = 1 + (x * bytesPerPixel);
|
||||
|
||||
int red = defilteredScanline[offset];
|
||||
int green = defilteredScanline[offset + bytesPerSample];
|
||||
int blue = defilteredScanline[offset + 2 * bytesPerSample];
|
||||
|
||||
data[(y * width) + x] = new Color(red, green, blue);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ColorType.RgbWithAlpha:
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
int offset = 1 + (x * bytesPerPixel);
|
||||
|
||||
int red = defilteredScanline[offset];
|
||||
int green = defilteredScanline[offset + bytesPerSample];
|
||||
int blue = defilteredScanline[offset + 2 * bytesPerSample];
|
||||
int alpha = defilteredScanline[offset + 3 * bytesPerSample];
|
||||
|
||||
data[(y * width) + x] = new Color(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private int CalculateBytesPerPixel()
|
||||
{
|
||||
switch (colorType)
|
||||
{
|
||||
case ColorType.Grayscale:
|
||||
return bitsPerSample / 8;
|
||||
|
||||
case ColorType.GrayscaleWithAlpha:
|
||||
return (2 * bitsPerSample) / 8;
|
||||
|
||||
case ColorType.Palette:
|
||||
return bitsPerSample / 8;
|
||||
|
||||
case ColorType.Rgb:
|
||||
return (3 * bitsPerSample) / 8;
|
||||
|
||||
case ColorType.RgbWithAlpha:
|
||||
return (4 * bitsPerSample) / 8;
|
||||
|
||||
default:
|
||||
throw new Exception("Unknown color type.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MonoGame.Utilities;
|
||||
using Microsoft.Xna.Framework.Graphics.PackedVector;
|
||||
|
||||
namespace MonoGame.Utilities.Png
|
||||
{
|
||||
public class PngWriter
|
||||
{
|
||||
private const int bitsPerSample = 8;
|
||||
private ColorType colorType;
|
||||
private Color[] colorData;
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
public PngWriter()
|
||||
{
|
||||
colorType = ColorType.RgbWithAlpha;
|
||||
}
|
||||
|
||||
public void Write(Texture2D texture2D, Stream outputStream)
|
||||
{
|
||||
width = texture2D.Width;
|
||||
height = texture2D.Height;
|
||||
|
||||
GetColorData(texture2D);
|
||||
|
||||
// write PNG signature
|
||||
outputStream.Write(HeaderChunk.PngSignature, 0, HeaderChunk.PngSignature.Length);
|
||||
|
||||
// write header chunk
|
||||
var headerChunk = new HeaderChunk();
|
||||
headerChunk.Width = (uint)texture2D.Width;
|
||||
headerChunk.Height = (uint)texture2D.Height;
|
||||
headerChunk.BitDepth = 8;
|
||||
headerChunk.ColorType = colorType;
|
||||
headerChunk.CompressionMethod = 0;
|
||||
headerChunk.FilterMethod = 0;
|
||||
headerChunk.InterlaceMethod = 0;
|
||||
|
||||
var headerChunkBytes = headerChunk.Encode();
|
||||
outputStream.Write(headerChunkBytes, 0, headerChunkBytes.Length);
|
||||
|
||||
// write data chunks
|
||||
var encodedPixelData = EncodePixelData(texture2D);
|
||||
var compressedPixelData = new MemoryStream();
|
||||
|
||||
ZlibStream deflateStream = null;
|
||||
try
|
||||
{
|
||||
deflateStream = new ZlibStream(compressedPixelData, CompressionMode.Compress);
|
||||
deflateStream.Write(encodedPixelData, 0, encodedPixelData.Length);
|
||||
deflateStream.Finish();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new Exception("An error occurred during DEFLATE compression.", exception);
|
||||
}
|
||||
|
||||
var dataChunk = new DataChunk();
|
||||
dataChunk.Data = compressedPixelData.ToArray();
|
||||
var dataChunkBytes = dataChunk.Encode();
|
||||
outputStream.Write(dataChunkBytes, 0, dataChunkBytes.Length);
|
||||
|
||||
deflateStream.Dispose();
|
||||
compressedPixelData.Dispose();
|
||||
|
||||
// write end chunk
|
||||
var endChunk = new EndChunk();
|
||||
var endChunkBytes = endChunk.Encode();
|
||||
outputStream.Write(endChunkBytes, 0, endChunkBytes.Length);
|
||||
}
|
||||
|
||||
private byte[] EncodePixelData(Texture2D texture2D)
|
||||
{
|
||||
List<byte[]> filteredScanlines = new List<byte[]>();
|
||||
|
||||
int bytesPerPixel = CalculateBytesPerPixel();
|
||||
byte[] previousScanline = new byte[width * bytesPerPixel];
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
var rawScanline = GetRawScanline(y);
|
||||
|
||||
var filteredScanline = GetOptimalFilteredScanline(rawScanline, previousScanline, bytesPerPixel);
|
||||
|
||||
filteredScanlines.Add(filteredScanline);
|
||||
|
||||
previousScanline = rawScanline;
|
||||
}
|
||||
|
||||
List<byte> result = new List<byte>();
|
||||
|
||||
foreach (var encodedScanline in filteredScanlines)
|
||||
{
|
||||
result.AddRange(encodedScanline);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed
|
||||
/// to be most compressible, using lowest total variation as proxy for compressibility.
|
||||
/// </summary>
|
||||
/// <param name="rawScanline"></param>
|
||||
/// <param name="previousScanline"></param>
|
||||
/// <param name="bytesPerPixel"></param>
|
||||
/// <returns></returns>
|
||||
private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int bytesPerPixel)
|
||||
{
|
||||
var candidates = new List<Tuple<byte[], int>>();
|
||||
|
||||
var sub = SubFilter.Encode(rawScanline, bytesPerPixel);
|
||||
candidates.Add(new Tuple<byte[], int>(sub, CalculateTotalVariation(sub)));
|
||||
|
||||
var up = UpFilter.Encode(rawScanline, previousScanline);
|
||||
candidates.Add(new Tuple<byte[], int>(up, CalculateTotalVariation(up)));
|
||||
|
||||
var average = AverageFilter.Encode(rawScanline, previousScanline, bytesPerPixel);
|
||||
candidates.Add(new Tuple<byte[], int>(average, CalculateTotalVariation(average)));
|
||||
|
||||
var paeth = PaethFilter.Encode(rawScanline, previousScanline, bytesPerPixel);
|
||||
candidates.Add(new Tuple<byte[], int>(paeth, CalculateTotalVariation(paeth)));
|
||||
|
||||
int lowestTotalVariation = Int32.MaxValue;
|
||||
int lowestTotalVariationIndex = 0;
|
||||
|
||||
for (int i = 0; i < candidates.Count; i++)
|
||||
{
|
||||
if (candidates[i].Item2 < lowestTotalVariation)
|
||||
{
|
||||
lowestTotalVariationIndex = i;
|
||||
lowestTotalVariation = candidates[i].Item2;
|
||||
}
|
||||
}
|
||||
|
||||
return candidates[lowestTotalVariationIndex].Item1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the total variation of given byte array. Total variation is the sum of the absolute values of
|
||||
/// neighbour differences.
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
private int CalculateTotalVariation(byte[] input)
|
||||
{
|
||||
int totalVariation = 0;
|
||||
|
||||
for (int i = 1; i < input.Length; i++)
|
||||
{
|
||||
totalVariation += Math.Abs(input[i] - input[i - 1]);
|
||||
}
|
||||
|
||||
return totalVariation;
|
||||
}
|
||||
|
||||
private byte[] GetRawScanline(int y)
|
||||
{
|
||||
var rawScanline = new byte[4 * width];
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
var color = colorData[(y * width) + x];
|
||||
|
||||
rawScanline[4 * x] = color.R;
|
||||
rawScanline[(4 * x) + 1] = color.G;
|
||||
rawScanline[(4 * x) + 2] = color.B;
|
||||
rawScanline[(4 * x) + 3] = color.A;
|
||||
}
|
||||
|
||||
return rawScanline;
|
||||
}
|
||||
|
||||
private int CalculateBytesPerPixel()
|
||||
{
|
||||
switch (colorType)
|
||||
{
|
||||
case ColorType.Grayscale:
|
||||
return bitsPerSample / 8;
|
||||
|
||||
case ColorType.GrayscaleWithAlpha:
|
||||
return (2 * bitsPerSample) / 8;
|
||||
|
||||
case ColorType.Palette:
|
||||
return bitsPerSample / 8;
|
||||
|
||||
case ColorType.Rgb:
|
||||
return (3 * bitsPerSample) / 8;
|
||||
|
||||
case ColorType.RgbWithAlpha:
|
||||
return (4 * bitsPerSample) / 8;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void GetColorData(Texture2D texture2D)
|
||||
{
|
||||
int colorDataLength = texture2D.Width * texture2D.Height;
|
||||
colorData = new Color[colorDataLength];
|
||||
|
||||
switch (texture2D.Format)
|
||||
{
|
||||
case SurfaceFormat.Single:
|
||||
var floatData = new float[colorDataLength];
|
||||
texture2D.GetData<float>(floatData);
|
||||
|
||||
for (int i = 0; i < colorDataLength; i++)
|
||||
{
|
||||
float brightness = floatData[i];
|
||||
// Export as a greyscale image.
|
||||
colorData[i] = new Color(brightness, brightness, brightness);
|
||||
}
|
||||
break;
|
||||
|
||||
case SurfaceFormat.Color:
|
||||
texture2D.GetData<Color>(colorData);
|
||||
break;
|
||||
|
||||
case SurfaceFormat.Alpha8:
|
||||
var alpha8Data = new Alpha8[colorDataLength];
|
||||
texture2D.GetData<Alpha8>(alpha8Data);
|
||||
|
||||
for (int i = 0; i < colorDataLength; i++)
|
||||
{
|
||||
colorData[i] = new Color(((IPackedVector)alpha8Data[i]).ToVector4());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SurfaceFormat.Bgr565:
|
||||
var bgr565Data = new Bgr565[colorDataLength];
|
||||
texture2D.GetData<Bgr565>(bgr565Data);
|
||||
|
||||
for (int i = 0; i < colorDataLength; i++)
|
||||
{
|
||||
colorData[i] = new Color(((IPackedVector)bgr565Data[i]).ToVector4());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SurfaceFormat.Bgra4444:
|
||||
var bgra4444Data = new Bgra4444[colorDataLength];
|
||||
texture2D.GetData<Bgra4444>(bgra4444Data);
|
||||
|
||||
for (int i = 0; i < colorDataLength; i++)
|
||||
{
|
||||
colorData[i] = new Color(((IPackedVector)bgra4444Data[i]).ToVector4());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SurfaceFormat.Bgra5551:
|
||||
var bgra5551Data = new Bgra5551[colorDataLength];
|
||||
texture2D.GetData<Bgra5551>(bgra5551Data);
|
||||
|
||||
for (int i = 0; i < colorDataLength; i++)
|
||||
{
|
||||
colorData[i] = new Color(((IPackedVector)bgra5551Data[i]).ToVector4());
|
||||
}
|
||||
break;
|
||||
|
||||
case SurfaceFormat.HalfSingle:
|
||||
var halfSingleData = new HalfSingle[colorDataLength];
|
||||
texture2D.GetData<HalfSingle>(halfSingleData);
|
||||
|
||||
for (int i = 0; i < colorDataLength; i++)
|
||||
{
|
||||
colorData[i] = new Color(((IPackedVector)halfSingleData[i]).ToVector4());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SurfaceFormat.HalfVector2:
|
||||
var halfVector2Data = new HalfVector2[colorDataLength];
|
||||
texture2D.GetData<HalfVector2>(halfVector2Data);
|
||||
|
||||
for (int i = 0; i < colorDataLength; i++)
|
||||
{
|
||||
colorData[i] = new Color(((IPackedVector)halfVector2Data[i]).ToVector4());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SurfaceFormat.HalfVector4:
|
||||
var halfVector4Data = new HalfVector4[colorDataLength];
|
||||
texture2D.GetData<HalfVector4>(halfVector4Data);
|
||||
|
||||
for (int i = 0; i < colorDataLength; i++)
|
||||
{
|
||||
colorData[i] = new Color(((IPackedVector)halfVector4Data[i]).ToVector4());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SurfaceFormat.NormalizedByte2:
|
||||
var normalizedByte2Data = new NormalizedByte2[colorDataLength];
|
||||
texture2D.GetData<NormalizedByte2>(normalizedByte2Data);
|
||||
|
||||
for (int i = 0; i < colorDataLength; i++)
|
||||
{
|
||||
colorData[i] = new Color(((IPackedVector)normalizedByte2Data[i]).ToVector4());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SurfaceFormat.NormalizedByte4:
|
||||
var normalizedByte4Data = new NormalizedByte4[colorDataLength];
|
||||
texture2D.GetData<NormalizedByte4>(normalizedByte4Data);
|
||||
|
||||
for (int i = 0; i < colorDataLength; i++)
|
||||
{
|
||||
colorData[i] = new Color(((IPackedVector)normalizedByte4Data[i]).ToVector4());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SurfaceFormat.Rg32:
|
||||
var rg32Data = new Rg32[colorDataLength];
|
||||
texture2D.GetData<Rg32>(rg32Data);
|
||||
|
||||
for (int i = 0; i < colorDataLength; i++)
|
||||
{
|
||||
colorData[i] = new Color(((IPackedVector)rg32Data[i]).ToVector4());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SurfaceFormat.Rgba64:
|
||||
var rgba64Data = new Rgba64[colorDataLength];
|
||||
texture2D.GetData<Rgba64>(rgba64Data);
|
||||
|
||||
for (int i = 0; i < colorDataLength; i++)
|
||||
{
|
||||
colorData[i] = new Color(((IPackedVector)rgba64Data[i]).ToVector4());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SurfaceFormat.Rgba1010102:
|
||||
var rgba1010102Data = new Rgba1010102[colorDataLength];
|
||||
texture2D.GetData<Rgba1010102>(rgba1010102Data);
|
||||
|
||||
for (int i = 0; i < colorDataLength; i++)
|
||||
{
|
||||
colorData[i] = new Color(((IPackedVector)rgba1010102Data[i]).ToVector4());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception("Texture surface format not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user