(ded4a3e0a) v0.9.0.7

This commit is contained in:
Joonas Rikkonen
2019-06-25 16:00:44 +03:00
parent e5ae622c77
commit 4a51db77b5
1777 changed files with 421528 additions and 917 deletions
@@ -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");
}
}
}
}