228 lines
9.0 KiB
C#
228 lines
9.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
|
|
namespace TEST_DecodePng
|
|
{
|
|
internal class PngDecoder
|
|
{
|
|
const int PNG_SIGNATURE_LENGTH = 8;
|
|
readonly byte[] EXPECTED_PNG_SIGNATURE = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 };
|
|
|
|
const int CHUNCK_SIZE_FIELD_LENGTH = 4;
|
|
const int CHUNCK_TYPE_FIELD_LENGTH = 4;
|
|
const int CHUNCK_CRC_FIELD_LENGTH = 4;
|
|
|
|
const int IMAGE_WIDTH_FIELD_SIZE = 4;
|
|
const int IMAGE_HEIGHT_FIELD_SIZE = 4;
|
|
const int GAMA_FILED_LENGTH = 4;
|
|
|
|
//Critical chunks
|
|
const string FIRST_CHUNK_TYPE = "IHDR";
|
|
const string LAST_CHUNCK_TYPE = "IEND";
|
|
const string DATA_CHUNCK_TYPE = "IDAT";
|
|
|
|
const string GAMA_CHUNCK_TYPE = "GAMA";
|
|
|
|
public Stopwatch chrono;
|
|
|
|
public PngDecoder()
|
|
{
|
|
chrono = new Stopwatch();
|
|
//I start and stop the chronometer so all the methods can just use the Restart method.
|
|
chrono.Start(); chrono.Stop();
|
|
}
|
|
public Bitmap Decode(string filename)
|
|
{
|
|
//Timing Stuff
|
|
chrono.Reset();
|
|
chrono.Start();
|
|
//Timing Stuff
|
|
|
|
byte[] content = File.ReadAllBytes(filename);
|
|
int cursor = 0;
|
|
|
|
byte[] pngSignature = new byte[PNG_SIGNATURE_LENGTH];
|
|
for (int i = 0; i < PNG_SIGNATURE_LENGTH; i++)
|
|
{
|
|
pngSignature[i] = content[cursor];
|
|
cursor++;
|
|
}
|
|
|
|
//Checks if the file has a valid png signature
|
|
if (!compareByteArrays(EXPECTED_PNG_SIGNATURE, pngSignature))
|
|
throw (new InvalidPngHeadersFileException("File signature not recognized"));
|
|
|
|
|
|
List<Byte[]> chuncks = new List<Byte[]>();
|
|
List<string> chuncksType = new List<string>();
|
|
List<int> chuncksSize = new List<int>();
|
|
|
|
int chunckSize = 0;
|
|
string chunckType = "";
|
|
|
|
while (chunckType.ToLower() != LAST_CHUNCK_TYPE.ToLower())
|
|
{
|
|
//Reading Chunck Size (Carefull, does not contain the size, type and crc bytes)
|
|
byte[] ckln = new byte[] { content[cursor], content[cursor + 1], content[cursor + 2], content[cursor + 3] };
|
|
Array.Reverse(ckln);
|
|
chunckSize = BitConverter.ToInt32(ckln, 0);
|
|
|
|
cursor += CHUNCK_SIZE_FIELD_LENGTH;
|
|
|
|
//Reading the chunck type
|
|
byte[] cktp = new byte[] { content[cursor], content[cursor + 1], content[cursor + 2], content[cursor + 3] };
|
|
chunckType = System.Text.Encoding.UTF8.GetString(cktp);
|
|
|
|
cursor += CHUNCK_TYPE_FIELD_LENGTH;
|
|
|
|
//Reading the chunck data
|
|
byte[] chunck = new byte[chunckSize];
|
|
for (int i = 0; i < chunckSize; i++)
|
|
{
|
|
chunck[i] = content[cursor];
|
|
cursor++;
|
|
}
|
|
|
|
//Reading the CRC
|
|
byte[] ckcrc = new byte[] { content[cursor], content[cursor + 1], content[cursor + 2], content[cursor + 3] };
|
|
cursor += CHUNCK_CRC_FIELD_LENGTH;
|
|
|
|
//PROCESS THE CRC
|
|
|
|
chuncks.Add(chunck);
|
|
chuncksSize.Add(chunckSize);
|
|
chuncksType.Add(chunckType);
|
|
}
|
|
|
|
//Now we have a list of all the chuncks in the PNG file
|
|
//We can now process it
|
|
|
|
//The First chunck MUST be a IHDR chunck so we test that
|
|
if (chuncksType[0].ToLower() != FIRST_CHUNK_TYPE.ToLower())
|
|
throw new InvalidPngHeadersFileException("Critial chunck not found : IHDR");
|
|
//The Last chunck MUST be an IEND chunck so we test that
|
|
if (chuncksType[chuncksType.Count - 1].ToLower() != LAST_CHUNCK_TYPE.ToLower())
|
|
throw new InvalidPngHeadersFileException("Critical chunck not found : IEND");
|
|
//There must be at least one IDAT chunck so we check that
|
|
int dataChunckcount = 0;
|
|
foreach(string cktype in chuncksType)
|
|
{
|
|
if (cktype.ToLower() == DATA_CHUNCK_TYPE.ToLower())
|
|
dataChunckcount++;
|
|
}
|
|
if (dataChunckcount == 0)
|
|
throw new InvalidPngHeadersFileException("No data chuncks where found");
|
|
|
|
|
|
PngMetaData header = null;
|
|
float gama = 0;
|
|
|
|
//Now we can process them
|
|
for (int chunckCounter = 0; chunckCounter < chuncks.Count; chunckCounter++)
|
|
{
|
|
string cktype = chuncksType[chunckCounter];
|
|
|
|
switch (cktype.ToUpper())
|
|
{
|
|
//IHDR
|
|
case FIRST_CHUNK_TYPE:
|
|
int chunckCursor = 0;
|
|
|
|
byte[] btaImageWidth = new byte[IMAGE_WIDTH_FIELD_SIZE];
|
|
byte[] btaImageHeight = new byte[IMAGE_HEIGHT_FIELD_SIZE];
|
|
|
|
for (int i = 0; i < IMAGE_WIDTH_FIELD_SIZE; i++)
|
|
{
|
|
btaImageWidth[i] = chuncks[chunckCounter][chunckCursor];
|
|
chunckCursor++;
|
|
}
|
|
for (int i = 0; i < IMAGE_HEIGHT_FIELD_SIZE; i++)
|
|
{
|
|
btaImageHeight[i] = chuncks[chunckCounter][chunckCursor];
|
|
chunckCursor++;
|
|
}
|
|
|
|
//Those are in little endien notation so we need to reverse it
|
|
Array.Reverse(btaImageWidth);
|
|
Array.Reverse(btaImageHeight);
|
|
|
|
int imageWidth = BitConverter.ToInt32(btaImageWidth, 0);
|
|
int imageHeight = BitConverter.ToInt32(btaImageHeight, 0);
|
|
|
|
int bitDepth = Convert.ToInt32(chuncks[chunckCounter][chunckCursor]);
|
|
chunckCursor++;
|
|
int colorType = Convert.ToInt32(chuncks[chunckCounter][chunckCursor]);
|
|
chunckCursor++;
|
|
int compressionMethod = Convert.ToInt32(chuncks[chunckCounter][chunckCursor]);
|
|
chunckCursor++;
|
|
int filterMethod = Convert.ToInt32(chuncks[chunckCounter][chunckCursor]);
|
|
chunckCursor++;
|
|
int interlaceMethod = Convert.ToInt32(chuncks[chunckCounter][chunckCursor]);
|
|
chunckCursor++;
|
|
|
|
header = new PngMetaData(imageHeight, imageWidth, (sbyte)bitDepth, (sbyte)colorType, (sbyte)compressionMethod, (sbyte)filterMethod, (sbyte)interlaceMethod);
|
|
|
|
if (!header.IsValid())
|
|
throw new InvalidPngHeadersFileException("Header invalide");
|
|
break;
|
|
|
|
case GAMA_CHUNCK_TYPE:
|
|
byte[] btGamma = new byte[GAMA_FILED_LENGTH];
|
|
//btGamma = new byte[] { chuncks[chunckCounter][0], chuncks[chunckCounter][1], chuncks[chunckCounter][2], chuncks[chunckCounter][3] };
|
|
for (int i = 0; i < GAMA_FILED_LENGTH; i++)
|
|
{
|
|
btGamma[i] = chuncks[chunckCounter][i];
|
|
}
|
|
//btGamma.Reverse();
|
|
Array.Reverse(btGamma);
|
|
gama = ((float)BitConverter.ToInt32(btGamma,0) / 1000F);
|
|
break;
|
|
default:
|
|
//Unrecognized chunck, as said in the doc should not stop the decoding
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (header != null)
|
|
{
|
|
MessageBox.Show("Image width: " + header.Width +
|
|
Environment.NewLine + "Image height: " + header.Height +
|
|
Environment.NewLine + "byte depth : " + header.BitDepth +
|
|
Environment.NewLine + "color type : " + header.ColorType +
|
|
Environment.NewLine + "compression method : " + header.CompressionMethod +
|
|
Environment.NewLine + "filter method : " + header.FilterMethod +
|
|
Environment.NewLine + "interface method : " + header.InterlaceMethod +
|
|
Environment.NewLine + "gama : " + gama
|
|
);
|
|
}
|
|
|
|
//Timing Stuff
|
|
chrono.Stop();
|
|
//Timing Stuff
|
|
|
|
//TO REMOVE
|
|
return new Bitmap(100, 100);
|
|
}
|
|
public bool compareByteArrays(byte[] firstArray, byte[] secondArray)
|
|
{
|
|
if (firstArray.Length != secondArray.Length)
|
|
return false;
|
|
bool result = true;
|
|
for (int i = 0; i < firstArray.Length; i++)
|
|
{
|
|
if (firstArray[0] != secondArray[0])
|
|
result = false;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
}
|
|
}
|