Big rework, but now we store every info of every chunck with the same method, Including the IHDR critical chunck
This commit is contained in:
@@ -80,6 +80,7 @@ namespace TEST_DecodePng
|
|||||||
private void btnDecode_Click(object sender, EventArgs e)
|
private void btnDecode_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
PngDecoder decoder = new PngDecoder();
|
PngDecoder decoder = new PngDecoder();
|
||||||
|
/*
|
||||||
try{
|
try{
|
||||||
decoder.Decode(selectedFile);
|
decoder.Decode(selectedFile);
|
||||||
}
|
}
|
||||||
@@ -87,7 +88,10 @@ namespace TEST_DecodePng
|
|||||||
{
|
{
|
||||||
MessageBox.Show(ex.Message);
|
MessageBox.Show(ex.Message);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
decoder.Decode(selectedFile);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,12 @@ namespace TEST_DecodePng
|
|||||||
|
|
||||||
const int CHUNCK_SIZE_FIELD_LENGTH = 4;
|
const int CHUNCK_SIZE_FIELD_LENGTH = 4;
|
||||||
const int CHUNCK_TYPE_FIELD_LENGTH = 4;
|
const int CHUNCK_TYPE_FIELD_LENGTH = 4;
|
||||||
|
const int CHUNCK_CRC_FIELD_LENGTH = 4;
|
||||||
|
|
||||||
const string FIRST_CHUNK_TYPE = "IHDR";
|
const string FIRST_CHUNK_TYPE = "IHDR";
|
||||||
|
const string LAST_CHUNCK_TYPE = "IEND";
|
||||||
|
const string DATA_CHUNCK_TYPE = "IDAT";
|
||||||
|
|
||||||
const int IMAGE_WIDTH_FIELD_SIZE = 4;
|
const int IMAGE_WIDTH_FIELD_SIZE = 4;
|
||||||
const int IMAGE_HEIGHT_FIELD_SIZE = 4;
|
const int IMAGE_HEIGHT_FIELD_SIZE = 4;
|
||||||
public Stopwatch chrono;
|
public Stopwatch chrono;
|
||||||
@@ -50,116 +54,132 @@ namespace TEST_DecodePng
|
|||||||
if (!compareByteArrays(EXPECTED_PNG_SIGNATURE, pngSignature))
|
if (!compareByteArrays(EXPECTED_PNG_SIGNATURE, pngSignature))
|
||||||
throw (new InvalidPngHeadersFileException("File signature not recognized"));
|
throw (new InvalidPngHeadersFileException("File signature not recognized"));
|
||||||
|
|
||||||
//We are now sure that we are working with a png file and we will then read chuncks
|
|
||||||
|
|
||||||
byte[] chunckLength = new byte[CHUNCK_SIZE_FIELD_LENGTH];
|
List<Byte[]> chuncks = new List<Byte[]>();
|
||||||
for (int i = 0; i < CHUNCK_SIZE_FIELD_LENGTH; i++)
|
List<string> chuncksType = new List<string>();
|
||||||
|
List<int> chuncksSize = new List<int>();
|
||||||
|
|
||||||
|
int chunckSize = 0;
|
||||||
|
string chunckType = "";
|
||||||
|
|
||||||
|
while (chunckType != "IEND")
|
||||||
{
|
{
|
||||||
chunckLength[i] = content[cursor];
|
//Reading Chunck Size (Carefull, does not contain the size, type and crc bytes)
|
||||||
cursor++;
|
byte[] ckln = new byte[] { content[cursor], content[cursor + 1], content[cursor + 2], content[cursor + 3] };
|
||||||
}
|
Array.Reverse(ckln);
|
||||||
byte[] chunckType = new byte[CHUNCK_TYPE_FIELD_LENGTH];
|
chunckSize = BitConverter.ToInt32(ckln, 0);
|
||||||
for (int i = 0; i < CHUNCK_TYPE_FIELD_LENGTH; i++)
|
|
||||||
{
|
cursor += CHUNCK_SIZE_FIELD_LENGTH;
|
||||||
chunckType[i] = content[cursor];
|
|
||||||
cursor++;
|
//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);
|
||||||
}
|
}
|
||||||
|
|
||||||
//The first chunck MUST be a IHDR chunck so we need to check if that is the case
|
//Now we have a list of all the chuncks in the PNG file
|
||||||
string ckType = System.Text.Encoding.UTF8.GetString(chunckType);
|
//We can now process it
|
||||||
|
|
||||||
//If the first chunck is not the IHDR that means that we should not continue to decompress it
|
//The First chunck MUST be a IHDR chunck so we test that
|
||||||
if (ckType != FIRST_CHUNK_TYPE)
|
if (chuncksType[0].ToLower() != FIRST_CHUNK_TYPE.ToLower())
|
||||||
throw (new InvalidPngHeadersFileException("First chunck not valid, possible corruption of the file"));
|
throw new InvalidPngHeadersFileException("Critial chunck not found : IHDR");
|
||||||
|
//The Last chunck MUST be an IEND chunck so we test that
|
||||||
//As it is the first chunck, his structure is a little bit different
|
if (chuncksType[chuncksType.Count - 1].ToLower() != LAST_CHUNCK_TYPE.ToLower())
|
||||||
|
throw new InvalidPngHeadersFileException("Critical chunck not found : IEND");
|
||||||
int imageWidth, imageHeight, bitDepth, colorType, compressionMethod, filterMethod, interlaceMethod;
|
//There must be at least one IDAT chunck so we check that
|
||||||
|
int dataChunckcount = 0;
|
||||||
byte[] btaImageWidth = new byte[IMAGE_WIDTH_FIELD_SIZE];
|
foreach(string cktype in chuncksType)
|
||||||
byte[] btaImageHeight = new byte[IMAGE_HEIGHT_FIELD_SIZE];
|
|
||||||
for (int i = 0; i < IMAGE_WIDTH_FIELD_SIZE; i++)
|
|
||||||
{
|
{
|
||||||
btaImageWidth[i] = content[cursor];
|
if (cktype.ToLower() == DATA_CHUNCK_TYPE.ToLower())
|
||||||
cursor++;
|
dataChunckcount++;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < IMAGE_HEIGHT_FIELD_SIZE; i++)
|
if (dataChunckcount == 0)
|
||||||
|
throw new InvalidPngHeadersFileException("No data chuncks where found");
|
||||||
|
|
||||||
|
|
||||||
|
PngMetaData header = null;
|
||||||
|
|
||||||
|
//Now we can process them
|
||||||
|
for (int chunckCounter = 0; chunckCounter < chuncks.Count; chunckCounter++)
|
||||||
{
|
{
|
||||||
btaImageHeight[i] = content[cursor];
|
string cktype = chuncksType[chunckCounter];
|
||||||
cursor++;
|
|
||||||
|
switch (cktype.ToUpper())
|
||||||
|
{
|
||||||
|
case "IHDR":
|
||||||
|
//Unrecognized chunck, as said in the doc we should not stop the decoding if this happends
|
||||||
|
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);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Those are in little endien notation so we need to reverse it
|
if (header != null)
|
||||||
Array.Reverse(btaImageWidth);
|
|
||||||
Array.Reverse(btaImageHeight);
|
|
||||||
imageWidth = BitConverter.ToInt32(btaImageWidth, 0);
|
|
||||||
imageHeight = BitConverter.ToInt32(btaImageHeight, 0);
|
|
||||||
|
|
||||||
//As specified in the documentation, images with 0 widht or height are invalid
|
|
||||||
if (imageWidth == 0 || imageHeight == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
bitDepth = Convert.ToInt32(content[cursor]);
|
|
||||||
cursor++;
|
|
||||||
colorType = Convert.ToInt32(content[cursor]);
|
|
||||||
cursor++;
|
|
||||||
compressionMethod = Convert.ToInt32(content[cursor]);
|
|
||||||
cursor++;
|
|
||||||
filterMethod = Convert.ToInt32(content[cursor]);
|
|
||||||
cursor++;
|
|
||||||
interlaceMethod = Convert.ToInt32(content[cursor]);
|
|
||||||
|
|
||||||
//Those are all the rules seen on the documentation at : https://www.w3.org/TR/PNG-Chunks.html
|
|
||||||
|
|
||||||
if (compressionMethod != 0 || filterMethod != 0 || interlaceMethod != 0 && interlaceMethod != 1)
|
|
||||||
throw (new InvalidPngHeadersFileException("Incoherent IHDR chunck informations"));
|
|
||||||
|
|
||||||
InvalidPngHeadersFileException incoherentColorsAndBitDepths = new InvalidPngHeadersFileException("Incoherent colorType and bitDepths, could be due to corruption of the file");
|
|
||||||
switch (colorType)
|
|
||||||
{
|
{
|
||||||
case 0:
|
MessageBox.Show("Image width: " + header.Width +
|
||||||
//Each pixel is a grayscale sample.
|
Environment.NewLine + "Image height: " + header.Height +
|
||||||
if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && bitDepth != 8 && bitDepth != 16)
|
Environment.NewLine + "byte depth : " + header.BitDepth +
|
||||||
throw (incoherentColorsAndBitDepths);
|
Environment.NewLine + "color type : " + header.ColorType +
|
||||||
break;
|
Environment.NewLine + "compression method : " + header.CompressionMethod +
|
||||||
case 2:
|
Environment.NewLine + "filter method : " + header.FilterMethod +
|
||||||
//Each pixel is an R,G,B triple.
|
Environment.NewLine + "interface method : " + header.InterlaceMethod
|
||||||
if (bitDepth != 8 && bitDepth != 16)
|
);
|
||||||
throw (incoherentColorsAndBitDepths);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
//Each pixel is a palette index, a PLTE chunk must appear.
|
|
||||||
if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && bitDepth != 8)
|
|
||||||
throw (incoherentColorsAndBitDepths);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
//Each pixel is a grayscale sample, followed by an alpha sample.
|
|
||||||
if (bitDepth != 8 && bitDepth != 16)
|
|
||||||
throw (incoherentColorsAndBitDepths);
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
//Each pixel is an R,G,B triple, followed by an alpha sample.
|
|
||||||
if (bitDepth != 8 && bitDepth != 16)
|
|
||||||
throw (incoherentColorsAndBitDepths);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
//Only 0,2,3,4 and 6 are valid color type values
|
|
||||||
throw (incoherentColorsAndBitDepths);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Insert code
|
|
||||||
|
|
||||||
MessageBox.Show("Image width: " + imageWidth +
|
|
||||||
Environment.NewLine + "Image height: " + imageHeight +
|
|
||||||
Environment.NewLine + "byte depth : " + bitDepth +
|
|
||||||
Environment.NewLine + "color type : " + colorType +
|
|
||||||
Environment.NewLine + "compression method : " + compressionMethod +
|
|
||||||
Environment.NewLine + "filter method : " + filterMethod +
|
|
||||||
Environment.NewLine + "interface method : " + interlaceMethod
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
//Timing Stuff
|
//Timing Stuff
|
||||||
chrono.Stop();
|
chrono.Stop();
|
||||||
//Timing Stuff
|
//Timing Stuff
|
||||||
|
|||||||
99
TEST_DecodePng/PngMetaData.cs
Normal file
99
TEST_DecodePng/PngMetaData.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TEST_DecodePng
|
||||||
|
{
|
||||||
|
public class PngMetaData
|
||||||
|
{
|
||||||
|
private static readonly sbyte[] ValidByteDepth = new sbyte[] { 1, 2, 4, 8, 16 };
|
||||||
|
private static readonly sbyte[] ValidColorTypes = new sbyte[] { 0, 2, 3, 4, 6 };
|
||||||
|
|
||||||
|
private int height;
|
||||||
|
private int width;
|
||||||
|
private sbyte bitDepth;
|
||||||
|
private sbyte colorType;
|
||||||
|
private sbyte compressionMethod;
|
||||||
|
private sbyte filterMethod;
|
||||||
|
private sbyte interlaceMethod;
|
||||||
|
|
||||||
|
public int Height { get => height; set => height = value; }
|
||||||
|
public int Width { get => width; set => width = value; }
|
||||||
|
public sbyte BitDepth { get => bitDepth; set => bitDepth = value; }
|
||||||
|
public sbyte ColorType { get => colorType; set => colorType = value; }
|
||||||
|
public sbyte CompressionMethod { get => compressionMethod; set => compressionMethod = value; }
|
||||||
|
public sbyte FilterMethod { get => filterMethod; set => filterMethod = value; }
|
||||||
|
public sbyte InterlaceMethod { get => interlaceMethod; set => interlaceMethod = value; }
|
||||||
|
|
||||||
|
public PngMetaData(int height, int width, sbyte bitDepth, sbyte colorType, sbyte compressionMethod, sbyte filterMethod, sbyte interlaceMethod)
|
||||||
|
{
|
||||||
|
Height = height;
|
||||||
|
Width = width;
|
||||||
|
BitDepth = bitDepth;
|
||||||
|
ColorType = colorType;
|
||||||
|
CompressionMethod = compressionMethod;
|
||||||
|
FilterMethod = filterMethod;
|
||||||
|
InterlaceMethod = interlaceMethod;
|
||||||
|
}
|
||||||
|
public bool IsValid()
|
||||||
|
{
|
||||||
|
// All those tests have been set up by following the PNG guidelines of the W3C for PNG file format.
|
||||||
|
// More info at : https://www.w3.org/TR/PNG-Chunks.html
|
||||||
|
|
||||||
|
//Width and Height cant be 0 (or negative obviously) so we test it
|
||||||
|
if (Width <= 0 || Height <= 0)
|
||||||
|
return false;
|
||||||
|
//There are only a few authorized bitDepths so we test if it complies
|
||||||
|
if (!Array.Exists(ValidByteDepth, element => element == BitDepth))
|
||||||
|
return false;
|
||||||
|
//There are only a few authorited colortypes so we test if it complies
|
||||||
|
if (!Array.Exists(ValidColorTypes, element => element == ColorType))
|
||||||
|
return false;
|
||||||
|
//Compression method indicates the algorythm used, for now only 0 is implemented for the deflate algorythm so we test it
|
||||||
|
if (CompressionMethod != 0)
|
||||||
|
return false;
|
||||||
|
//Filter Method just like Compression method only is implemented with the 0 value so we test it
|
||||||
|
if (FilterMethod != 0)
|
||||||
|
return false;
|
||||||
|
//Interlace method can be 0 for no Interlacing or 1 for Adam7 interlace
|
||||||
|
if (InterlaceMethod != 0 && InterlaceMethod != 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//Now the tricky part, depending on the color type, only certain bit depth are allowed
|
||||||
|
switch (colorType)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
//Each pixel is a grayscale sample.
|
||||||
|
if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && bitDepth != 8 && bitDepth != 16)
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
//Each pixel is an R,G,B triple.
|
||||||
|
if (bitDepth != 8 && bitDepth != 16)
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
//Each pixel is a palette index, a PLTE chunk must appear.
|
||||||
|
if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && bitDepth != 8)
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
//Each pixel is a grayscale sample, followed by an alpha sample.
|
||||||
|
if (bitDepth != 8 && bitDepth != 16)
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
//Each pixel is an R,G,B triple, followed by an alpha sample.
|
||||||
|
if (bitDepth != 8 && bitDepth != 16)
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,6 +54,7 @@
|
|||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="InvalidPngHeadersFileException.cs" />
|
<Compile Include="InvalidPngHeadersFileException.cs" />
|
||||||
<Compile Include="PngDecoder.cs" />
|
<Compile Include="PngDecoder.cs" />
|
||||||
|
<Compile Include="PngMetaData.cs" />
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<EmbeddedResource Include="Form1.resx">
|
<EmbeddedResource Include="Form1.resx">
|
||||||
|
|||||||
Reference in New Issue
Block a user