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)
|
||||
{
|
||||
PngDecoder decoder = new PngDecoder();
|
||||
/*
|
||||
try{
|
||||
decoder.Decode(selectedFile);
|
||||
}
|
||||
@@ -87,7 +88,10 @@ namespace TEST_DecodePng
|
||||
{
|
||||
MessageBox.Show(ex.Message);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
decoder.Decode(selectedFile);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,12 @@ namespace TEST_DecodePng
|
||||
|
||||
const int CHUNCK_SIZE_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 LAST_CHUNCK_TYPE = "IEND";
|
||||
const string DATA_CHUNCK_TYPE = "IDAT";
|
||||
|
||||
const int IMAGE_WIDTH_FIELD_SIZE = 4;
|
||||
const int IMAGE_HEIGHT_FIELD_SIZE = 4;
|
||||
public Stopwatch chrono;
|
||||
@@ -50,116 +54,132 @@ namespace TEST_DecodePng
|
||||
if (!compareByteArrays(EXPECTED_PNG_SIGNATURE, pngSignature))
|
||||
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];
|
||||
for (int i = 0; i < CHUNCK_SIZE_FIELD_LENGTH; i++)
|
||||
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 != "IEND")
|
||||
{
|
||||
chunckLength[i] = content[cursor];
|
||||
cursor++;
|
||||
}
|
||||
byte[] chunckType = new byte[CHUNCK_TYPE_FIELD_LENGTH];
|
||||
for (int i = 0; i < CHUNCK_TYPE_FIELD_LENGTH; i++)
|
||||
{
|
||||
chunckType[i] = content[cursor];
|
||||
cursor++;
|
||||
//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);
|
||||
}
|
||||
|
||||
//The first chunck MUST be a IHDR chunck so we need to check if that is the case
|
||||
string ckType = System.Text.Encoding.UTF8.GetString(chunckType);
|
||||
//Now we have a list of all the chuncks in the PNG file
|
||||
//We can now process it
|
||||
|
||||
//If the first chunck is not the IHDR that means that we should not continue to decompress it
|
||||
if (ckType != FIRST_CHUNK_TYPE)
|
||||
throw (new InvalidPngHeadersFileException("First chunck not valid, possible corruption of the file"));
|
||||
|
||||
//As it is the first chunck, his structure is a little bit different
|
||||
|
||||
int imageWidth, imageHeight, bitDepth, colorType, compressionMethod, filterMethod, interlaceMethod;
|
||||
|
||||
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++)
|
||||
//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)
|
||||
{
|
||||
btaImageWidth[i] = content[cursor];
|
||||
cursor++;
|
||||
if (cktype.ToLower() == DATA_CHUNCK_TYPE.ToLower())
|
||||
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];
|
||||
cursor++;
|
||||
string cktype = chuncksType[chunckCounter];
|
||||
|
||||
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
|
||||
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)
|
||||
if (header != null)
|
||||
{
|
||||
case 0:
|
||||
//Each pixel is a grayscale sample.
|
||||
if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && bitDepth != 8 && bitDepth != 16)
|
||||
throw (incoherentColorsAndBitDepths);
|
||||
break;
|
||||
case 2:
|
||||
//Each pixel is an R,G,B triple.
|
||||
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;
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
//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
|
||||
chrono.Stop();
|
||||
//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 Include="InvalidPngHeadersFileException.cs" />
|
||||
<Compile Include="PngDecoder.cs" />
|
||||
<Compile Include="PngMetaData.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<EmbeddedResource Include="Form1.resx">
|
||||
|
||||
Reference in New Issue
Block a user