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:
2022-09-25 15:33:47 +02:00
parent 4eae601fc7
commit 35cba21842
4 changed files with 223 additions and 99 deletions

View File

@@ -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);
}
}
}

View File

@@ -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

View 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;
}
}
}

View File

@@ -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">