Skip to content

Commit

Permalink
Png work, thinking about Xng support.
Browse files Browse the repository at this point in the history
  • Loading branch information
juliusfriedman committed Oct 27, 2024
1 parent b315478 commit 6f11eb8
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 82 deletions.
70 changes: 69 additions & 1 deletion Codecs/Image/Png/Codec.Png/PngCodec.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
using Media.Codec.Interfaces;
using Media.Codecs.Image;
using Media.Common;
using System.IO.Compression;
using System.IO.Hashing;

namespace Media.Codec.Png
{
public class PngCodec : ImageCodec, IEncoder, IDecoder
{
const int ComponentCount = 4;
internal const int ComponentCount = 4;
internal const byte ZLibHeaderLength = 2;
internal const byte Deflate32KbWindow = 120;
internal const byte ChecksumBits = 1;

public PngCodec()
: base("PNG", Binary.ByteOrder.Little, ComponentCount, Binary.BitsPerByte)
Expand Down Expand Up @@ -53,5 +58,68 @@ public static IEnumerable<Chunk> ReadChunks(Stream inputStream, ulong expectedSi
yield return chunk;
}
}

internal static void WriteHeader(Stream stream, PngImage pngImage)
{
using var ihdr = new Chunk(ChunkName.Header, 13);
var offset = ihdr.DataOffset;
Binary.Write32(ihdr.Array, ref offset, Binary.IsLittleEndian, pngImage.Width);
Binary.Write32(ihdr.Array, ref offset, Binary.IsLittleEndian, pngImage.Height);
Binary.Write8(ihdr.Array, ref offset, Binary.IsBigEndian, pngImage.PngState.BitDepth);
Binary.Write8(ihdr.Array, ref offset, Binary.IsBigEndian, pngImage.PngState.ColorType);
Binary.Write8(ihdr.Array, ref offset, Binary.IsBigEndian, pngImage.PngState.CompressionMethod);
Binary.Write8(ihdr.Array, ref offset, Binary.IsBigEndian, pngImage.PngState.FilterMethod);
Binary.Write8(ihdr.Array, ref offset, Binary.IsBigEndian, pngImage.PngState.InterlaceMethod);
WriteChunk(stream, ihdr);
}

internal static void WriteData(Stream stream, PngImage pngImage, CompressionLevel compressionLevel = CompressionLevel.Optimal)
{
Chunk idat;
using (MemoryStream ms = new MemoryStream(pngImage.Data.Count))
{
using (DeflateStream deflateStream = new DeflateStream(ms, compressionLevel, true))
{
deflateStream.Write(pngImage.Data.Array, pngImage.Data.Offset, pngImage.Data.Count);
deflateStream.Close();

ms.TryGetBuffer(out var buffer);

// Create the chunk memory segment
idat = new Chunk(ChunkName.Data, buffer.Count + ZLibHeaderLength);

// Write the ZLib header.
idat[idat.DataOffset] = Deflate32KbWindow;
idat[idat.DataOffset + 1] = ChecksumBits;

// Copy the compressed data.
Buffer.BlockCopy(buffer.Array!, buffer.Offset, idat.Array, idat.DataOffset + ZLibHeaderLength, buffer.Count);

//Todo calculate the CRC and write to idat.
}
}
WriteChunk(stream, idat);
idat.Dispose();
}

internal static void WriteEnd(Stream stream)
{
using var iend = new Chunk(ChunkName.End, 0);
WriteChunk(stream, iend);
}

internal static void WriteChunk(Stream stream, Chunk chunk)
{
Crc32 crc32 = new();
using var header = chunk.Header;
var headerSpan = header.ToSpan();
crc32.Append(headerSpan.Slice(ChunkHeader.LengthBytes, ChunkHeader.NameLength));
using var chunkData = chunk.Data;
var chunkSpan = chunkData.ToSpan();
crc32.Append(chunkSpan);
var expectedCrc = crc32.GetCurrentHashAsUInt32();
chunk.Crc = (int)expectedCrc;
stream.Write(chunk.Array, chunk.Offset, chunk.Count);
}
}
}
107 changes: 27 additions & 80 deletions Codecs/Image/Png/Codec.Png/PngImage.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.IO.Compression;
using System.IO.Hashing;
using System.Numerics;
using Codec.Png;
using Media.Codecs.Image;
using Media.Common;
using Media.Common.Collections.Generic;
Expand All @@ -10,15 +9,17 @@ namespace Media.Codec.Png;

public class PngImage : Image
{
#region Constants

//https://en.wikipedia.org/wiki/JPEG_Network_Graphics
//https://en.wikipedia.org/wiki/Multiple-image_Network_Graphics
public const ulong PNGSignature = 0x89504E470D0A1A0A;
public const ulong MNGSignature = 0x8A4D4E470D0A1A0A;
public const ulong JNGSignature = 0x8B4A4E470D0A1A0A;

const byte ZLibHeaderLength = 2;
const byte Deflate32KbWindow = 120;
const byte ChecksumBits = 1;
#endregion

#region Private Static Functions

private static ImageFormat CreateImageFormat(byte bitDepth, byte colorType)
{
Expand Down Expand Up @@ -58,6 +59,8 @@ private static ColorType ResolveColorType(ImageFormat imageFormat)
}
}

#endregion

#region Fields

internal readonly PngState PngState;
Expand Down Expand Up @@ -106,20 +109,20 @@ private PngImage(ImageFormat imageFormat, int width, int height, MemorySegment d

#endregion

#region Writing
#region Reading

public static PngImage FromStream(Stream stream, ulong expectedSignature = PNGSignature)
{
int width = 0, height = 0;
ImageFormat? imageFormat = default;
SegmentStream dataSegments = new SegmentStream();
var dataSegments = new SegmentStream();
PngState pngState = new()
{
FileSignature = expectedSignature,
};
ConcurrentThesaurus<ChunkName, Chunk> chunks = new ConcurrentThesaurus<ChunkName, Chunk>();
Crc32 crc32 = new Crc32();
foreach(var chunk in PngCodec.ReadChunks(stream, expectedSignature))
var chunks = new ConcurrentThesaurus<ChunkName, Chunk>();
var crc32 = new Crc32();
foreach (var chunk in PngCodec.ReadChunks(stream, expectedSignature))
{
var actualCrc = chunk.Crc;
if (actualCrc > 0)
Expand All @@ -131,7 +134,7 @@ public static PngImage FromStream(Stream stream, ulong expectedSignature = PNGSi
using var chunkData = chunk.Data;
var chunkSpan = chunkData.ToSpan();
crc32.Append(chunkSpan);
var expectedCrc = crc32.GetCurrentHashAsUInt32();
var expectedCrc = crc32.GetCurrentHashAsUInt32();
if (actualCrc > 0 && expectedCrc != actualCrc)
throw new InvalidDataException($"There is an error in the provided stream Crc32 failed, Expected: ${expectedCrc}, Found: {actualCrc}.");
}
Expand Down Expand Up @@ -160,7 +163,7 @@ public static PngImage FromStream(Stream stream, ulong expectedSignature = PNGSi
var cmf = chunk[chunk.DataOffset];
var flg = chunk[chunk.DataOffset + 1];

var offset = chunk.DataOffset + ZLibHeaderLength;
var offset = chunk.DataOffset + PngCodec.ZLibHeaderLength;

// The preset dictionary.
bool fdict = (flg & 32) != 0;
Expand Down Expand Up @@ -201,103 +204,45 @@ public static PngImage FromStream(Stream stream, ulong expectedSignature = PNGSi

LoadImage:

if(imageFormat == null || dataSegments.Length == 0)
if (imageFormat == null || dataSegments.Length == 0)
throw new InvalidDataException("The provided stream does not contain valid PNG image data.");

// Create and return the PngImage
return new PngImage(imageFormat, width, height, new(dataSegments.ToArray()), pngState, chunks);
}

#endregion

#region Writing

public void Save(Stream stream, CompressionLevel compressionLevel = CompressionLevel.Optimal)
{
// Write the file signature
stream.Write(Binary.GetBytes(PngState.FileSignature, BitConverter.IsLittleEndian));

// Write the IHDR chunk
WriteHeader(stream);
PngCodec.WriteHeader(stream, this);

//Write any chunks we found while processing
if (Chunks != null)
{
foreach (var chunk in Chunks.Values)
{
WriteChunk(stream, chunk);
PngCodec.WriteChunk(stream, chunk);
}
}

// Write the IDAT chunk
WriteData(stream, compressionLevel);
PngCodec.WriteData(stream, this, compressionLevel);

// Write the IEND chunk
WriteEnd(stream);
}

private void WriteHeader(Stream stream)
{
using var ihdr = new Chunk(ChunkName.Header, 13);
var offset = ihdr.DataOffset;
Binary.Write32(ihdr.Array, ref offset, Binary.IsLittleEndian, Width);
Binary.Write32(ihdr.Array, ref offset, Binary.IsLittleEndian, Height);
Binary.Write8(ihdr.Array, ref offset, Binary.IsBigEndian, PngState.BitDepth);
Binary.Write8(ihdr.Array, ref offset, Binary.IsBigEndian, PngState.ColorType);
Binary.Write8(ihdr.Array, ref offset, Binary.IsBigEndian, PngState.CompressionMethod);
Binary.Write8(ihdr.Array, ref offset, Binary.IsBigEndian, PngState.FilterMethod);
Binary.Write8(ihdr.Array, ref offset, Binary.IsBigEndian, PngState.InterlaceMethod);
WriteChunk(stream, ihdr);
}

private void WriteData(Stream stream, CompressionLevel compressionLevel = CompressionLevel.Optimal)
{
Chunk idat;
using (MemoryStream ms = new MemoryStream(Data.Count))
{
using (DeflateStream deflateStream = new DeflateStream(ms, compressionLevel, true))
{
deflateStream.Write(Data.Array, Data.Offset, Data.Count);

deflateStream.Close();

ms.TryGetBuffer(out var buffer);

// Create the chunk memory segment
idat = new Chunk(ChunkName.Data, buffer.Count + ZLibHeaderLength);

// Write the ZLib header.
idat[idat.DataOffset] = Deflate32KbWindow;
idat[idat.DataOffset + 1] = ChecksumBits;

// Copy the compressed data.
Buffer.BlockCopy(buffer.Array!, buffer.Offset, idat.Array, idat.DataOffset + ZLibHeaderLength, buffer.Count);

//Todo calculate the CRC and write to idat.
}
}
WriteChunk(stream, idat);
idat.Dispose();
}

private void WriteEnd(Stream stream)
{
using var iend = new Chunk(ChunkName.End, 0);
WriteChunk(stream, iend);
}

private void WriteChunk(Stream stream, Chunk chunk)
{
Crc32 crc32 = new();
using var header = chunk.Header;
var headerSpan = header.ToSpan();
crc32.Append(headerSpan.Slice(ChunkHeader.LengthBytes, ChunkHeader.NameLength));
using var chunkData = chunk.Data;
var chunkSpan = chunkData.ToSpan();
crc32.Append(chunkSpan);
var expectedCrc = crc32.GetCurrentHashAsUInt32();
chunk.Crc = (int)expectedCrc;
stream.Write(chunk.Array, chunk.Offset, chunk.Count);
PngCodec.WriteEnd(stream);
}

#endregion

#region Pixel Data Access

public MemorySegment GetPixelDataAt(int x, int y)
{
if (x < 0 || x >= Width || y < 0 || y >= Height)
Expand Down Expand Up @@ -330,4 +275,6 @@ public void SetVectorDataAt(int x, int y, Vector<byte> vectorData)
offset -= offset % Vector<byte>.Count; // Align the offset to vector size
vectorData.CopyTo(new Span<byte>(Data.Array, Data.Offset + offset, Vector<byte>.Count));
}

#endregion
}
2 changes: 1 addition & 1 deletion Codecs/Image/Png/Codec.Png/PngState.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

namespace Codec.Png;
namespace Media.Codec.Png;

internal class PngState
{
Expand Down

0 comments on commit 6f11eb8

Please sign in to comment.