|
|
|
@ -108,7 +108,14 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals |
|
|
|
|
|
|
|
// Write scans with actual pixel data
|
|
|
|
using SpectralConverter<TPixel> spectralConverter = new(frame, image, this.QuantizationTables); |
|
|
|
this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, buffer, cancellationToken); |
|
|
|
if (this.encoder.Progressive) |
|
|
|
{ |
|
|
|
this.WriteProgressiveScans(frame, frameConfig, spectralConverter, scanEncoder, buffer, cancellationToken); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, buffer, cancellationToken); |
|
|
|
} |
|
|
|
|
|
|
|
// Write the End Of Image marker.
|
|
|
|
this.WriteEndOfImageMarker(buffer); |
|
|
|
@ -569,7 +576,8 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals |
|
|
|
|
|
|
|
// Length (high byte, low byte), 8 + components * 3.
|
|
|
|
int markerlen = 8 + (3 * components.Length); |
|
|
|
this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen, buffer); |
|
|
|
byte marker = this.encoder.Progressive ? JpegConstants.Markers.SOF2 : JpegConstants.Markers.SOF0; |
|
|
|
this.WriteMarkerHeader(marker, markerlen, buffer); |
|
|
|
buffer[5] = (byte)components.Length; |
|
|
|
buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported
|
|
|
|
buffer[1] = (byte)(height >> 8); |
|
|
|
@ -603,7 +611,17 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals |
|
|
|
/// </summary>
|
|
|
|
/// <param name="components">The collecction of component configuration items.</param>
|
|
|
|
/// <param name="buffer">Temporary buffer.</param>
|
|
|
|
private void WriteStartOfScan(Span<JpegComponentConfig> components, Span<byte> buffer) |
|
|
|
private void WriteStartOfScan(Span<JpegComponentConfig> components, Span<byte> buffer) => |
|
|
|
this.WriteStartOfScan(components, buffer, 0x00, 0x3f); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Writes the StartOfScan marker.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="components">The collecction of component configuration items.</param>
|
|
|
|
/// <param name="buffer">Temporary buffer.</param>
|
|
|
|
/// <param name="spectralStart">Start of spectral selection</param>
|
|
|
|
/// <param name="spectralEnd">End of spectral selection</param>
|
|
|
|
private void WriteStartOfScan(Span<JpegComponentConfig> components, Span<byte> buffer, byte spectralStart, byte spectralEnd) |
|
|
|
{ |
|
|
|
// Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes:
|
|
|
|
// - the marker length "\x00\x0c",
|
|
|
|
@ -636,8 +654,8 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals |
|
|
|
buffer[i2 + 6] = (byte)tableSelectors; |
|
|
|
} |
|
|
|
|
|
|
|
buffer[sosSize - 1] = 0x00; // Ss - Start of spectral selection.
|
|
|
|
buffer[sosSize] = 0x3f; // Se - End of spectral selection.
|
|
|
|
buffer[sosSize - 1] = spectralStart; // Ss - Start of spectral selection.
|
|
|
|
buffer[sosSize] = spectralEnd; // Se - End of spectral selection.
|
|
|
|
buffer[sosSize + 1] = 0x00; // Ah + Ah (Successive approximation bit position high + low)
|
|
|
|
this.outputStream.Write(buffer, 0, sosSize + 2); |
|
|
|
} |
|
|
|
@ -700,6 +718,55 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Writes the progressive scans
|
|
|
|
/// </summary>
|
|
|
|
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
|
|
|
|
/// <param name="frame">The current frame.</param>
|
|
|
|
/// <param name="frameConfig">The frame configuration.</param>
|
|
|
|
/// <param name="spectralConverter">The spectral converter.</param>
|
|
|
|
/// <param name="encoder">The scan encoder.</param>
|
|
|
|
/// <param name="buffer">Temporary buffer.</param>
|
|
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
|
|
private void WriteProgressiveScans<TPixel>( |
|
|
|
JpegFrame frame, |
|
|
|
JpegFrameConfig frameConfig, |
|
|
|
SpectralConverter<TPixel> spectralConverter, |
|
|
|
HuffmanScanEncoder encoder, |
|
|
|
Span<byte> buffer, |
|
|
|
CancellationToken cancellationToken) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
frame.AllocateComponents(fullScan: true); |
|
|
|
spectralConverter.ConvertFull(); |
|
|
|
|
|
|
|
Span<JpegComponentConfig> components = frameConfig.Components; |
|
|
|
|
|
|
|
// Phase 1: DC scan
|
|
|
|
for (int i = 0; i < frame.Components.Length; i++) |
|
|
|
{ |
|
|
|
this.WriteStartOfScan(components.Slice(i, 1), buffer, 0x00, 0x00); |
|
|
|
|
|
|
|
encoder.EncodeDcScan(frame.Components[i], cancellationToken); |
|
|
|
} |
|
|
|
|
|
|
|
// Phase 2: AC scans
|
|
|
|
int acScans = this.encoder.ProgressiveScans - 1; |
|
|
|
int valuesPerScan = 64 / acScans; |
|
|
|
for (int scan = 0; scan < acScans; scan++) |
|
|
|
{ |
|
|
|
int start = Math.Max(1, scan * valuesPerScan); |
|
|
|
int end = scan == acScans - 1 ? 64 : (scan + 1) * valuesPerScan; |
|
|
|
|
|
|
|
for (int i = 0; i < components.Length; i++) |
|
|
|
{ |
|
|
|
this.WriteStartOfScan(components.Slice(i, 1), buffer, (byte)start, (byte)(end - 1)); |
|
|
|
|
|
|
|
encoder.EncodeAcScan(frame.Components[i], start, end, cancellationToken); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Writes the header for a marker with the given length.
|
|
|
|
/// </summary>
|
|
|
|
|