mirror of https://github.com/SixLabors/ImageSharp
11 changed files with 967 additions and 7 deletions
@ -0,0 +1,53 @@ |
|||
// <copyright file="Component.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a single color component
|
|||
/// </summary>
|
|||
internal struct Component |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the component Id
|
|||
/// </summary>
|
|||
public byte Id; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the horizontal sampling factor.
|
|||
/// </summary>
|
|||
public int HorizontalFactor; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the vertical sampling factor.
|
|||
/// </summary>
|
|||
public int VerticalFactor; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the identifier
|
|||
/// </summary>
|
|||
public byte QuantizationIdentifier; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the quantization table
|
|||
/// </summary>
|
|||
public short[] QuantizationTable; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the block data
|
|||
/// </summary>
|
|||
public short[] BlockData; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of blocks per line
|
|||
/// </summary>
|
|||
public int BlocksPerLine; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of blocks per column
|
|||
/// </summary>
|
|||
public int BlocksPerColumn; |
|||
} |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
// <copyright file="QuantizationTables.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
/// <summary>
|
|||
/// Represent a single jpeg frame
|
|||
/// </summary>
|
|||
internal class Frame |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the frame uses the extended specification
|
|||
/// </summary>
|
|||
public bool Extended { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the frame uses the progressive specification
|
|||
/// </summary>
|
|||
public bool Progressive { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the precision
|
|||
/// </summary>
|
|||
public byte Precision { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of scanlines within the frame
|
|||
/// </summary>
|
|||
public short Scanlines { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of samples per scanline
|
|||
/// </summary>
|
|||
public short SamplesPerLine { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of components within a frame. In progressive frames this value can range from only 1 to 4
|
|||
/// </summary>
|
|||
public byte ComponentCount { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the component id collection
|
|||
/// </summary>
|
|||
public byte[] ComponentIds { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the frame component collection
|
|||
/// </summary>
|
|||
public Component[] Components { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum horizontal sampling factor
|
|||
/// </summary>
|
|||
public int MaxHorizontalFactor { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum vertical sampling factor
|
|||
/// </summary>
|
|||
public int MaxVerticalFactor { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of MCU's per line
|
|||
/// </summary>
|
|||
public int McusPerLine { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of MCU's per column
|
|||
/// </summary>
|
|||
public int McusPerColumn { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using ImageSharp.Memory; |
|||
|
|||
/// <summary>
|
|||
/// Defines a pair of huffman tables
|
|||
/// </summary>
|
|||
internal class HuffmanTables |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the quantization tables.
|
|||
/// </summary>
|
|||
public Fast2DArray<short> Tables { get; set; } = new Fast2DArray<short>(256, 2); |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
// <copyright file="QuantizationTables.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using ImageSharp.Memory; |
|||
|
|||
/// <summary>
|
|||
/// Contains the quantization tables.
|
|||
/// TODO: This all needs optimizing for memory. I'm just stubbing out functionality for now.
|
|||
/// </summary>
|
|||
internal class QuantizationTables |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the ZigZag scan table
|
|||
/// </summary>
|
|||
public static byte[] DctZigZag { get; } = |
|||
{ |
|||
0, |
|||
1, 8, |
|||
16, 9, 2, |
|||
3, 10, 17, 24, |
|||
32, 25, 18, 11, 4, |
|||
5, 12, 19, 26, 33, 40, |
|||
48, 41, 34, 27, 20, 13, 6, |
|||
7, 14, 21, 28, 35, 42, 49, 56, |
|||
57, 50, 43, 36, 29, 22, 15, |
|||
23, 30, 37, 44, 51, 58, |
|||
59, 52, 45, 38, 31, |
|||
39, 46, 53, 60, |
|||
61, 54, 47, |
|||
55, 62, |
|||
63 |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the quantization tables.
|
|||
/// </summary>
|
|||
public Fast2DArray<short> Tables { get; set; } = new Fast2DArray<short>(64, 4); |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace ImageSharp.Formats.Jpeg.Port |
|||
{ |
|||
class Huffman |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,185 @@ |
|||
// ReSharper disable InconsistentNaming
|
|||
namespace ImageSharp.Formats.Jpeg.Port |
|||
{ |
|||
/// <summary>
|
|||
/// Contains jpeg constant values
|
|||
/// </summary>
|
|||
internal static class JpegConstants |
|||
{ |
|||
/// <summary>
|
|||
/// Contains marker specific constants
|
|||
/// </summary>
|
|||
public static class Markers |
|||
{ |
|||
/// <summary>
|
|||
/// The prefix used for all markers.
|
|||
/// </summary>
|
|||
public const byte Prefix = 0xFF; |
|||
|
|||
/// <summary>
|
|||
/// The Start of Image marker
|
|||
/// </summary>
|
|||
public const ushort SOI = 0xFFD8; |
|||
|
|||
/// <summary>
|
|||
/// The End of Image marker
|
|||
/// </summary>
|
|||
public const ushort EOI = 0xFFD9; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker for marking the jpeg format.
|
|||
/// <see href="http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html"/>
|
|||
/// </summary>
|
|||
public const ushort APP0 = 0xFFE0; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker for marking where to store metadata.
|
|||
/// </summary>
|
|||
public const ushort APP1 = 0xFFE1; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker for marking where to store ICC profile information.
|
|||
/// </summary>
|
|||
public const ushort APP2 = 0xFFE2; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP3 = 0xFFE3; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP4 = 0xFFE4; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP5 = 0xFFE5; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP6 = 0xFFE6; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP7 = 0xFFE7; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP8 = 0xFFE8; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP9 = 0xFFE9; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP10 = 0xFFEA; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP11 = 0xFFEB; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP12 = 0xFFEC; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP13 = 0xFFED; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker used by Adobe for storing encoding information for DCT filters.
|
|||
/// </summary>
|
|||
public const ushort APP14 = 0xFFEE; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker used by GraphicConverter to store JPEG quality.
|
|||
/// </summary>
|
|||
public const ushort APP15 = 0xFFEF; |
|||
|
|||
/// <summary>
|
|||
/// The text comment marker
|
|||
/// </summary>
|
|||
public const ushort COM = 0xFFFE; |
|||
|
|||
/// <summary>
|
|||
/// Define Quantization Table(s) marker
|
|||
/// <remarks>
|
|||
/// Specifies one or more quantization tables.
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public const ushort DQT = 0xFFDB; |
|||
|
|||
/// <summary>
|
|||
/// Start of Frame (baseline DCT)
|
|||
/// <remarks>
|
|||
/// Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components,
|
|||
/// and component subsampling (e.g., 4:2:0).
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public const ushort SOF0 = 0xFFC0; |
|||
|
|||
/// <summary>
|
|||
/// Start Of Frame (Extended Sequential DCT)
|
|||
/// <remarks>
|
|||
/// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components,
|
|||
/// and component subsampling (e.g., 4:2:0).
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public const ushort SOF1 = 0xFFC1; |
|||
|
|||
/// <summary>
|
|||
/// Start Of Frame (progressive DCT)
|
|||
/// <remarks>
|
|||
/// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components,
|
|||
/// and component subsampling (e.g., 4:2:0).
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public const ushort SOF2 = 0xFFC2; |
|||
|
|||
/// <summary>
|
|||
/// Define Huffman Table(s)
|
|||
/// <remarks>
|
|||
/// Specifies one or more Huffman tables.
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public const ushort DHT = 0xFFC4; |
|||
|
|||
/// <summary>
|
|||
/// Contains JFIF specific markers
|
|||
/// </summary>
|
|||
public static class JFif |
|||
{ |
|||
/// <summary>
|
|||
/// Represents J in ASCII
|
|||
/// </summary>
|
|||
public const byte J = 0x4A; |
|||
|
|||
/// <summary>
|
|||
/// Represents F in ASCII
|
|||
/// </summary>
|
|||
public const byte F = 0x46; |
|||
|
|||
/// <summary>
|
|||
/// Represents I in ASCII
|
|||
/// </summary>
|
|||
public const byte I = 0x49; |
|||
|
|||
/// <summary>
|
|||
/// Represents the null "0" marker
|
|||
/// </summary>
|
|||
public const byte Null = 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,522 @@ |
|||
// <copyright file="JpegDecoderCore.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port |
|||
{ |
|||
using System; |
|||
using System.Buffers; |
|||
using System.IO; |
|||
|
|||
using ImageSharp.Formats.Jpeg.Port.Components; |
|||
using ImageSharp.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Performs the jpeg decoding operation.
|
|||
/// </summary>
|
|||
internal class JpegDecoderCore |
|||
{ |
|||
/// <summary>
|
|||
/// The decoder options.
|
|||
/// </summary>
|
|||
private readonly IDecoderOptions options; |
|||
|
|||
/// <summary>
|
|||
/// The global configuration
|
|||
/// </summary>
|
|||
private readonly Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// Gets the temporary buffer used to store bytes read from the stream.
|
|||
/// </summary>
|
|||
private readonly byte[] temp = new byte[2 * 16 * 4]; |
|||
|
|||
private readonly byte[] uint16Buffer = new byte[2]; |
|||
|
|||
private QuantizationTables quantizationTables; |
|||
|
|||
private HuffmanTables dcHuffmanTables; |
|||
|
|||
private HuffmanTables acHuffmanTables; |
|||
|
|||
private Frame frame; |
|||
|
|||
/// <summary>
|
|||
/// COntains information about the jFIF marker
|
|||
/// </summary>
|
|||
private JFif jFif; |
|||
|
|||
/// <summary>
|
|||
/// Whether the image has a EXIF header
|
|||
/// </summary>
|
|||
private bool isExif; |
|||
|
|||
private int offset; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
|
|||
/// </summary>
|
|||
/// <param name="options">The decoder options.</param>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
public JpegDecoderCore(IDecoderOptions options, Configuration configuration) |
|||
{ |
|||
this.configuration = configuration ?? Configuration.Default; |
|||
this.options = options ?? new DecoderOptions(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the input stream.
|
|||
/// </summary>
|
|||
public Stream InputStream { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Decodes the image from the specified <see cref="Stream"/> and sets
|
|||
/// the data to image.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="stream">The stream, where the image should be.</param>
|
|||
/// <returns>The decoded image.</returns>
|
|||
public Image<TPixel> Decode<TPixel>(Stream stream) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
this.InputStream = stream; |
|||
this.ParseStream(); |
|||
|
|||
var image = new Image<TPixel>(1, 1); |
|||
return image; |
|||
} |
|||
|
|||
private void ParseStream() |
|||
{ |
|||
// Check for the Start Of Image marker.
|
|||
ushort fileMarker = this.ReadUint16(); |
|||
if (fileMarker != JpegConstants.Markers.SOI) |
|||
{ |
|||
throw new ImageFormatException("Missing SOI marker."); |
|||
} |
|||
|
|||
fileMarker = this.ReadUint16(); |
|||
|
|||
this.quantizationTables = new QuantizationTables(); |
|||
|
|||
while (fileMarker != JpegConstants.Markers.EOI) |
|||
{ |
|||
// Get the marker length
|
|||
int remaining = this.ReadUint16() - 2; |
|||
|
|||
switch (fileMarker) |
|||
{ |
|||
case JpegConstants.Markers.APP0: |
|||
this.ProcessApplicationHeaderMarker(remaining); |
|||
break; |
|||
|
|||
case JpegConstants.Markers.APP1: |
|||
case JpegConstants.Markers.APP2: |
|||
case JpegConstants.Markers.APP3: |
|||
case JpegConstants.Markers.APP4: |
|||
case JpegConstants.Markers.APP5: |
|||
case JpegConstants.Markers.APP6: |
|||
case JpegConstants.Markers.APP7: |
|||
case JpegConstants.Markers.APP8: |
|||
case JpegConstants.Markers.APP9: |
|||
case JpegConstants.Markers.APP10: |
|||
case JpegConstants.Markers.APP11: |
|||
case JpegConstants.Markers.APP12: |
|||
case JpegConstants.Markers.APP13: |
|||
case JpegConstants.Markers.APP14: |
|||
case JpegConstants.Markers.APP15: |
|||
case JpegConstants.Markers.COM: |
|||
break; |
|||
|
|||
case JpegConstants.Markers.DQT: |
|||
this.ProcessDqtMarker(remaining); |
|||
break; |
|||
|
|||
case JpegConstants.Markers.SOF0: |
|||
case JpegConstants.Markers.SOF1: |
|||
case JpegConstants.Markers.SOF2: |
|||
this.ProcessStartOfFrameMarker(remaining, fileMarker); |
|||
break; |
|||
|
|||
case JpegConstants.Markers.DHT: |
|||
this.ProcessDefineHuffmanTablesMarker(remaining); |
|||
break; |
|||
} |
|||
|
|||
// Read on
|
|||
fileMarker = this.FindNextFileMarker(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the application header containing the JFIF identifier plus extra data.
|
|||
/// </summary>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
private void ProcessApplicationHeaderMarker(int remaining) |
|||
{ |
|||
if (remaining < 5) |
|||
{ |
|||
// Skip the application header length
|
|||
this.InputStream.Skip(remaining); |
|||
return; |
|||
} |
|||
|
|||
this.InputStream.Read(this.temp, 0, 13); |
|||
remaining -= 13; |
|||
|
|||
bool isJfif = this.temp[0] == JpegConstants.Markers.JFif.J && |
|||
this.temp[1] == JpegConstants.Markers.JFif.F && |
|||
this.temp[2] == JpegConstants.Markers.JFif.I && |
|||
this.temp[3] == JpegConstants.Markers.JFif.F && |
|||
this.temp[4] == JpegConstants.Markers.JFif.Null; |
|||
|
|||
if (isJfif) |
|||
{ |
|||
this.jFif = new JFif |
|||
{ |
|||
MajorVersion = this.temp[5], |
|||
MinorVersion = this.temp[6], |
|||
DensityUnits = this.temp[7], |
|||
XDensity = (short)((this.temp[8] << 8) | this.temp[9]), |
|||
YDensity = (short)((this.temp[10] << 8) | this.temp[11]) |
|||
}; |
|||
} |
|||
|
|||
// Skip thumbnails for now.
|
|||
if (remaining > 0) |
|||
{ |
|||
this.InputStream.Skip(remaining); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1.
|
|||
/// </summary>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
/// <exception cref="ImageFormatException">
|
|||
/// Thrown if the tables do not match the header
|
|||
/// </exception>
|
|||
private void ProcessDqtMarker(int remaining) |
|||
{ |
|||
while (remaining > 0) |
|||
{ |
|||
bool done = false; |
|||
remaining--; |
|||
int quantizationTableSpec = this.InputStream.ReadByte(); |
|||
|
|||
if (quantizationTableSpec > 3) |
|||
{ |
|||
throw new ImageFormatException("Bad Tq index value"); |
|||
} |
|||
|
|||
switch (quantizationTableSpec >> 4) |
|||
{ |
|||
case 0: |
|||
{ |
|||
// 8 bit values
|
|||
if (remaining < 64) |
|||
{ |
|||
done = true; |
|||
break; |
|||
} |
|||
|
|||
this.InputStream.Read(this.temp, 0, 64); |
|||
remaining -= 64; |
|||
|
|||
Span<short> tableSpan = this.quantizationTables.Tables.GetRowSpan(quantizationTableSpec); |
|||
for (int j = 0; j < 64; j++) |
|||
{ |
|||
tableSpan[QuantizationTables.DctZigZag[j]] = this.temp[j]; |
|||
} |
|||
} |
|||
|
|||
break; |
|||
case 1: |
|||
{ |
|||
// 16 bit values
|
|||
if (remaining < 128) |
|||
{ |
|||
done = true; |
|||
break; |
|||
} |
|||
|
|||
this.InputStream.Read(this.temp, 0, 128); |
|||
remaining -= 128; |
|||
|
|||
Span<short> tableSpan = this.quantizationTables.Tables.GetRowSpan(quantizationTableSpec); |
|||
for (int j = 0; j < 64; j++) |
|||
{ |
|||
tableSpan[QuantizationTables.DctZigZag[j]] = (short)((this.temp[2 * j] << 8) | this.temp[(2 * j) + 1]); |
|||
} |
|||
} |
|||
|
|||
break; |
|||
default: |
|||
throw new ImageFormatException("Bad Tq index value"); |
|||
} |
|||
|
|||
if (done) |
|||
{ |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (remaining != 0) |
|||
{ |
|||
throw new ImageFormatException("DQT has wrong length"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the Start of Frame marker. Specified in section B.2.2.
|
|||
/// </summary>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
/// <param name="frameMarker">The current frame marker.</param>
|
|||
private void ProcessStartOfFrameMarker(int remaining, ushort frameMarker) |
|||
{ |
|||
if (this.frame != null) |
|||
{ |
|||
throw new ImageFormatException("Multiple SOF markers. Only single frame jpegs supported."); |
|||
} |
|||
|
|||
this.InputStream.Read(this.temp, 0, remaining); |
|||
|
|||
this.frame = new Frame |
|||
{ |
|||
Extended = frameMarker == JpegConstants.Markers.SOF1, |
|||
Progressive = frameMarker == JpegConstants.Markers.SOF1, |
|||
Precision = this.temp[0], |
|||
Scanlines = (short)((this.temp[1] << 8) | this.temp[2]), |
|||
SamplesPerLine = (short)((this.temp[3] << 8) | this.temp[4]), |
|||
ComponentCount = this.temp[5] |
|||
}; |
|||
|
|||
int maxH = 0; |
|||
int maxV = 0; |
|||
int index = 6; |
|||
|
|||
// TODO: Pool this.
|
|||
this.frame.ComponentIds = new byte[this.frame.ComponentCount]; |
|||
this.frame.Components = new Component[this.frame.ComponentCount]; |
|||
|
|||
for (int i = 0; i < this.frame.ComponentCount; i++) |
|||
{ |
|||
int h = this.temp[index + 1] >> 4; |
|||
int v = this.temp[index + 1] & 15; |
|||
|
|||
if (maxH < h) |
|||
{ |
|||
maxH = h; |
|||
} |
|||
|
|||
if (maxV < v) |
|||
{ |
|||
maxV = v; |
|||
} |
|||
|
|||
ref var component = ref this.frame.Components[i]; |
|||
component.Id = this.temp[index]; |
|||
component.HorizontalFactor = h; |
|||
component.VerticalFactor = v; |
|||
component.QuantizationIdentifier = this.temp[index + 2]; |
|||
|
|||
// Don't assign the table yet.
|
|||
index += 3; |
|||
} |
|||
|
|||
this.frame.MaxHorizontalFactor = maxH; |
|||
this.frame.MaxVerticalFactor = maxV; |
|||
this.PrepareComponents(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes a Define Huffman Table marker, and initializes a huffman
|
|||
/// struct from its contents. Specified in section B.2.4.2.
|
|||
/// </summary>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
private void ProcessDefineHuffmanTablesMarker(int remaining) |
|||
{ |
|||
if (remaining < 17) |
|||
{ |
|||
throw new ImageFormatException("DHT has wrong length"); |
|||
} |
|||
|
|||
// TODO: Pool this
|
|||
byte[] huffmanData = new byte[remaining]; |
|||
this.InputStream.Read(huffmanData, 0, remaining); |
|||
|
|||
int o = 0; |
|||
for (int i = 0; i < remaining;) |
|||
{ |
|||
byte huffmanTableSpec = huffmanData[i]; |
|||
byte[] codeLengths = new byte[16]; |
|||
int codeLengthSum = 0; |
|||
|
|||
for (int j = 0; j < 16; j++) |
|||
{ |
|||
codeLengthSum += codeLengths[j] = huffmanData[o++]; |
|||
} |
|||
|
|||
short[] huffmanValues = new short[codeLengthSum]; |
|||
|
|||
byte[] values = null; |
|||
try |
|||
{ |
|||
values = ArrayPool<byte>.Shared.Rent(256); |
|||
this.InputStream.Read(values, 0, codeLengthSum); |
|||
|
|||
for (int j = 0; j < codeLengthSum; j++) |
|||
{ |
|||
huffmanValues[j] = values[o++]; |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
if (values != null) |
|||
{ |
|||
ArrayPool<byte>.Shared.Return(values); |
|||
} |
|||
} |
|||
|
|||
i += 17 + codeLengthSum; |
|||
|
|||
this.BuildHuffmanTable( |
|||
huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables, |
|||
huffmanTableSpec & 15, |
|||
codeLengths, |
|||
huffmanValues); |
|||
} |
|||
} |
|||
|
|||
private void BuildHuffmanTable(HuffmanTables tables, int index, byte[] codeLengths, short[] values) |
|||
{ |
|||
Span<short> tableSpan = tables.Tables.GetRowSpan(index); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Allocates the frame component blocks
|
|||
/// </summary>
|
|||
private void PrepareComponents() |
|||
{ |
|||
int mcusPerLine = this.frame.SamplesPerLine / 8 / this.frame.MaxHorizontalFactor; |
|||
int mcusPerColumn = this.frame.Scanlines / 8 / this.frame.MaxVerticalFactor; |
|||
|
|||
for (int i = 0; i < this.frame.ComponentCount; i++) |
|||
{ |
|||
ref var component = ref this.frame.Components[i]; |
|||
int blocksPerLine = this.frame.SamplesPerLine / 8 * component.HorizontalFactor / this.frame.MaxHorizontalFactor; |
|||
int blocksPerColumn = this.frame.Scanlines / 8 * component.VerticalFactor / this.frame.MaxVerticalFactor; |
|||
int blocksPerLineForMcu = mcusPerLine * component.HorizontalFactor; |
|||
int blocksPerColumnForMcu = mcusPerColumn * component.VerticalFactor; |
|||
|
|||
int blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1); |
|||
|
|||
// TODO: Pool this
|
|||
component.BlockData = new short[blocksBufferSize]; |
|||
component.BlocksPerLine = blocksPerLine; |
|||
component.BlocksPerColumn = blocksPerColumn; |
|||
} |
|||
|
|||
this.frame.McusPerLine = mcusPerLine; |
|||
this.frame.McusPerColumn = mcusPerColumn; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Finds the next file marker within the byte stream
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="ushort"/></returns>
|
|||
private ushort FindNextFileMarker() |
|||
{ |
|||
while (true) |
|||
{ |
|||
int value = this.InputStream.Read(this.uint16Buffer, 0, 2); |
|||
|
|||
if (value <= 0) |
|||
{ |
|||
return JpegConstants.Markers.EOI; |
|||
} |
|||
|
|||
while (this.uint16Buffer[0] != JpegConstants.Markers.Prefix) |
|||
{ |
|||
// Strictly speaking, this is a format error. However, libjpeg is
|
|||
// liberal in what it accepts. As of version 9, next_marker in
|
|||
// jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and
|
|||
// continues to decode the stream. Even before next_marker sees
|
|||
// extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many
|
|||
// bytes as it can, possibly past the end of a scan's data. It
|
|||
// effectively puts back any markers that it overscanned (e.g. an
|
|||
// "\xff\xd9" EOI marker), but it does not put back non-marker data,
|
|||
// and thus it can silently ignore a small number of extraneous
|
|||
// non-marker bytes before next_marker has a chance to see them (and
|
|||
// print a warning).
|
|||
// We are therefore also liberal in what we accept. Extraneous data
|
|||
// is silently ignore
|
|||
// This is similar to, but not exactly the same as, the restart
|
|||
// mechanism within a scan (the RST[0-7] markers).
|
|||
// Note that extraneous 0xff bytes in e.g. SOS data are escaped as
|
|||
// "\xff\x00", and so are detected a little further down below.
|
|||
this.uint16Buffer[0] = this.uint16Buffer[1]; |
|||
|
|||
value = this.InputStream.ReadByte(); |
|||
if (value <= 0) |
|||
{ |
|||
return JpegConstants.Markers.EOI; |
|||
} |
|||
|
|||
this.uint16Buffer[1] = (byte)value; |
|||
} |
|||
|
|||
return (ushort)((this.uint16Buffer[0] << 8) | this.uint16Buffer[1]); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a <see cref="ushort"/> from the stream advancing it by two bytes
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="ushort"/></returns>
|
|||
private ushort ReadUint16() |
|||
{ |
|||
this.InputStream.Read(this.uint16Buffer, 0, 2); |
|||
ushort value = (ushort)((this.uint16Buffer[0] << 8) | this.uint16Buffer[1]); |
|||
this.offset += 2; |
|||
return value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Provides information about the JFIF marker segment
|
|||
/// </summary>
|
|||
internal struct JFif |
|||
{ |
|||
/// <summary>
|
|||
/// The major version
|
|||
/// </summary>
|
|||
public byte MajorVersion; |
|||
|
|||
/// <summary>
|
|||
/// The minor version
|
|||
/// </summary>
|
|||
public byte MinorVersion; |
|||
|
|||
/// <summary>
|
|||
/// Units for the following pixel density fields
|
|||
/// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity
|
|||
/// 01 : Pixels per inch (2.54 cm)
|
|||
/// 02 : Pixels per centimeter
|
|||
/// </summary>
|
|||
public byte DensityUnits; |
|||
|
|||
/// <summary>
|
|||
/// Horizontal pixel density. Must not be zero.
|
|||
/// </summary>
|
|||
public short XDensity; |
|||
|
|||
/// <summary>
|
|||
/// Vertical pixel density. Must not be zero.
|
|||
/// </summary>
|
|||
public short YDensity; |
|||
|
|||
// TODO: Thumbnail?
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
namespace ImageSharp.Formats.Jpeg.Port |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a jpeg frame
|
|||
/// </summary>
|
|||
internal class JpegFrame |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the fame is extended
|
|||
/// </summary>
|
|||
public bool Extended { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the fame is progressive
|
|||
/// </summary>
|
|||
public bool Progressive { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the precision
|
|||
/// </summary>
|
|||
public byte Precision { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of scanlines within the frame
|
|||
/// </summary>
|
|||
public short Scanlines { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of samples per scanline
|
|||
/// </summary>
|
|||
public short SamplesPerLine { get; set; } |
|||
} |
|||
} |
|||
Loading…
Reference in new issue