diff --git a/Codecs/Image/Png/Codec.Png/Chunk.cs b/Codecs/Image/Png/Codec.Png/Chunk.cs index 7cdd3543..6e1f7ecc 100644 --- a/Codecs/Image/Png/Codec.Png/Chunk.cs +++ b/Codecs/Image/Png/Codec.Png/Chunk.cs @@ -60,9 +60,9 @@ public uint RawType set => Header.Type = value; } - public ChunkNames ChunkName + public ChunkName ChunkName { - get => (ChunkNames)RawType; + get => (ChunkName)RawType; set => RawType = (uint)value; } diff --git a/Codecs/Image/Png/Codec.Png/ChunkNames.cs b/Codecs/Image/Png/Codec.Png/ChunkName.cs similarity index 99% rename from Codecs/Image/Png/Codec.Png/ChunkNames.cs rename to Codecs/Image/Png/Codec.Png/ChunkName.cs index e94661e5..8815bbd3 100644 --- a/Codecs/Image/Png/Codec.Png/ChunkNames.cs +++ b/Codecs/Image/Png/Codec.Png/ChunkName.cs @@ -1,6 +1,6 @@ namespace Media.Codec.Png; -public enum ChunkNames : uint +public enum ChunkName : uint { /// /// This chunk contains the actual image data. The image can contains more diff --git a/Codecs/Image/Png/Codec.Png/PngImage.cs b/Codecs/Image/Png/Codec.Png/PngImage.cs index 611147eb..d64cabd1 100644 --- a/Codecs/Image/Png/Codec.Png/PngImage.cs +++ b/Codecs/Image/Png/Codec.Png/PngImage.cs @@ -2,6 +2,7 @@ using System.Numerics; using Media.Codecs.Image; using Media.Common; +using Media.Common.Collections.Generic; namespace Media.Codec.Png; @@ -14,13 +15,14 @@ private static ImageFormat CreateImageFormat(byte bitDepth, byte colorType) switch (colorType) { case 0: // Grayscale - case 3: // Indexed-color - case 4: // Grayscale with alpha + case 3: // Indexed-color return ImageFormat.Monochrome(bitDepth); + case 4: // Grayscale with alpha + return ImageFormat.WithPreceedingAlphaComponent(ImageFormat.Monochrome(bitDepth / 2), bitDepth / 2); case 2: // Truecolor (RGB) - return ImageFormat.RGB(bitDepth); + return ImageFormat.RGB(bitDepth/ 3); case 6: // Truecolor with alpha (RGBA) - return ImageFormat.RGBA(bitDepth); + return ImageFormat.RGBA(bitDepth / 4); default: throw new NotSupportedException($"Color type {colorType} is not supported."); } @@ -28,7 +30,7 @@ private static ImageFormat CreateImageFormat(byte bitDepth, byte colorType) private static byte[] CalculateCrc(IEnumerable bytes) { - uint checksum = 0; + uint checksum = 0; //Should be seed value... foreach (var b in bytes) checksum = (checksum >> 8) ^ BitOperations.Crc32C(checksum, b); return Binary.GetBytes(checksum, BitConverter.IsLittleEndian); @@ -37,20 +39,25 @@ private static byte[] CalculateCrc(IEnumerable bytes) public readonly byte ColorType; + public readonly ConcurrentThesaurus Chunks; + public PngImage(ImageFormat imageFormat, int width, int height) : base(imageFormat, width, height, new PngCodec()) { + Chunks = new ConcurrentThesaurus(); } private PngImage(ImageFormat imageFormat, int width, int height, MemorySegment data) : base(imageFormat, width, height, data, new PngCodec()) { + Chunks = new ConcurrentThesaurus(); } - private PngImage(ImageFormat imageFormat, int width, int height, MemorySegment data, byte colorType) + private PngImage(ImageFormat imageFormat, int width, int height, MemorySegment data, byte colorType, ConcurrentThesaurus chunks) : this(imageFormat, width, height, data) { ColorType = colorType; + Chunks = chunks; } public static PngImage FromStream(Stream stream) @@ -60,12 +67,14 @@ public static PngImage FromStream(Stream stream) ImageFormat? imageFormat = default; MemorySegment? dataSegment = default; byte colorType = default; + ConcurrentThesaurus chunks = new ConcurrentThesaurus(); foreach(var chunk in PngCodec.ReadChunks(stream)) { - switch (chunk.ChunkName) + var chunkName = chunk.ChunkName; + switch (chunkName) { - case ChunkNames.Header: + case ChunkName.Header: var offset = chunk.DataOffset; width = Binary.Read32(chunk.Array, ref offset, Binary.IsLittleEndian); height = Binary.Read32(chunk.Array, ref offset, Binary.IsLittleEndian); @@ -77,15 +86,17 @@ public static PngImage FromStream(Stream stream) // Create the image format based on the IHDR data imageFormat = CreateImageFormat(bitDepth, colorType); + //ToDo, Crc is in data segment, so we need to read it and validate it. continue; - case ChunkNames.Data: + case ChunkName.Data: //ToDo, Crc is in data segment, so we need to read it and validate it. dataSegment = chunk.Data.Slice(0); continue; - case ChunkNames.End: + case ChunkName.End: continue; default: + chunks.Add(chunkName, chunk); continue; } } @@ -94,7 +105,7 @@ public static PngImage FromStream(Stream stream) throw new InvalidDataException("The provided stream does not contain valid PNG image data."); // Create and return the PngImage - return new PngImage(imageFormat, width, height, dataSegment, colorType); + return new PngImage(imageFormat, width, height, dataSegment, colorType, chunks); } public void Save(Stream stream) @@ -111,6 +122,14 @@ public void Save(Stream stream) // Write the IDAT chunk WriteIDATChunk(stream); + if (Chunks != null) + { + foreach (var chunk in Chunks.Values) + { + stream.Write(chunk.Array, chunk.Offset, chunk.Count); + } + } + // Write the IEND chunk WriteIENDChunk(stream); } diff --git a/Codecs/Image/Png/Codec.Png/PngUnitTests.cs b/Codecs/Image/Png/Codec.Png/PngUnitTests.cs index 90279f1e..f3819573 100644 --- a/Codecs/Image/Png/Codec.Png/PngUnitTests.cs +++ b/Codecs/Image/Png/Codec.Png/PngUnitTests.cs @@ -209,12 +209,27 @@ public static void TestLoad() pngStream.Seek(0, SeekOrigin.Begin); - using var pngImage = PngImage.FromStream(pngStream); + using (var pngImage = PngImage.FromStream(pngStream)) + { + var saveFileName = Path.Combine(outputDir, Path.GetFileName(filePath)); - var saveFileName = Path.Combine(outputDir, Path.GetFileName(filePath)); + using (var outputNew = new FileStream(saveFileName, FileMode.OpenOrCreate, FileAccess.Write)) + { + pngImage.Save(outputNew); + } - using var outputNew = new FileStream(saveFileName, FileMode.OpenOrCreate, FileAccess.Write); - pngImage.Save(outputNew); + using (var inputNew = new FileStream(saveFileName, FileMode.OpenOrCreate, FileAccess.Read)) + { + using (var newPngImage = PngImage.FromStream(inputNew)) + { + if (newPngImage.Width != pngImage.Width || + newPngImage.Height != pngImage.Height || + newPngImage.ImageFormat.Components.Length != pngImage.ImageFormat.Components.Length || + newPngImage.ImageFormat.Size != pngImage.ImageFormat.Size) + throw new InvalidDataException(); + } + } + } } } } \ No newline at end of file