Browse Source

Add JPEG writer (incomplete)

pull/3153/head
winscripter 4 weeks ago
parent
commit
aaf4d1ad1d
  1. 190
      src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegBitWriter.cs
  2. 17
      src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegDctCodingState.cs
  3. 22
      src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegHuffmanCodeTable.cs
  4. 2
      src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegHuffmanDecoder.cs
  5. 12
      src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegSerializationStage.cs
  6. 40
      src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegSerializationState.cs
  7. 12
      src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegSerializationStatus.cs
  8. 1304
      src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegWriter.cs

190
src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegBitWriter.cs

@ -0,0 +1,190 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg;
internal sealed class JpegBitWriter
{
private const int ChunkSize = 16 * 1024;
private readonly Stream stream;
private readonly byte[] buffer;
private int pos;
private ulong putBuffer;
private int putBits;
public JpegBitWriter(Stream stream)
{
this.stream = stream;
this.buffer = new byte[ChunkSize];
this.putBits = 64;
}
public bool Healthy { get; private set; } = true;
private void SwapBuffer()
{
if (this.pos == 0)
{
return;
}
this.stream.Write(this.buffer, 0, this.pos);
this.pos = 0;
}
public void Reserve(int nBytes)
{
if (this.pos + nBytes > this.buffer.Length)
{
this.SwapBuffer();
}
}
private void EmitByte(int value)
{
this.buffer[this.pos++] = (byte)value;
if (value == 0xff)
{
this.buffer[this.pos++] = 0;
}
}
public void EmitMarker(int marker)
{
this.Reserve(2);
this.buffer[this.pos++] = 0xff;
this.buffer[this.pos++] = (byte)marker;
}
public void StoreBe64(ulong value)
{
this.buffer[this.pos++] = (byte)(value >> 56);
this.buffer[this.pos++] = (byte)(value >> 48);
this.buffer[this.pos++] = (byte)(value >> 40);
this.buffer[this.pos++] = (byte)(value >> 32);
this.buffer[this.pos++] = (byte)(value >> 24);
this.buffer[this.pos++] = (byte)(value >> 16);
this.buffer[this.pos++] = (byte)(value >> 8);
this.buffer[this.pos++] = (byte)value;
}
public void DischargeBitBuffer(int nBits, ulong bits)
{
this.putBuffer |= bits >> -this.putBits;
if (HasZeroByte(~this.putBuffer))
{
this.Reserve(16);
this.EmitByte((int)(this.putBuffer >> 56));
this.EmitByte((int)(this.putBuffer >> 48));
this.EmitByte((int)(this.putBuffer >> 40));
this.EmitByte((int)(this.putBuffer >> 32));
this.EmitByte((int)(this.putBuffer >> 24));
this.EmitByte((int)(this.putBuffer >> 16));
this.EmitByte((int)(this.putBuffer >> 8));
this.EmitByte((int)this.putBuffer);
}
else
{
this.Reserve(8);
this.StoreBe64(this.putBuffer);
}
this.putBits += 64;
this.putBuffer = bits << this.putBits;
}
public void WriteBits(int nBits, ulong bits)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(nBits);
this.putBits -= nBits;
if (this.putBits < 0)
{
if (nBits > 64)
{
this.putBits += nBits;
this.Healthy = false;
}
else
{
this.DischargeBitBuffer(nBits, bits);
}
}
else
{
this.putBuffer |= bits << this.putBits;
}
}
public bool JumpToByteBoundary(ref ReadOnlySpan<byte> padBits)
{
int nBits = this.putBits & 7;
byte padPattern;
if (padBits.IsEmpty)
{
padPattern = (byte)((1 << nBits) - 1);
}
else
{
padPattern = 0;
byte danglingBits = 0;
for (int i = 0; i < nBits; ++i)
{
if (padBits.IsEmpty)
{
return false;
}
byte bit = padBits[0];
padBits = padBits[1..];
danglingBits |= bit;
padPattern <<= 1;
padPattern |= bit;
}
if ((danglingBits & ~1) != 0)
{
return false;
}
}
this.Reserve(16);
while (this.putBits <= 56)
{
int c = (int)(this.putBuffer >> 56);
this.EmitByte(c);
this.putBuffer <<= 8;
this.putBits += 8;
}
if (this.putBits < 64)
{
int padMask = 0xff >> (64 - this.putBits);
int c =
((int)(this.putBuffer >> 56) & ~padMask) |
padPattern;
this.EmitByte(c);
}
this.putBuffer = 0;
this.putBits = 64;
return true;
}
}

17
src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegDctCodingState.cs

@ -0,0 +1,17 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg;
internal sealed class JpegDctCodingState
{
public int EobRun { get; set; }
public JpegHuffmanCodeTable? CurAcHuff { get; set; }
public List<ushort> RefinementBits { get; } = new(64);
public int RefinementBitsCount { get; set; }
public void Flush(JpegBitWriter writer) => JpegWriter.Flush(this, writer);
}

22
src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegHuffmanCodeTable.cs

@ -0,0 +1,22 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg;
// InlineArray values cannot be assigned as a property
#pragma warning disable SA1401 // Fields should be private
internal sealed class JpegHuffmanCodeTable
{
public InlineArray256<byte> Depth;
public InlineArray256<short> Code;
public bool Initialized { get; set; }
public void InitializeDepths(byte value = 0)
{
Span<byte> depths = this.Depth;
depths.Fill(value);
}
}

2
src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegHuffmanDecoder.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg;
internal static class JpegHuffmanDecoder
{
public const int RootTableBits = 8;
public const int LookupSize = 8;
public const int LookupSize = 1024;
private static int NextTableBitSize(Span<int> count, int length)
{

12
src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegSerializationStage.cs

@ -0,0 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg;
internal enum JpegSerializationStage
{
Initialize,
SerializeSection,
Done,
Error
}

40
src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegSerializationState.cs

@ -0,0 +1,40 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg;
internal sealed class JpegSerializationState
{
public JpegSerializationStage Stage { get; set; } = JpegSerializationStage.Initialize;
public int SectionIndex { get; set; }
public int DhtIndex { get; set; }
public int DqtIndex { get; set; }
public int AppIndex { get; set; }
public int ComIndex { get; set; }
public int DataIndex { get; set; }
public int ScanIndex { get; set; }
public JpegHuffmanCodeTable[] DcHuffTable { get; set; } = [];
public JpegHuffmanCodeTable[] AcHuffTable { get; set; } = [];
// Should be field to get ref to it
public ReadOnlyMemory<byte> PadBits;
public int PadBitsPos { get; set; }
public bool SeenDriMarker { get; set; }
public bool IsProgressive { get; set; }
public List<List<byte>> OutputQueue { get; set; } = [];
public EncodeScanState ScanState { get; set; }
}

12
src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegSerializationStatus.cs

@ -0,0 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg;
internal enum JpegSerializationStatus : byte
{
NeedsMoreInput,
NeedsMoreOutput,
Error,
Done
}

1304
src/ImageSharp/Formats/Jxl/IO/Jpeg/JpegWriter.cs

File diff suppressed because it is too large
Loading…
Cancel
Save