From 7bda1a997d9919676026091c8419f3b3e0712103 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 16 Jun 2017 22:56:58 +1000 Subject: [PATCH] Begin port --- .../Common/Extensions/StreamExtensions.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 10 +- .../Formats/Jpeg/Port/Components/Component.cs | 53 ++ .../Formats/Jpeg/Port/Components/Frame.cs | 73 +++ .../Jpeg/Port/Components/HuffmanTables.cs | 15 + .../Port/Components/QuantizationTables.cs | 43 ++ src/ImageSharp/Formats/Jpeg/Port/Huffman.cs | 6 + .../Formats/Jpeg/Port/JpegConstants.cs | 185 +++++++ .../Formats/Jpeg/Port/JpegDecoderCore.cs | 522 ++++++++++++++++++ src/ImageSharp/Formats/Jpeg/Port/JpegFrame.cs | 33 ++ src/ImageSharp/Memory/Fast2DArray{T}.cs | 32 +- 11 files changed, 967 insertions(+), 7 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Huffman.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Port/JpegFrame.cs diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index 6de94dd229..6b474b92e9 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -27,7 +27,7 @@ namespace ImageSharp if (stream.CanSeek) { - stream.Position += count; + stream.Seek(count, SeekOrigin.Current); // Position += count; } else { diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 56d025504d..e900e51ac8 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -21,10 +21,12 @@ namespace ImageSharp.Formats { Guard.NotNull(stream, "stream"); - using (JpegDecoderCore decoder = new JpegDecoderCore(options, configuration)) - { - return decoder.Decode(stream); - } + // using (JpegDecoderCore decoder = new JpegDecoderCore(options, configuration)) + // { + // return decoder.Decode(stream); + // } + var decoder = new Jpeg.Port.JpegDecoderCore(options, configuration); + return decoder.Decode(stream); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs new file mode 100644 index 0000000000..524e3b913d --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpeg.Port.Components +{ + /// + /// Represents a single color component + /// + internal struct Component + { + /// + /// Gets or sets the component Id + /// + public byte Id; + + /// + /// Gets or sets the horizontal sampling factor. + /// + public int HorizontalFactor; + + /// + /// Gets or sets the vertical sampling factor. + /// + public int VerticalFactor; + + /// + /// Gets or sets the identifier + /// + public byte QuantizationIdentifier; + + /// + /// Gets or sets the quantization table + /// + public short[] QuantizationTable; + + /// + /// Gets or sets the block data + /// + public short[] BlockData; + + /// + /// Gets or sets the number of blocks per line + /// + public int BlocksPerLine; + + /// + /// Gets or sets the number of blocks per column + /// + public int BlocksPerColumn; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs new file mode 100644 index 0000000000..7e72df8b0a --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs @@ -0,0 +1,73 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpeg.Port.Components +{ + /// + /// Represent a single jpeg frame + /// + internal class Frame + { + /// + /// Gets or sets a value indicating whether the frame uses the extended specification + /// + public bool Extended { get; set; } + + /// + /// Gets or sets a value indicating whether the frame uses the progressive specification + /// + public bool Progressive { get; set; } + + /// + /// Gets or sets the precision + /// + public byte Precision { get; set; } + + /// + /// Gets or sets the number of scanlines within the frame + /// + public short Scanlines { get; set; } + + /// + /// Gets or sets the number of samples per scanline + /// + public short SamplesPerLine { get; set; } + + /// + /// Gets or sets the number of components within a frame. In progressive frames this value can range from only 1 to 4 + /// + public byte ComponentCount { get; set; } + + /// + /// Gets or sets the component id collection + /// + public byte[] ComponentIds { get; set; } + + /// + /// Gets or sets the frame component collection + /// + public Component[] Components { get; set; } + + /// + /// Gets or sets the maximum horizontal sampling factor + /// + public int MaxHorizontalFactor { get; set; } + + /// + /// Gets or sets the maximum vertical sampling factor + /// + public int MaxVerticalFactor { get; set; } + + /// + /// Gets or sets the number of MCU's per line + /// + public int McusPerLine { get; set; } + + /// + /// Gets or sets the number of MCU's per column + /// + public int McusPerColumn { get; set; } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs new file mode 100644 index 0000000000..5c4ffce248 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs @@ -0,0 +1,15 @@ +namespace ImageSharp.Formats.Jpeg.Port.Components +{ + using ImageSharp.Memory; + + /// + /// Defines a pair of huffman tables + /// + internal class HuffmanTables + { + /// + /// Gets or sets the quantization tables. + /// + public Fast2DArray Tables { get; set; } = new Fast2DArray(256, 2); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs new file mode 100644 index 0000000000..f808eecfde --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpeg.Port.Components +{ + using ImageSharp.Memory; + + /// + /// Contains the quantization tables. + /// TODO: This all needs optimizing for memory. I'm just stubbing out functionality for now. + /// + internal class QuantizationTables + { + /// + /// Gets the ZigZag scan table + /// + 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 + }; + + /// + /// Gets or sets the quantization tables. + /// + public Fast2DArray Tables { get; set; } = new Fast2DArray(64, 4); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Huffman.cs b/src/ImageSharp/Formats/Jpeg/Port/Huffman.cs new file mode 100644 index 0000000000..75b6dc5624 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Huffman.cs @@ -0,0 +1,6 @@ +namespace ImageSharp.Formats.Jpeg.Port +{ + class Huffman + { + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs new file mode 100644 index 0000000000..236e38f961 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs @@ -0,0 +1,185 @@ +// ReSharper disable InconsistentNaming +namespace ImageSharp.Formats.Jpeg.Port +{ + /// + /// Contains jpeg constant values + /// + internal static class JpegConstants + { + /// + /// Contains marker specific constants + /// + public static class Markers + { + /// + /// The prefix used for all markers. + /// + public const byte Prefix = 0xFF; + + /// + /// The Start of Image marker + /// + public const ushort SOI = 0xFFD8; + + /// + /// The End of Image marker + /// + public const ushort EOI = 0xFFD9; + + /// + /// Application specific marker for marking the jpeg format. + /// + /// + public const ushort APP0 = 0xFFE0; + + /// + /// Application specific marker for marking where to store metadata. + /// + public const ushort APP1 = 0xFFE1; + + /// + /// Application specific marker for marking where to store ICC profile information. + /// + public const ushort APP2 = 0xFFE2; + + /// + /// Application specific marker. + /// + public const ushort APP3 = 0xFFE3; + + /// + /// Application specific marker. + /// + public const ushort APP4 = 0xFFE4; + + /// + /// Application specific marker. + /// + public const ushort APP5 = 0xFFE5; + + /// + /// Application specific marker. + /// + public const ushort APP6 = 0xFFE6; + + /// + /// Application specific marker. + /// + public const ushort APP7 = 0xFFE7; + + /// + /// Application specific marker. + /// + public const ushort APP8 = 0xFFE8; + + /// + /// Application specific marker. + /// + public const ushort APP9 = 0xFFE9; + + /// + /// Application specific marker. + /// + public const ushort APP10 = 0xFFEA; + + /// + /// Application specific marker. + /// + public const ushort APP11 = 0xFFEB; + + /// + /// Application specific marker. + /// + public const ushort APP12 = 0xFFEC; + + /// + /// Application specific marker. + /// + public const ushort APP13 = 0xFFED; + + /// + /// Application specific marker used by Adobe for storing encoding information for DCT filters. + /// + public const ushort APP14 = 0xFFEE; + + /// + /// Application specific marker used by GraphicConverter to store JPEG quality. + /// + public const ushort APP15 = 0xFFEF; + + /// + /// The text comment marker + /// + public const ushort COM = 0xFFFE; + + /// + /// Define Quantization Table(s) marker + /// + /// Specifies one or more quantization tables. + /// + /// + public const ushort DQT = 0xFFDB; + + /// + /// Start of Frame (baseline DCT) + /// + /// 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). + /// + /// + public const ushort SOF0 = 0xFFC0; + + /// + /// Start Of Frame (Extended Sequential DCT) + /// + /// 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). + /// + /// + public const ushort SOF1 = 0xFFC1; + + /// + /// Start Of Frame (progressive DCT) + /// + /// 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). + /// + /// + public const ushort SOF2 = 0xFFC2; + + /// + /// Define Huffman Table(s) + /// + /// Specifies one or more Huffman tables. + /// + /// + public const ushort DHT = 0xFFC4; + + /// + /// Contains JFIF specific markers + /// + public static class JFif + { + /// + /// Represents J in ASCII + /// + public const byte J = 0x4A; + + /// + /// Represents F in ASCII + /// + public const byte F = 0x46; + + /// + /// Represents I in ASCII + /// + public const byte I = 0x49; + + /// + /// Represents the null "0" marker + /// + public const byte Null = 0; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs new file mode 100644 index 0000000000..bb2d099382 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -0,0 +1,522 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpeg.Port +{ + using System; + using System.Buffers; + using System.IO; + + using ImageSharp.Formats.Jpeg.Port.Components; + using ImageSharp.PixelFormats; + + /// + /// Performs the jpeg decoding operation. + /// + internal class JpegDecoderCore + { + /// + /// The decoder options. + /// + private readonly IDecoderOptions options; + + /// + /// The global configuration + /// + private readonly Configuration configuration; + + /// + /// Gets the temporary buffer used to store bytes read from the stream. + /// + 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; + + /// + /// COntains information about the jFIF marker + /// + private JFif jFif; + + /// + /// Whether the image has a EXIF header + /// + private bool isExif; + + private int offset; + + /// + /// Initializes a new instance of the class. + /// + /// The decoder options. + /// The configuration. + public JpegDecoderCore(IDecoderOptions options, Configuration configuration) + { + this.configuration = configuration ?? Configuration.Default; + this.options = options ?? new DecoderOptions(); + } + + /// + /// Gets the input stream. + /// + public Stream InputStream { get; private set; } + + /// + /// Decodes the image from the specified and sets + /// the data to image. + /// + /// The pixel format. + /// The stream, where the image should be. + /// The decoded image. + public Image Decode(Stream stream) + where TPixel : struct, IPixel + { + this.InputStream = stream; + this.ParseStream(); + + var image = new Image(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(); + } + } + + /// + /// Processes the application header containing the JFIF identifier plus extra data. + /// + /// The remaining bytes in the segment block. + 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); + } + } + + /// + /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1. + /// + /// The remaining bytes in the segment block. + /// + /// Thrown if the tables do not match the header + /// + 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 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 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"); + } + } + + /// + /// Processes the Start of Frame marker. Specified in section B.2.2. + /// + /// The remaining bytes in the segment block. + /// The current frame marker. + 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(); + } + + /// + /// Processes a Define Huffman Table marker, and initializes a huffman + /// struct from its contents. Specified in section B.2.4.2. + /// + /// The remaining bytes in the segment block. + 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.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.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 tableSpan = tables.Tables.GetRowSpan(index); + } + + /// + /// Allocates the frame component blocks + /// + 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; + } + + /// + /// Finds the next file marker within the byte stream + /// + /// The + 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]); + } + } + + /// + /// Reads a from the stream advancing it by two bytes + /// + /// The + 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; + } + + /// + /// Provides information about the JFIF marker segment + /// + internal struct JFif + { + /// + /// The major version + /// + public byte MajorVersion; + + /// + /// The minor version + /// + public byte MinorVersion; + + /// + /// 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 + /// + public byte DensityUnits; + + /// + /// Horizontal pixel density. Must not be zero. + /// + public short XDensity; + + /// + /// Vertical pixel density. Must not be zero. + /// + public short YDensity; + + // TODO: Thumbnail? + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegFrame.cs new file mode 100644 index 0000000000..a279339e78 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegFrame.cs @@ -0,0 +1,33 @@ +namespace ImageSharp.Formats.Jpeg.Port +{ + /// + /// Represents a jpeg frame + /// + internal class JpegFrame + { + /// + /// Gets or sets a value indicating whether the fame is extended + /// + public bool Extended { get; set; } + + /// + /// Gets or sets a value indicating whether the fame is progressive + /// + public bool Progressive { get; set; } + + /// + /// Gets or sets the precision + /// + public byte Precision { get; set; } + + /// + /// Gets or sets the number of scanlines within the frame + /// + public short Scanlines { get; set; } + + /// + /// Gets or sets the number of samples per scanline + /// + public short SamplesPerLine { get; set; } + } +} diff --git a/src/ImageSharp/Memory/Fast2DArray{T}.cs b/src/ImageSharp/Memory/Fast2DArray{T}.cs index 260c829e21..ee8a609836 100644 --- a/src/ImageSharp/Memory/Fast2DArray{T}.cs +++ b/src/ImageSharp/Memory/Fast2DArray{T}.cs @@ -106,11 +106,23 @@ namespace ImageSharp.Memory return new Fast2DArray(data); } + /// + /// Gets a representing the row beginning from the the first item on that row. + /// + /// The y-coordinate of the row. Must be greater than or equal to zero and less than the height of the 2D array. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetRowSpan(int row) + { + this.CheckCoordinates(row); + return new Span(this.Data, row * this.Width, this.Width); + } + /// /// Checks the coordinates to ensure they are within bounds. /// - /// The row-coordinate of the item. Must be greater than zero and smaller than the height of the array. - /// The column-coordinate of the item. Must be greater than zero and smaller than the width of the array. + /// The y-coordinate of the item. Must be greater than zero and smaller than the height of the array. + /// The x-coordinate of the item. Must be greater than zero and smaller than the width of the array. /// /// Thrown if the coordinates are not within the bounds of the array. /// @@ -127,5 +139,21 @@ namespace ImageSharp.Memory throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outwith the array bounds."); } } + + /// + /// Checks the coordinates to ensure they are within bounds. + /// + /// The y-coordinate of the item. Must be greater than zero and smaller than the height of the array. + /// + /// Thrown if the coordinates are not within the bounds of the image. + /// + [Conditional("DEBUG")] + private void CheckCoordinates(int row) + { + if (row < 0 || row >= this.Height) + { + throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the array bounds."); + } + } } } \ No newline at end of file