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