From 7bda1a997d9919676026091c8419f3b3e0712103 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 16 Jun 2017 22:56:58 +1000 Subject: [PATCH 01/43] 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 6de94dd22..6b474b92e 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 56d025504..e900e51ac 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 000000000..524e3b913 --- /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 000000000..7e72df8b0 --- /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 000000000..5c4ffce24 --- /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 000000000..f808eecfd --- /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 000000000..75b6dc562 --- /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 000000000..236e38f96 --- /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 000000000..bb2d09938 --- /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 000000000..a279339e7 --- /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 260c829e2..ee8a60983 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 From e35224662d2df33a7a4f2a0eba2e8fa807a0311d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 19 Jun 2017 02:01:12 +1000 Subject: [PATCH 02/43] Fix header finder --- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 78 +++++++++++-------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index bb2d09938..50ac407a4 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -10,6 +10,7 @@ namespace ImageSharp.Formats.Jpeg.Port using System.IO; using ImageSharp.Formats.Jpeg.Port.Components; + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -99,6 +100,8 @@ namespace ImageSharp.Formats.Jpeg.Port fileMarker = this.ReadUint16(); this.quantizationTables = new QuantizationTables(); + this.dcHuffmanTables = new HuffmanTables(); + this.acHuffmanTables = new HuffmanTables(); while (fileMarker != JpegConstants.Markers.EOI) { @@ -342,55 +345,62 @@ namespace ImageSharp.Formats.Jpeg.Port 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;) + using (var huffmanData = new Buffer(remaining)) { - byte huffmanTableSpec = huffmanData[i]; - byte[] codeLengths = new byte[16]; - int codeLengthSum = 0; + this.InputStream.Read(huffmanData.Array, 1, remaining); - for (int j = 0; j < 16; j++) + int o = 0; + for (int i = 0; i < remaining;) { - codeLengthSum += codeLengths[j] = huffmanData[o++]; - } + byte huffmanTableSpec = huffmanData[i]; + byte[] codeLengths = new byte[16]; + int codeLengthSum = 0; - short[] huffmanValues = new short[codeLengthSum]; + for (int j = 0; j < 16; j++) + { + codeLengthSum += codeLengths[j] = huffmanData[o++]; + } - byte[] values = null; - try - { - values = ArrayPool.Shared.Rent(256); - this.InputStream.Read(values, 0, codeLengthSum); + short[] huffmanValues = new short[codeLengthSum]; - for (int j = 0; j < codeLengthSum; j++) + byte[] values = null; + try { - huffmanValues[j] = values[o++]; + 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) + finally { - ArrayPool.Shared.Return(values); + if (values != null) + { + ArrayPool.Shared.Return(values); + } } - } - i += 17 + codeLengthSum; + i += 17 + codeLengthSum; - this.BuildHuffmanTable( - huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables, - huffmanTableSpec & 15, - codeLengths, - huffmanValues); + this.BuildHuffmanTable( + huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables, + huffmanTableSpec & 15, + codeLengths, + huffmanValues); + } } } private void BuildHuffmanTable(HuffmanTables tables, int index, byte[] codeLengths, short[] values) { + int length = 16; + while (length > 0 && codeLengths[length - 1] == 0) + { + length--; + } + Span tableSpan = tables.Tables.GetRowSpan(index); } @@ -432,7 +442,7 @@ namespace ImageSharp.Formats.Jpeg.Port { int value = this.InputStream.Read(this.uint16Buffer, 0, 2); - if (value <= 0) + if (value == 0) { return JpegConstants.Markers.EOI; } @@ -459,7 +469,7 @@ namespace ImageSharp.Formats.Jpeg.Port this.uint16Buffer[0] = this.uint16Buffer[1]; value = this.InputStream.ReadByte(); - if (value <= 0) + if (value == -1) { return JpegConstants.Markers.EOI; } From f29bb65492e7f74f2ba8d066d8e45052bd4fed0a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 19 Jun 2017 02:04:57 +1000 Subject: [PATCH 03/43] Add js source link --- src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 50ac407a4..3d36d61d9 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -15,6 +15,7 @@ namespace ImageSharp.Formats.Jpeg.Port /// /// Performs the jpeg decoding operation. + /// Ported from /// internal class JpegDecoderCore { From 49c13e307310bb671cbe656961a8210bf2c667f9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 19 Jun 2017 02:08:24 +1000 Subject: [PATCH 04/43] Use buffer --- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 3d36d61d9..300cb7aef 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -362,26 +362,17 @@ namespace ImageSharp.Formats.Jpeg.Port codeLengthSum += codeLengths[j] = huffmanData[o++]; } + // TODO: Pooling? short[] huffmanValues = new short[codeLengthSum]; - - byte[] values = null; - try + using (var values = new Buffer(256)) { - values = ArrayPool.Shared.Rent(256); - this.InputStream.Read(values, 0, codeLengthSum); + this.InputStream.Read(values.Array, 0, codeLengthSum); for (int j = 0; j < codeLengthSum; j++) { huffmanValues[j] = values[o++]; } } - finally - { - if (values != null) - { - ArrayPool.Shared.Return(values); - } - } i += 17 + codeLengthSum; From 5675e56e77d4f157dae4362d05cc316664392535 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 19 Jun 2017 02:09:30 +1000 Subject: [PATCH 05/43] Remove offset --- src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 300cb7aef..b29446fdb 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -54,8 +54,6 @@ namespace ImageSharp.Formats.Jpeg.Port /// private bool isExif; - private int offset; - /// /// Initializes a new instance of the class. /// @@ -481,7 +479,6 @@ namespace ImageSharp.Formats.Jpeg.Port { this.InputStream.Read(this.uint16Buffer, 0, 2); ushort value = (ushort)((this.uint16Buffer[0] << 8) | this.uint16Buffer[1]); - this.offset += 2; return value; } From 6feb98f763cb1d4697b88e580e5cef51e6c55970 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 19 Jun 2017 02:19:15 +1000 Subject: [PATCH 06/43] Fix progressive bool assignment --- src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index b29446fdb..1b2b8b99f 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -287,7 +287,7 @@ namespace ImageSharp.Formats.Jpeg.Port this.frame = new Frame { Extended = frameMarker == JpegConstants.Markers.SOF1, - Progressive = frameMarker == JpegConstants.Markers.SOF1, + Progressive = frameMarker == JpegConstants.Markers.SOF2, Precision = this.temp[0], Scanlines = (short)((this.temp[1] << 8) | this.temp[2]), SamplesPerLine = (short)((this.temp[3] << 8) | this.temp[4]), From 74ba49f162b8675b8b2dc99d048499fc8aac4841 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 20 Jun 2017 01:19:47 +1000 Subject: [PATCH 07/43] =?UTF-8?q?(=E2=95=AF=C2=B0=E2=96=A1=C2=B0=EF=BC=89?= =?UTF-8?q?=E2=95=AF=EF=B8=B5=20=E2=94=BB=E2=94=81=E2=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Jpeg/Port/Components/HuffmanTables.cs | 30 ++++++- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 83 +++++++++++++++---- 2 files changed, 98 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs index 5c4ffce24..5fcc2007b 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs @@ -1,5 +1,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { + using System.Collections.Generic; + using ImageSharp.Memory; /// @@ -10,6 +12,32 @@ /// /// Gets or sets the quantization tables. /// - public Fast2DArray Tables { get; set; } = new Fast2DArray(256, 2); + public Fast2DArray Tables { get; set; } = new Fast2DArray(256, 2); + } + + internal struct HuffmanBranch + { + public HuffmanBranch(short value) + : this(value, new List()) + { + } + + public HuffmanBranch(List children) + : this(0, children) + { + } + + private HuffmanBranch(short value, List children) + { + this.Index = 0; + this.Value = value; + this.Children = children; + } + + public int Index; + + public short Value; + + public List Children; } } diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 1b2b8b99f..db25ba845 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -6,8 +6,9 @@ namespace ImageSharp.Formats.Jpeg.Port { using System; - using System.Buffers; + using System.Collections.Generic; using System.IO; + using System.Linq; using ImageSharp.Formats.Jpeg.Port.Components; using ImageSharp.Memory; @@ -346,9 +347,9 @@ namespace ImageSharp.Formats.Jpeg.Port using (var huffmanData = new Buffer(remaining)) { - this.InputStream.Read(huffmanData.Array, 1, remaining); + this.InputStream.Skip(1); + this.InputStream.Read(huffmanData.Array, 0, remaining); - int o = 0; for (int i = 0; i < remaining;) { byte huffmanTableSpec = huffmanData[i]; @@ -357,41 +358,95 @@ namespace ImageSharp.Formats.Jpeg.Port for (int j = 0; j < 16; j++) { - codeLengthSum += codeLengths[j] = huffmanData[o++]; + codeLengthSum += codeLengths[j] = huffmanData[j]; } // TODO: Pooling? short[] huffmanValues = new short[codeLengthSum]; - using (var values = new Buffer(256)) + using (var values = new Buffer(codeLengthSum)) { this.InputStream.Read(values.Array, 0, codeLengthSum); for (int j = 0; j < codeLengthSum; j++) { - huffmanValues[j] = values[o++]; + huffmanValues[j] = values[j]; } - } - i += 17 + codeLengthSum; + i += 17 + codeLengthSum; - this.BuildHuffmanTable( - huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables, - huffmanTableSpec & 15, - codeLengths, - huffmanValues); + this.BuildHuffmanTable( + huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables, + huffmanTableSpec & 15, + codeLengths, + huffmanValues); + } } } } private void BuildHuffmanTable(HuffmanTables tables, int index, byte[] codeLengths, short[] values) { + // (╯°□°)╯︵ ┻━┻ Everything up to here is going well. I can't match the JavaScript now though. int length = 16; while (length > 0 && codeLengths[length - 1] == 0) { length--; } - Span tableSpan = tables.Tables.GetRowSpan(index); + var code = new Queue(); + code.Enqueue(new HuffmanBranch(new List())); + HuffmanBranch p = code.Peek(); + p.Children = new List(); + HuffmanBranch q; + int k = 0; + try + { + for (int i = 0; i < length; i++) + { + for (int j = 0; j < codeLengths[i]; j++) + { + p = code.Dequeue(); + p.Children.Add(new HuffmanBranch(values[k])); + while (p.Index > 0) + { + p = code.Dequeue(); + } + + p.Index++; + code.Enqueue(p); + while (code.Count <= i) + { + q = new HuffmanBranch(new List()); + code.Enqueue(q); + p.Children.Add(new HuffmanBranch(q.Children)); + p = q; + } + + k++; + } + + if (i + 1 < length) + { + // p here points to last code + q = new HuffmanBranch(new List()); + code.Enqueue(q); + p.Children.Add(new HuffmanBranch(q.Children)); + p = q; + } + } + + Span tableSpan = tables.Tables.GetRowSpan(index); + + List result = code.Peek().Children; + for (int i = 0; i < result.Count; i++) + { + tableSpan[i] = result[i]; + } + } + catch (Exception e) + { + throw; + } } /// From 1ccca34ce4fae2b86aa3c1b6372a3ccbed034656 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 20 Jun 2017 12:13:11 +1000 Subject: [PATCH 08/43] Can now build huffman tables --- .../Common/Extensions/ListExtensions.cs | 48 +++++++ .../Jpeg/Port/Components/HuffmanBranch.cs | 55 ++++++++ .../Jpeg/Port/Components/HuffmanTables.cs | 67 +++++---- src/ImageSharp/Formats/Jpeg/Port/Huffman.cs | 6 - .../Formats/Jpeg/Port/JpegDecoderCore.cs | 130 ++++++++---------- src/ImageSharp/Formats/Jpeg/Port/JpegFrame.cs | 33 ----- 6 files changed, 196 insertions(+), 143 deletions(-) create mode 100644 src/ImageSharp/Common/Extensions/ListExtensions.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanBranch.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Port/Huffman.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Port/JpegFrame.cs diff --git a/src/ImageSharp/Common/Extensions/ListExtensions.cs b/src/ImageSharp/Common/Extensions/ListExtensions.cs new file mode 100644 index 000000000..752f7ef21 --- /dev/null +++ b/src/ImageSharp/Common/Extensions/ListExtensions.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Common.Extensions +{ + using System.Collections.Generic; + + /// + /// Encapsulates a series of time saving extension methods to the class. + /// + internal static class ListExtensions + { + /// + /// Inserts an item at the given index automatically expanding the capacity if required. + /// + /// The type of object within the list + /// The list + /// The index + /// The item to insert + public static void SafeInsert(this List list, int index, T item) + { + if (index >= list.Count) + { + list.Add(item); + } + else + { + list[index] = item; + } + } + + /// + /// Removes the last element from a list and returns that element. This method changes the length of the list. + /// + /// The type of object within the list + /// The list + /// The last element in the specified sequence. + public static T Pop(this List list) + { + int last = list.Count - 1; + T item = list[last]; + list.RemoveAt(last); + return item; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanBranch.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanBranch.cs new file mode 100644 index 000000000..0f0a9b540 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanBranch.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpeg.Port.Components +{ + using System.Collections.Generic; + + /// + /// Represents a branch in the huffman tree + /// + internal struct HuffmanBranch + { + /// + /// The index + /// + public int Index; + + /// + /// The value + /// + public short Value; + + /// + /// The children + /// + public List Children; + + /// + /// Initializes a new instance of the struct. + /// + /// The value + public HuffmanBranch(short value) + : this(value, new List()) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The branch children + public HuffmanBranch(List children) + : this((short)0, children) + { + } + + private HuffmanBranch(short value, List children) + { + this.Index = 0; + this.Value = value; + this.Children = children; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs index 5fcc2007b..08c37bcbc 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs @@ -1,43 +1,50 @@ -namespace ImageSharp.Formats.Jpeg.Port.Components +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpeg.Port.Components { using System.Collections.Generic; - - using ImageSharp.Memory; + using System.Runtime.CompilerServices; /// /// Defines a pair of huffman tables /// internal class HuffmanTables { - /// - /// Gets or sets the quantization tables. - /// - public Fast2DArray Tables { get; set; } = new Fast2DArray(256, 2); - } - - internal struct HuffmanBranch - { - public HuffmanBranch(short value) - : this(value, new List()) - { - } + private List first = new List(); - public HuffmanBranch(List children) - : this(0, children) - { - } + private List second = new List(); - private HuffmanBranch(short value, List children) + /// + /// Gets or sets the table at the given index. + /// + /// The index + /// The + public List this[int index] { - this.Index = 0; - this.Value = value; - this.Children = children; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (index == 0) + { + return this.first; + } + + return this.second; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + if (index == 0) + { + this.first = value; + } + + this.second = value; + } } - - public int Index; - - public short Value; - - public List Children; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Huffman.cs b/src/ImageSharp/Formats/Jpeg/Port/Huffman.cs deleted file mode 100644 index 75b6dc562..000000000 --- a/src/ImageSharp/Formats/Jpeg/Port/Huffman.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace ImageSharp.Formats.Jpeg.Port -{ - class Huffman - { - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index db25ba845..95a83b086 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -8,8 +8,8 @@ namespace ImageSharp.Formats.Jpeg.Port using System; using System.Collections.Generic; using System.IO; - using System.Linq; + using ImageSharp.Common.Extensions; using ImageSharp.Formats.Jpeg.Port.Components; using ImageSharp.Memory; using ImageSharp.PixelFormats; @@ -50,11 +50,6 @@ namespace ImageSharp.Formats.Jpeg.Port /// private JFif jFif; - /// - /// Whether the image has a EXIF header - /// - private bool isExif; - /// /// Initializes a new instance of the class. /// @@ -345,108 +340,96 @@ namespace ImageSharp.Formats.Jpeg.Port throw new ImageFormatException("DHT has wrong length"); } - using (var huffmanData = new Buffer(remaining)) + using (var huffmanData = new Buffer(16)) { - this.InputStream.Skip(1); - this.InputStream.Read(huffmanData.Array, 0, remaining); - - for (int i = 0; i < remaining;) + for (int i = 2; 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[j]; - } + byte huffmanTableSpec = (byte)this.InputStream.ReadByte(); + this.InputStream.Read(huffmanData.Array, 0, 16); - // TODO: Pooling? - short[] huffmanValues = new short[codeLengthSum]; - using (var values = new Buffer(codeLengthSum)) + using (var codeLengths = new Buffer(16)) { - this.InputStream.Read(values.Array, 0, codeLengthSum); + int codeLengthSum = 0; - for (int j = 0; j < codeLengthSum; j++) + for (int j = 0; j < 16; j++) { - huffmanValues[j] = values[j]; + codeLengthSum += codeLengths[j] = huffmanData[j]; } - i += 17 + codeLengthSum; + using (var huffmanValues = new Buffer(codeLengthSum)) + { + this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum); + + i += 17 + codeLengthSum; - this.BuildHuffmanTable( - huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables, - huffmanTableSpec & 15, - codeLengths, - huffmanValues); + // Everything I can discover indicates there's a max of two table per DC AC pair though this limits the index to 16? + this.BuildHuffmanTable( + huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables, + huffmanTableSpec & 15, + codeLengths.Array, + huffmanValues.Array); + } } } } } - private void BuildHuffmanTable(HuffmanTables tables, int index, byte[] codeLengths, short[] values) + /// + /// Builds the huffman tables + /// + /// The tables + /// The table index + /// The codelengths + /// The values + private void BuildHuffmanTable(HuffmanTables tables, int index, byte[] codeLengths, byte[] values) { - // (╯°□°)╯︵ ┻━┻ Everything up to here is going well. I can't match the JavaScript now though. int length = 16; while (length > 0 && codeLengths[length - 1] == 0) { length--; } - var code = new Queue(); - code.Enqueue(new HuffmanBranch(new List())); - HuffmanBranch p = code.Peek(); - p.Children = new List(); - HuffmanBranch q; + // TODO: Check the capacity here. Seems to max at 2 + var code = new List { new HuffmanBranch(new List()) }; + HuffmanBranch p = code[0]; int k = 0; - try + + for (int i = 0; i < length; i++) { - for (int i = 0; i < length; i++) + HuffmanBranch q; + for (int j = 0; j < codeLengths[i]; j++) { - for (int j = 0; j < codeLengths[i]; j++) + p = code.Pop(); + p.Children.SafeInsert(p.Index, new HuffmanBranch(values[k])); + while (p.Index > 0) { - p = code.Dequeue(); - p.Children.Add(new HuffmanBranch(values[k])); - while (p.Index > 0) - { - p = code.Dequeue(); - } - - p.Index++; - code.Enqueue(p); - while (code.Count <= i) - { - q = new HuffmanBranch(new List()); - code.Enqueue(q); - p.Children.Add(new HuffmanBranch(q.Children)); - p = q; - } - - k++; + p = code.Pop(); } - if (i + 1 < length) + p.Index++; + code.Add(p); + while (code.Count <= i) { - // p here points to last code q = new HuffmanBranch(new List()); - code.Enqueue(q); - p.Children.Add(new HuffmanBranch(q.Children)); + code.Add(q); + p.Children.SafeInsert(p.Index, new HuffmanBranch(q.Children)); p = q; } - } - Span tableSpan = tables.Tables.GetRowSpan(index); + k++; + } - List result = code.Peek().Children; - for (int i = 0; i < result.Count; i++) + if (i + 1 < length) { - tableSpan[i] = result[i]; + // p here points to last code + q = new HuffmanBranch(new List()); + code.Add(q); + p.Children.SafeInsert(p.Index, new HuffmanBranch(q.Children)); + p = q; } } - catch (Exception e) - { - throw; - } + + tables[index] = code[0].Children; } /// @@ -533,8 +516,7 @@ namespace ImageSharp.Formats.Jpeg.Port private ushort ReadUint16() { this.InputStream.Read(this.uint16Buffer, 0, 2); - ushort value = (ushort)((this.uint16Buffer[0] << 8) | this.uint16Buffer[1]); - return value; + return (ushort)((this.uint16Buffer[0] << 8) | this.uint16Buffer[1]); } /// diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegFrame.cs deleted file mode 100644 index a279339e7..000000000 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegFrame.cs +++ /dev/null @@ -1,33 +0,0 @@ -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; } - } -} From a2431cb5d0b99171305964325689eebbf19fa015 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 21 Jun 2017 00:54:24 +1000 Subject: [PATCH 09/43] Begin ProcessStartOfScan --- .../Formats/Jpeg/Port/Components/Component.cs | 10 +++++ .../Formats/Jpeg/Port/JpegConstants.cs | 18 ++++++++ .../Formats/Jpeg/Port/JpegDecoderCore.cs | 43 ++++++++++++++++++- 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs index 524e3b913..ca8744022 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs @@ -49,5 +49,15 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// Gets or sets the number of blocks per column /// public int BlocksPerColumn; + + /// + /// Gets the index for the DC Huffman table + /// + public int DCHuffmanTableId; + + /// + /// Gets the index for the AC Huffman table + /// + public int ACHuffmanTableId; } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs index 236e38f96..f26dbded5 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs @@ -155,6 +155,24 @@ namespace ImageSharp.Formats.Jpeg.Port /// public const ushort DHT = 0xFFC4; + /// + /// Define Restart Interval + /// + /// Specifies the interval between RSTn markers, in macroblocks.This marker is followed by two bytes indicating the fixed size so it can be treated like any other variable size segment. + /// + /// + public const ushort DRI = 0xFFDD; + + /// + /// Start of Scan + /// + /// Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan. + /// Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it + /// will contain, and is immediately followed by entropy-coded data. + /// + /// + public const ushort SOS = 0xFFDA; + /// /// Contains JFIF specific markers /// diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 95a83b086..9dd45f53b 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -45,6 +45,8 @@ namespace ImageSharp.Formats.Jpeg.Port private Frame frame; + private ushort resetInterval; + /// /// COntains information about the jFIF marker /// @@ -140,6 +142,14 @@ namespace ImageSharp.Formats.Jpeg.Port case JpegConstants.Markers.DHT: this.ProcessDefineHuffmanTablesMarker(remaining); break; + + case JpegConstants.Markers.DRI: + this.resetInterval = this.ReadUint16(); + break; + + case JpegConstants.Markers.SOS: + this.ProcessStartOfScan(); + break; } // Read on @@ -319,6 +329,8 @@ namespace ImageSharp.Formats.Jpeg.Port component.VerticalFactor = v; component.QuantizationIdentifier = this.temp[index + 2]; + this.frame.ComponentIds[i] = (byte)i; + // Don't assign the table yet. index += 3; } @@ -374,6 +386,35 @@ namespace ImageSharp.Formats.Jpeg.Port } } + /// + /// Processes the SOS (Start of scan marker). + /// + private void ProcessStartOfScan() + { + int selectorsCount = this.InputStream.ReadByte(); + var components = new List(); + + for (int i = 0; i < selectorsCount; i++) + { + byte componentIndex = this.frame.ComponentIds[this.InputStream.ReadByte() - 1]; + Component component = this.frame.Components[componentIndex]; + int tableSpec = this.InputStream.ReadByte(); + component.DCHuffmanTableId = tableSpec >> 4; + component.ACHuffmanTableId = tableSpec & 15; + components.Add(component); + } + + this.InputStream.Read(this.temp, 0, 3); + int spectralStart = this.temp[0]; + int spectralEnd = this.temp[1]; + int successiveApproximation = this.temp[2]; + } + + private int DecodeScan(List components, int spectralStart, int spectralEnd, int successivePrev, int successive) + { + return 0; + } + /// /// Builds the huffman tables /// @@ -555,4 +596,4 @@ namespace ImageSharp.Formats.Jpeg.Port // TODO: Thumbnail? } } -} +} \ No newline at end of file From 8b699bf1d41586718097a144bfa01c7e7322e2e6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 22 Jun 2017 15:40:18 +1000 Subject: [PATCH 10/43] Can now decode a scan --- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 6 +- .../Jpeg/Port/Components/FileMarker.cs | 53 ++ .../Formats/Jpeg/Port/Components/Frame.cs | 41 +- .../{Component.cs => FrameComponent.cs} | 23 +- .../Jpeg/Port/Components/HuffmanBranch.cs | 21 +- .../Jpeg/Port/Components/HuffmanTables.cs | 6 +- .../Port/Components/QuantizationTables.cs | 42 +- .../Jpeg/Port/Components/ScanDecoder.cs | 471 ++++++++++++++++++ .../Formats/Jpeg/Port/JpegConstants.cs | 18 + .../Formats/Jpeg/Port/JpegDecoderCore.cs | 261 ++++++---- .../ImageSharp.Benchmarks/Image/DecodeJpeg.cs | 24 +- 11 files changed, 828 insertions(+), 138 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs rename src/ImageSharp/Formats/Jpeg/Port/Components/{Component.cs => FrameComponent.cs} (75%) create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index e900e51ac..117edb225 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -25,8 +25,10 @@ namespace ImageSharp.Formats // { // return decoder.Decode(stream); // } - var decoder = new Jpeg.Port.JpegDecoderCore(options, configuration); - return decoder.Decode(stream); + using (var decoder = new Jpeg.Port.JpegDecoderCore(options, configuration)) + { + return decoder.Decode(stream); + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs new file mode 100644 index 000000000..39adba5cd --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.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 jpeg file marker + /// + internal struct FileMarker + { + /// + /// Initializes a new instance of the struct. + /// + /// The marker + /// The position within the stream + public FileMarker(ushort marker, long position) + { + this.Marker = marker; + this.Position = position; + this.Invalid = false; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The marker + /// The position within the stream + /// Whether the current marker is invalid + public FileMarker(ushort marker, long position, bool invalid) + { + this.Marker = marker; + this.Position = position; + this.Invalid = invalid; + } + + /// + /// Gets or sets a value indicating whether the current marker is invalid + /// + public bool Invalid { get; set; } + + /// + /// Gets the position of the marker within a stream + /// + public ushort Marker { get; } + + /// + /// Gets the position of the marker within a stream + /// + public long Position { get; } + } +} \ 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 index 7e72df8b0..97c422ca3 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs @@ -5,11 +5,15 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { + using System; + /// /// Represent a single jpeg frame /// - internal class Frame + internal class Frame : IDisposable { + private bool isDisposed; + /// /// Gets or sets a value indicating whether the frame uses the extended specification /// @@ -48,7 +52,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// /// Gets or sets the frame component collection /// - public Component[] Components { get; set; } + public FrameComponent[] Components { get; set; } /// /// Gets or sets the maximum horizontal sampling factor @@ -69,5 +73,36 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// Gets or sets the number of MCU's per column /// public int McusPerColumn { get; set; } + + /// + public void Dispose() + { + this.Dispose(true); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// Whether to dispose of managed objects + protected virtual void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + foreach (FrameComponent component in this.Components) + { + component.Dispose(); + } + } + + // Set large fields to null. + this.Components = null; + + this.isDisposed = true; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs similarity index 75% rename from src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs rename to src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs index ca8744022..18176bde7 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs @@ -1,20 +1,29 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageSharp.Formats.Jpeg.Port.Components { + using System; + + using ImageSharp.Memory; + /// - /// Represents a single color component + /// Represents a single frame component /// - internal struct Component + internal struct FrameComponent : IDisposable { /// /// Gets or sets the component Id /// public byte Id; + /// + /// TODO: What does pred stand for? + /// + public int Pred; + /// /// Gets or sets the horizontal sampling factor. /// @@ -38,7 +47,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// /// Gets or sets the block data /// - public short[] BlockData; + public Buffer BlockData; /// /// Gets or sets the number of blocks per line @@ -59,5 +68,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// Gets the index for the AC Huffman table /// public int ACHuffmanTableId; + + /// + public void Dispose() + { + this.BlockData?.Dispose(); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanBranch.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanBranch.cs index 0f0a9b540..d716355ad 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanBranch.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanBranch.cs @@ -5,7 +5,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { - using System.Collections.Generic; + using System.Runtime.CompilerServices; /// /// Represents a branch in the huffman tree @@ -23,32 +23,31 @@ namespace ImageSharp.Formats.Jpeg.Port.Components public short Value; /// - /// The children + /// The children. /// - public List Children; + public HuffmanBranch[] Children; /// /// Initializes a new instance of the struct. /// /// The value + [MethodImpl(MethodImplOptions.AggressiveInlining)] public HuffmanBranch(short value) - : this(value, new List()) { + this.Index = 0; + this.Value = value; + this.Children = new HuffmanBranch[2]; } /// /// Initializes a new instance of the struct. /// /// The branch children - public HuffmanBranch(List children) - : this((short)0, children) - { - } - - private HuffmanBranch(short value, List children) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public HuffmanBranch(HuffmanBranch[] children) { this.Index = 0; - this.Value = value; + this.Value = -1; this.Children = children; } } diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs index 08c37bcbc..a040d21e7 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs @@ -13,16 +13,16 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// internal class HuffmanTables { - private List first = new List(); + private HuffmanBranch[] first; - private List second = new List(); + private HuffmanBranch[] second; /// /// Gets or sets the table at the given index. /// /// The index /// The - public List this[int index] + public HuffmanBranch[] this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs index f808eecfd..1ca31b31b 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { + using System.Runtime.CompilerServices; + using ImageSharp.Memory; /// @@ -16,24 +18,30 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// /// Gets the ZigZag scan table /// - public static byte[] DctZigZag { get; } = + public static byte[] DctZigZag { - 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 - }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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. diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs new file mode 100644 index 000000000..09837aef0 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -0,0 +1,471 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpeg.Port.Components +{ + using System; +#if DEBUG + using System.Diagnostics; +#endif + using System.IO; + using System.Runtime.CompilerServices; + + /// + /// Encapsulates a decode method. TODO: This may well be a bottleneck + /// + /// The component + /// The offset + /// The DC Huffman tables + /// The AC Huffman tables + /// The input stream + internal delegate void DecodeAction(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream); + + /// + /// Provides the means to decode a spectral scan + /// + internal struct ScanDecoder + { + private int bitsData; + + private int bitsCount; + + private int specStart; + + private int specEnd; + + private int eobrun; + + private int successiveState; + + private int successiveACState; + + /// + /// Decodes the spectral scan + /// + /// The image frame + /// The input stream + /// The DC Huffman tables + /// The AC Huffman tables + /// The scan components + /// The reset interval + /// The spectral selection start + /// The spectral selection end + /// The successive approximation bit high end + /// The successive approximation bit low end + public void DecodeScan(Frame frame, Stream stream, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, FrameComponent[] components, ushort resetInterval, int spectralStart, int spectralEnd, int successivePrev, int successive) + { + this.specStart = spectralStart; + this.specEnd = spectralEnd; + this.successiveState = successive; + bool progressive = frame.Progressive; + int componentsLength = components.Length; + int mcusPerLine = frame.McusPerLine; + + // TODO: Delegate action will not be fast + DecodeAction decodeFn; + + if (progressive) + { + if (this.specStart == 0) + { + if (successivePrev == 0) + { + decodeFn = this.DecodeDCFirst; + } + else + { + decodeFn = this.DecodeDCSuccessive; + } + } + else + { + if (successivePrev == 0) + { + decodeFn = this.DecodeACFirst; + } + else + { + decodeFn = this.DecodeACSuccessive; + } + } + } + else + { + decodeFn = this.DecodeBaseline; + } + + int mcu = 0; + int mcuExpected; + if (componentsLength == 1) + { + mcuExpected = components[0].BlocksPerLine * components[0].BlocksPerColumn; + } + else + { + mcuExpected = mcusPerLine * frame.McusPerColumn; + } + + FileMarker fileMarker; + while (mcu < mcuExpected) + { + // Reset interval stuff + int mcuToRead = resetInterval > 0 ? Math.Min(mcuExpected - mcu, resetInterval) : mcuExpected; + for (int i = 0; i < componentsLength; i++) + { + ref FrameComponent c = ref components[i]; + c.Pred = 0; + } + + this.eobrun = 0; + + if (componentsLength == 1) + { + ref FrameComponent component = ref components[0]; + for (int n = 0; n < mcuToRead; n++) + { + DecodeBlock(dcHuffmanTables, acHuffmanTables, ref component, decodeFn, mcu, stream); + mcu++; + } + } + else + { + for (int n = 0; n < mcuToRead; n++) + { + for (int i = 0; i < componentsLength; i++) + { + ref FrameComponent component = ref components[i]; + int h = component.HorizontalFactor; + int v = component.VerticalFactor; + for (int j = 0; j < v; j++) + { + for (int k = 0; k < h; k++) + { + DecodeMcu(dcHuffmanTables, acHuffmanTables, ref component, decodeFn, mcusPerLine, mcu, j, k, stream); + } + } + } + + mcu++; + } + } + + // Find marker + this.bitsCount = 0; + + // TODO: We need to make sure we are not overwriting anything here. + fileMarker = JpegDecoderCore.FindNextFileMarker(stream); + + // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past + // those to attempt to find a valid marker (fixes issue4090.pdf) in original code. + if (fileMarker.Invalid) + { +#if DEBUG + Debug.WriteLine("DecodeScan - Unexpected MCU data, next marker is: " + fileMarker.Marker.ToString("X")); +#endif + } + + ushort marker = fileMarker.Marker; + if (marker <= 0xFF00) + { + throw new ImageFormatException("Marker was not found"); + } + + if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) + { + // RSTx + stream.Skip(2); + } + else + { + break; + } + } + + fileMarker = JpegDecoderCore.FindNextFileMarker(stream); + + // Some images include more Scan blocks than expected, skip past those and + // attempt to find the next valid marker (fixes issue8182.pdf) in original code. + if (fileMarker.Invalid) + { +#if DEBUG + Debug.WriteLine("DecodeScan - Unexpected MCU data, next marker is: " + fileMarker.Marker.ToString("X")); +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetBlockBufferOffset(FrameComponent component, int row, int col) + { + return 64 * (((component.BlocksPerLine + 1) * row) + col); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DecodeMcu(HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, ref FrameComponent component, DecodeAction decode, int mcusPerLine, int mcu, int row, int col, Stream stream) + { + int mcuRow = (mcu / mcusPerLine) | 0; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * component.VerticalFactor) + row; + int blockCol = (mcuCol * component.HorizontalFactor) + col; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + decode(ref component, offset, dcHuffmanTables, acHuffmanTables, stream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DecodeBlock(HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, ref FrameComponent component, DecodeAction decode, int mcu, Stream stream) + { + int blockRow = (mcu / component.BlocksPerLine) | 0; + int blockCol = mcu % component.BlocksPerLine; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + decode(ref component, offset, dcHuffmanTables, acHuffmanTables, stream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ReadBit(Stream stream) + { + if (this.bitsCount > 0) + { + this.bitsCount--; + return (this.bitsData >> this.bitsCount) & 1; + } + + this.bitsData = stream.ReadByte(); + if (this.bitsData == 0xFF) + { + int nextByte = stream.ReadByte(); + if (nextByte > 0) + { + throw new ImageFormatException($"Unexpected marker {(this.bitsData << 8) | nextByte}"); + } + + // Unstuff 0 + } + + this.bitsCount = 7; + return this.bitsData >> 7; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private short DecodeHuffman(HuffmanBranch[] tree, Stream stream) + { + HuffmanBranch[] node = tree; + while (true) + { + int index; + index = this.ReadBit(stream); + HuffmanBranch branch = node[index]; + node = branch.Children; + + if (branch.Value > -1) + { + return branch.Value; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int Receive(int length, Stream stream) + { + int n = 0; + while (length > 0) + { + n = (n << 1) | this.ReadBit(stream); + length--; + } + + return n; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ReceiveAndExtend(int length, Stream stream) + { + if (length == 1) + { + return this.ReadBit(stream) == 1 ? 1 : -1; + } + + int n = this.Receive(length, stream); + if (n >= 1 << (length - 1)) + { + return n; + } + + return n + (-1 << length) + 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeBaseline(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) + { + int t = this.DecodeHuffman(dcHuffmanTables[component.DCHuffmanTableId], stream); + int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream); + component.BlockData[offset] = (short)(component.Pred += diff); + + int k = 1; + while (k < 64) + { + int rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream); + int s = rs & 15; + int r = rs >> 4; + + if (s == 0) + { + if (r < 15) + { + break; + } + + k += 16; + continue; + } + + k += r; + byte z = QuantizationTables.DctZigZag[k]; + short re = (short)this.ReceiveAndExtend(s, stream); + component.BlockData[offset + z] = re; + k++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeDCFirst(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) + { + int t = this.DecodeHuffman(dcHuffmanTables[component.DCHuffmanTableId], stream); + int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream) << this.successiveState; + component.BlockData[offset] = (short)(component.Pred += diff); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeDCSuccessive(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) + { + component.BlockData[offset] |= (short)(this.ReadBit(stream) << this.successiveState); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeACFirst(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) + { + if (this.eobrun > 0) + { + this.eobrun--; + return; + } + + int k = this.specStart; + int e = this.specEnd; + while (k <= e) + { + short rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream); + int s = rs & 15; + int r = rs >> 4; + + if (s == 0) + { + if (r < 15) + { + this.eobrun = this.Receive(r, stream) + (1 << r) - 1; + break; + } + + k += 16; + continue; + } + + k += r; + byte z = QuantizationTables.DctZigZag[k]; + component.BlockData[offset + z] = (short)(this.ReceiveAndExtend(s, stream) * (1 << this.successiveState)); + k++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeACSuccessive(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) + { + int k = this.specStart; + int e = this.specEnd; + int r = 0; + while (k <= e) + { + byte z = QuantizationTables.DctZigZag[k]; + int successiveACNextValue = 0; + switch (this.successiveACState) + { + case 0: // Initial state + short rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream); + int s = rs & 15; + r = rs >> 4; + if (s == 0) + { + if (r < 15) + { + this.eobrun = this.Receive(r, stream) + (1 << r); + this.successiveACState = 4; + } + else + { + r = 16; + this.successiveACState = 1; + } + } + else + { + if (s != 1) + { + throw new ImageFormatException("Invalid ACn encoding"); + } + + successiveACNextValue = this.ReceiveAndExtend(s, stream); + this.successiveACState = r > 0 ? 2 : 3; + } + + continue; + case 1: // Skipping r zero items + case 2: + if (component.BlockData[offset + z] > 0) + { + component.BlockData[offset + z] += (short)(this.ReadBit(stream) << this.successiveState); + } + else + { + r--; + if (r == 0) + { + this.successiveACState = this.successiveACState == 2 ? 3 : 0; + } + } + + break; + case 3: // Set value for a zero item + if (component.BlockData[offset + z] > 0) + { + component.BlockData[offset + z] += (short)(this.ReadBit(stream) << this.successiveState); + } + else + { + component.BlockData[offset + z] = (short)(successiveACNextValue << this.successiveState); + this.successiveACState = 0; + } + + break; + case 4: // Eob + if (component.BlockData[offset + z] > 0) + { + component.BlockData[offset + z] += (short)(this.ReadBit(stream) << this.successiveState); + } + + break; + } + + k++; + } + + if (this.successiveACState == 4) + { + this.eobrun--; + if (this.eobrun == 0) + { + this.successiveACState = 0; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs index f26dbded5..0ad8afa91 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs @@ -173,6 +173,24 @@ namespace ImageSharp.Formats.Jpeg.Port /// public const ushort SOS = 0xFFDA; + /// + /// Define First Restart + /// + /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. + /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. + /// + /// + public const ushort RST0 = 0xFFD0; + + /// + /// Define Eigth Restart + /// + /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. + /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. + /// + /// + public const ushort RST7 = 0xFFD7; + /// /// Contains JFIF specific markers /// diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 9dd45f53b..873d4623d 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -8,6 +8,7 @@ namespace ImageSharp.Formats.Jpeg.Port using System; using System.Collections.Generic; using System.IO; + using System.Runtime.CompilerServices; using ImageSharp.Common.Extensions; using ImageSharp.Formats.Jpeg.Port.Components; @@ -18,7 +19,7 @@ namespace ImageSharp.Formats.Jpeg.Port /// Performs the jpeg decoding operation. /// Ported from /// - internal class JpegDecoderCore + internal class JpegDecoderCore : IDisposable { /// /// The decoder options. @@ -48,10 +49,12 @@ namespace ImageSharp.Formats.Jpeg.Port private ushort resetInterval; /// - /// COntains information about the jFIF marker + /// Contains information about the jFIF marker /// private JFif jFif; + private bool isDisposed; + /// /// Initializes a new instance of the class. /// @@ -68,6 +71,109 @@ namespace ImageSharp.Formats.Jpeg.Port /// public Stream InputStream { get; private set; } + /// + /// Finds the next file marker within the byte stream. Not used but I'm keeping it for now for testing + /// + /// The input stream + /// The + public static FileMarker FindNextFileMarkerOld(Stream stream) + { + byte[] buffer = new byte[2]; + while (true) + { + int value = stream.Read(buffer, 0, 2); + + if (value == 0) + { + return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); + } + + while (buffer[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. + buffer[0] = buffer[1]; + + value = stream.ReadByte(); + if (value == -1) + { + return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); + } + + buffer[1] = (byte)value; + } + + return new FileMarker((ushort)((buffer[0] << 8) | buffer[1]), (int)(stream.Position - 2)); + } + } + + /// + /// Finds the next file marker within the byte stream + /// + /// The input stream + /// The + public static FileMarker FindNextFileMarker(Stream stream) + { + byte[] buffer = new byte[2]; + long maxPos = stream.Length - 1; + long currentPos = stream.Position; + long newPos = currentPos; + + if (currentPos >= maxPos) + { + return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); + } + + int value = stream.Read(buffer, 0, 2); + + if (value == 0) + { + return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); + } + + ushort currentMarker = (ushort)((buffer[0] << 8) | buffer[1]); + if (currentMarker >= JpegConstants.Markers.SOF0 && currentMarker <= JpegConstants.Markers.COM) + { + return new FileMarker(currentMarker, stream.Position - 2); + } + + value = stream.Read(buffer, 0, 2); + + if (value == 0) + { + return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); + } + + ushort newMarker = (ushort)((buffer[0] << 8) | buffer[1]); + while (!(newMarker >= JpegConstants.Markers.SOF0 && newMarker <= JpegConstants.Markers.COM)) + { + if (++newPos >= maxPos) + { + return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); + } + + stream.Read(buffer, 0, 2); + newMarker = (ushort)((buffer[0] << 8) | buffer[1]); + } + + return new FileMarker(newMarker, newPos, true); + } + /// /// Decodes the image from the specified and sets /// the data to image. @@ -85,27 +191,52 @@ namespace ImageSharp.Formats.Jpeg.Port return image; } + /// + public void Dispose() + { + this.Dispose(true); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// Whether to dispose of managed objects + protected virtual void Dispose(bool disposing) + { + if (!this.isDisposed) + { + if (disposing) + { + this.frame.Dispose(); + } + + // TODO: set large fields to null. + this.isDisposed = true; + } + } + private void ParseStream() { // Check for the Start Of Image marker. - ushort fileMarker = this.ReadUint16(); - if (fileMarker != JpegConstants.Markers.SOI) + var fileMarker = new FileMarker(this.ReadUint16(), 0); + if (fileMarker.Marker != JpegConstants.Markers.SOI) { throw new ImageFormatException("Missing SOI marker."); } - fileMarker = this.ReadUint16(); + ushort marker = this.ReadUint16(); + fileMarker = new FileMarker(marker, (int)this.InputStream.Position - 2); this.quantizationTables = new QuantizationTables(); this.dcHuffmanTables = new HuffmanTables(); this.acHuffmanTables = new HuffmanTables(); - while (fileMarker != JpegConstants.Markers.EOI) + while (fileMarker.Marker != JpegConstants.Markers.EOI) { // Get the marker length int remaining = this.ReadUint16() - 2; - switch (fileMarker) + switch (fileMarker.Marker) { case JpegConstants.Markers.APP0: this.ProcessApplicationHeaderMarker(remaining); @@ -148,12 +279,12 @@ namespace ImageSharp.Formats.Jpeg.Port break; case JpegConstants.Markers.SOS: - this.ProcessStartOfScan(); + this.ProcessStartOfScanMarker(); break; } // Read on - fileMarker = this.FindNextFileMarker(); + fileMarker = FindNextFileMarker(this.InputStream); } } @@ -281,7 +412,7 @@ namespace ImageSharp.Formats.Jpeg.Port /// /// The remaining bytes in the segment block. /// The current frame marker. - private void ProcessStartOfFrameMarker(int remaining, ushort frameMarker) + private void ProcessStartOfFrameMarker(int remaining, FileMarker frameMarker) { if (this.frame != null) { @@ -292,8 +423,8 @@ namespace ImageSharp.Formats.Jpeg.Port this.frame = new Frame { - Extended = frameMarker == JpegConstants.Markers.SOF1, - Progressive = frameMarker == JpegConstants.Markers.SOF2, + Extended = frameMarker.Marker == JpegConstants.Markers.SOF1, + Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2, Precision = this.temp[0], Scanlines = (short)((this.temp[1] << 8) | this.temp[2]), SamplesPerLine = (short)((this.temp[3] << 8) | this.temp[4]), @@ -304,9 +435,9 @@ namespace ImageSharp.Formats.Jpeg.Port int maxV = 0; int index = 6; - // TODO: Pool this. + // No need to pool this. They max out at 4 this.frame.ComponentIds = new byte[this.frame.ComponentCount]; - this.frame.Components = new Component[this.frame.ComponentCount]; + this.frame.Components = new FrameComponent[this.frame.ComponentCount]; for (int i = 0; i < this.frame.ComponentCount; i++) { @@ -389,30 +520,36 @@ namespace ImageSharp.Formats.Jpeg.Port /// /// Processes the SOS (Start of scan marker). /// - private void ProcessStartOfScan() + private void ProcessStartOfScanMarker() { int selectorsCount = this.InputStream.ReadByte(); - var components = new List(); - for (int i = 0; i < selectorsCount; i++) { byte componentIndex = this.frame.ComponentIds[this.InputStream.ReadByte() - 1]; - Component component = this.frame.Components[componentIndex]; + ref FrameComponent component = ref this.frame.Components[componentIndex]; int tableSpec = this.InputStream.ReadByte(); component.DCHuffmanTableId = tableSpec >> 4; component.ACHuffmanTableId = tableSpec & 15; - components.Add(component); } this.InputStream.Read(this.temp, 0, 3); + int spectralStart = this.temp[0]; int spectralEnd = this.temp[1]; int successiveApproximation = this.temp[2]; - } - - private int DecodeScan(List components, int spectralStart, int spectralEnd, int successivePrev, int successive) - { - return 0; + var scanDecoder = default(ScanDecoder); + + scanDecoder.DecodeScan( + this.frame, + this.InputStream, + this.dcHuffmanTables, + this.acHuffmanTables, + this.frame.Components, + this.resetInterval, + spectralStart, + spectralEnd, + successiveApproximation >> 4, + successiveApproximation & 15); } /// @@ -430,8 +567,8 @@ namespace ImageSharp.Formats.Jpeg.Port length--; } - // TODO: Check the capacity here. Seems to max at 2 - var code = new List { new HuffmanBranch(new List()) }; + // TODO: Check the branch children capacity here. Seems to max at 2 + var code = new List { new HuffmanBranch(-1) }; HuffmanBranch p = code[0]; int k = 0; @@ -441,7 +578,7 @@ namespace ImageSharp.Formats.Jpeg.Port for (int j = 0; j < codeLengths[i]; j++) { p = code.Pop(); - p.Children.SafeInsert(p.Index, new HuffmanBranch(values[k])); + p.Children[p.Index] = new HuffmanBranch(values[k]); while (p.Index > 0) { p = code.Pop(); @@ -451,9 +588,9 @@ namespace ImageSharp.Formats.Jpeg.Port code.Add(p); while (code.Count <= i) { - q = new HuffmanBranch(new List()); + q = new HuffmanBranch(-1); code.Add(q); - p.Children.SafeInsert(p.Index, new HuffmanBranch(q.Children)); + p.Children[p.Index] = new HuffmanBranch(q.Children); p = q; } @@ -463,9 +600,9 @@ namespace ImageSharp.Formats.Jpeg.Port if (i + 1 < length) { // p here points to last code - q = new HuffmanBranch(new List()); + q = new HuffmanBranch(-1); code.Add(q); - p.Children.SafeInsert(p.Index, new HuffmanBranch(q.Children)); + p.Children[p.Index] = new HuffmanBranch(q.Children); p = q; } } @@ -478,21 +615,21 @@ namespace ImageSharp.Formats.Jpeg.Port /// private void PrepareComponents() { - int mcusPerLine = this.frame.SamplesPerLine / 8 / this.frame.MaxHorizontalFactor; - int mcusPerColumn = this.frame.Scanlines / 8 / this.frame.MaxVerticalFactor; + int mcusPerLine = (int)Math.Ceiling(this.frame.SamplesPerLine / 8D / this.frame.MaxHorizontalFactor); + int mcusPerColumn = (int)Math.Ceiling(this.frame.Scanlines / 8D / 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 blocksPerLine = (int)Math.Ceiling(Math.Ceiling(this.frame.SamplesPerLine / 8D) * component.HorizontalFactor / this.frame.MaxHorizontalFactor); + int blocksPerColumn = (int)Math.Ceiling(Math.Ceiling(this.frame.Scanlines / 8D) * 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]; + // Pooled. Disposed via frame siposal + component.BlockData = new Buffer(blocksBufferSize); component.BlocksPerLine = blocksPerLine; component.BlocksPerColumn = blocksPerColumn; } @@ -501,59 +638,11 @@ namespace ImageSharp.Formats.Jpeg.Port 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 == -1) - { - 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 + [MethodImpl(MethodImplOptions.AggressiveInlining)] private ushort ReadUint16() { this.InputStream.Read(this.uint16Buffer, 0, 2); diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs index 455af48ad..56771bcf5 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs @@ -23,21 +23,21 @@ namespace ImageSharp.Benchmarks.Image { if (this.jpegBytes == null) { - this.jpegBytes = File.ReadAllBytes("../ImageSharp.Tests/TestImages/Formats/Jpg/Baseline/Calliphora.jpg"); + this.jpegBytes = File.ReadAllBytes("../../../../../../../../ImageSharp.Tests/TestImages/Formats/Jpg/Baseline/Calliphora.jpg"); } } - [Benchmark(Baseline = true, Description = "System.Drawing Jpeg")] - public Size JpegSystemDrawing() - { - using (MemoryStream memoryStream = new MemoryStream(this.jpegBytes)) - { - using (Image image = Image.FromStream(memoryStream)) - { - return image.Size; - } - } - } + //[Benchmark(Baseline = true, Description = "System.Drawing Jpeg")] + //public Size JpegSystemDrawing() + //{ + // using (MemoryStream memoryStream = new MemoryStream(this.jpegBytes)) + // { + // using (Image image = Image.FromStream(memoryStream)) + // { + // return image.Size; + // } + // } + //} [Benchmark(Description = "ImageSharp Jpeg")] public CoreSize JpegCore() From d572b750369bcb413da99b8ec527a580f7f8f3c0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 23 Jun 2017 01:04:13 +1000 Subject: [PATCH 11/43] Begin second phase of decoding --- .../Formats/Jpeg/Port/Components/Adobe.cs | 38 ++++ .../Formats/Jpeg/Port/Components/Component.cs | 48 +++++ .../Jpeg/Port/Components/ComponentBlocks.cs | 53 +++++ .../Formats/Jpeg/Port/Components/Frame.cs | 8 +- .../Jpeg/Port/Components/FrameComponent.cs | 5 - .../Formats/Jpeg/Port/Components/JFif.cs | 43 ++++ .../Formats/Jpeg/Port/JpegConstants.cs | 58 +++++- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 188 ++++++++++++++---- 8 files changed, 389 insertions(+), 52 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/ComponentBlocks.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/JFif.cs diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs new file mode 100644 index 000000000..130b7bdb3 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// ReSharper disable InconsistentNaming +namespace ImageSharp.Formats.Jpeg.Port.Components +{ + /// + /// Provides information about the Adobe marker segment + /// + internal struct Adobe + { + /// + /// The DCT Encode Version + /// + public short DCTEncodeVersion; + + /// + /// 0x0 : (none) + /// Bit 15 : Encoded with Blend=1 downsampling + /// + public short APP14Flags0; + + /// + /// 0x0 : (none) + /// + public short APP14Flags1; + + /// + /// Determines the colorspace transform + /// 00 : Unknown (RGB or CMYK) + /// 01 : YCbCr + /// 02 : YCCK + /// + public byte ColorTransform; + } +} 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 000000000..e5ae70f1f --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpeg.Port.Components +{ + using System; + + using ImageSharp.Memory; + + /// + /// Represents a component block + /// + internal struct Component : IDisposable + { + /// + /// Gets or sets the output + /// + public Buffer Output; + + /// + /// Gets or sets the horizontal scaling factor + /// + public int ScaleX; + + /// + /// Gets or sets the vertical scaling factor + /// + public int ScaleY; + + /// + /// Gets or sets the number of blocks per line + /// + public int BlocksPerLine; + + /// + /// Gets or sets the number of blocks per column + /// + public int BlocksPerColumn; + + /// + public void Dispose() + { + this.Output?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ComponentBlocks.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ComponentBlocks.cs new file mode 100644 index 000000000..5b66ad598 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ComponentBlocks.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 +{ + using System; + + /// + /// Contains all the decoded component blocks + /// + internal class ComponentBlocks : IDisposable + { + private bool isDisposed; + + /// + /// Gets or sets the component blocks + /// + public Component[] Components { get; set; } + + /// + public void Dispose() + { + this.Dispose(true); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// Whether to dispose of managed objects + protected virtual void Dispose(bool disposing) + { + if (!this.isDisposed) + { + if (disposing) + { + if (this.Components != null) + { + for (int i = 0; i < this.Components.Length; i++) + { + this.Components[i].Dispose(); + } + } + } + + // Set large fields to null. + this.Components = null; + this.isDisposed = true; + } + } + } +} \ 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 index 97c422ca3..20fd2e9e8 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs @@ -93,15 +93,17 @@ namespace ImageSharp.Formats.Jpeg.Port.Components if (disposing) { - foreach (FrameComponent component in this.Components) + if (this.Components != null) { - component.Dispose(); + for (int i = 0; i < this.Components.Length; i++) + { + this.Components[i].Dispose(); + } } } // Set large fields to null. this.Components = null; - this.isDisposed = true; } } diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs index 18176bde7..0cb9bbb1c 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs @@ -39,11 +39,6 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// public byte QuantizationIdentifier; - /// - /// Gets or sets the quantization table - /// - public short[] QuantizationTable; - /// /// Gets or sets the block data /// diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/JFif.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/JFif.cs new file mode 100644 index 000000000..7fa6c44d0 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/JFif.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 +{ + /// + /// 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? + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs index 0ad8afa91..08ae5543d 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs @@ -1,4 +1,9 @@ -// ReSharper disable InconsistentNaming +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// ReSharper disable InconsistentNaming namespace ImageSharp.Formats.Jpeg.Port { /// @@ -191,6 +196,11 @@ namespace ImageSharp.Formats.Jpeg.Port /// public const ushort RST7 = 0xFFD7; + /// + /// Marker prefix. Next byte is a marker. + /// + public const ushort XFF = 0xFFFF; + /// /// Contains JFIF specific markers /// @@ -216,6 +226,52 @@ namespace ImageSharp.Formats.Jpeg.Port /// public const byte Null = 0; } + + /// + /// Contains Adobe specific markers + /// + public static class Adobe + { + /// + /// Represents A in ASCII + /// + public const byte A = 0x41; + + /// + /// Represents d in ASCII + /// + public const byte D = 0x64; + + /// + /// Represents b in ASCII + /// + public const byte O = 0x6F; + + /// + /// Represents b in ASCII + /// + public const byte B = 0x62; + + /// + /// Represents e in ASCII + /// + public const byte E = 0x65; + + /// + /// The color transform is unknown.(RGB or CMYK) + /// + public const byte ColorTransformUnknown = 0; + + /// + /// The color transform is YCbCr (luminance, red chroma, blue chroma) + /// + public const byte ColorTransformYCbCr = 1; + + /// + /// The color transform is YCCK (luminance, red chroma, blue chroma, keyline) + /// + public const byte ColorTransformYcck = 2; + } } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 873d4623d..deeec34fc 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -46,13 +46,29 @@ namespace ImageSharp.Formats.Jpeg.Port private Frame frame; + private ComponentBlocks components; + private ushort resetInterval; + private int width; + + private int height; + + private int numComponents; + /// - /// Contains information about the jFIF marker + /// Contains information about the JFIF marker /// private JFif jFif; + /// + /// Contains information about the Adobe marker + /// + private Adobe adobe; + + /// + /// A value indicating whether the decoder has been disposed + /// private bool isDisposed; /// @@ -208,9 +224,12 @@ namespace ImageSharp.Formats.Jpeg.Port if (disposing) { this.frame.Dispose(); + this.components.Dispose(); } - // TODO: set large fields to null. + // Set large fields to null. + this.frame = null; + this.components = null; this.isDisposed = true; } } @@ -255,9 +274,16 @@ namespace ImageSharp.Formats.Jpeg.Port case JpegConstants.Markers.APP11: case JpegConstants.Markers.APP12: case JpegConstants.Markers.APP13: + break; + case JpegConstants.Markers.APP14: + this.ProcessApp14Marker(remaining); + break; + case JpegConstants.Markers.APP15: case JpegConstants.Markers.COM: + + // TODO: Read data block break; case JpegConstants.Markers.DQT: @@ -275,17 +301,66 @@ namespace ImageSharp.Formats.Jpeg.Port break; case JpegConstants.Markers.DRI: - this.resetInterval = this.ReadUint16(); + this.ProcessDefineRestartIntervalMarker(remaining); break; case JpegConstants.Markers.SOS: this.ProcessStartOfScanMarker(); break; + + case JpegConstants.Markers.XFF: + if ((byte)this.InputStream.ReadByte() != 0xFF) + { + // Avoid skipping a valid marker + this.InputStream.Position -= 2; + } + else + { + // Rewind that last byte we read + this.InputStream.Position -= 1; + } + + break; + + default: + + // Skip back as it could be incorrect encoding -- last 0xFF byte of the previous + // block was eaten by the encoder + this.InputStream.Position -= 3; + this.InputStream.Read(this.temp, 0, 2); + if (this.temp[0] == 0xFF && this.temp[1] >= 0xC0 && this.temp[1] <= 0xFE) + { + // Rewind that last bytes we read + this.InputStream.Position -= 2; + } + + break; } - // Read on + // Read on. TODO: Test this on damaged images. fileMarker = FindNextFileMarker(this.InputStream); } + + this.width = this.frame.SamplesPerLine; + this.height = this.frame.Scanlines; + this.components = new ComponentBlocks { Components = new Component[this.frame.ComponentCount] }; + + for (int i = 0; i < this.components.Components.Length; i++) + { + ref var frameComponent = ref this.frame.Components[i]; + var component = new Component + { + ScaleX = frameComponent.HorizontalFactor / this.frame.MaxHorizontalFactor, + ScaleY = frameComponent.VerticalFactor / this.frame.MaxVerticalFactor, + BlocksPerLine = frameComponent.BlocksPerLine, + BlocksPerColumn = frameComponent.BlocksPerColumn + }; + + this.BuildComponentData(ref component); + this.components.Components[i] = component; + } + + this.numComponents = this.components.Components.Length; } /// @@ -322,7 +397,47 @@ namespace ImageSharp.Formats.Jpeg.Port }; } - // Skip thumbnails for now. + // TODO: thumbnail + if (remaining > 0) + { + this.InputStream.Skip(remaining); + } + } + + /// + /// Processes the application header containing the Adobe identifier + /// which stores image encoding information for DCT filters. + /// + /// The remaining bytes in the segment block. + private void ProcessApp14Marker(int remaining) + { + if (remaining < 12) + { + // Skip the application header length + this.InputStream.Skip(remaining); + return; + } + + this.InputStream.Read(this.temp, 0, 12); + remaining -= 12; + + bool isAdobe = this.temp[0] == JpegConstants.Markers.Adobe.A && + this.temp[1] == JpegConstants.Markers.Adobe.D && + this.temp[2] == JpegConstants.Markers.Adobe.O && + this.temp[3] == JpegConstants.Markers.Adobe.B && + this.temp[4] == JpegConstants.Markers.Adobe.E; + + if (isAdobe) + { + this.adobe = new Adobe + { + DCTEncodeVersion = (short)((this.temp[5] << 8) | this.temp[6]), + APP14Flags0 = (short)((this.temp[7] << 8) | this.temp[8]), + APP14Flags1 = (short)((this.temp[9] << 8) | this.temp[10]), + ColorTransform = this.temp[11] + }; + } + if (remaining > 0) { this.InputStream.Skip(remaining); @@ -439,7 +554,7 @@ namespace ImageSharp.Formats.Jpeg.Port this.frame.ComponentIds = new byte[this.frame.ComponentCount]; this.frame.Components = new FrameComponent[this.frame.ComponentCount]; - for (int i = 0; i < this.frame.ComponentCount; i++) + for (int i = 0; i < this.frame.Components.Length; i++) { int h = this.temp[index + 1] >> 4; int v = this.temp[index + 1] & 15; @@ -462,7 +577,6 @@ namespace ImageSharp.Formats.Jpeg.Port this.frame.ComponentIds[i] = (byte)i; - // Don't assign the table yet. index += 3; } @@ -517,6 +631,21 @@ namespace ImageSharp.Formats.Jpeg.Port } } + /// + /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in + /// macroblocks + /// + /// The remaining bytes in the segment block. + private void ProcessDefineRestartIntervalMarker(int remaining) + { + if (remaining != 2) + { + throw new ImageFormatException("DRI has wrong length"); + } + + this.resetInterval = this.ReadUint16(); + } + /// /// Processes the SOS (Start of scan marker). /// @@ -552,6 +681,15 @@ namespace ImageSharp.Formats.Jpeg.Port successiveApproximation & 15); } + /// + /// Build the data for the given component + /// + /// The component + private void BuildComponentData(ref Component component) + { + // TODO: Write this + } + /// /// Builds the huffman tables /// @@ -648,41 +786,5 @@ namespace ImageSharp.Formats.Jpeg.Port this.InputStream.Read(this.uint16Buffer, 0, 2); return (ushort)((this.uint16Buffer[0] << 8) | this.uint16Buffer[1]); } - - /// - /// 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? - } } } \ No newline at end of file From 0b7a08a06281c3c23ec2e9ea36cc44838a5878f1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 23 Jun 2017 13:06:52 +1000 Subject: [PATCH 12/43] Impove disposal --- .../Jpeg/Port/Components/ComponentBlocks.cs | 27 ++-------- .../Formats/Jpeg/Port/Components/Frame.cs | 33 +++--------- .../Port/Components/QuantizationTables.cs | 16 ++++-- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 53 +++++++------------ 4 files changed, 42 insertions(+), 87 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ComponentBlocks.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ComponentBlocks.cs index 5b66ad598..a72835e75 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ComponentBlocks.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ComponentBlocks.cs @@ -10,10 +10,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// /// Contains all the decoded component blocks /// - internal class ComponentBlocks : IDisposable + internal sealed class ComponentBlocks : IDisposable { - private bool isDisposed; - /// /// Gets or sets the component blocks /// @@ -22,31 +20,14 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// public void Dispose() { - this.Dispose(true); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// Whether to dispose of managed objects - protected virtual void Dispose(bool disposing) - { - if (!this.isDisposed) + if (this.Components != null) { - if (disposing) + for (int i = 0; i < this.Components.Length; i++) { - if (this.Components != null) - { - for (int i = 0; i < this.Components.Length; i++) - { - this.Components[i].Dispose(); - } - } + this.Components[i].Dispose(); } - // Set large fields to null. this.Components = null; - this.isDisposed = true; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs index 20fd2e9e8..06b4bbc24 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs @@ -10,10 +10,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// /// Represent a single jpeg frame /// - internal class Frame : IDisposable + internal sealed class Frame : IDisposable { - private bool isDisposed; - /// /// Gets or sets a value indicating whether the frame uses the extended specification /// @@ -77,34 +75,15 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// public void Dispose() { - this.Dispose(true); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// Whether to dispose of managed objects - protected virtual void Dispose(bool disposing) - { - if (this.isDisposed) + if (this.Components != null) { - return; - } - - if (disposing) - { - if (this.Components != null) + for (int i = 0; i < this.Components.Length; i++) { - for (int i = 0; i < this.Components.Length; i++) - { - this.Components[i].Dispose(); - } + this.Components[i].Dispose(); } - } - // Set large fields to null. - this.Components = null; - this.isDisposed = true; + this.Components = null; + } } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs index 1ca31b31b..fa57a18dd 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs @@ -5,15 +5,15 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { + using System; using System.Runtime.CompilerServices; 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 + internal sealed class QuantizationTables : IDisposable { /// /// Gets the ZigZag scan table @@ -46,6 +46,16 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// /// Gets or sets the quantization tables. /// - public Fast2DArray Tables { get; set; } = new Fast2DArray(64, 4); + public Buffer2D Tables { get; set; } + + /// + public void Dispose() + { + if (this.Tables != null) + { + this.Tables.Dispose(); + this.Tables = null; + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index deeec34fc..8d3cec616 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -19,7 +19,7 @@ namespace ImageSharp.Formats.Jpeg.Port /// Performs the jpeg decoding operation. /// Ported from /// - internal class JpegDecoderCore : IDisposable + internal sealed class JpegDecoderCore : IDisposable { /// /// The decoder options. @@ -66,11 +66,6 @@ namespace ImageSharp.Formats.Jpeg.Port /// private Adobe adobe; - /// - /// A value indicating whether the decoder has been disposed - /// - private bool isDisposed; - /// /// Initializes a new instance of the class. /// @@ -210,28 +205,14 @@ namespace ImageSharp.Formats.Jpeg.Port /// public void Dispose() { - this.Dispose(true); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// Whether to dispose of managed objects - protected virtual void Dispose(bool disposing) - { - if (!this.isDisposed) - { - if (disposing) - { - this.frame.Dispose(); - this.components.Dispose(); - } - - // Set large fields to null. - this.frame = null; - this.components = null; - this.isDisposed = true; - } + this.frame.Dispose(); + this.components.Dispose(); + this.quantizationTables.Dispose(); + + // Set large fields to null. + this.frame = null; + this.components = null; + this.quantizationTables = null; } private void ParseStream() @@ -253,11 +234,12 @@ namespace ImageSharp.Formats.Jpeg.Port while (fileMarker.Marker != JpegConstants.Markers.EOI) { // Get the marker length - int remaining = this.ReadUint16() - 2; + int remaining; switch (fileMarker.Marker) { case JpegConstants.Markers.APP0: + remaining = this.ReadUint16() - 2; this.ProcessApplicationHeaderMarker(remaining); break; @@ -277,6 +259,7 @@ namespace ImageSharp.Formats.Jpeg.Port break; case JpegConstants.Markers.APP14: + remaining = this.ReadUint16() - 2; this.ProcessApp14Marker(remaining); break; @@ -287,24 +270,29 @@ namespace ImageSharp.Formats.Jpeg.Port break; case JpegConstants.Markers.DQT: + remaining = this.ReadUint16() - 2; this.ProcessDqtMarker(remaining); break; case JpegConstants.Markers.SOF0: case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF2: + remaining = this.ReadUint16() - 2; this.ProcessStartOfFrameMarker(remaining, fileMarker); break; case JpegConstants.Markers.DHT: + remaining = this.ReadUint16() - 2; this.ProcessDefineHuffmanTablesMarker(remaining); break; case JpegConstants.Markers.DRI: + remaining = this.ReadUint16() - 2; this.ProcessDefineRestartIntervalMarker(remaining); break; case JpegConstants.Markers.SOS: + this.InputStream.Skip(2); this.ProcessStartOfScanMarker(); break; @@ -312,11 +300,6 @@ namespace ImageSharp.Formats.Jpeg.Port if ((byte)this.InputStream.ReadByte() != 0xFF) { // Avoid skipping a valid marker - this.InputStream.Position -= 2; - } - else - { - // Rewind that last byte we read this.InputStream.Position -= 1; } @@ -453,6 +436,8 @@ namespace ImageSharp.Formats.Jpeg.Port /// private void ProcessDqtMarker(int remaining) { + // Pooled. Disposed on disposal of decoder + this.quantizationTables.Tables = new Buffer2D(64, 4); while (remaining > 0) { bool done = false; From 12d81aec4bda1b307495508e966f9884b6e3ca88 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 24 Jun 2017 00:03:01 +1000 Subject: [PATCH 13/43] Experiment with new file marker finder --- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 2 +- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 50 +++++++++++++++++-- .../ImageSharp.Benchmarks/Image/DecodeJpeg.cs | 1 + 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 117edb225..33d82ace8 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -21,7 +21,7 @@ namespace ImageSharp.Formats { Guard.NotNull(stream, "stream"); - // using (JpegDecoderCore decoder = new JpegDecoderCore(options, configuration)) + // using (var decoder = new JpegDecoderCore(options, configuration)) // { // return decoder.Decode(stream); // } diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 8d3cec616..4bb93151a 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -185,6 +185,45 @@ namespace ImageSharp.Formats.Jpeg.Port return new FileMarker(newMarker, newPos, true); } + /// + /// Finds the next file marker within the byte stream. Used for testing. Slower as it only reads on byte at a time + /// + /// The input stream + /// The + public static FileMarker FindNextFileMarkerNew(Stream stream) + { + while (true) + { + int value = stream.ReadByte(); + + if (value == -1) + { + // We've reached the end of the stream + return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); + } + + byte prefix = (byte)value; + byte suffix = JpegConstants.Markers.Prefix; + + // According to Section B.1.1.2: + // "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code 0xFF." + while (prefix == JpegConstants.Markers.Prefix && suffix == JpegConstants.Markers.Prefix) + { + value = stream.ReadByte(); + + if (value == -1) + { + // We've reached the end of the stream + return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); + } + + suffix = (byte)value; + } + + return new FileMarker((ushort)((prefix << 8) | suffix), (int)(stream.Position - 2)); + } + } + /// /// Decodes the image from the specified and sets /// the data to image. @@ -205,9 +244,9 @@ namespace ImageSharp.Formats.Jpeg.Port /// public void Dispose() { - this.frame.Dispose(); - this.components.Dispose(); - this.quantizationTables.Dispose(); + this.frame?.Dispose(); + this.components?.Dispose(); + this.quantizationTables?.Dispose(); // Set large fields to null. this.frame = null; @@ -315,13 +354,14 @@ namespace ImageSharp.Formats.Jpeg.Port { // Rewind that last bytes we read this.InputStream.Position -= 2; + break; } - break; + throw new ImageFormatException($"Unknown Marker {fileMarker.Marker} at {fileMarker.Position}"); } // Read on. TODO: Test this on damaged images. - fileMarker = FindNextFileMarker(this.InputStream); + fileMarker = FindNextFileMarkerNew(this.InputStream); } this.width = this.frame.SamplesPerLine; diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs index 56771bcf5..28c7d461c 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs @@ -14,6 +14,7 @@ namespace ImageSharp.Benchmarks.Image using CoreSize = ImageSharp.Size; + [Config(typeof(Config))] public class DecodeJpeg : BenchmarkBase { private byte[] jpegBytes; From e01f466eb6d1ad63691a79c817d9eb5fa3b5afaf Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 25 Jun 2017 22:44:07 +1000 Subject: [PATCH 14/43] Decoder now doesn't break tests --- .../Jpeg/Port/Components/HuffmanTables.cs | 18 +-- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 117 ++++++------------ tests/ImageSharp.Tests/xunit.runner.json | 3 +- 3 files changed, 45 insertions(+), 93 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs index a040d21e7..a8644d645 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs @@ -13,9 +13,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// internal class HuffmanTables { - private HuffmanBranch[] first; - - private HuffmanBranch[] second; + private readonly HuffmanBranch[][] tables = new HuffmanBranch[4][]; /// /// Gets or sets the table at the given index. @@ -27,23 +25,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if (index == 0) - { - return this.first; - } - - return this.second; + return this.tables[index]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] set { - if (index == 0) - { - this.first = value; - } - - this.second = value; + this.tables[index] = value; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 4bb93151a..5d50ae535 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -90,47 +90,32 @@ namespace ImageSharp.Formats.Jpeg.Port public static FileMarker FindNextFileMarkerOld(Stream stream) { byte[] buffer = new byte[2]; - while (true) + int value = stream.Read(buffer, 0, 2); + + if (value == 0) { - int value = stream.Read(buffer, 0, 2); + return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); + } - if (value == 0) - { - return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); - } + // According to Section B.1.1.2: + // "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code 0xFF." + if (buffer[1] != JpegConstants.Markers.Prefix) + { + return new FileMarker((ushort)((buffer[0] << 8) | buffer[1]), (int)(stream.Position - 2)); + } - while (buffer[0] != JpegConstants.Markers.Prefix) + while (buffer[1] == JpegConstants.Markers.Prefix) + { + int suffix = stream.ReadByte(); + if (suffix == -1) { - // 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. - buffer[0] = buffer[1]; - - value = stream.ReadByte(); - if (value == -1) - { - return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); - } - - buffer[1] = (byte)value; + return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); } - return new FileMarker((ushort)((buffer[0] << 8) | buffer[1]), (int)(stream.Position - 2)); + buffer[1] = (byte)value; } + + return new FileMarker((ushort)((buffer[0] << 8) | buffer[1]), (int)(stream.Position - 2)); } /// @@ -185,45 +170,6 @@ namespace ImageSharp.Formats.Jpeg.Port return new FileMarker(newMarker, newPos, true); } - /// - /// Finds the next file marker within the byte stream. Used for testing. Slower as it only reads on byte at a time - /// - /// The input stream - /// The - public static FileMarker FindNextFileMarkerNew(Stream stream) - { - while (true) - { - int value = stream.ReadByte(); - - if (value == -1) - { - // We've reached the end of the stream - return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); - } - - byte prefix = (byte)value; - byte suffix = JpegConstants.Markers.Prefix; - - // According to Section B.1.1.2: - // "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code 0xFF." - while (prefix == JpegConstants.Markers.Prefix && suffix == JpegConstants.Markers.Prefix) - { - value = stream.ReadByte(); - - if (value == -1) - { - // We've reached the end of the stream - return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); - } - - suffix = (byte)value; - } - - return new FileMarker((ushort)((prefix << 8) | suffix), (int)(stream.Position - 2)); - } - } - /// /// Decodes the image from the specified and sets /// the data to image. @@ -357,11 +303,12 @@ namespace ImageSharp.Formats.Jpeg.Port break; } - throw new ImageFormatException($"Unknown Marker {fileMarker.Marker} at {fileMarker.Position}"); + // throw new ImageFormatException($"Unknown Marker {fileMarker.Marker} at {fileMarker.Position}"); + break; } // Read on. TODO: Test this on damaged images. - fileMarker = FindNextFileMarkerNew(this.InputStream); + fileMarker = FindNextFileMarkerOld(this.InputStream); } this.width = this.frame.SamplesPerLine; @@ -644,7 +591,6 @@ namespace ImageSharp.Formats.Jpeg.Port i += 17 + codeLengthSum; - // Everything I can discover indicates there's a max of two table per DC AC pair though this limits the index to 16? this.BuildHuffmanTable( huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables, huffmanTableSpec & 15, @@ -679,7 +625,23 @@ namespace ImageSharp.Formats.Jpeg.Port int selectorsCount = this.InputStream.ReadByte(); for (int i = 0; i < selectorsCount; i++) { - byte componentIndex = this.frame.ComponentIds[this.InputStream.ReadByte() - 1]; + int index = -1; + int selector = this.InputStream.ReadByte(); + + foreach (byte id in this.frame.ComponentIds) + { + if (selector == id) + { + index = selector; + } + } + + if (index < 0) + { + throw new ImageFormatException("Unknown component selector"); + } + + byte componentIndex = this.frame.ComponentIds[index]; ref FrameComponent component = ref this.frame.Components[componentIndex]; int tableSpec = this.InputStream.ReadByte(); component.DCHuffmanTableId = tableSpec >> 4; @@ -717,6 +679,7 @@ namespace ImageSharp.Formats.Jpeg.Port /// /// Builds the huffman tables + /// TODO: This is our bottleneck. We should use a faster algorithm with a LUT. /// /// The tables /// The table index diff --git a/tests/ImageSharp.Tests/xunit.runner.json b/tests/ImageSharp.Tests/xunit.runner.json index df1c3d50d..cbaa8f432 100644 --- a/tests/ImageSharp.Tests/xunit.runner.json +++ b/tests/ImageSharp.Tests/xunit.runner.json @@ -1,3 +1,4 @@ { - "methodDisplay": "method" + "methodDisplay": "method", + "diagnosticMessages": true } \ No newline at end of file From 33893e26e9c2581b96e4cc5faafa0560c70d8b2e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 26 Jun 2017 01:18:31 +1000 Subject: [PATCH 15/43] Fix progressive decoding --- .../Jpeg/Port/Components/ScanDecoder.cs | 98 ++++++++++--------- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 50 +++++----- 2 files changed, 79 insertions(+), 69 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 09837aef0..b0c9979d2 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -49,18 +49,31 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// The DC Huffman tables /// The AC Huffman tables /// The scan components + /// The component index within the array + /// The length of the components. Different to the array length /// The reset interval /// The spectral selection start /// The spectral selection end /// The successive approximation bit high end /// The successive approximation bit low end - public void DecodeScan(Frame frame, Stream stream, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, FrameComponent[] components, ushort resetInterval, int spectralStart, int spectralEnd, int successivePrev, int successive) + public void DecodeScan( + Frame frame, + Stream stream, + HuffmanTables dcHuffmanTables, + HuffmanTables acHuffmanTables, + FrameComponent[] components, + int componentIndex, + int componentsLength, + ushort resetInterval, + int spectralStart, + int spectralEnd, + int successivePrev, + int successive) { this.specStart = spectralStart; this.specEnd = spectralEnd; this.successiveState = successive; bool progressive = frame.Progressive; - int componentsLength = components.Length; int mcusPerLine = frame.McusPerLine; // TODO: Delegate action will not be fast @@ -100,14 +113,14 @@ namespace ImageSharp.Formats.Jpeg.Port.Components int mcuExpected; if (componentsLength == 1) { - mcuExpected = components[0].BlocksPerLine * components[0].BlocksPerColumn; + mcuExpected = components[componentIndex].BlocksPerLine * components[componentIndex].BlocksPerColumn; } else { mcuExpected = mcusPerLine * frame.McusPerColumn; } - FileMarker fileMarker; + // FileMarker fileMarker; while (mcu < mcuExpected) { // Reset interval stuff @@ -122,7 +135,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components if (componentsLength == 1) { - ref FrameComponent component = ref components[0]; + ref FrameComponent component = ref components[componentIndex]; for (int n = 0; n < mcuToRead; n++) { DecodeBlock(dcHuffmanTables, acHuffmanTables, ref component, decodeFn, mcu, stream); @@ -154,45 +167,41 @@ namespace ImageSharp.Formats.Jpeg.Port.Components // Find marker this.bitsCount = 0; - // TODO: We need to make sure we are not overwriting anything here. - fileMarker = JpegDecoderCore.FindNextFileMarker(stream); - - // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past - // those to attempt to find a valid marker (fixes issue4090.pdf) in original code. - if (fileMarker.Invalid) - { -#if DEBUG - Debug.WriteLine("DecodeScan - Unexpected MCU data, next marker is: " + fileMarker.Marker.ToString("X")); -#endif - } - - ushort marker = fileMarker.Marker; - if (marker <= 0xFF00) - { - throw new ImageFormatException("Marker was not found"); - } - - if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) - { - // RSTx - stream.Skip(2); - } - else - { - break; - } + // // TODO: We need to make sure we are not overwriting anything here. + // fileMarker = JpegDecoderCore.FindNextFileMarker(stream); + // // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past + // // those to attempt to find a valid marker (fixes issue4090.pdf) in original code. + // if (fileMarker.Invalid) + // { + // #if DEBUG + // Debug.WriteLine("DecodeScan - Unexpected MCU data, next marker is: " + fileMarker.Marker.ToString("X")); + // #endif + // } + // ushort marker = fileMarker.Marker; + // if (marker <= 0xFF00) + // { + // throw new ImageFormatException("Marker was not found"); + // } + // if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) + // { + // // RSTx + // stream.Skip(2); + // } + // else + // { + // break; + // } } - fileMarker = JpegDecoderCore.FindNextFileMarker(stream); - - // Some images include more Scan blocks than expected, skip past those and - // attempt to find the next valid marker (fixes issue8182.pdf) in original code. - if (fileMarker.Invalid) - { -#if DEBUG - Debug.WriteLine("DecodeScan - Unexpected MCU data, next marker is: " + fileMarker.Marker.ToString("X")); -#endif - } + // fileMarker = JpegDecoderCore.FindNextFileMarker(stream); + // // Some images include more Scan blocks than expected, skip past those and + // // attempt to find the next valid marker (fixes issue8182.pdf) in original code. + // if (fileMarker.Invalid) + // { + // #if DEBUG + // Debug.WriteLine("DecodeScan - Unexpected MCU data, next marker is: " + fileMarker.Marker.ToString("X")); + // #endif + // } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -231,7 +240,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } this.bitsData = stream.ReadByte(); - if (this.bitsData == 0xFF) + if (this.bitsData == JpegConstants.Markers.Prefix) { int nextByte = stream.ReadByte(); if (nextByte > 0) @@ -252,8 +261,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components HuffmanBranch[] node = tree; while (true) { - int index; - index = this.ReadBit(stream); + int index = this.ReadBit(stream); HuffmanBranch branch = node[index]; node = branch.Children; diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 5d50ae535..abdabcd49 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -87,35 +87,33 @@ namespace ImageSharp.Formats.Jpeg.Port /// /// The input stream /// The - public static FileMarker FindNextFileMarkerOld(Stream stream) + public static FileMarker FindNextFileMarkerNew(Stream stream) { - byte[] buffer = new byte[2]; - int value = stream.Read(buffer, 0, 2); + byte[] marker = new byte[2]; + int value = stream.Read(marker, 0, 2); if (value == 0) { return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); } - // According to Section B.1.1.2: - // "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code 0xFF." - if (buffer[1] != JpegConstants.Markers.Prefix) - { - return new FileMarker((ushort)((buffer[0] << 8) | buffer[1]), (int)(stream.Position - 2)); - } - - while (buffer[1] == JpegConstants.Markers.Prefix) + if (marker[0] == JpegConstants.Markers.Prefix) { - int suffix = stream.ReadByte(); - if (suffix == -1) + // According to Section B.1.1.2: + // "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code 0xFF." + while (marker[1] == JpegConstants.Markers.Prefix) { - return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); - } + int suffix = stream.ReadByte(); + if (suffix == -1) + { + return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); + } - buffer[1] = (byte)value; + marker[1] = (byte)value; + } } - return new FileMarker((ushort)((buffer[0] << 8) | buffer[1]), (int)(stream.Position - 2)); + return new FileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2)); } /// @@ -292,6 +290,7 @@ namespace ImageSharp.Formats.Jpeg.Port default: + // TODO: Not convinced this is required // Skip back as it could be incorrect encoding -- last 0xFF byte of the previous // block was eaten by the encoder this.InputStream.Position -= 3; @@ -308,7 +307,7 @@ namespace ImageSharp.Formats.Jpeg.Port } // Read on. TODO: Test this on damaged images. - fileMarker = FindNextFileMarkerOld(this.InputStream); + fileMarker = FindNextFileMarkerNew(this.InputStream); } this.width = this.frame.SamplesPerLine; @@ -547,7 +546,7 @@ namespace ImageSharp.Formats.Jpeg.Port component.VerticalFactor = v; component.QuantizationIdentifier = this.temp[index + 2]; - this.frame.ComponentIds[i] = (byte)i; + this.frame.ComponentIds[i] = component.Id; index += 3; } @@ -623,16 +622,18 @@ namespace ImageSharp.Formats.Jpeg.Port private void ProcessStartOfScanMarker() { int selectorsCount = this.InputStream.ReadByte(); + int index = -1; for (int i = 0; i < selectorsCount; i++) { - int index = -1; + index = -1; int selector = this.InputStream.ReadByte(); - foreach (byte id in this.frame.ComponentIds) + for (int j = 0; j < this.frame.ComponentIds.Length; j++) { + byte id = this.frame.ComponentIds[j]; if (selector == id) { - index = selector; + index = j; } } @@ -641,8 +642,7 @@ namespace ImageSharp.Formats.Jpeg.Port throw new ImageFormatException("Unknown component selector"); } - byte componentIndex = this.frame.ComponentIds[index]; - ref FrameComponent component = ref this.frame.Components[componentIndex]; + ref FrameComponent component = ref this.frame.Components[index]; int tableSpec = this.InputStream.ReadByte(); component.DCHuffmanTableId = tableSpec >> 4; component.ACHuffmanTableId = tableSpec & 15; @@ -661,6 +661,8 @@ namespace ImageSharp.Formats.Jpeg.Port this.dcHuffmanTables, this.acHuffmanTables, this.frame.Components, + index, + selectorsCount, this.resetInterval, spectralStart, spectralEnd, From 55e9280a74e599a6b2e67ebe7abdae19fd428eaf Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 27 Jun 2017 00:24:54 +1000 Subject: [PATCH 16/43] baseline decode works progressive nearly --- .../Formats/Jpeg/Port/Components/Component.cs | 2 +- .../Formats/Jpeg/Port/Components/IDCT.cs | 222 +++ .../Port/Components/QuantizationTables.cs | 8 +- .../Jpeg/Port/Components/ScanDecoder.cs | 38 +- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 53 +- .../TestImages/Formats/Jpg/jpeg.htm | 63 + .../TestImages/Formats/Jpg/jpg.js | 1205 +++++++++++++++++ 7 files changed, 1568 insertions(+), 23 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpeg.htm create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpg.js diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs index e5ae70f1f..3b462514c 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs @@ -17,7 +17,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// /// Gets or sets the output /// - public Buffer Output; + public Buffer Output; /// /// Gets or sets the horizontal scaling factor diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs new file mode 100644 index 000000000..0e5a97012 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs @@ -0,0 +1,222 @@ +namespace ImageSharp.Formats.Jpeg.Port.Components +{ + using System; + using ImageSharp.Memory; + + /// + /// Performa the invers + /// + internal static class IDCT + { + private const int DctCos1 = 4017; // cos(pi/16) + private const int DctSin1 = 799; // sin(pi/16) + private const int DctCos3 = 3406; // cos(3*pi/16) + private const int DctSin3 = 2276; // sin(3*pi/16) + private const int DctCos6 = 1567; // cos(6*pi/16) + private const int DctSin6 = 3784; // sin(6*pi/16) + private const int DctSqrt2 = 5793; // sqrt(2) + private const int DctSqrt1D2 = 2896; // sqrt(2) / 2 + + /// + /// A port of Poppler's IDCT method which in turn is taken from: + /// Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz, + /// 'Practical Fast 1-D DCT Algorithms with 11 Multiplications', + /// IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, 988-991. + /// + /// The quantization tables + /// The fram component + /// The block buffer offset + /// The computational buffer for holding temp values + public static void QuantizeAndInverse(QuantizationTables quantizationTables, ref FrameComponent component, int blockBufferOffset, Buffer computationBuffer) + { + Span qt = quantizationTables.Tables.GetRowSpan(component.QuantizationIdentifier); + Buffer blockData = component.BlockData; + int v0, v1, v2, v3, v4, v5, v6, v7; + int p0, p1, p2, p3, p4, p5, p6, p7; + int t; + + // inverse DCT on rows + for (int row = 0; row < 64; row += 8) + { + // gather block data + p0 = blockData[blockBufferOffset + row]; + p1 = blockData[blockBufferOffset + row + 1]; + p2 = blockData[blockBufferOffset + row + 2]; + p3 = blockData[blockBufferOffset + row + 3]; + p4 = blockData[blockBufferOffset + row + 4]; + p5 = blockData[blockBufferOffset + row + 5]; + p6 = blockData[blockBufferOffset + row + 6]; + p7 = blockData[blockBufferOffset + row + 7]; + + // dequant p0 + p0 *= qt[row]; + + // check for all-zero AC coefficients + if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) + { + t = ((DctSqrt2 * p0) + 512) >> 10; + short st = (short)t; + computationBuffer[row] = st; + computationBuffer[row + 1] = st; + computationBuffer[row + 2] = st; + computationBuffer[row + 3] = st; + computationBuffer[row + 4] = st; + computationBuffer[row + 5] = st; + computationBuffer[row + 6] = st; + computationBuffer[row + 7] = st; + continue; + } + + // dequant p1 ... p7 + p1 *= qt[row + 1]; + p2 *= qt[row + 2]; + p3 *= qt[row + 3]; + p4 *= qt[row + 4]; + p5 *= qt[row + 5]; + p6 *= qt[row + 6]; + p7 *= qt[row + 7]; + + // stage 4 + v0 = ((DctSqrt2 * p0) + 128) >> 8; + v1 = ((DctSqrt2 * p4) + 128) >> 8; + v2 = p2; + v3 = p6; + v4 = ((DctSqrt1D2 * (p1 - p7)) + 128) >> 8; + v7 = ((DctSqrt1D2 * (p1 + p7)) + 128) >> 8; + v5 = p3 << 4; + v6 = p5 << 4; + + // stage 3 + v0 = (v0 + v1 + 1) >> 1; + v1 = v0 - v1; + t = ((v2 * DctSin6) + (v3 * DctCos6) + 128) >> 8; + v2 = ((v2 * DctCos6) - (v3 * DctSin6) + 128) >> 8; + v3 = t; + v4 = (v4 + v6 + 1) >> 1; + v6 = v4 - v6; + v7 = (v7 + v5 + 1) >> 1; + v5 = v7 - v5; + + // stage 2 + v0 = (v0 + v3 + 1) >> 1; + v3 = v0 - v3; + v1 = (v1 + v2 + 1) >> 1; + v2 = v1 - v2; + t = ((v4 * DctSin3) + (v7 * DctCos3) + 2048) >> 12; + v4 = ((v4 * DctCos3) - (v7 * DctSin3) + 2048) >> 12; + v7 = t; + t = ((v5 * DctSin1) + (v6 * DctCos1) + 2048) >> 12; + v5 = ((v5 * DctCos1) - (v6 * DctSin1) + 2048) >> 12; + v6 = t; + + // stage 1 + computationBuffer[row] = (short)(v0 + v7); + computationBuffer[row + 7] = (short)(v0 - v7); + computationBuffer[row + 1] = (short)(v1 + v6); + computationBuffer[row + 6] = (short)(v1 - v6); + computationBuffer[row + 2] = (short)(v2 + v5); + computationBuffer[row + 5] = (short)(v2 - v5); + computationBuffer[row + 3] = (short)(v3 + v4); + computationBuffer[row + 4] = (short)(v3 - v4); + } + + // inverse DCT on columns + for (int col = 0; col < 8; ++col) + { + p0 = computationBuffer[col]; + p1 = computationBuffer[col + 8]; + p2 = computationBuffer[col + 16]; + p3 = computationBuffer[col + 24]; + p4 = computationBuffer[col + 32]; + p5 = computationBuffer[col + 40]; + p6 = computationBuffer[col + 48]; + p7 = computationBuffer[col + 56]; + + // check for all-zero AC coefficients + if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) + { + t = ((DctSqrt2 * p0) + 8192) >> 14; + + // convert to 8 bit + t = (t < -2040) ? 0 : (t >= 2024) ? 255 : (t + 2056) >> 4; + short st = (short)t; + + blockData[blockBufferOffset + col] = st; + blockData[blockBufferOffset + col + 8] = st; + blockData[blockBufferOffset + col + 16] = st; + blockData[blockBufferOffset + col + 24] = st; + blockData[blockBufferOffset + col + 32] = st; + blockData[blockBufferOffset + col + 40] = st; + blockData[blockBufferOffset + col + 48] = st; + blockData[blockBufferOffset + col + 56] = st; + continue; + } + + // stage 4 + v0 = ((DctSqrt2 * p0) + 2048) >> 12; + v1 = ((DctSqrt2 * p4) + 2048) >> 12; + v2 = p2; + v3 = p6; + v4 = ((DctSqrt1D2 * (p1 - p7)) + 2048) >> 12; + v7 = ((DctSqrt1D2 * (p1 + p7)) + 2048) >> 12; + v5 = p3; + v6 = p5; + + // stage 3 + // Shift v0 by 128.5 << 5 here, so we don't need to shift p0...p7 when + // converting to UInt8 range later. + v0 = ((v0 + v1 + 1) >> 1) + 4112; + v1 = v0 - v1; + t = ((v2 * DctSin6) + (v3 * DctCos6) + 2048) >> 12; + v2 = ((v2 * DctCos6) - (v3 * DctSin6) + 2048) >> 12; + v3 = t; + v4 = (v4 + v6 + 1) >> 1; + v6 = v4 - v6; + v7 = (v7 + v5 + 1) >> 1; + v5 = v7 - v5; + + // stage 2 + v0 = (v0 + v3 + 1) >> 1; + v3 = v0 - v3; + v1 = (v1 + v2 + 1) >> 1; + v2 = v1 - v2; + t = ((v4 * DctSin3) + (v7 * DctCos3) + 2048) >> 12; + v4 = ((v4 * DctCos3) - (v7 * DctSin3) + 2048) >> 12; + v7 = t; + t = ((v5 * DctSin1) + (v6 * DctCos1) + 2048) >> 12; + v5 = ((v5 * DctCos1) - (v6 * DctSin1) + 2048) >> 12; + v6 = t; + + // stage 1 + p0 = v0 + v7; + p7 = v0 - v7; + p1 = v1 + v6; + p6 = v1 - v6; + p2 = v2 + v5; + p5 = v2 - v5; + p3 = v3 + v4; + p4 = v3 - v4; + + // convert to 8-bit integers + p0 = (p0 < 16) ? 0 : (p0 >= 4080) ? 255 : p0 >> 4; + p1 = (p1 < 16) ? 0 : (p1 >= 4080) ? 255 : p1 >> 4; + p2 = (p2 < 16) ? 0 : (p2 >= 4080) ? 255 : p2 >> 4; + p3 = (p3 < 16) ? 0 : (p3 >= 4080) ? 255 : p3 >> 4; + p4 = (p4 < 16) ? 0 : (p4 >= 4080) ? 255 : p4 >> 4; + p5 = (p5 < 16) ? 0 : (p5 >= 4080) ? 255 : p5 >> 4; + p6 = (p6 < 16) ? 0 : (p6 >= 4080) ? 255 : p6 >> 4; + p7 = (p7 < 16) ? 0 : (p7 >= 4080) ? 255 : p7 >> 4; + + // store block data + blockData[blockBufferOffset + col] = (short)p0; + blockData[blockBufferOffset + col + 8] = (short)p1; + blockData[blockBufferOffset + col + 16] = (short)p2; + blockData[blockBufferOffset + col + 24] = (short)p3; + blockData[blockBufferOffset + col + 32] = (short)p4; + blockData[blockBufferOffset + col + 40] = (short)p5; + blockData[blockBufferOffset + col + 48] = (short)p6; + blockData[blockBufferOffset + col + 56] = (short)p7; + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs index fa57a18dd..352dc43f2 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs @@ -46,7 +46,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// /// Gets or sets the quantization tables. /// - public Buffer2D Tables { get; set; } + public Buffer2D Tables + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; set; + } + + = new Buffer2D(64, 4); /// public void Dispose() diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index b0c9979d2..59867006f 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -41,6 +41,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private int successiveACState; + private int successiveACNextValue; + /// /// Decodes the spectral scan /// @@ -91,6 +93,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { decodeFn = this.DecodeDCSuccessive; } + + Debug.WriteLine(successivePrev == 0 ? "decodeDCFirst" : "decodeDCSuccessive"); } else { @@ -102,6 +106,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { decodeFn = this.DecodeACSuccessive; } + + Debug.WriteLine(successivePrev == 0 ? "decodeACFirst" : "decodeACSuccessive"); } } else @@ -120,16 +126,28 @@ namespace ImageSharp.Formats.Jpeg.Port.Components mcuExpected = mcusPerLine * frame.McusPerColumn; } + Debug.WriteLine("mcuExpected = " + mcuExpected); + // FileMarker fileMarker; while (mcu < mcuExpected) { - // Reset interval stuff + // Reset interval int mcuToRead = resetInterval > 0 ? Math.Min(mcuExpected - mcu, resetInterval) : mcuExpected; - for (int i = 0; i < componentsLength; i++) + + // TODO: We might just be able to loop here. + if (componentsLength == 1) { - ref FrameComponent c = ref components[i]; + ref FrameComponent c = ref components[componentIndex]; c.Pred = 0; } + else + { + for (int i = 0; i < componentsLength; i++) + { + ref FrameComponent c = ref components[i]; + c.Pred = 0; + } + } this.eobrun = 0; @@ -165,8 +183,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } // Find marker - this.bitsCount = 0; - + // this.bitsCount = 0; // // TODO: We need to make sure we are not overwriting anything here. // fileMarker = JpegDecoderCore.FindNextFileMarker(stream); // // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past @@ -205,7 +222,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetBlockBufferOffset(FrameComponent component, int row, int col) + private static int GetBlockBufferOffset(ref FrameComponent component, int row, int col) { return 64 * (((component.BlocksPerLine + 1) * row) + col); } @@ -217,7 +234,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components int mcuCol = mcu % mcusPerLine; int blockRow = (mcuRow * component.VerticalFactor) + row; int blockCol = (mcuCol * component.HorizontalFactor) + col; - int offset = GetBlockBufferOffset(component, blockRow, blockCol); + int offset = GetBlockBufferOffset(ref component, blockRow, blockCol); decode(ref component, offset, dcHuffmanTables, acHuffmanTables, stream); } @@ -226,7 +243,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { int blockRow = (mcu / component.BlocksPerLine) | 0; int blockCol = mcu % component.BlocksPerLine; - int offset = GetBlockBufferOffset(component, blockRow, blockCol); + int offset = GetBlockBufferOffset(ref component, blockRow, blockCol); decode(ref component, offset, dcHuffmanTables, acHuffmanTables, stream); } @@ -394,7 +411,6 @@ namespace ImageSharp.Formats.Jpeg.Port.Components while (k <= e) { byte z = QuantizationTables.DctZigZag[k]; - int successiveACNextValue = 0; switch (this.successiveACState) { case 0: // Initial state @@ -421,7 +437,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components throw new ImageFormatException("Invalid ACn encoding"); } - successiveACNextValue = this.ReceiveAndExtend(s, stream); + this.successiveACNextValue = this.ReceiveAndExtend(s, stream); this.successiveACState = r > 0 ? 2 : 3; } @@ -449,7 +465,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } else { - component.BlockData[offset + z] = (short)(successiveACNextValue << this.successiveState); + component.BlockData[offset + z] = (short)(this.successiveACNextValue << this.successiveState); this.successiveACState = 0; } diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index abdabcd49..21c28043b 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Formats.Jpeg.Port { using System; using System.Collections.Generic; + using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; @@ -198,6 +199,12 @@ namespace ImageSharp.Formats.Jpeg.Port this.quantizationTables = null; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetBlockBufferOffset(ref Component component, int row, int col) + { + return 64 * (((component.BlocksPerLine + 1) * row) + col); + } + private void ParseStream() { // Check for the Start Of Image marker. @@ -325,7 +332,7 @@ namespace ImageSharp.Formats.Jpeg.Port BlocksPerColumn = frameComponent.BlocksPerColumn }; - this.BuildComponentData(ref component); + this.BuildComponentData(ref component, ref frameComponent); this.components.Components[i] = component; } @@ -422,8 +429,6 @@ namespace ImageSharp.Formats.Jpeg.Port /// private void ProcessDqtMarker(int remaining) { - // Pooled. Disposed on disposal of decoder - this.quantizationTables.Tables = new Buffer2D(64, 4); while (remaining > 0) { bool done = false; @@ -622,10 +627,10 @@ namespace ImageSharp.Formats.Jpeg.Port private void ProcessStartOfScanMarker() { int selectorsCount = this.InputStream.ReadByte(); - int index = -1; + int componentIndex = -1; for (int i = 0; i < selectorsCount; i++) { - index = -1; + componentIndex = -1; int selector = this.InputStream.ReadByte(); for (int j = 0; j < this.frame.ComponentIds.Length; j++) @@ -633,16 +638,16 @@ namespace ImageSharp.Formats.Jpeg.Port byte id = this.frame.ComponentIds[j]; if (selector == id) { - index = j; + componentIndex = j; } } - if (index < 0) + if (componentIndex < 0) { throw new ImageFormatException("Unknown component selector"); } - ref FrameComponent component = ref this.frame.Components[index]; + ref FrameComponent component = ref this.frame.Components[componentIndex]; int tableSpec = this.InputStream.ReadByte(); component.DCHuffmanTableId = tableSpec >> 4; component.ACHuffmanTableId = tableSpec & 15; @@ -661,22 +666,50 @@ namespace ImageSharp.Formats.Jpeg.Port this.dcHuffmanTables, this.acHuffmanTables, this.frame.Components, - index, + componentIndex, selectorsCount, this.resetInterval, spectralStart, spectralEnd, successiveApproximation >> 4, successiveApproximation & 15); + + Debug.WriteLine("spectralStart= " + spectralStart); + Debug.WriteLine("spectralEnd= " + spectralEnd); + Debug.WriteLine("successiveApproximation= " + successiveApproximation); + Debug.WriteLine("Components after"); + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 10; j++) + { + Debug.WriteLine("component [" + i + "] : value [" + j + "] =" + this.frame.Components[i].BlockData[j] + "]"); + } + } } /// /// Build the data for the given component /// /// The component - private void BuildComponentData(ref Component component) + /// The frame component + private void BuildComponentData(ref Component component, ref FrameComponent frameComponent) { // TODO: Write this + int blocksPerLine = component.BlocksPerLine; + int blocksPerColumn = component.BlocksPerColumn; + using (var computationBuffer = Buffer.CreateClean(64)) + { + for (int blockRow = 0; blockRow < blocksPerColumn; blockRow++) + { + for (int blockCol = 0; blockCol < blocksPerLine; blockCol++) + { + int offset = GetBlockBufferOffset(ref component, blockRow, blockCol); + IDCT.QuantizeAndInverse(this.quantizationTables, ref frameComponent, offset, computationBuffer); + } + } + } + + component.Output = frameComponent.BlockData; } /// diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpeg.htm b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpeg.htm new file mode 100644 index 000000000..72a5e448b --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpeg.htm @@ -0,0 +1,63 @@ + + + + + + + + +
+ + + + + + \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpg.js b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpg.js new file mode 100644 index 000000000..6ebf71a69 --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpg.js @@ -0,0 +1,1205 @@ +/* Copyright 2014 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-disable no-multi-spaces */ + +// import { error, warn } from '../shared/util'; + +/** + * This code was forked from https://github.com/notmasteryet/jpgjs. + * The original version was created by GitHub user notmasteryet. + * + * - The JPEG specification can be found in the ITU CCITT Recommendation T.81 + * (www.w3.org/Graphics/JPEG/itu-t81.pdf) + * - The JFIF specification can be found in the JPEG File Interchange Format + * (www.w3.org/Graphics/JPEG/jfif3.pdf) + * - The Adobe Application-Specific JPEG markers in the + * Supporting the DCT Filters in PostScript Level 2, Technical Note #5116 + * (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf) + */ + +var error = function(val){ + console.log(val); +} + +var warn = function(val){ + console.log(val); +} + +var JpegImage = (function JpegImageClosure() { + var dctZigZag = new Uint8Array([ + 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 + ]); + + var dctCos1 = 4017; // cos(pi/16) + var dctSin1 = 799; // sin(pi/16) + var dctCos3 = 3406; // cos(3*pi/16) + var dctSin3 = 2276; // sin(3*pi/16) + var dctCos6 = 1567; // cos(6*pi/16) + var dctSin6 = 3784; // sin(6*pi/16) + var dctSqrt2 = 5793; // sqrt(2) + var dctSqrt1d2 = 2896; // sqrt(2) / 2 + + function JpegImage() { + this.decodeTransform = null; + this.colorTransform = -1; + } + + function buildHuffmanTable(codeLengths, values) { + console.log(codeLengths); + console.log(values); + + var k = 0, code = [], i, j, length = 16; + while (length > 0 && !codeLengths[length - 1]) { + length--; + } + + code.push({ children: [], index: 0, }); + var p = code[0], q; + for (i = 0; i < length; i++) { + for (j = 0; j < codeLengths[i]; j++) { + p = code.pop(); + p.children[p.index] = values[k]; + + while (p.index > 0) { + p = code.pop(); + } + p.index++; + code.push(p); + while (code.length <= i) { + code.push(q = { children: [], index: 0, }); + p.children[p.index] = q.children; + p = q; + } + k++; + } + if (i + 1 < length) { + // p here points to last code + code.push(q = { children: [], index: 0, }); + p.children[p.index] = q.children; + p = q; + } + } + console.log(code[0].children); + console.log(k); + return code[0].children; + } + + function getBlockBufferOffset(component, row, col) { + return 64 * ((component.blocksPerLine + 1) * row + col); + } + + function decodeScan(data, offset, frame, components, resetInterval, + spectralStart, spectralEnd, successivePrev, successive) { + var mcusPerLine = frame.mcusPerLine; + var progressive = frame.progressive; + var startOffset = offset, bitsData = 0, bitsCount = 0; + + function readBit() { + if (bitsCount > 0) { + bitsCount--; + return (bitsData >> bitsCount) & 1; + } + bitsData = data[offset++]; + if (bitsData === 0xFF) { + var nextByte = data[offset++]; + if (nextByte) { + error('JPEG error: unexpected marker ' + + ((bitsData << 8) | nextByte).toString(16)); + } + // unstuff 0 + } + bitsCount = 7; + return bitsData >>> 7; + } + + function decodeHuffman(tree) { + var node = tree; + while (true) { + node = node[readBit()]; + if (typeof node === 'number') { + return node; + } + if (typeof node !== 'object') { + error('JPEG error: invalid huffman sequence'); + } + } + } + + function receive(length) { + var n = 0; + while (length > 0) { + n = (n << 1) | readBit(); + length--; + } + return n; + } + + function receiveAndExtend(length) { + if (length === 1) { + return readBit() === 1 ? 1 : -1; + } + var n = receive(length); + if (n >= 1 << (length - 1)) { + return n; + } + return n + (-1 << length) + 1; + } + + function decodeBaseline(component, offset) { + var t = decodeHuffman(component.huffmanTableDC); + var diff = t === 0 ? 0 : receiveAndExtend(t); + component.blockData[offset] = (component.pred += diff); + // console.log("component"); + // console.log(component); + + if(offset === 0){ + console.log("component at 0"); + console.log(component.blockData[offset]) + } + + var k = 1; + while (k < 64) { + var rs = decodeHuffman(component.huffmanTableAC); + var s = rs & 15, r = rs >> 4; + if (s === 0) { + if (r < 15) { + break; + } + k += 16; + continue; + } + k += r; + var z = dctZigZag[k]; + component.blockData[offset + z] = receiveAndExtend(s); + k++; + } + } + + function decodeDCFirst(component, offset) { + var t = decodeHuffman(component.huffmanTableDC); + var diff = t === 0 ? 0 : (receiveAndExtend(t) << successive); + component.blockData[offset] = (component.pred += diff); + } + + function decodeDCSuccessive(component, offset) { + component.blockData[offset] |= readBit() << successive; + } + + var eobrun = 0; + function decodeACFirst(component, offset) { + if (eobrun > 0) { + eobrun--; + return; + } + var k = spectralStart, e = spectralEnd; + while (k <= e) { + var rs = decodeHuffman(component.huffmanTableAC); + var s = rs & 15, r = rs >> 4; + if (s === 0) { + if (r < 15) { + eobrun = receive(r) + (1 << r) - 1; + break; + } + k += 16; + continue; + } + k += r; + var z = dctZigZag[k]; + component.blockData[offset + z] = + receiveAndExtend(s) * (1 << successive); + k++; + } + } + + var successiveACState = 0, successiveACNextValue; + function decodeACSuccessive(component, offset) { + var k = spectralStart; + var e = spectralEnd; + var r = 0; + var s; + var rs; + while (k <= e) { + var z = dctZigZag[k]; + switch (successiveACState) { + case 0: // initial state + rs = decodeHuffman(component.huffmanTableAC); + s = rs & 15; + r = rs >> 4; + if (s === 0) { + if (r < 15) { + eobrun = receive(r) + (1 << r); + successiveACState = 4; + } else { + r = 16; + successiveACState = 1; + } + } else { + if (s !== 1) { + error('JPEG error: invalid ACn encoding'); + } + successiveACNextValue = receiveAndExtend(s); + successiveACState = r ? 2 : 3; + } + continue; + case 1: // skipping r zero items + case 2: + if (component.blockData[offset + z]) { + component.blockData[offset + z] += (readBit() << successive); + } else { + r--; + if (r === 0) { + successiveACState = successiveACState === 2 ? 3 : 0; + } + } + break; + case 3: // set value for a zero item + if (component.blockData[offset + z]) { + component.blockData[offset + z] += (readBit() << successive); + } else { + component.blockData[offset + z] = + successiveACNextValue << successive; + successiveACState = 0; + } + break; + case 4: // eob + if (component.blockData[offset + z]) { + component.blockData[offset + z] += (readBit() << successive); + } + break; + } + k++; + } + if (successiveACState === 4) { + eobrun--; + if (eobrun === 0) { + successiveACState = 0; + } + } + } + + function decodeMcu(component, decode, mcu, row, col) { + var mcuRow = (mcu / mcusPerLine) | 0; + var mcuCol = mcu % mcusPerLine; + var blockRow = mcuRow * component.v + row; + var blockCol = mcuCol * component.h + col; + var offset = getBlockBufferOffset(component, blockRow, blockCol); + + // console.log("MCU Offset: " + offset); + decode(component, offset); + } + + function decodeBlock(component, decode, mcu) { + var blockRow = (mcu / component.blocksPerLine) | 0; + var blockCol = mcu % component.blocksPerLine; + var offset = getBlockBufferOffset(component, blockRow, blockCol); + decode(component, offset); + } + + var componentsLength = components.length; + var component, i, j, k, n; + var decodeFn; + if (progressive) { + if (spectralStart === 0) { + decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; + console.log(successivePrev === 0 ? "decodeDCFirst" : "decodeDCSuccessive"); + } else { + decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; + console.log(successivePrev === 0 ? "decodeACFirst" : "decodeACSuccessive"); + } + } else { + decodeFn = decodeBaseline; + } + + var mcu = 0, fileMarker; + var mcuExpected; + if (componentsLength === 1) { + mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn; + } else { + mcuExpected = mcusPerLine * frame.mcusPerColumn; + } + + console.log("mcuExpected = "+ mcuExpected); + + var h, v; + while (mcu < mcuExpected) { + // reset interval stuff + var mcuToRead = resetInterval ? + Math.min(mcuExpected - mcu, resetInterval) : mcuExpected; + for (i = 0; i < componentsLength; i++) { + components[i].pred = 0; + } + eobrun = 0; + + if (componentsLength === 1) { + component = components[0]; + + for (n = 0; n < mcuToRead; n++) { + decodeBlock(component, decodeFn, mcu); + mcu++; + } + } else { + for (n = 0; n < mcuToRead; n++) { + for (i = 0; i < componentsLength; i++) { + component = components[i]; + h = component.h; + v = component.v; + for (j = 0; j < v; j++) { + for (k = 0; k < h; k++) { + decodeMcu(component, decodeFn, mcu, j, k); + } + } + } + mcu++; + } + } + + // find marker + bitsCount = 0; + fileMarker = findNextFileMarker(data, offset); + // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past + // those to attempt to find a valid marker (fixes issue4090.pdf). + if (fileMarker && fileMarker.invalid) { + warn('decodeScan - unexpected MCU data, next marker is: ' + + fileMarker.invalid); + offset = fileMarker.offset; + } + var marker = fileMarker && fileMarker.marker; + if (!marker || marker <= 0xFF00) { + error('JPEG error: marker was not found'); + } + + if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx + offset += 2; + } else { + break; + } + } + + fileMarker = findNextFileMarker(data, offset); + // Some images include more Scan blocks than expected, skip past those and + // attempt to find the next valid marker (fixes issue8182.pdf). + if (fileMarker && fileMarker.invalid) { + warn('decodeScan - unexpected Scan data, next marker is: ' + + fileMarker.invalid); + offset = fileMarker.offset; + } + + return offset - startOffset; + } + + // A port of poppler's IDCT method which in turn is taken from: + // Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz, + // 'Practical Fast 1-D DCT Algorithms with 11 Multiplications', + // IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, + // 988-991. + function quantizeAndInverse(component, blockBufferOffset, p) { + var qt = component.quantizationTable, blockData = component.blockData; + var v0, v1, v2, v3, v4, v5, v6, v7; + var p0, p1, p2, p3, p4, p5, p6, p7; + var t; + + if (!qt) { + error('JPEG error: missing required Quantization Table.'); + } + + // inverse DCT on rows + for (var row = 0; row < 64; row += 8) { + // gather block data + p0 = blockData[blockBufferOffset + row]; + p1 = blockData[blockBufferOffset + row + 1]; + p2 = blockData[blockBufferOffset + row + 2]; + p3 = blockData[blockBufferOffset + row + 3]; + p4 = blockData[blockBufferOffset + row + 4]; + p5 = blockData[blockBufferOffset + row + 5]; + p6 = blockData[blockBufferOffset + row + 6]; + p7 = blockData[blockBufferOffset + row + 7]; + + // dequant p0 + p0 *= qt[row]; + + // check for all-zero AC coefficients + if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) { + t = (dctSqrt2 * p0 + 512) >> 10; + p[row] = t; + p[row + 1] = t; + p[row + 2] = t; + p[row + 3] = t; + p[row + 4] = t; + p[row + 5] = t; + p[row + 6] = t; + p[row + 7] = t; + continue; + } + // dequant p1 ... p7 + p1 *= qt[row + 1]; + p2 *= qt[row + 2]; + p3 *= qt[row + 3]; + p4 *= qt[row + 4]; + p5 *= qt[row + 5]; + p6 *= qt[row + 6]; + p7 *= qt[row + 7]; + + // stage 4 + v0 = (dctSqrt2 * p0 + 128) >> 8; + v1 = (dctSqrt2 * p4 + 128) >> 8; + v2 = p2; + v3 = p6; + v4 = (dctSqrt1d2 * (p1 - p7) + 128) >> 8; + v7 = (dctSqrt1d2 * (p1 + p7) + 128) >> 8; + v5 = p3 << 4; + v6 = p5 << 4; + + // stage 3 + v0 = (v0 + v1 + 1) >> 1; + v1 = v0 - v1; + t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8; + v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8; + v3 = t; + v4 = (v4 + v6 + 1) >> 1; + v6 = v4 - v6; + v7 = (v7 + v5 + 1) >> 1; + v5 = v7 - v5; + + // stage 2 + v0 = (v0 + v3 + 1) >> 1; + v3 = v0 - v3; + v1 = (v1 + v2 + 1) >> 1; + v2 = v1 - v2; + t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; + v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; + v7 = t; + t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; + v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; + v6 = t; + + // stage 1 + p[row] = v0 + v7; + p[row + 7] = v0 - v7; + p[row + 1] = v1 + v6; + p[row + 6] = v1 - v6; + p[row + 2] = v2 + v5; + p[row + 5] = v2 - v5; + p[row + 3] = v3 + v4; + p[row + 4] = v3 - v4; + } + + // inverse DCT on columns + for (var col = 0; col < 8; ++col) { + p0 = p[col]; + p1 = p[col + 8]; + p2 = p[col + 16]; + p3 = p[col + 24]; + p4 = p[col + 32]; + p5 = p[col + 40]; + p6 = p[col + 48]; + p7 = p[col + 56]; + + // check for all-zero AC coefficients + if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) { + t = (dctSqrt2 * p0 + 8192) >> 14; + // convert to 8 bit + t = (t < -2040) ? 0 : (t >= 2024) ? 255 : (t + 2056) >> 4; + blockData[blockBufferOffset + col] = t; + blockData[blockBufferOffset + col + 8] = t; + blockData[blockBufferOffset + col + 16] = t; + blockData[blockBufferOffset + col + 24] = t; + blockData[blockBufferOffset + col + 32] = t; + blockData[blockBufferOffset + col + 40] = t; + blockData[blockBufferOffset + col + 48] = t; + blockData[blockBufferOffset + col + 56] = t; + continue; + } + + // stage 4 + v0 = (dctSqrt2 * p0 + 2048) >> 12; + v1 = (dctSqrt2 * p4 + 2048) >> 12; + v2 = p2; + v3 = p6; + v4 = (dctSqrt1d2 * (p1 - p7) + 2048) >> 12; + v7 = (dctSqrt1d2 * (p1 + p7) + 2048) >> 12; + v5 = p3; + v6 = p5; + + // stage 3 + // Shift v0 by 128.5 << 5 here, so we don't need to shift p0...p7 when + // converting to UInt8 range later. + v0 = ((v0 + v1 + 1) >> 1) + 4112; + v1 = v0 - v1; + t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12; + v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12; + v3 = t; + v4 = (v4 + v6 + 1) >> 1; + v6 = v4 - v6; + v7 = (v7 + v5 + 1) >> 1; + v5 = v7 - v5; + + // stage 2 + v0 = (v0 + v3 + 1) >> 1; + v3 = v0 - v3; + v1 = (v1 + v2 + 1) >> 1; + v2 = v1 - v2; + t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; + v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; + v7 = t; + t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; + v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; + v6 = t; + + // stage 1 + p0 = v0 + v7; + p7 = v0 - v7; + p1 = v1 + v6; + p6 = v1 - v6; + p2 = v2 + v5; + p5 = v2 - v5; + p3 = v3 + v4; + p4 = v3 - v4; + + // convert to 8-bit integers + p0 = (p0 < 16) ? 0 : (p0 >= 4080) ? 255 : p0 >> 4; + p1 = (p1 < 16) ? 0 : (p1 >= 4080) ? 255 : p1 >> 4; + p2 = (p2 < 16) ? 0 : (p2 >= 4080) ? 255 : p2 >> 4; + p3 = (p3 < 16) ? 0 : (p3 >= 4080) ? 255 : p3 >> 4; + p4 = (p4 < 16) ? 0 : (p4 >= 4080) ? 255 : p4 >> 4; + p5 = (p5 < 16) ? 0 : (p5 >= 4080) ? 255 : p5 >> 4; + p6 = (p6 < 16) ? 0 : (p6 >= 4080) ? 255 : p6 >> 4; + p7 = (p7 < 16) ? 0 : (p7 >= 4080) ? 255 : p7 >> 4; + + // store block data + blockData[blockBufferOffset + col] = p0; + blockData[blockBufferOffset + col + 8] = p1; + blockData[blockBufferOffset + col + 16] = p2; + blockData[blockBufferOffset + col + 24] = p3; + blockData[blockBufferOffset + col + 32] = p4; + blockData[blockBufferOffset + col + 40] = p5; + blockData[blockBufferOffset + col + 48] = p6; + blockData[blockBufferOffset + col + 56] = p7; + } + } + + function buildComponentData(frame, component) { + var blocksPerLine = component.blocksPerLine; + var blocksPerColumn = component.blocksPerColumn; + var computationBuffer = new Int16Array(64); + console.log("qt"); + console.log(component.quantizationTable); + for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { + for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { + var offset = getBlockBufferOffset(component, blockRow, blockCol); + quantizeAndInverse(component, offset, computationBuffer); + } + } + + console.log("component.blockData"); + console.log(component.blockData); + return component.blockData; + } + + function clamp0to255(a) { + return a <= 0 ? 0 : a >= 255 ? 255 : a; + } + + function findNextFileMarker(data, currentPos, startPos) { + function peekUint16(pos) { + return (data[pos] << 8) | data[pos + 1]; + } + + var maxPos = data.length - 1; + var newPos = startPos < currentPos ? startPos : currentPos; + + if (currentPos >= maxPos) { + return null; // Don't attempt to read non-existent data and just return. + } + var currentMarker = peekUint16(currentPos); + if (currentMarker >= 0xFFC0 && currentMarker <= 0xFFFE) { + return { + invalid: null, + marker: currentMarker, + offset: currentPos, + }; + } + var newMarker = peekUint16(newPos); + while (!(newMarker >= 0xFFC0 && newMarker <= 0xFFFE)) { + if (++newPos >= maxPos) { + return null; // Don't attempt to read non-existent data and just return. + } + newMarker = peekUint16(newPos); + } + return { + invalid: currentMarker.toString(16), + marker: newMarker, + offset: newPos, + }; + } + + JpegImage.prototype = { + parse: function parse(data) { + + function readUint16() { + var value = (data[offset] << 8) | data[offset + 1]; + offset += 2; + return value; + } + + function readDataBlock() { + var length = readUint16(); + var endOffset = offset + length - 2; + + var fileMarker = findNextFileMarker(data, endOffset, offset); + if (fileMarker && fileMarker.invalid) { + warn('readDataBlock - incorrect length, next marker is: ' + + fileMarker.invalid); + endOffset = fileMarker.offset; + } + + var array = data.subarray(offset, endOffset); + offset += array.length; + return array; + } + + function prepareComponents(frame) { + var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH); + var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV); + for (var i = 0; i < frame.components.length; i++) { + component = frame.components[i]; + var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * + component.h / frame.maxH); + var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * + component.v / frame.maxV); + var blocksPerLineForMcu = mcusPerLine * component.h; + var blocksPerColumnForMcu = mcusPerColumn * component.v; + + var blocksBufferSize = 64 * blocksPerColumnForMcu * + (blocksPerLineForMcu + 1); + component.blockData = new Int16Array(blocksBufferSize); + component.blocksPerLine = blocksPerLine; + component.blocksPerColumn = blocksPerColumn; + } + frame.mcusPerLine = mcusPerLine; + frame.mcusPerColumn = mcusPerColumn; + } + + var offset = 0; + var jfif = null; + var adobe = null; + var frame, resetInterval; + var quantizationTables = []; + var huffmanTablesAC = [], huffmanTablesDC = []; + var fileMarker = readUint16(); + if (fileMarker !== 0xFFD8) { // SOI (Start of Image) + error('JPEG error: SOI not found'); + } + + fileMarker = readUint16(); + while (fileMarker !== 0xFFD9) { // EOI (End of image) + var i, j, l; + switch (fileMarker) { + case 0xFFE0: // APP0 (Application Specific) + case 0xFFE1: // APP1 + case 0xFFE2: // APP2 + case 0xFFE3: // APP3 + case 0xFFE4: // APP4 + case 0xFFE5: // APP5 + case 0xFFE6: // APP6 + case 0xFFE7: // APP7 + case 0xFFE8: // APP8 + case 0xFFE9: // APP9 + case 0xFFEA: // APP10 + case 0xFFEB: // APP11 + case 0xFFEC: // APP12 + case 0xFFED: // APP13 + case 0xFFEE: // APP14 + case 0xFFEF: // APP15 + case 0xFFFE: // COM (Comment) + var appData = readDataBlock(); + + if (fileMarker === 0xFFE0) { + if (appData[0] === 0x4A && appData[1] === 0x46 && + appData[2] === 0x49 && appData[3] === 0x46 && + appData[4] === 0) { // 'JFIF\x00' + jfif = { + version: { major: appData[5], minor: appData[6], }, + densityUnits: appData[7], + xDensity: (appData[8] << 8) | appData[9], + yDensity: (appData[10] << 8) | appData[11], + thumbWidth: appData[12], + thumbHeight: appData[13], + thumbData: appData.subarray(14, 14 + + 3 * appData[12] * appData[13]), + }; + } + } + // TODO APP1 - Exif + if (fileMarker === 0xFFEE) { + if (appData[0] === 0x41 && appData[1] === 0x64 && + appData[2] === 0x6F && appData[3] === 0x62 && + appData[4] === 0x65) { // 'Adobe' + adobe = { + version: (appData[5] << 8) | appData[6], + flags0: (appData[7] << 8) | appData[8], + flags1: (appData[9] << 8) | appData[10], + transformCode: appData[11], + }; + } + } + break; + + case 0xFFDB: // DQT (Define Quantization Tables) + var quantizationTablesLength = readUint16(); + var quantizationTablesEnd = quantizationTablesLength + offset - 2; + var z; + while (offset < quantizationTablesEnd) { + var quantizationTableSpec = data[offset++]; + var tableData = new Uint16Array(64); + if ((quantizationTableSpec >> 4) === 0) { // 8 bit values + for (j = 0; j < 64; j++) { + z = dctZigZag[j]; + tableData[z] = data[offset++]; + } + } else if ((quantizationTableSpec >> 4) === 1) { // 16 bit values + for (j = 0; j < 64; j++) { + z = dctZigZag[j]; + tableData[z] = readUint16(); + } + } else { + error('JPEG error: DQT - invalid table spec'); + } + quantizationTables[quantizationTableSpec & 15] = tableData; + } + break; + + case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT) + case 0xFFC1: // SOF1 (Start of Frame, Extended DCT) + case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT) + if (frame) { + error('JPEG error: Only single frame JPEGs supported'); + } + console.log("filemarker"); + console.log(fileMarker); + console.log(offset); + readUint16(); // skip data length + frame = {}; + frame.extended = (fileMarker === 0xFFC1); + frame.progressive = (fileMarker === 0xFFC2); + frame.precision = data[offset++]; + frame.scanLines = readUint16(); + frame.samplesPerLine = readUint16(); + frame.components = []; + frame.componentIds = {}; + var componentsCount = data[offset++], componentId; + var maxH = 0, maxV = 0; + for (i = 0; i < componentsCount; i++) { + componentId = data[offset]; + var h = data[offset + 1] >> 4; + var v = data[offset + 1] & 15; + if (maxH < h) { + maxH = h; + } + if (maxV < v) { + maxV = v; + } + var qId = data[offset + 2]; + l = frame.components.push({ + h, + v, + quantizationId: qId, + quantizationTable: null, // See comment below. + }); + frame.componentIds[componentId] = l - 1; + offset += 3; + } + + frame.maxH = maxH; + frame.maxV = maxV; + prepareComponents(frame); + break; + + case 0xFFC4: // DHT (Define Huffman Tables) + var huffmanLength = readUint16(); + for (i = 2; i < huffmanLength;) { + console.log("offset= " + offset); + var huffmanTableSpec = data[offset++]; + console.log("huffmanTableSpec= " + huffmanTableSpec); + + var codeLengths = new Uint8Array(16); + var codeLengthSum = 0; + for (j = 0; j < 16; j++, offset++) { + codeLengthSum += (codeLengths[j] = data[offset]); + } + console.log("codelengthsum = " + codeLengthSum); + console.log("offset = " + offset); + var huffmanValues = new Uint8Array(codeLengthSum); + for (j = 0; j < codeLengthSum; j++, offset++) { + huffmanValues[j] = data[offset]; + } + i += 17 + codeLengthSum; + + + console.log((huffmanTableSpec >> 4) === 0 ? "DC":"AC"); + ((huffmanTableSpec >> 4) === 0 + ? huffmanTablesDC + : huffmanTablesAC)[huffmanTableSpec & 15] = + buildHuffmanTable(codeLengths, huffmanValues); + } + break; + + case 0xFFDD: // DRI (Define Restart Interval) + readUint16(); // skip data length + resetInterval = readUint16(); + break; + + case 0xFFDA: // SOS (Start of Scan) + readUint16(); // scanLength + var selectorsCount = data[offset++]; + var components = [], component; + for (i = 0; i < selectorsCount; i++) { + var ci = data[offset++]; + console.log("ci= " + ci); + console.log("offset= " + offset); + + var componentIndex = frame.componentIds[ci]; + console.log("componentIndex= " + componentIndex); + component = frame.components[componentIndex]; + var tableSpec = data[offset++]; + component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; + component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; + components.push(component); + } + console.log("components= " + components); + + var spectralStart = data[offset++]; + var spectralEnd = data[offset++]; + var successiveApproximation = data[offset++]; + + console.log(frame.componentIds); + console.log("spectralStart= " + spectralStart); + console.log("spectralEnd= " + spectralEnd); + console.log("successiveApproximation= " + successiveApproximation); + // console.log("components before") + // console.log(components) + var processed = decodeScan(data, offset, + frame, components, resetInterval, + spectralStart, spectralEnd, + successiveApproximation >> 4, successiveApproximation & 15); + offset += processed; + console.log("components after"); + // console.log(frame); + for (var i = 0; i < 3; i++){ + for (var j = 0; j < 10; j++){ + console.log("component ["+ i +"] : value ["+j+"] ="+ frame.components[i].blockData[j]+"]"); + } + } + break; + + case 0xFFFF: // Fill bytes + if (data[offset] !== 0xFF) { // Avoid skipping a valid marker. + offset--; + } + break; + + default: + if (data[offset - 3] === 0xFF && + data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) { + // could be incorrect encoding -- last 0xFF byte of the previous + // block was eaten by the encoder + offset -= 3; + break; + } + + // TODO: Delete this after testing + fileMarker = 0xFFD9; + // error('JPEG error: unknown marker ' + fileMarker.toString(16)); + } + fileMarker = readUint16(); + } + + console.log("quantizationTables"); + console.log(quantizationTables); + + this.width = frame.samplesPerLine; + this.height = frame.scanLines; + this.jfif = jfif; + this.adobe = adobe; + this.components = []; + for (i = 0; i < frame.components.length; i++) { + component = frame.components[i]; + + // Prevent errors when DQT markers are placed after SOF{n} markers, + // by assigning the `quantizationTable` entry after the entire image + // has been parsed (fixes issue7406.pdf). + var quantizationTable = quantizationTables[component.quantizationId]; + if (quantizationTable) { + component.quantizationTable = quantizationTable; + } + + this.components.push({ + output: buildComponentData(frame, component), + scaleX: component.h / frame.maxH, + scaleY: component.v / frame.maxV, + blocksPerLine: component.blocksPerLine, + blocksPerColumn: component.blocksPerColumn, + }); + } + + console.log("components"); + console.log(this.components); + this.numComponents = this.components.length; + }, + + _getLinearizedBlockData: function getLinearizedBlockData(width, height) { + var scaleX = this.width / width, scaleY = this.height / height; + + var component, componentScaleX, componentScaleY, blocksPerScanline; + var x, y, i, j, k; + var index; + var offset = 0; + var output; + var numComponents = this.components.length; + var dataLength = width * height * numComponents; + var data = new Uint8Array(dataLength); + var xScaleBlockOffset = new Uint32Array(width); + var mask3LSB = 0xfffffff8; // used to clear the 3 LSBs + + for (i = 0; i < numComponents; i++) { + component = this.components[i]; + componentScaleX = component.scaleX * scaleX; + componentScaleY = component.scaleY * scaleY; + offset = i; + output = component.output; + blocksPerScanline = (component.blocksPerLine + 1) << 3; + // precalculate the xScaleBlockOffset + for (x = 0; x < width; x++) { + j = 0 | (x * componentScaleX); + xScaleBlockOffset[x] = ((j & mask3LSB) << 3) | (j & 7); + } + // linearize the blocks of the component + for (y = 0; y < height; y++) { + j = 0 | (y * componentScaleY); + index = blocksPerScanline * (j & mask3LSB) | ((j & 7) << 3); + for (x = 0; x < width; x++) { + data[offset] = output[index + xScaleBlockOffset[x]]; + offset += numComponents; + } + } + } + + // decodeTransform contains pairs of multiplier (-256..256) and additive + var transform = this.decodeTransform; + if (transform) { + for (i = 0; i < dataLength;) { + for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) { + data[i] = ((data[i] * transform[k]) >> 8) + transform[k + 1]; + } + } + } + return data; + }, + + _isColorConversionNeeded: function isColorConversionNeeded() { + if (this.adobe && this.adobe.transformCode) { + // The adobe transform marker overrides any previous setting + return true; + } else if (this.numComponents === 3) { + if (!this.adobe && this.colorTransform === 0) { + // If the Adobe transform marker is not present and the image + // dictionary has a 'ColorTransform' entry, explicitly set to `0`, + // then the colours should *not* be transformed. + return false; + } + return true; + } + // `this.numComponents !== 3` + if (!this.adobe && this.colorTransform === 1) { + // If the Adobe transform marker is not present and the image + // dictionary has a 'ColorTransform' entry, explicitly set to `1`, + // then the colours should be transformed. + return true; + } + return false; + }, + + _convertYccToRgb: function convertYccToRgb(data) { + var Y, Cb, Cr; + for (var i = 0, length = data.length; i < length; i += 3) { + Y = data[i ]; + Cb = data[i + 1]; + Cr = data[i + 2]; + data[i ] = clamp0to255(Y - 179.456 + 1.402 * Cr); + data[i + 1] = clamp0to255(Y + 135.459 - 0.344 * Cb - 0.714 * Cr); + data[i + 2] = clamp0to255(Y - 226.816 + 1.772 * Cb); + } + return data; + }, + + _convertYcckToRgb: function convertYcckToRgb(data) { + var Y, Cb, Cr, k; + var offset = 0; + for (var i = 0, length = data.length; i < length; i += 4) { + Y = data[i]; + Cb = data[i + 1]; + Cr = data[i + 2]; + k = data[i + 3]; + + var r = -122.67195406894 + + Cb * (-6.60635669420364e-5 * Cb + 0.000437130475926232 * Cr - + 5.4080610064599e-5 * Y + 0.00048449797120281 * k - + 0.154362151871126) + + Cr * (-0.000957964378445773 * Cr + 0.000817076911346625 * Y - + 0.00477271405408747 * k + 1.53380253221734) + + Y * (0.000961250184130688 * Y - 0.00266257332283933 * k + + 0.48357088451265) + + k * (-0.000336197177618394 * k + 0.484791561490776); + + var g = 107.268039397724 + + Cb * (2.19927104525741e-5 * Cb - 0.000640992018297945 * Cr + + 0.000659397001245577 * Y + 0.000426105652938837 * k - + 0.176491792462875) + + Cr * (-0.000778269941513683 * Cr + 0.00130872261408275 * Y + + 0.000770482631801132 * k - 0.151051492775562) + + Y * (0.00126935368114843 * Y - 0.00265090189010898 * k + + 0.25802910206845) + + k * (-0.000318913117588328 * k - 0.213742400323665); + + var b = -20.810012546947 + + Cb * (-0.000570115196973677 * Cb - 2.63409051004589e-5 * Cr + + 0.0020741088115012 * Y - 0.00288260236853442 * k + + 0.814272968359295) + + Cr * (-1.53496057440975e-5 * Cr - 0.000132689043961446 * Y + + 0.000560833691242812 * k - 0.195152027534049) + + Y * (0.00174418132927582 * Y - 0.00255243321439347 * k + + 0.116935020465145) + + k * (-0.000343531996510555 * k + 0.24165260232407); + + data[offset++] = clamp0to255(r); + data[offset++] = clamp0to255(g); + data[offset++] = clamp0to255(b); + } + return data; + }, + + _convertYcckToCmyk: function convertYcckToCmyk(data) { + var Y, Cb, Cr; + for (var i = 0, length = data.length; i < length; i += 4) { + Y = data[i]; + Cb = data[i + 1]; + Cr = data[i + 2]; + data[i ] = clamp0to255(434.456 - Y - 1.402 * Cr); + data[i + 1] = clamp0to255(119.541 - Y + 0.344 * Cb + 0.714 * Cr); + data[i + 2] = clamp0to255(481.816 - Y - 1.772 * Cb); + // K in data[i + 3] is unchanged + } + return data; + }, + + _convertCmykToRgb: function convertCmykToRgb(data) { + var c, m, y, k; + var offset = 0; + var min = -255 * 255 * 255; + var scale = 1 / 255 / 255; + for (var i = 0, length = data.length; i < length; i += 4) { + c = data[i]; + m = data[i + 1]; + y = data[i + 2]; + k = data[i + 3]; + + var r = + c * (-4.387332384609988 * c + 54.48615194189176 * m + + 18.82290502165302 * y + 212.25662451639585 * k - + 72734.4411664936) + + m * (1.7149763477362134 * m - 5.6096736904047315 * y - + 17.873870861415444 * k - 1401.7366389350734) + + y * (-2.5217340131683033 * y - 21.248923337353073 * k + + 4465.541406466231) - + k * (21.86122147463605 * k + 48317.86113160301); + var g = + c * (8.841041422036149 * c + 60.118027045597366 * m + + 6.871425592049007 * y + 31.159100130055922 * k - + 20220.756542821975) + + m * (-15.310361306967817 * m + 17.575251261109482 * y + + 131.35250912493976 * k - 48691.05921601825) + + y * (4.444339102852739 * y + 9.8632861493405 * k - + 6341.191035517494) - + k * (20.737325471181034 * k + 47890.15695978492); + var b = + c * (0.8842522430003296 * c + 8.078677503112928 * m + + 30.89978309703729 * y - 0.23883238689178934 * k - + 3616.812083916688) + + m * (10.49593273432072 * m + 63.02378494754052 * y + + 50.606957656360734 * k - 28620.90484698408) + + y * (0.03296041114873217 * y + 115.60384449646641 * k - + 49363.43385999684) - + k * (22.33816807309886 * k + 45932.16563550634); + + data[offset++] = r >= 0 ? 255 : r <= min ? 0 : 255 + r * scale | 0; + data[offset++] = g >= 0 ? 255 : g <= min ? 0 : 255 + g * scale | 0; + data[offset++] = b >= 0 ? 255 : b <= min ? 0 : 255 + b * scale | 0; + } + return data; + }, + + getData: function getData(width, height, forceRGBoutput) { + if (this.numComponents > 4) { + error('JPEG error: Unsupported color mode'); + } + // type of data: Uint8Array(width * height * numComponents) + var data = this._getLinearizedBlockData(width, height); + + if (this.numComponents === 1 && forceRGBoutput) { + var dataLength = data.length; + var rgbData = new Uint8Array(dataLength * 3); + var offset = 0; + for (var i = 0; i < dataLength; i++) { + var grayColor = data[i]; + rgbData[offset++] = grayColor; + rgbData[offset++] = grayColor; + rgbData[offset++] = grayColor; + } + return rgbData; + } else if (this.numComponents === 3 && this._isColorConversionNeeded()) { + return this._convertYccToRgb(data); + } else if (this.numComponents === 4) { + if (this._isColorConversionNeeded()) { + if (forceRGBoutput) { + return this._convertYcckToRgb(data); + } + return this._convertYcckToCmyk(data); + } else if (forceRGBoutput) { + return this._convertCmykToRgb(data); + } + } + return data; + }, + }; + + return JpegImage; +})(); + +// export { +// JpegImage, +// }; From aaf7b4bd85ae12dfc9325a1509963b939ede9c51 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 27 Jun 2017 09:22:09 +1000 Subject: [PATCH 17/43] Fix progressive scan decoding --- .../Formats/Jpeg/Port/Components/Component.cs | 4 +-- .../Jpeg/Port/Components/ScanDecoder.cs | 31 ++++++++++--------- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 4 +-- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs index 3b462514c..db3170613 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs @@ -22,12 +22,12 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// /// Gets or sets the horizontal scaling factor /// - public int ScaleX; + public float ScaleX; /// /// Gets or sets the vertical scaling factor /// - public int ScaleY; + public float ScaleY; /// /// Gets or sets the number of blocks per line diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 59867006f..2ec9ea905 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -135,20 +135,20 @@ namespace ImageSharp.Formats.Jpeg.Port.Components int mcuToRead = resetInterval > 0 ? Math.Min(mcuExpected - mcu, resetInterval) : mcuExpected; // TODO: We might just be able to loop here. - if (componentsLength == 1) + // if (componentsLength == 1) + // { + // ref FrameComponent c = ref components[componentIndex]; + // c.Pred = 0; + // } + // else + // { + for (int i = 0; i < components.Length; i++) { - ref FrameComponent c = ref components[componentIndex]; + ref FrameComponent c = ref components[i]; c.Pred = 0; } - else - { - for (int i = 0; i < componentsLength; i++) - { - ref FrameComponent c = ref components[i]; - c.Pred = 0; - } - } + // } this.eobrun = 0; if (componentsLength == 1) @@ -280,12 +280,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { int index = this.ReadBit(stream); HuffmanBranch branch = node[index]; - node = branch.Children; if (branch.Value > -1) { return branch.Value; } + + node = branch.Children; } } @@ -438,13 +439,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } this.successiveACNextValue = this.ReceiveAndExtend(s, stream); - this.successiveACState = r > 0 ? 2 : 3; + this.successiveACState = r != 0 ? 2 : 3; } continue; case 1: // Skipping r zero items case 2: - if (component.BlockData[offset + z] > 0) + if (component.BlockData[offset + z] != 0) { component.BlockData[offset + z] += (short)(this.ReadBit(stream) << this.successiveState); } @@ -459,7 +460,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components break; case 3: // Set value for a zero item - if (component.BlockData[offset + z] > 0) + if (component.BlockData[offset + z] != 0) { component.BlockData[offset + z] += (short)(this.ReadBit(stream) << this.successiveState); } @@ -471,7 +472,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components break; case 4: // Eob - if (component.BlockData[offset + z] > 0) + if (component.BlockData[offset + z] != 0) { component.BlockData[offset + z] += (short)(this.ReadBit(stream) << this.successiveState); } diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 21c28043b..b608b4951 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -326,8 +326,8 @@ namespace ImageSharp.Formats.Jpeg.Port ref var frameComponent = ref this.frame.Components[i]; var component = new Component { - ScaleX = frameComponent.HorizontalFactor / this.frame.MaxHorizontalFactor, - ScaleY = frameComponent.VerticalFactor / this.frame.MaxVerticalFactor, + ScaleX = frameComponent.HorizontalFactor / (float)this.frame.MaxHorizontalFactor, + ScaleY = frameComponent.VerticalFactor / (float)this.frame.MaxVerticalFactor, BlocksPerLine = frameComponent.BlocksPerLine, BlocksPerColumn = frameComponent.BlocksPerColumn }; From 00712a277e6966d41009868589dd1bcf061afdfa Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 28 Jun 2017 02:23:43 +1000 Subject: [PATCH 18/43] Can now decode many images --- .../Formats/Jpeg/Port/Components/Adobe.cs | 40 +- .../Jpeg/Port/Components/FileMarker.cs | 6 + .../Jpeg/Port/Components/JpegPixelArea.cs | 146 ++++++ .../Jpeg/Port/Components/ScanDecoder.cs | 496 +++++++++++++----- .../Jpeg/Port/Components/YCbCrToRgbTables.cs | 128 +++++ .../Formats/Jpeg/Port/JpegDecoderCore.cs | 301 ++++++++--- tests/ImageSharp.Tests/TestFile.cs | 19 +- .../Formats/Jpg/baseline/ycck - Copy.jpg | 3 + 8 files changed, 935 insertions(+), 204 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/YCbCrToRgbTables.cs create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ycck - Copy.jpg diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs index 130b7bdb3..6ef128ccb 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs @@ -6,10 +6,12 @@ // ReSharper disable InconsistentNaming namespace ImageSharp.Formats.Jpeg.Port.Components { + using System; + /// /// Provides information about the Adobe marker segment /// - internal struct Adobe + internal struct Adobe : IEquatable { /// /// The DCT Encode Version @@ -34,5 +36,39 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// 02 : YCCK /// public byte ColorTransform; + + /// + public bool Equals(Adobe other) + { + return this.DCTEncodeVersion == other.DCTEncodeVersion + && this.APP14Flags0 == other.APP14Flags0 + && this.APP14Flags1 == other.APP14Flags1 + && this.ColorTransform == other.ColorTransform; + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + return obj is Adobe && this.Equals((Adobe)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + // TODO: Merge and use HashCodeHelpers + int hashCode = this.DCTEncodeVersion.GetHashCode(); + hashCode = (hashCode * 397) ^ this.APP14Flags0.GetHashCode(); + hashCode = (hashCode * 397) ^ this.APP14Flags1.GetHashCode(); + hashCode = (hashCode * 397) ^ this.ColorTransform.GetHashCode(); + return hashCode; + } + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs index 39adba5cd..eaf3dafb9 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs @@ -49,5 +49,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// Gets the position of the marker within a stream /// public long Position { get; } + + /// + public override string ToString() + { + return this.Marker.ToString("X"); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs new file mode 100644 index 000000000..eafc6c33c --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs @@ -0,0 +1,146 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpeg.Port.Components +{ + using System; + using System.Diagnostics; + using System.Runtime.CompilerServices; + using ImageSharp.Memory; + + /// + /// Represents a section of the jpeg component data laid out in pixel order. + /// + internal struct JpegPixelArea : IDisposable + { + private readonly int imageWidth; + + private readonly int imageHeight; + + private Buffer componentData; + + private int rowStride; + + /// + /// Initializes a new instance of the struct. + /// + /// The image width + /// The image height + /// The number of components + public JpegPixelArea(int imageWidth, int imageHeight, int numberOfComponents) + { + this.imageWidth = imageWidth; + this.imageHeight = imageHeight; + this.Width = 0; + this.Height = 0; + this.NumberOfComponents = numberOfComponents; + this.componentData = null; + this.rowStride = 0; + } + + /// + /// Gets the number of components + /// + public int NumberOfComponents { get; } + + /// + /// Gets the width + /// + public int Width { get; private set; } + + /// + /// Gets the height + /// + public int Height { get; private set; } + + /// + /// Organsizes the decoded jpeg components into a linear array ordered by component. + /// This must be called before attempting to retrieve the data. + /// + /// The jpeg component blocks + /// The pixel area width + /// The pixel area height + public void LinearizeBlockData(ComponentBlocks components, int width, int height) + { + this.Width = width; + this.Height = height; + int numberOfComponents = this.NumberOfComponents; + this.rowStride = width * numberOfComponents; + + float scaleX = this.imageWidth / (float)width; + float scaleY = this.imageHeight / (float)height; + this.componentData = new Buffer(width * height * numberOfComponents); + const uint Mask3Lsb = 0xFFFFFFF8; // Used to clear the 3 LSBs + + using (var xScaleBlockOffset = new Buffer(width)) + { + for (int i = 0; i < numberOfComponents; i++) + { + ref Component component = ref components.Components[i]; + float componentScaleX = component.ScaleX * scaleX; + float componentScaleY = component.ScaleY * scaleY; + int offset = i; + Buffer output = component.Output; + int blocksPerScanline = (component.BlocksPerLine + 1) << 3; + + // Precalculate the xScaleBlockOffset + int j; + for (int x = 0; x < width; x++) + { + j = 0 | (int)(x * componentScaleX); + xScaleBlockOffset[x] = (int)((j & Mask3Lsb) << 3) | (j & 7); + } + + // Linearize the blocks of the component + for (int y = 0; y < height; y++) + { + j = 0 | (int)(y * componentScaleY); + int index = blocksPerScanline * (int)(j & Mask3Lsb) | ((j & 7) << 3); + for (int x = 0; x < width; x++) + { + this.componentData[offset] = (byte)output[index + xScaleBlockOffset[x]]; + offset += numberOfComponents; + } + } + } + } + } + + /// + /// Gets a representing the row 'y' beginning from the the first byte on that row. + /// + /// The y-coordinate of the pixel row. Must be greater than or equal to zero and less than the height of the pixel area. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetRowSpan(int y) + { + this.CheckCoordinates(y); + return this.componentData.Slice(y * this.rowStride, this.rowStride); + } + + /// + public void Dispose() + { + this.componentData?.Dispose(); + this.componentData = null; + } + + /// + /// Checks the coordinates to ensure they are within bounds. + /// + /// The y-coordinate of the row. Must be greater than zero and less than the height of the area. + /// + /// Thrown if the coordinates are not within the bounds of the image. + /// + [Conditional("DEBUG")] + private void CheckCoordinates(int y) + { + if (y < 0 || y >= this.Height) + { + throw new ArgumentOutOfRangeException(nameof(y), y, $"{y} is outwith the area bounds."); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 2ec9ea905..da7bb52a9 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -12,16 +12,6 @@ namespace ImageSharp.Formats.Jpeg.Port.Components using System.IO; using System.Runtime.CompilerServices; - /// - /// Encapsulates a decode method. TODO: This may well be a bottleneck - /// - /// The component - /// The offset - /// The DC Huffman tables - /// The AC Huffman tables - /// The input stream - internal delegate void DecodeAction(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream); - /// /// Provides the means to decode a spectral scan /// @@ -37,6 +27,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private int eobrun; + private int compIndex; + private int successiveState; private int successiveACState; @@ -58,7 +50,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// The spectral selection end /// The successive approximation bit high end /// The successive approximation bit low end - public void DecodeScan( + /// The representing the processed length in bytes + public long DecodeScan( Frame frame, Stream stream, HuffmanTables dcHuffmanTables, @@ -72,179 +65,438 @@ namespace ImageSharp.Formats.Jpeg.Port.Components int successivePrev, int successive) { + this.compIndex = componentIndex; this.specStart = spectralStart; this.specEnd = spectralEnd; this.successiveState = successive; bool progressive = frame.Progressive; int mcusPerLine = frame.McusPerLine; + long startPosition = stream.Position; - // TODO: Delegate action will not be fast - DecodeAction decodeFn; + int mcu = 0; + int mcuExpected; + if (componentsLength == 1) + { + mcuExpected = components[this.compIndex].BlocksPerLine * components[this.compIndex].BlocksPerColumn; + } + else + { + mcuExpected = mcusPerLine * frame.McusPerColumn; + } - if (progressive) + FileMarker fileMarker; + while (mcu < mcuExpected) { - if (this.specStart == 0) + // Reset interval stuff + int mcuToRead = resetInterval != 0 ? Math.Min(mcuExpected - mcu, resetInterval) : mcuExpected; + for (int i = 0; i < components.Length; i++) { - if (successivePrev == 0) - { - decodeFn = this.DecodeDCFirst; - } - else - { - decodeFn = this.DecodeDCSuccessive; - } + ref FrameComponent c = ref components[i]; + c.Pred = 0; + } + + this.eobrun = 0; - Debug.WriteLine(successivePrev == 0 ? "decodeDCFirst" : "decodeDCSuccessive"); + if (!progressive) + { + this.DecodeScanBaseline(dcHuffmanTables, acHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); } else { - if (successivePrev == 0) + if (this.specStart == 0) { - decodeFn = this.DecodeACFirst; + if (successivePrev == 0) + { + this.DecodeScanDCFirst(dcHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); + } + else + { + this.DecodeScanDCSuccessive(components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); + } } else { - decodeFn = this.DecodeACSuccessive; + if (successivePrev == 0) + { + this.DecodeScanACFirst(acHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); + } + else + { + this.DecodeScanACSuccessive(acHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); + } } + } + + // Find marker + this.bitsCount = 0; + long position = stream.Position; + fileMarker = JpegDecoderCore.FindNextFileMarkerNew(stream); + + // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past + // those to attempt to find a valid marker (fixes issue4090.pdf) in original code. + if (fileMarker.Invalid) + { +#if DEBUG + Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker}"); + +#endif + // stream.Position = fileMarker.Position; + } + + ushort marker = fileMarker.Marker; + + // if (marker <= 0xFF00) + // { + // throw new ImageFormatException("Marker was not found"); + // } + + // RSTn We've alread read the bytes and altered the position so no need to skip + if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) + { + continue; + } - Debug.WriteLine(successivePrev == 0 ? "decodeACFirst" : "decodeACSuccessive"); + if (!fileMarker.Invalid) + { + // We've found a valid marker. + // Rewind the stream to the position of the marker and beak + stream.Position = fileMarker.Position; + break; } + + // Rewind the stream + stream.Position = position; } - else + + fileMarker = JpegDecoderCore.FindNextFileMarkerNew(stream); + + // Some images include more Scan blocks than expected, skip past those and + // attempt to find the next valid marker (fixes issue8182.pdf) in original code. + if (fileMarker.Invalid) { - decodeFn = this.DecodeBaseline; +#if DEBUG + Debug.WriteLine($"DecodeScan - Unexpected MCU data, next marker is: {fileMarker.Marker}"); +#endif + stream.Position = fileMarker.Position; } - int mcu = 0; - int mcuExpected; + return stream.Position - startPosition; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetBlockBufferOffset(FrameComponent component, int row, int col) + { + return 64 * (((component.BlocksPerLine + 1) * row) + col); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeScanBaseline( + HuffmanTables dcHuffmanTables, + HuffmanTables acHuffmanTables, + FrameComponent[] components, + int componentsLength, + int mcusPerLine, + int mcuToRead, + ref int mcu, + Stream stream) + { if (componentsLength == 1) { - mcuExpected = components[componentIndex].BlocksPerLine * components[componentIndex].BlocksPerColumn; + ref FrameComponent component = ref components[this.compIndex]; + for (int n = 0; n < mcuToRead; n++) + { + this.DecodeBlockBaseline(dcHuffmanTables, acHuffmanTables, ref component, mcu, stream); + mcu++; + } } else { - mcuExpected = mcusPerLine * frame.McusPerColumn; - } + for (int n = 0; n < mcuToRead; n++) + { + for (int i = 0; i < componentsLength; i++) + { + ref FrameComponent component = ref components[i]; + int h = component.HorizontalFactor; + int v = component.VerticalFactor; + for (int j = 0; j < v; j++) + { + for (int k = 0; k < h; k++) + { + this.DecodeMcuBaseline(dcHuffmanTables, acHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); + } + } + } - Debug.WriteLine("mcuExpected = " + mcuExpected); + mcu++; + } + } + } - // FileMarker fileMarker; - while (mcu < mcuExpected) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeScanDCFirst( + HuffmanTables dcHuffmanTables, + FrameComponent[] components, + int componentsLength, + int mcusPerLine, + int mcuToRead, + ref int mcu, + Stream stream) + { + if (componentsLength == 1) { - // Reset interval - int mcuToRead = resetInterval > 0 ? Math.Min(mcuExpected - mcu, resetInterval) : mcuExpected; - - // TODO: We might just be able to loop here. - // if (componentsLength == 1) - // { - // ref FrameComponent c = ref components[componentIndex]; - // c.Pred = 0; - // } - // else - // { - for (int i = 0; i < components.Length; i++) + ref FrameComponent component = ref components[this.compIndex]; + for (int n = 0; n < mcuToRead; n++) { - ref FrameComponent c = ref components[i]; - c.Pred = 0; + this.DecodeBlockDCFirst(dcHuffmanTables, ref component, mcu, stream); + mcu++; } + } + else + { + for (int n = 0; n < mcuToRead; n++) + { + for (int i = 0; i < componentsLength; i++) + { + ref FrameComponent component = ref components[i]; + int h = component.HorizontalFactor; + int v = component.VerticalFactor; + for (int j = 0; j < v; j++) + { + for (int k = 0; k < h; k++) + { + this.DecodeMcuDCFirst(dcHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); + } + } + } - // } - this.eobrun = 0; + mcu++; + } + } + } - if (componentsLength == 1) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeScanDCSuccessive( + FrameComponent[] components, + int componentsLength, + int mcusPerLine, + int mcuToRead, + ref int mcu, + Stream stream) + { + if (componentsLength == 1) + { + ref FrameComponent component = ref components[this.compIndex]; + for (int n = 0; n < mcuToRead; n++) + { + this.DecodeBlockDCSuccessive(ref component, mcu, stream); + mcu++; + } + } + else + { + for (int n = 0; n < mcuToRead; n++) { - ref FrameComponent component = ref components[componentIndex]; - for (int n = 0; n < mcuToRead; n++) + for (int i = 0; i < componentsLength; i++) { - DecodeBlock(dcHuffmanTables, acHuffmanTables, ref component, decodeFn, mcu, stream); - mcu++; + ref FrameComponent component = ref components[i]; + int h = component.HorizontalFactor; + int v = component.VerticalFactor; + for (int j = 0; j < v; j++) + { + for (int k = 0; k < h; k++) + { + this.DecodeMcuDCSuccessive(ref component, mcusPerLine, mcu, j, k, stream); + } + } } + + mcu++; } - else + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeScanACFirst( + HuffmanTables acHuffmanTables, + FrameComponent[] components, + int componentsLength, + int mcusPerLine, + int mcuToRead, + ref int mcu, + Stream stream) + { + if (componentsLength == 1) + { + ref FrameComponent component = ref components[this.compIndex]; + for (int n = 0; n < mcuToRead; n++) + { + this.DecodeBlockACFirst(acHuffmanTables, ref component, mcu, stream); + mcu++; + } + } + else + { + for (int n = 0; n < mcuToRead; n++) { - for (int n = 0; n < mcuToRead; n++) + for (int i = 0; i < componentsLength; i++) { - for (int i = 0; i < componentsLength; i++) + ref FrameComponent component = ref components[i]; + int h = component.HorizontalFactor; + int v = component.VerticalFactor; + for (int j = 0; j < v; j++) { - ref FrameComponent component = ref components[i]; - int h = component.HorizontalFactor; - int v = component.VerticalFactor; - for (int j = 0; j < v; j++) + for (int k = 0; k < h; k++) { - for (int k = 0; k < h; k++) - { - DecodeMcu(dcHuffmanTables, acHuffmanTables, ref component, decodeFn, mcusPerLine, mcu, j, k, stream); - } + this.DecodeMcuACFirst(acHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); } } - - mcu++; } + + mcu++; } + } + } - // Find marker - // this.bitsCount = 0; - // // TODO: We need to make sure we are not overwriting anything here. - // fileMarker = JpegDecoderCore.FindNextFileMarker(stream); - // // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past - // // those to attempt to find a valid marker (fixes issue4090.pdf) in original code. - // if (fileMarker.Invalid) - // { - // #if DEBUG - // Debug.WriteLine("DecodeScan - Unexpected MCU data, next marker is: " + fileMarker.Marker.ToString("X")); - // #endif - // } - // ushort marker = fileMarker.Marker; - // if (marker <= 0xFF00) - // { - // throw new ImageFormatException("Marker was not found"); - // } - // if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) - // { - // // RSTx - // stream.Skip(2); - // } - // else - // { - // break; - // } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeScanACSuccessive( + HuffmanTables acHuffmanTables, + FrameComponent[] components, + int componentsLength, + int mcusPerLine, + int mcuToRead, + ref int mcu, + Stream stream) + { + if (componentsLength == 1) + { + ref FrameComponent component = ref components[this.compIndex]; + for (int n = 0; n < mcuToRead; n++) + { + this.DecodeBlockACSuccessive(acHuffmanTables, ref component, mcu, stream); + mcu++; + } + } + else + { + for (int n = 0; n < mcuToRead; n++) + { + for (int i = 0; i < componentsLength; i++) + { + ref FrameComponent component = ref components[i]; + int h = component.HorizontalFactor; + int v = component.VerticalFactor; + for (int j = 0; j < v; j++) + { + for (int k = 0; k < h; k++) + { + this.DecodeMcuACSuccessive(acHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); + } + } + } + + mcu++; + } } + } - // fileMarker = JpegDecoderCore.FindNextFileMarker(stream); - // // Some images include more Scan blocks than expected, skip past those and - // // attempt to find the next valid marker (fixes issue8182.pdf) in original code. - // if (fileMarker.Invalid) - // { - // #if DEBUG - // Debug.WriteLine("DecodeScan - Unexpected MCU data, next marker is: " + fileMarker.Marker.ToString("X")); - // #endif - // } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeBlockBaseline(HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, ref FrameComponent component, int mcu, Stream stream) + { + int blockRow = (mcu / component.BlocksPerLine) | 0; + int blockCol = mcu % component.BlocksPerLine; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeBaseline(ref component, offset, dcHuffmanTables, acHuffmanTables, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetBlockBufferOffset(ref FrameComponent component, int row, int col) + private void DecodeMcuBaseline(HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { - return 64 * (((component.BlocksPerLine + 1) * row) + col); + int mcuRow = (mcu / mcusPerLine) | 0; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * component.VerticalFactor) + row; + int blockCol = (mcuCol * component.HorizontalFactor) + col; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeBaseline(ref component, offset, dcHuffmanTables, acHuffmanTables, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DecodeMcu(HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, ref FrameComponent component, DecodeAction decode, int mcusPerLine, int mcu, int row, int col, Stream stream) + private void DecodeBlockDCFirst(HuffmanTables dcHuffmanTables, ref FrameComponent component, int mcu, Stream stream) + { + int blockRow = (mcu / component.BlocksPerLine) | 0; + int blockCol = mcu % component.BlocksPerLine; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeDCFirst(ref component, offset, dcHuffmanTables, stream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeMcuDCFirst(HuffmanTables dcHuffmanTables, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { int mcuRow = (mcu / mcusPerLine) | 0; int mcuCol = mcu % mcusPerLine; int blockRow = (mcuRow * component.VerticalFactor) + row; int blockCol = (mcuCol * component.HorizontalFactor) + col; - int offset = GetBlockBufferOffset(ref component, blockRow, blockCol); - decode(ref component, offset, dcHuffmanTables, acHuffmanTables, stream); + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeDCFirst(ref component, offset, dcHuffmanTables, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DecodeBlock(HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, ref FrameComponent component, DecodeAction decode, int mcu, Stream stream) + private void DecodeBlockDCSuccessive(ref FrameComponent component, int mcu, Stream stream) { int blockRow = (mcu / component.BlocksPerLine) | 0; int blockCol = mcu % component.BlocksPerLine; - int offset = GetBlockBufferOffset(ref component, blockRow, blockCol); - decode(ref component, offset, dcHuffmanTables, acHuffmanTables, stream); + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeDCSuccessive(ref component, offset, stream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeMcuDCSuccessive(ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) + { + int mcuRow = (mcu / mcusPerLine) | 0; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * component.VerticalFactor) + row; + int blockCol = (mcuCol * component.HorizontalFactor) + col; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeDCSuccessive(ref component, offset, stream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeBlockACFirst(HuffmanTables acHuffmanTables, ref FrameComponent component, int mcu, Stream stream) + { + int blockRow = (mcu / component.BlocksPerLine) | 0; + int blockCol = mcu % component.BlocksPerLine; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeACFirst(ref component, offset, acHuffmanTables, stream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeMcuACFirst(HuffmanTables acHuffmanTables, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) + { + int mcuRow = (mcu / mcusPerLine) | 0; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * component.VerticalFactor) + row; + int blockCol = (mcuCol * component.HorizontalFactor) + col; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeACFirst(ref component, offset, acHuffmanTables, stream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeBlockACSuccessive(HuffmanTables acHuffmanTables, ref FrameComponent component, int mcu, Stream stream) + { + int blockRow = (mcu / component.BlocksPerLine) | 0; + int blockCol = mcu % component.BlocksPerLine; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeACSuccessive(ref component, offset, acHuffmanTables, stream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeMcuACSuccessive(HuffmanTables acHuffmanTables, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) + { + int mcuRow = (mcu / mcusPerLine) | 0; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * component.VerticalFactor) + row; + int blockCol = (mcuCol * component.HorizontalFactor) + col; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeACSuccessive(ref component, offset, acHuffmanTables, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -257,10 +509,10 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } this.bitsData = stream.ReadByte(); - if (this.bitsData == JpegConstants.Markers.Prefix) + if (this.bitsData == 0xFF) { int nextByte = stream.ReadByte(); - if (nextByte > 0) + if (nextByte != 0) { throw new ImageFormatException($"Unexpected marker {(this.bitsData << 8) | nextByte}"); } @@ -354,7 +606,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeDCFirst(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) + private void DecodeDCFirst(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, Stream stream) { int t = this.DecodeHuffman(dcHuffmanTables[component.DCHuffmanTableId], stream); int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream) << this.successiveState; @@ -362,13 +614,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeDCSuccessive(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) + private void DecodeDCSuccessive(ref FrameComponent component, int offset, Stream stream) { component.BlockData[offset] |= (short)(this.ReadBit(stream) << this.successiveState); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeACFirst(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) + private void DecodeACFirst(ref FrameComponent component, int offset, HuffmanTables acHuffmanTables, Stream stream) { if (this.eobrun > 0) { @@ -404,7 +656,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeACSuccessive(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) + private void DecodeACSuccessive(ref FrameComponent component, int offset, HuffmanTables acHuffmanTables, Stream stream) { int k = this.specStart; int e = this.specEnd; @@ -439,7 +691,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } this.successiveACNextValue = this.ReceiveAndExtend(s, stream); - this.successiveACState = r != 0 ? 2 : 3; + this.successiveACState = r > 0 ? 2 : 3; } continue; diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/YCbCrToRgbTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/YCbCrToRgbTables.cs new file mode 100644 index 000000000..02397e1d7 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/YCbCrToRgbTables.cs @@ -0,0 +1,128 @@ +namespace ImageSharp.Formats.Jpeg.Port.Components +{ + using System.Runtime.CompilerServices; + using ImageSharp.PixelFormats; + + /// + /// Provides 8-bit lookup tables for converting from YCbCr to Rgb colorspace. + /// Methods to build the tables are based on libjpeg implementation. + /// + internal struct YCbCrToRgbTables + { + /// + /// The red red-chrominance table + /// + public static int[] CrRTable = new int[256]; + + /// + /// The blue blue-chrominance table + /// + public static int[] CbBTable = new int[256]; + + /// + /// The green red-chrominance table + /// + public static int[] CrGTable = new int[256]; + + /// + /// The green blue-chrominance table + /// + public static int[] CbGTable = new int[256]; + + // Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places. + private const int ScaleBits = 16; + + private const int Half = 1 << (ScaleBits - 1); + + private const int MinSample = 0; + + private const int HalfSample = 128; + + private const int MaxSample = 255; + + /// + /// Initializes the YCbCr tables + /// + public static void Create() + { + for (int i = 0, x = -128; i <= 255; i++, x++) + { + // i is the actual input pixel value, in the range 0..255 + // The Cb or Cr value we are thinking of is x = i - 128 + // Cr=>R value is nearest int to 1.402 * x + CrRTable[i] = RightShift((Fix(1.402F) * x) + Half); + + // Cb=>B value is nearest int to 1.772 * x + CbBTable[i] = RightShift((Fix(1.772F) * x) + Half); + + // Cr=>G value is scaled-up -0.714136286 + CrGTable[i] = (-Fix(0.714136286F)) * x; + + // Cb => G value is scaled - up - 0.344136286 * x + // We also add in Half so that need not do it in inner loop + CbGTable[i] = ((-Fix(0.344136286F)) * x) + Half; + } + } + + /// + /// Optimized method to pack bytes to the image from the YCbCr color space. + /// + /// The pixel format. + /// The packed pixel. + /// The y luminance component. + /// The cb chroma component. + /// The cr chroma component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void PackYCbCr(ref TPixel packed, byte y, byte cb, byte cr) + where TPixel : struct, IPixel + { + byte r = (byte)(y + CrRTable[cr]).Clamp(0, 255); + + // The values for the G calculation are left scaled up, since we must add them together before rounding. + byte g = (byte)(y + RightShift(CbGTable[cb] + CrGTable[cr])).Clamp(0, 255); + + byte b = (byte)(y + CbBTable[cb]).Clamp(0, 255); + + packed.PackFromRgba32(new Rgba32(r, g, b, 255)); + } + + /// + /// Optimized method to pack bytes to the image from the YccK color space. + /// + /// The pixel format. + /// The packed pixel. + /// The y luminance component. + /// The cb chroma component. + /// The cr chroma component. + /// The keyline component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void PackYccK(ref TPixel packed, byte y, byte cb, byte cr, byte k) + where TPixel : struct, IPixel + { + int c = (MaxSample - (y + CrRTable[cr])).Clamp(0, 255); + + // The values for the G calculation are left scaled up, since we must add them together before rounding. + int m = (MaxSample - (y + RightShift(CbGTable[cb] + CrGTable[cr]))).Clamp(0, 255); + + int cy = (MaxSample - (y + CbBTable[cb])).Clamp(0, 255); + + byte r = (byte)((c * k) / MaxSample); + byte g = (byte)((m * k) / MaxSample); + byte b = (byte)((cy * k) / MaxSample); + + packed.PackFromRgba32(new Rgba32(r, g, b, MaxSample)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Fix(float x) + { + return (int)((x * (1L << ScaleBits)) + 0.5F); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int RightShift(int x) + { + return x >> ScaleBits; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index b608b4951..2c2cd57a5 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -49,11 +49,13 @@ namespace ImageSharp.Formats.Jpeg.Port private ComponentBlocks components; + private JpegPixelArea pixelArea; + private ushort resetInterval; - private int width; + private int imageWidth; - private int height; + private int imageHeight; private int numComponents; @@ -67,6 +69,14 @@ namespace ImageSharp.Formats.Jpeg.Port ///
private Adobe adobe; + /// + /// Initializes static members of the class. + /// + static JpegDecoderCore() + { + YCbCrToRgbTables.Create(); + } + /// /// Initializes a new instance of the class. /// @@ -91,11 +101,12 @@ namespace ImageSharp.Formats.Jpeg.Port public static FileMarker FindNextFileMarkerNew(Stream stream) { byte[] marker = new byte[2]; + int value = stream.Read(marker, 0, 2); if (value == 0) { - return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); + return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length - 2); } if (marker[0] == JpegConstants.Markers.Prefix) @@ -107,14 +118,16 @@ namespace ImageSharp.Formats.Jpeg.Port int suffix = stream.ReadByte(); if (suffix == -1) { - return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); + return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length - 2); } marker[1] = (byte)value; } + + return new FileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2)); } - return new FileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2)); + return new FileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2), true); } /// @@ -182,7 +195,8 @@ namespace ImageSharp.Formats.Jpeg.Port this.InputStream = stream; this.ParseStream(); - var image = new Image(1, 1); + var image = new Image(this.imageWidth, this.imageHeight); + this.GetData(image); return image; } @@ -192,6 +206,7 @@ namespace ImageSharp.Formats.Jpeg.Port this.frame?.Dispose(); this.components?.Dispose(); this.quantizationTables?.Dispose(); + this.pixelArea.Dispose(); // Set large fields to null. this.frame = null; @@ -205,6 +220,9 @@ namespace ImageSharp.Formats.Jpeg.Port return 64 * (((component.BlocksPerLine + 1) * row) + col); } + /// + /// Parses the input stream for file markers + /// private void ParseStream() { // Check for the Start Of Image marker. @@ -224,12 +242,11 @@ namespace ImageSharp.Formats.Jpeg.Port while (fileMarker.Marker != JpegConstants.Markers.EOI) { // Get the marker length - int remaining; + int remaining = this.ReadUint16() - 2; switch (fileMarker.Marker) { case JpegConstants.Markers.APP0: - remaining = this.ReadUint16() - 2; this.ProcessApplicationHeaderMarker(remaining); break; @@ -246,10 +263,10 @@ namespace ImageSharp.Formats.Jpeg.Port case JpegConstants.Markers.APP11: case JpegConstants.Markers.APP12: case JpegConstants.Markers.APP13: + this.InputStream.Skip(remaining); break; case JpegConstants.Markers.APP14: - remaining = this.ReadUint16() - 2; this.ProcessApp14Marker(remaining); break; @@ -257,32 +274,28 @@ namespace ImageSharp.Formats.Jpeg.Port case JpegConstants.Markers.COM: // TODO: Read data block + this.InputStream.Skip(remaining); break; case JpegConstants.Markers.DQT: - remaining = this.ReadUint16() - 2; - this.ProcessDqtMarker(remaining); + this.ProcessDefineQuantizationTablesMarker(remaining); break; case JpegConstants.Markers.SOF0: case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF2: - remaining = this.ReadUint16() - 2; this.ProcessStartOfFrameMarker(remaining, fileMarker); break; case JpegConstants.Markers.DHT: - remaining = this.ReadUint16() - 2; this.ProcessDefineHuffmanTablesMarker(remaining); break; case JpegConstants.Markers.DRI: - remaining = this.ReadUint16() - 2; this.ProcessDefineRestartIntervalMarker(remaining); break; case JpegConstants.Markers.SOS: - this.InputStream.Skip(2); this.ProcessStartOfScanMarker(); break; @@ -295,30 +308,30 @@ namespace ImageSharp.Formats.Jpeg.Port break; - default: - - // TODO: Not convinced this is required - // Skip back as it could be incorrect encoding -- last 0xFF byte of the previous - // block was eaten by the encoder - this.InputStream.Position -= 3; - this.InputStream.Read(this.temp, 0, 2); - if (this.temp[0] == 0xFF && this.temp[1] >= 0xC0 && this.temp[1] <= 0xFE) - { - // Rewind that last bytes we read - this.InputStream.Position -= 2; - break; - } - - // throw new ImageFormatException($"Unknown Marker {fileMarker.Marker} at {fileMarker.Position}"); - break; + //default: + + // // TODO: Not convinced this is required + // // Skip back as it could be incorrect encoding -- last 0xFF byte of the previous + // // block was eaten by the encoder + // this.InputStream.Position -= 3; + // this.InputStream.Read(this.temp, 0, 2); + // if (this.temp[0] == 0xFF && this.temp[1] >= 0xC0 && this.temp[1] <= 0xFE) + // { + // // Rewind that last bytes we read + // this.InputStream.Position -= 2; + // break; + // } + + // // throw new ImageFormatException($"Unknown Marker {fileMarker.Marker} at {fileMarker.Position}"); + // break; } // Read on. TODO: Test this on damaged images. fileMarker = FindNextFileMarkerNew(this.InputStream); } - this.width = this.frame.SamplesPerLine; - this.height = this.frame.Scanlines; + this.imageWidth = this.frame.SamplesPerLine; + this.imageHeight = this.frame.Scanlines; this.components = new ComponentBlocks { Components = new Component[this.frame.ComponentCount] }; for (int i = 0; i < this.components.Components.Length; i++) @@ -339,6 +352,53 @@ namespace ImageSharp.Formats.Jpeg.Port this.numComponents = this.components.Components.Length; } + /// + /// Fills the given image with the color data + /// + /// The pixel format. + /// The image + private void GetData(Image image) + where TPixel : struct, IPixel + { + if (this.numComponents > 4) + { + throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.numComponents}"); + } + + this.pixelArea = new JpegPixelArea(image.Width, image.Height, this.numComponents); + this.pixelArea.LinearizeBlockData(this.components, image.Width, image.Height); + + if (this.numComponents == 1) + { + this.FillGrayScaleImage(image); + return; + } + + if (this.numComponents == 3) + { + if (this.adobe.Equals(default(Adobe)) || this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformYCbCr) + { + this.FillYCbCrImage(image); + } + else if (this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformUnknown) + { + this.FillRgbImage(image); + } + } + + if (this.numComponents == 4) + { + if (this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformYcck) + { + this.FillYcckImage(image); + } + else + { + this.FillCmykImage(image); + } + } + } + /// /// Processes the application header containing the JFIF identifier plus extra data. /// @@ -398,10 +458,10 @@ namespace ImageSharp.Formats.Jpeg.Port remaining -= 12; bool isAdobe = this.temp[0] == JpegConstants.Markers.Adobe.A && - this.temp[1] == JpegConstants.Markers.Adobe.D && - this.temp[2] == JpegConstants.Markers.Adobe.O && - this.temp[3] == JpegConstants.Markers.Adobe.B && - this.temp[4] == JpegConstants.Markers.Adobe.E; + this.temp[1] == JpegConstants.Markers.Adobe.D && + this.temp[2] == JpegConstants.Markers.Adobe.O && + this.temp[3] == JpegConstants.Markers.Adobe.B && + this.temp[4] == JpegConstants.Markers.Adobe.E; if (isAdobe) { @@ -427,7 +487,7 @@ namespace ImageSharp.Formats.Jpeg.Port /// /// Thrown if the tables do not match the header /// - private void ProcessDqtMarker(int remaining) + private void ProcessDefineQuantizationTablesMarker(int remaining) { while (remaining > 0) { @@ -437,7 +497,7 @@ namespace ImageSharp.Formats.Jpeg.Port if (quantizationTableSpec > 3) { - throw new ImageFormatException("Bad Tq index value"); + throw new ImageFormatException($"Bad Tq index value: {quantizationTableSpec}"); } switch (quantizationTableSpec >> 4) @@ -570,17 +630,17 @@ namespace ImageSharp.Formats.Jpeg.Port { if (remaining < 17) { - throw new ImageFormatException("DHT has wrong length"); + throw new ImageFormatException($"DHT has wrong length: {remaining}"); } - using (var huffmanData = new Buffer(16)) + using (var huffmanData = Buffer.CreateClean(16)) { for (int i = 2; i < remaining;) { byte huffmanTableSpec = (byte)this.InputStream.ReadByte(); this.InputStream.Read(huffmanData.Array, 0, 16); - using (var codeLengths = new Buffer(16)) + using (var codeLengths = Buffer.CreateClean(16)) { int codeLengthSum = 0; @@ -589,7 +649,7 @@ namespace ImageSharp.Formats.Jpeg.Port codeLengthSum += codeLengths[j] = huffmanData[j]; } - using (var huffmanValues = new Buffer(codeLengthSum)) + using (var huffmanValues = Buffer.CreateClean(codeLengthSum)) { this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum); @@ -615,7 +675,7 @@ namespace ImageSharp.Formats.Jpeg.Port { if (remaining != 2) { - throw new ImageFormatException("DRI has wrong length"); + throw new ImageFormatException($"DRI has wrong length: {remaining}"); } this.resetInterval = this.ReadUint16(); @@ -660,31 +720,33 @@ namespace ImageSharp.Formats.Jpeg.Port int successiveApproximation = this.temp[2]; var scanDecoder = default(ScanDecoder); - scanDecoder.DecodeScan( - this.frame, - this.InputStream, - this.dcHuffmanTables, - this.acHuffmanTables, - this.frame.Components, - componentIndex, - selectorsCount, - this.resetInterval, - spectralStart, - spectralEnd, - successiveApproximation >> 4, - successiveApproximation & 15); + long position = scanDecoder.DecodeScan( + this.frame, + this.InputStream, + this.dcHuffmanTables, + this.acHuffmanTables, + this.frame.Components, + componentIndex, + selectorsCount, + this.resetInterval, + spectralStart, + spectralEnd, + successiveApproximation >> 4, + successiveApproximation & 15); + + this.InputStream.Position += position; Debug.WriteLine("spectralStart= " + spectralStart); Debug.WriteLine("spectralEnd= " + spectralEnd); Debug.WriteLine("successiveApproximation= " + successiveApproximation); Debug.WriteLine("Components after"); - for (int i = 0; i < 3; i++) - { - for (int j = 0; j < 10; j++) - { - Debug.WriteLine("component [" + i + "] : value [" + j + "] =" + this.frame.Components[i].BlockData[j] + "]"); - } - } + //for (int i = 0; i < 3; i++) + //{ + // for (int j = 0; j < 10; j++) + // { + // Debug.WriteLine("component [" + i + "] : value [" + j + "] =" + this.frame.Components[i].BlockData[j] + "]"); + // } + //} } /// @@ -694,7 +756,6 @@ namespace ImageSharp.Formats.Jpeg.Port /// The frame component private void BuildComponentData(ref Component component, ref FrameComponent frameComponent) { - // TODO: Write this int blocksPerLine = component.BlocksPerLine; int blocksPerColumn = component.BlocksPerColumn; using (var computationBuffer = Buffer.CreateClean(64)) @@ -776,21 +837,21 @@ namespace ImageSharp.Formats.Jpeg.Port /// private void PrepareComponents() { - int mcusPerLine = (int)Math.Ceiling(this.frame.SamplesPerLine / 8D / this.frame.MaxHorizontalFactor); - int mcusPerColumn = (int)Math.Ceiling(this.frame.Scanlines / 8D / this.frame.MaxVerticalFactor); + int mcusPerLine = (int)MathF.Ceiling(this.frame.SamplesPerLine / 8F / this.frame.MaxHorizontalFactor); + int mcusPerColumn = (int)MathF.Ceiling(this.frame.Scanlines / 8F / this.frame.MaxVerticalFactor); for (int i = 0; i < this.frame.ComponentCount; i++) { ref var component = ref this.frame.Components[i]; - int blocksPerLine = (int)Math.Ceiling(Math.Ceiling(this.frame.SamplesPerLine / 8D) * component.HorizontalFactor / this.frame.MaxHorizontalFactor); - int blocksPerColumn = (int)Math.Ceiling(Math.Ceiling(this.frame.Scanlines / 8D) * component.VerticalFactor / this.frame.MaxVerticalFactor); + int blocksPerLine = (int)MathF.Ceiling(MathF.Ceiling(this.frame.SamplesPerLine / 8F) * component.HorizontalFactor / this.frame.MaxHorizontalFactor); + int blocksPerColumn = (int)MathF.Ceiling(MathF.Ceiling(this.frame.Scanlines / 8F) * component.VerticalFactor / this.frame.MaxVerticalFactor); int blocksPerLineForMcu = mcusPerLine * component.HorizontalFactor; int blocksPerColumnForMcu = mcusPerColumn * component.VerticalFactor; int blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1); - // Pooled. Disposed via frame siposal - component.BlockData = new Buffer(blocksBufferSize); + // Pooled. Disposed via frame disposal + component.BlockData = Buffer.CreateClean(blocksBufferSize); component.BlocksPerLine = blocksPerLine; component.BlocksPerColumn = blocksPerColumn; } @@ -799,6 +860,104 @@ namespace ImageSharp.Formats.Jpeg.Port this.frame.McusPerColumn = mcusPerColumn; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FillGrayScaleImage(Image image) + where TPixel : struct, IPixel + { + for (int y = 0; y < image.Height; y++) + { + Span imageRowSpan = image.GetRowSpan(y); + Span areaRowSpan = this.pixelArea.GetRowSpan(y); + + for (int x = 0; x < image.Width; x++) + { + ref byte luminance = ref areaRowSpan[x]; + ref TPixel pixel = ref imageRowSpan[x]; + var rgba = new Rgba32(luminance, luminance, luminance); + pixel.PackFromRgba32(rgba); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FillYCbCrImage(Image image) + where TPixel : struct, IPixel + { + for (int y = 0; y < image.Height; y++) + { + Span imageRowSpan = image.GetRowSpan(y); + Span areaRowSpan = this.pixelArea.GetRowSpan(y); + for (int x = 0, o = 0; x < image.Width; x++, o += 3) + { + ref byte yy = ref areaRowSpan[o]; + ref byte cb = ref areaRowSpan[o + 1]; + ref byte cr = ref areaRowSpan[o + 2]; + ref TPixel pixel = ref imageRowSpan[x]; + YCbCrToRgbTables.PackYCbCr(ref pixel, yy, cb, cr); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FillYcckImage(Image image) + where TPixel : struct, IPixel + { + for (int y = 0; y < image.Height; y++) + { + Span imageRowSpan = image.GetRowSpan(y); + Span areaRowSpan = this.pixelArea.GetRowSpan(y); + for (int x = 0, o = 0; x < image.Width; x++, o += 4) + { + ref byte yy = ref areaRowSpan[o]; + ref byte cb = ref areaRowSpan[o + 1]; + ref byte cr = ref areaRowSpan[o + 2]; + ref byte k = ref areaRowSpan[o + 3]; + + ref TPixel pixel = ref imageRowSpan[x]; + YCbCrToRgbTables.PackYccK(ref pixel, yy, cb, cr, k); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FillCmykImage(Image image) + where TPixel : struct, IPixel + { + for (int y = 0; y < image.Height; y++) + { + Span imageRowSpan = image.GetRowSpan(y); + Span areaRowSpan = this.pixelArea.GetRowSpan(y); + for (int x = 0, o = 0; x < image.Width; x++, o += 4) + { + ref byte c = ref areaRowSpan[o]; + ref byte m = ref areaRowSpan[o + 1]; + ref byte cy = ref areaRowSpan[o + 2]; + ref byte k = ref areaRowSpan[o + 3]; + + byte r = (byte)((c * k) / 255); + byte g = (byte)((m * k) / 255); + byte b = (byte)((cy * k) / 255); + + ref TPixel pixel = ref imageRowSpan[x]; + var rgba = new Rgba32(r, g, b); + pixel.PackFromRgba32(rgba); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FillRgbImage(Image image) + where TPixel : struct, IPixel + { + for (int y = 0; y < image.Height; y++) + { + Span imageRowSpan = image.GetRowSpan(y); + Span areaRowSpan = this.pixelArea.GetRowSpan(y); + + PixelOperations.Instance.PackFromRgb24Bytes(areaRowSpan, imageRowSpan, image.Width); + } + } + /// /// Reads a from the stream advancing it by two bytes /// diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index f1b78383c..d274d61a2 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -12,8 +12,6 @@ namespace ImageSharp.Tests using System.Linq; using System.Reflection; - using ImageSharp.PixelFormats; - /// /// A test image file. /// @@ -32,7 +30,7 @@ namespace ImageSharp.Tests /// /// The image. /// - private readonly Image image; + private Image image; /// /// The file. @@ -46,9 +44,7 @@ namespace ImageSharp.Tests private TestFile(string file) { this.file = file; - this.Bytes = File.ReadAllBytes(file); - this.image = Image.Load(this.Bytes); } /// @@ -129,7 +125,7 @@ namespace ImageSharp.Tests /// public Image CreateImage() { - return new Image(this.image); + return new Image(this.GetImage()); } /// @@ -144,6 +140,11 @@ namespace ImageSharp.Tests return Image.Load(this.Bytes, options); } + private Image GetImage() + { + return this.image ?? (this.image = Image.Load(this.Bytes)); + } + /// /// Gets the correct path to the formats directory. /// @@ -152,7 +153,7 @@ namespace ImageSharp.Tests /// private static string GetFormatsDirectory() { - List directories = new List< string > { + var directories = new List { "TestImages/Formats/", // Here for code coverage tests. "tests/ImageSharp.Tests/TestImages/Formats/", // from travis/build script "../../../../../ImageSharp.Tests/TestImages/Formats/", // from Sandbox46 @@ -167,9 +168,9 @@ namespace ImageSharp.Tests AddFormatsDirectoryFromTestAssebmlyPath(directories); - string directory = directories.FirstOrDefault(x => Directory.Exists(x)); + string directory = directories.FirstOrDefault(Directory.Exists); - if(directory != null) + if (directory != null) { return directory; } diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ycck - Copy.jpg b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ycck - Copy.jpg new file mode 100644 index 000000000..2fe8f0a61 --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ycck - Copy.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33e3546a64df7fa1d528441926421b193e399a83490a6307762fb7eee9640bf0 +size 611572 From 039e18f098a8b37981648a875b8f9ef132206f10 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 28 Jun 2017 12:37:25 +1000 Subject: [PATCH 19/43] Can now decode that bad progressive image --- .../Jpeg/Port/Components/ScanDecoder.cs | 162 +++++++++++++++--- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 16 +- 2 files changed, 138 insertions(+), 40 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index da7bb52a9..73e597ccd 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -35,6 +35,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private int successiveACNextValue; + private bool endOfStreamReached; + /// /// Decodes the spectral scan /// @@ -50,8 +52,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// The spectral selection end /// The successive approximation bit high end /// The successive approximation bit low end - /// The representing the processed length in bytes - public long DecodeScan( + public void DecodeScan( Frame frame, Stream stream, HuffmanTables dcHuffmanTables, @@ -69,9 +70,10 @@ namespace ImageSharp.Formats.Jpeg.Port.Components this.specStart = spectralStart; this.specEnd = spectralEnd; this.successiveState = successive; + this.endOfStreamReached = false; + bool progressive = frame.Progressive; int mcusPerLine = frame.McusPerLine; - long startPosition = stream.Position; int mcu = 0; int mcuExpected; @@ -129,7 +131,6 @@ namespace ImageSharp.Formats.Jpeg.Port.Components // Find marker this.bitsCount = 0; - long position = stream.Position; fileMarker = JpegDecoderCore.FindNextFileMarkerNew(stream); // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past @@ -137,20 +138,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components if (fileMarker.Invalid) { #if DEBUG - Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker}"); - + Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}"); #endif - // stream.Position = fileMarker.Position; } ushort marker = fileMarker.Marker; - // if (marker <= 0xFF00) - // { - // throw new ImageFormatException("Marker was not found"); - // } - - // RSTn We've alread read the bytes and altered the position so no need to skip + // RSTn - We've alread read the bytes and altered the position so no need to skip if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) { continue; @@ -159,13 +153,10 @@ namespace ImageSharp.Formats.Jpeg.Port.Components if (!fileMarker.Invalid) { // We've found a valid marker. - // Rewind the stream to the position of the marker and beak + // Rewind the stream to the position of the marker and break stream.Position = fileMarker.Position; break; } - - // Rewind the stream - stream.Position = position; } fileMarker = JpegDecoderCore.FindNextFileMarkerNew(stream); @@ -175,12 +166,15 @@ namespace ImageSharp.Formats.Jpeg.Port.Components if (fileMarker.Invalid) { #if DEBUG - Debug.WriteLine($"DecodeScan - Unexpected MCU data, next marker is: {fileMarker.Marker}"); + Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}"); #endif + } + else + { + // We've found a valid marker. + // Rewind the stream to the position of the marker stream.Position = fileMarker.Position; } - - return stream.Position - startPosition; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -205,6 +199,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components ref FrameComponent component = ref components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeBlockBaseline(dcHuffmanTables, acHuffmanTables, ref component, mcu, stream); mcu++; } @@ -222,6 +221,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { for (int k = 0; k < h; k++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeMcuBaseline(dcHuffmanTables, acHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); } } @@ -247,6 +251,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components ref FrameComponent component = ref components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeBlockDCFirst(dcHuffmanTables, ref component, mcu, stream); mcu++; } @@ -264,6 +273,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { for (int k = 0; k < h; k++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeMcuDCFirst(dcHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); } } @@ -288,6 +302,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components ref FrameComponent component = ref components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeBlockDCSuccessive(ref component, mcu, stream); mcu++; } @@ -305,6 +324,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { for (int k = 0; k < h; k++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeMcuDCSuccessive(ref component, mcusPerLine, mcu, j, k, stream); } } @@ -330,6 +354,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components ref FrameComponent component = ref components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeBlockACFirst(acHuffmanTables, ref component, mcu, stream); mcu++; } @@ -347,6 +376,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { for (int k = 0; k < h; k++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeMcuACFirst(acHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); } } @@ -372,6 +406,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components ref FrameComponent component = ref components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeBlockACSuccessive(acHuffmanTables, ref component, mcu, stream); mcu++; } @@ -389,6 +428,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { for (int k = 0; k < h; k++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeMcuACSuccessive(acHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); } } @@ -509,6 +553,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } this.bitsData = stream.ReadByte(); + + if (this.bitsData == -0x1) + { + // We've encountered the end of the file stream which means there's no EOI marker in the image + this.endOfStreamReached = true; + } + if (this.bitsData == 0xFF) { int nextByte = stream.ReadByte(); @@ -527,10 +578,16 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private short DecodeHuffman(HuffmanBranch[] tree, Stream stream) { + // TODO: This is our bottleneck. We should use a faster algorithm with a LUT. HuffmanBranch[] node = tree; while (true) { int index = this.ReadBit(stream); + if (this.endOfStreamReached) + { + return -1; + } + HuffmanBranch branch = node[index]; if (branch.Value > -1) @@ -548,7 +605,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components int n = 0; while (length > 0) { - n = (n << 1) | this.ReadBit(stream); + int bit = this.ReadBit(stream); + if (this.endOfStreamReached) + { + return -1; + } + + n = (n << 1) | bit; length--; } @@ -576,6 +639,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private void DecodeBaseline(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) { int t = this.DecodeHuffman(dcHuffmanTables[component.DCHuffmanTableId], stream); + if (this.endOfStreamReached) + { + return; + } + int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream); component.BlockData[offset] = (short)(component.Pred += diff); @@ -583,6 +651,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components while (k < 64) { int rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream); + if (this.endOfStreamReached) + { + return; + } + int s = rs & 15; int r = rs >> 4; @@ -609,6 +682,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private void DecodeDCFirst(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, Stream stream) { int t = this.DecodeHuffman(dcHuffmanTables[component.DCHuffmanTableId], stream); + if (this.endOfStreamReached) + { + return; + } + int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream) << this.successiveState; component.BlockData[offset] = (short)(component.Pred += diff); } @@ -616,7 +694,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeDCSuccessive(ref FrameComponent component, int offset, Stream stream) { - component.BlockData[offset] |= (short)(this.ReadBit(stream) << this.successiveState); + int bit = this.ReadBit(stream); + if (this.endOfStreamReached) + { + return; + } + + component.BlockData[offset] |= (short)(bit << this.successiveState); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -633,6 +717,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components while (k <= e) { short rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream); + if (this.endOfStreamReached) + { + return; + } + int s = rs & 15; int r = rs >> 4; @@ -668,6 +757,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { case 0: // Initial state short rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream); + if (this.endOfStreamReached) + { + return; + } + int s = rs & 15; r = rs >> 4; if (s == 0) @@ -699,7 +793,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components case 2: if (component.BlockData[offset + z] != 0) { - component.BlockData[offset + z] += (short)(this.ReadBit(stream) << this.successiveState); + int bit = this.ReadBit(stream); + if (this.endOfStreamReached) + { + return; + } + + component.BlockData[offset + z] += (short)(bit << this.successiveState); } else { @@ -714,7 +814,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components case 3: // Set value for a zero item if (component.BlockData[offset + z] != 0) { - component.BlockData[offset + z] += (short)(this.ReadBit(stream) << this.successiveState); + int bit = this.ReadBit(stream); + if (this.endOfStreamReached) + { + return; + } + + component.BlockData[offset + z] += (short)(bit << this.successiveState); } else { @@ -726,7 +832,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components case 4: // Eob if (component.BlockData[offset + z] != 0) { - component.BlockData[offset + z] += (short)(this.ReadBit(stream) << this.successiveState); + int bit = this.ReadBit(stream); + if (this.endOfStreamReached) + { + return; + } + + component.BlockData[offset + z] += (short)(bit << this.successiveState); } break; diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 2c2cd57a5..152a2b43a 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -720,7 +720,7 @@ namespace ImageSharp.Formats.Jpeg.Port int successiveApproximation = this.temp[2]; var scanDecoder = default(ScanDecoder); - long position = scanDecoder.DecodeScan( + scanDecoder.DecodeScan( this.frame, this.InputStream, this.dcHuffmanTables, @@ -733,20 +733,6 @@ namespace ImageSharp.Formats.Jpeg.Port spectralEnd, successiveApproximation >> 4, successiveApproximation & 15); - - this.InputStream.Position += position; - - Debug.WriteLine("spectralStart= " + spectralStart); - Debug.WriteLine("spectralEnd= " + spectralEnd); - Debug.WriteLine("successiveApproximation= " + successiveApproximation); - Debug.WriteLine("Components after"); - //for (int i = 0; i < 3; i++) - //{ - // for (int j = 0; j < 10; j++) - // { - // Debug.WriteLine("component [" + i + "] : value [" + j + "] =" + this.frame.Components[i].BlockData[j] + "]"); - // } - //} } /// From fb5ec5ebfc89b02732ecdedb263dbb95eb228b26 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 28 Jun 2017 14:35:46 +1000 Subject: [PATCH 20/43] Now decodes all images --- .../Formats/Jpeg/Port/Components/JFif.cs | 40 ++- .../Jpeg/Port/Components/ScanDecoder.cs | 7 +- .../Formats/Jpeg/Port/JpegConstants.cs | 94 ++++++- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 251 ++++++++++-------- tests/ImageSharp.Tests/TestFile.cs | 20 +- 5 files changed, 291 insertions(+), 121 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/JFif.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/JFif.cs index 7fa6c44d0..6baecdf68 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/JFif.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/JFif.cs @@ -5,10 +5,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { + using System; + /// /// Provides information about the JFIF marker segment + /// TODO: Thumbnail? /// - internal struct JFif + internal struct JFif : IEquatable { /// /// The major version @@ -38,6 +41,39 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// public short YDensity; - // TODO: Thumbnail? + /// + public bool Equals(JFif other) + { + return this.MajorVersion == other.MajorVersion + && this.MinorVersion == other.MinorVersion + && this.DensityUnits == other.DensityUnits + && this.XDensity == other.XDensity + && this.YDensity == other.YDensity; + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + return obj is JFif && this.Equals((JFif)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.MajorVersion.GetHashCode(); + hashCode = (hashCode * 397) ^ this.MinorVersion.GetHashCode(); + hashCode = (hashCode * 397) ^ this.DensityUnits.GetHashCode(); + hashCode = (hashCode * 397) ^ this.XDensity.GetHashCode(); + hashCode = (hashCode * 397) ^ this.YDensity.GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 73e597ccd..4ec7571b5 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -17,6 +17,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// internal struct ScanDecoder { + private byte[] markerBuffer; + private int bitsData; private int bitsCount; @@ -66,6 +68,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components int successivePrev, int successive) { + this.markerBuffer = new byte[2]; this.compIndex = componentIndex; this.specStart = spectralStart; this.specEnd = spectralEnd; @@ -131,7 +134,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components // Find marker this.bitsCount = 0; - fileMarker = JpegDecoderCore.FindNextFileMarkerNew(stream); + fileMarker = JpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream); // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past // those to attempt to find a valid marker (fixes issue4090.pdf) in original code. @@ -159,7 +162,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } } - fileMarker = JpegDecoderCore.FindNextFileMarkerNew(stream); + fileMarker = JpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream); // Some images include more Scan blocks than expected, skip past those and // attempt to find the next valid marker (fixes issue8182.pdf) in original code. diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs index 08ae5543d..a02e05591 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs @@ -196,11 +196,6 @@ namespace ImageSharp.Formats.Jpeg.Port /// public const ushort RST7 = 0xFFD7; - /// - /// Marker prefix. Next byte is a marker. - /// - public const ushort XFF = 0xFFFF; - /// /// Contains JFIF specific markers /// @@ -224,7 +219,7 @@ namespace ImageSharp.Formats.Jpeg.Port /// /// Represents the null "0" marker /// - public const byte Null = 0; + public const byte Null = 0x0; } /// @@ -272,6 +267,93 @@ namespace ImageSharp.Formats.Jpeg.Port /// public const byte ColorTransformYcck = 2; } + + /// + /// Contains EXIF specific markers + /// + public static class Exif + { + /// + /// Represents E in ASCII + /// + public const byte E = 0x45; + + /// + /// Represents x in ASCII + /// + public const byte X = 0x78; + + /// + /// Represents i in ASCII + /// + public const byte I = 0x69; + + /// + /// Represents f in ASCII + /// + public const byte F = 0x66; + + /// + /// Represents the null "0" marker + /// + public const byte Null = 0x0; + } + + /// + /// Contains ICC specific markers + /// + public static class ICC + { + /// + /// Represents I in ASCII + /// + public const byte I = 0x49; + + /// + /// Represents C in ASCII + /// + public const byte C = 0x43; + + /// + /// Represents _ in ASCII + /// + public const byte UnderScore = 0x5F; + + /// + /// Represents P in ASCII + /// + public const byte P = 0x50; + + /// + /// Represents R in ASCII + /// + public const byte R = 0x52; + + /// + /// Represents O in ASCII + /// + public const byte O = 0x4F; + + /// + /// Represents F in ASCII + /// + public const byte F = 0x46; + + /// + /// Represents L in ASCII + /// + public const byte L = 0x4C; + + /// + /// Represents E in ASCII + /// + public const byte E = 0x45; + + /// + /// Represents the null "0" marker + /// + public const byte Null = 0x0; + } } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 152a2b43a..6a1d6311c 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -7,7 +7,6 @@ namespace ImageSharp.Formats.Jpeg.Port { using System; using System.Collections.Generic; - using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; @@ -18,7 +17,7 @@ namespace ImageSharp.Formats.Jpeg.Port /// /// Performs the jpeg decoding operation. - /// Ported from + /// Ported from with additional fixes to handle common encoding errors /// internal sealed class JpegDecoderCore : IDisposable { @@ -37,7 +36,7 @@ namespace ImageSharp.Formats.Jpeg.Port /// private readonly byte[] temp = new byte[2 * 16 * 4]; - private readonly byte[] uint16Buffer = new byte[2]; + private readonly byte[] markerBuffer = new byte[2]; private QuantizationTables quantizationTables; @@ -57,7 +56,12 @@ namespace ImageSharp.Formats.Jpeg.Port private int imageHeight; - private int numComponents; + private int numberOfComponents; + + /// + /// Whether the image has a EXIF header + /// + private bool isExif; /// /// Contains information about the JFIF marker @@ -94,14 +98,13 @@ namespace ImageSharp.Formats.Jpeg.Port public Stream InputStream { get; private set; } /// - /// Finds the next file marker within the byte stream. Not used but I'm keeping it for now for testing + /// Finds the next file marker within the byte stream. /// + /// The buffer to read file markers to /// The input stream /// The - public static FileMarker FindNextFileMarkerNew(Stream stream) + public static FileMarker FindNextFileMarker(byte[] marker, Stream stream) { - byte[] marker = new byte[2]; - int value = stream.Read(marker, 0, 2); if (value == 0) @@ -131,60 +134,7 @@ namespace ImageSharp.Formats.Jpeg.Port } /// - /// Finds the next file marker within the byte stream - /// - /// The input stream - /// The - public static FileMarker FindNextFileMarker(Stream stream) - { - byte[] buffer = new byte[2]; - long maxPos = stream.Length - 1; - long currentPos = stream.Position; - long newPos = currentPos; - - if (currentPos >= maxPos) - { - return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); - } - - int value = stream.Read(buffer, 0, 2); - - if (value == 0) - { - return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); - } - - ushort currentMarker = (ushort)((buffer[0] << 8) | buffer[1]); - if (currentMarker >= JpegConstants.Markers.SOF0 && currentMarker <= JpegConstants.Markers.COM) - { - return new FileMarker(currentMarker, stream.Position - 2); - } - - value = stream.Read(buffer, 0, 2); - - if (value == 0) - { - return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); - } - - ushort newMarker = (ushort)((buffer[0] << 8) | buffer[1]); - while (!(newMarker >= JpegConstants.Markers.SOF0 && newMarker <= JpegConstants.Markers.COM)) - { - if (++newPos >= maxPos) - { - return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); - } - - stream.Read(buffer, 0, 2); - newMarker = (ushort)((buffer[0] << 8) | buffer[1]); - } - - return new FileMarker(newMarker, newPos, true); - } - - /// - /// Decodes the image from the specified and sets - /// the data to image. + /// Decodes the image from the specified and sets the data to image. /// /// The pixel format. /// The stream, where the image should be. @@ -193,10 +143,13 @@ namespace ImageSharp.Formats.Jpeg.Port where TPixel : struct, IPixel { this.InputStream = stream; - this.ParseStream(); - var image = new Image(this.imageWidth, this.imageHeight); - this.GetData(image); + var metadata = new ImageMetaData(); + this.ParseStream(metadata, false); + + var image = new Image(this.configuration, this.imageWidth, this.imageHeight, metadata); + this.FillPixelData(image); + this.AssignResolution(image); return image; } @@ -223,8 +176,11 @@ namespace ImageSharp.Formats.Jpeg.Port /// /// Parses the input stream for file markers /// - private void ParseStream() + /// Contains the metadata for an image + /// Whether to decode metadata only. + private void ParseStream(ImageMetaData metaData, bool metadataOnly) { + // TODO: metadata only logic // Check for the Start Of Image marker. var fileMarker = new FileMarker(this.ReadUint16(), 0); if (fileMarker.Marker != JpegConstants.Markers.SOI) @@ -251,7 +207,12 @@ namespace ImageSharp.Formats.Jpeg.Port break; case JpegConstants.Markers.APP1: + this.ProcessApp1Marker(remaining, metaData); + break; + case JpegConstants.Markers.APP2: + this.ProcessApp2Marker(remaining, metaData); + break; case JpegConstants.Markers.APP3: case JpegConstants.Markers.APP4: case JpegConstants.Markers.APP5: @@ -298,36 +259,10 @@ namespace ImageSharp.Formats.Jpeg.Port case JpegConstants.Markers.SOS: this.ProcessStartOfScanMarker(); break; - - case JpegConstants.Markers.XFF: - if ((byte)this.InputStream.ReadByte() != 0xFF) - { - // Avoid skipping a valid marker - this.InputStream.Position -= 1; - } - - break; - - //default: - - // // TODO: Not convinced this is required - // // Skip back as it could be incorrect encoding -- last 0xFF byte of the previous - // // block was eaten by the encoder - // this.InputStream.Position -= 3; - // this.InputStream.Read(this.temp, 0, 2); - // if (this.temp[0] == 0xFF && this.temp[1] >= 0xC0 && this.temp[1] <= 0xFE) - // { - // // Rewind that last bytes we read - // this.InputStream.Position -= 2; - // break; - // } - - // // throw new ImageFormatException($"Unknown Marker {fileMarker.Marker} at {fileMarker.Position}"); - // break; } - // Read on. TODO: Test this on damaged images. - fileMarker = FindNextFileMarkerNew(this.InputStream); + // Read on. + fileMarker = FindNextFileMarker(this.markerBuffer, this.InputStream); } this.imageWidth = this.frame.SamplesPerLine; @@ -349,7 +284,7 @@ namespace ImageSharp.Formats.Jpeg.Port this.components.Components[i] = component; } - this.numComponents = this.components.Components.Length; + this.numberOfComponents = this.components.Components.Length; } /// @@ -357,24 +292,24 @@ namespace ImageSharp.Formats.Jpeg.Port /// /// The pixel format. /// The image - private void GetData(Image image) + private void FillPixelData(Image image) where TPixel : struct, IPixel { - if (this.numComponents > 4) + if (this.numberOfComponents > 4) { - throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.numComponents}"); + throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.numberOfComponents}"); } - this.pixelArea = new JpegPixelArea(image.Width, image.Height, this.numComponents); + this.pixelArea = new JpegPixelArea(image.Width, image.Height, this.numberOfComponents); this.pixelArea.LinearizeBlockData(this.components, image.Width, image.Height); - if (this.numComponents == 1) + if (this.numberOfComponents == 1) { this.FillGrayScaleImage(image); return; } - if (this.numComponents == 3) + if (this.numberOfComponents == 3) { if (this.adobe.Equals(default(Adobe)) || this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformYCbCr) { @@ -386,7 +321,7 @@ namespace ImageSharp.Formats.Jpeg.Port } } - if (this.numComponents == 4) + if (this.numberOfComponents == 4) { if (this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformYcck) { @@ -399,6 +334,34 @@ namespace ImageSharp.Formats.Jpeg.Port } } + /// + /// Assigns the horizontal and vertical resolution to the image if it has a JFIF header or EXIF metadata. + /// + /// The pixel format. + /// The image to assign the resolution to. + private void AssignResolution(Image image) + where TPixel : struct, IPixel + { + if (this.isExif) + { + ExifValue horizontal = image.MetaData.ExifProfile.GetValue(ExifTag.XResolution); + ExifValue vertical = image.MetaData.ExifProfile.GetValue(ExifTag.YResolution); + double horizontalValue = horizontal != null ? ((Rational)horizontal.Value).ToDouble() : 0; + double verticalValue = vertical != null ? ((Rational)vertical.Value).ToDouble() : 0; + + if (horizontalValue > 0 && verticalValue > 0) + { + image.MetaData.HorizontalResolution = horizontalValue; + image.MetaData.VerticalResolution = verticalValue; + } + } + else if (this.jFif.XDensity > 0 && this.jFif.YDensity > 0) + { + image.MetaData.HorizontalResolution = this.jFif.XDensity; + image.MetaData.VerticalResolution = this.jFif.YDensity; + } + } + /// /// Processes the application header containing the JFIF identifier plus extra data. /// @@ -440,6 +403,86 @@ namespace ImageSharp.Formats.Jpeg.Port } } + /// + /// Processes the App1 marker retrieving any stored metadata + /// + /// The remaining bytes in the segment block. + /// The image. + private void ProcessApp1Marker(int remaining, ImageMetaData metadata) + { + if (remaining < 6 || this.options.IgnoreMetadata) + { + // Skip the application header length + this.InputStream.Skip(remaining); + return; + } + + byte[] profile = new byte[remaining]; + this.InputStream.Read(profile, 0, remaining); + + if (profile[0] == JpegConstants.Markers.Exif.E && + profile[1] == JpegConstants.Markers.Exif.X && + profile[2] == JpegConstants.Markers.Exif.I && + profile[3] == JpegConstants.Markers.Exif.F && + profile[4] == JpegConstants.Markers.Exif.Null && + profile[5] == JpegConstants.Markers.Exif.Null) + { + this.isExif = true; + metadata.ExifProfile = new ExifProfile(profile); + } + } + + /// + /// Processes the App2 marker retrieving any stored ICC profile information + /// + /// The remaining bytes in the segment block. + /// The image. + private void ProcessApp2Marker(int remaining, ImageMetaData metadata) + { + // Length is 14 though we only need to check 12. + const int Icclength = 14; + if (remaining < Icclength || this.options.IgnoreMetadata) + { + this.InputStream.Skip(remaining); + return; + } + + byte[] identifier = new byte[Icclength]; + this.InputStream.Read(identifier, 0, Icclength); + remaining -= Icclength; // We have read it by this point + + if (identifier[0] == JpegConstants.Markers.ICC.I && + identifier[1] == JpegConstants.Markers.ICC.C && + identifier[2] == JpegConstants.Markers.ICC.C && + identifier[3] == JpegConstants.Markers.ICC.UnderScore && + identifier[4] == JpegConstants.Markers.ICC.P && + identifier[5] == JpegConstants.Markers.ICC.R && + identifier[6] == JpegConstants.Markers.ICC.O && + identifier[7] == JpegConstants.Markers.ICC.F && + identifier[8] == JpegConstants.Markers.ICC.I && + identifier[9] == JpegConstants.Markers.ICC.L && + identifier[10] == JpegConstants.Markers.ICC.E && + identifier[11] == JpegConstants.Markers.ICC.Null) + { + byte[] profile = new byte[remaining]; + this.InputStream.Read(profile, 0, remaining); + + if (metadata.IccProfile == null) + { + metadata.IccProfile = new IccProfile(profile); + } + else + { + metadata.IccProfile.Extend(profile); + } + } + else + { + // Not an ICC profile we can handle. Skip the remaining bytes so we can carry on and ignore this. + this.InputStream.Skip(remaining); + } + } + /// /// Processes the application header containing the Adobe identifier /// which stores image encoding information for DCT filters. @@ -951,8 +994,8 @@ namespace ImageSharp.Formats.Jpeg.Port [MethodImpl(MethodImplOptions.AggressiveInlining)] private ushort ReadUint16() { - this.InputStream.Read(this.uint16Buffer, 0, 2); - return (ushort)((this.uint16Buffer[0] << 8) | this.uint16Buffer[1]); + this.InputStream.Read(this.markerBuffer, 0, 2); + return (ushort)((this.markerBuffer[0] << 8) | this.markerBuffer[1]); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index d274d61a2..bcfb6cb13 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -27,6 +27,8 @@ namespace ImageSharp.Tests /// private static readonly string FormatsDirectory = GetFormatsDirectory(); + private static readonly object Locker = new object(); + /// /// The image. /// @@ -142,7 +144,10 @@ namespace ImageSharp.Tests private Image GetImage() { - return this.image ?? (this.image = Image.Load(this.Bytes)); + lock (Locker) + { + return this.image ?? (this.image = Image.Load(this.Bytes)); + } } /// @@ -155,16 +160,17 @@ namespace ImageSharp.Tests { var directories = new List { "TestImages/Formats/", // Here for code coverage tests. - "tests/ImageSharp.Tests/TestImages/Formats/", // from travis/build script - "../../../../../ImageSharp.Tests/TestImages/Formats/", // from Sandbox46 + "tests/ImageSharp.Tests/TestImages/Formats/", // From travis/build script + "../../../../../ImageSharp.Tests/TestImages/Formats/", // From Sandbox46 "../../../../TestImages/Formats/", "../../../TestImages/Formats/" }; - directories = directories.SelectMany(x => new[] - { - Path.GetFullPath(x) - }).ToList(); + directories = directories + .SelectMany(x => new[] + { + Path.GetFullPath(x) + }).ToList(); AddFormatsDirectoryFromTestAssebmlyPath(directories); From c75077be27a3979a0a4e64595d5ac0767a3d66e0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 28 Jun 2017 15:26:13 +1000 Subject: [PATCH 21/43] Fix #159 --- .../Jpeg/Port/Components/ScanDecoder.cs | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 4ec7571b5..13a63c5c3 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -39,6 +39,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private bool endOfStreamReached; + private bool unexpectedMarkerReached; + /// /// Decodes the spectral scan /// @@ -74,6 +76,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components this.specEnd = spectralEnd; this.successiveState = successive; this.endOfStreamReached = false; + this.unexpectedMarkerReached = false; bool progressive = frame.Progressive; int mcusPerLine = frame.McusPerLine; @@ -202,7 +205,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components ref FrameComponent component = ref components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { continue; } @@ -224,7 +227,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { for (int k = 0; k < h; k++) { - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { continue; } @@ -254,7 +257,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components ref FrameComponent component = ref components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { continue; } @@ -276,7 +279,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { for (int k = 0; k < h; k++) { - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { continue; } @@ -305,7 +308,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components ref FrameComponent component = ref components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { continue; } @@ -327,7 +330,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { for (int k = 0; k < h; k++) { - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { continue; } @@ -357,7 +360,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components ref FrameComponent component = ref components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { continue; } @@ -379,7 +382,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { for (int k = 0; k < h; k++) { - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { continue; } @@ -409,7 +412,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components ref FrameComponent component = ref components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { continue; } @@ -431,7 +434,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { for (int k = 0; k < h; k++) { - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { continue; } @@ -568,7 +571,12 @@ namespace ImageSharp.Formats.Jpeg.Port.Components int nextByte = stream.ReadByte(); if (nextByte != 0) { - throw new ImageFormatException($"Unexpected marker {(this.bitsData << 8) | nextByte}"); +#if DEBUG + Debug.WriteLine($"DecodeScan - Unexpected marker {(this.bitsData << 8) | nextByte:X} at {stream.Position}"); +#endif + // We've encountered an unexpected marker. Reverse the stream and exit. + this.unexpectedMarkerReached = true; + stream.Position -= 2; } // Unstuff 0 @@ -586,7 +594,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components while (true) { int index = this.ReadBit(stream); - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { return -1; } @@ -609,7 +617,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components while (length > 0) { int bit = this.ReadBit(stream); - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { return -1; } @@ -642,7 +650,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private void DecodeBaseline(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) { int t = this.DecodeHuffman(dcHuffmanTables[component.DCHuffmanTableId], stream); - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; } @@ -654,7 +662,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components while (k < 64) { int rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream); - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; } @@ -685,7 +693,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private void DecodeDCFirst(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, Stream stream) { int t = this.DecodeHuffman(dcHuffmanTables[component.DCHuffmanTableId], stream); - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; } @@ -698,7 +706,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private void DecodeDCSuccessive(ref FrameComponent component, int offset, Stream stream) { int bit = this.ReadBit(stream); - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; } @@ -720,7 +728,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components while (k <= e) { short rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream); - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; } @@ -760,7 +768,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { case 0: // Initial state short rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream); - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; } @@ -797,7 +805,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components if (component.BlockData[offset + z] != 0) { int bit = this.ReadBit(stream); - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; } @@ -818,7 +826,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components if (component.BlockData[offset + z] != 0) { int bit = this.ReadBit(stream); - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; } @@ -836,7 +844,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components if (component.BlockData[offset + z] != 0) { int bit = this.ReadBit(stream); - if (this.endOfStreamReached) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; } From 79a3d975578722708b75fb97160443b98e14ca74 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 28 Jun 2017 11:30:59 +0100 Subject: [PATCH 22/43] use an offset span instead of buffer --- .../Formats/Jpeg/Port/Components/IDCT.cs | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs index 0e5a97012..760844ae2 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs @@ -30,7 +30,7 @@ public static void QuantizeAndInverse(QuantizationTables quantizationTables, ref FrameComponent component, int blockBufferOffset, Buffer computationBuffer) { Span qt = quantizationTables.Tables.GetRowSpan(component.QuantizationIdentifier); - Buffer blockData = component.BlockData; + Span blockData = component.BlockData.Slice(blockBufferOffset); int v0, v1, v2, v3, v4, v5, v6, v7; int p0, p1, p2, p3, p4, p5, p6, p7; int t; @@ -39,14 +39,14 @@ for (int row = 0; row < 64; row += 8) { // gather block data - p0 = blockData[blockBufferOffset + row]; - p1 = blockData[blockBufferOffset + row + 1]; - p2 = blockData[blockBufferOffset + row + 2]; - p3 = blockData[blockBufferOffset + row + 3]; - p4 = blockData[blockBufferOffset + row + 4]; - p5 = blockData[blockBufferOffset + row + 5]; - p6 = blockData[blockBufferOffset + row + 6]; - p7 = blockData[blockBufferOffset + row + 7]; + p0 = blockData[row]; + p1 = blockData[row + 1]; + p2 = blockData[row + 2]; + p3 = blockData[row + 3]; + p4 = blockData[row + 4]; + p5 = blockData[row + 5]; + p6 = blockData[row + 6]; + p7 = blockData[row + 7]; // dequant p0 p0 *= qt[row]; @@ -141,14 +141,14 @@ t = (t < -2040) ? 0 : (t >= 2024) ? 255 : (t + 2056) >> 4; short st = (short)t; - blockData[blockBufferOffset + col] = st; - blockData[blockBufferOffset + col + 8] = st; - blockData[blockBufferOffset + col + 16] = st; - blockData[blockBufferOffset + col + 24] = st; - blockData[blockBufferOffset + col + 32] = st; - blockData[blockBufferOffset + col + 40] = st; - blockData[blockBufferOffset + col + 48] = st; - blockData[blockBufferOffset + col + 56] = st; + blockData[col] = st; + blockData[col + 8] = st; + blockData[col + 16] = st; + blockData[col + 24] = st; + blockData[col + 32] = st; + blockData[col + 40] = st; + blockData[col + 48] = st; + blockData[col + 56] = st; continue; } @@ -208,14 +208,14 @@ p7 = (p7 < 16) ? 0 : (p7 >= 4080) ? 255 : p7 >> 4; // store block data - blockData[blockBufferOffset + col] = (short)p0; - blockData[blockBufferOffset + col + 8] = (short)p1; - blockData[blockBufferOffset + col + 16] = (short)p2; - blockData[blockBufferOffset + col + 24] = (short)p3; - blockData[blockBufferOffset + col + 32] = (short)p4; - blockData[blockBufferOffset + col + 40] = (short)p5; - blockData[blockBufferOffset + col + 48] = (short)p6; - blockData[blockBufferOffset + col + 56] = (short)p7; + blockData[col] = (short)p0; + blockData[col + 8] = (short)p1; + blockData[col + 16] = (short)p2; + blockData[col + 24] = (short)p3; + blockData[col + 32] = (short)p4; + blockData[col + 40] = (short)p5; + blockData[col + 48] = (short)p6; + blockData[col + 56] = (short)p7; } } } From 338203723b4b14c8bc86ff1e9cba7b0186a522a3 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 28 Jun 2017 12:33:47 +0100 Subject: [PATCH 23/43] additional usages of Span --- .../Formats/Jpeg/Port/Components/IDCT.cs | 49 ++++++++++--------- .../Jpeg/Port/Components/JpegPixelArea.cs | 8 +-- .../Jpeg/Port/Components/ScanDecoder.cs | 18 ++++--- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs index 760844ae2..f931c3d6b 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs @@ -31,6 +31,7 @@ { Span qt = quantizationTables.Tables.GetRowSpan(component.QuantizationIdentifier); Span blockData = component.BlockData.Slice(blockBufferOffset); + Span computationBufferSpan = computationBuffer; int v0, v1, v2, v3, v4, v5, v6, v7; int p0, p1, p2, p3, p4, p5, p6, p7; int t; @@ -56,14 +57,14 @@ { t = ((DctSqrt2 * p0) + 512) >> 10; short st = (short)t; - computationBuffer[row] = st; - computationBuffer[row + 1] = st; - computationBuffer[row + 2] = st; - computationBuffer[row + 3] = st; - computationBuffer[row + 4] = st; - computationBuffer[row + 5] = st; - computationBuffer[row + 6] = st; - computationBuffer[row + 7] = st; + computationBufferSpan[row] = st; + computationBufferSpan[row + 1] = st; + computationBufferSpan[row + 2] = st; + computationBufferSpan[row + 3] = st; + computationBufferSpan[row + 4] = st; + computationBufferSpan[row + 5] = st; + computationBufferSpan[row + 6] = st; + computationBufferSpan[row + 7] = st; continue; } @@ -110,27 +111,27 @@ v6 = t; // stage 1 - computationBuffer[row] = (short)(v0 + v7); - computationBuffer[row + 7] = (short)(v0 - v7); - computationBuffer[row + 1] = (short)(v1 + v6); - computationBuffer[row + 6] = (short)(v1 - v6); - computationBuffer[row + 2] = (short)(v2 + v5); - computationBuffer[row + 5] = (short)(v2 - v5); - computationBuffer[row + 3] = (short)(v3 + v4); - computationBuffer[row + 4] = (short)(v3 - v4); + computationBufferSpan[row] = (short)(v0 + v7); + computationBufferSpan[row + 7] = (short)(v0 - v7); + computationBufferSpan[row + 1] = (short)(v1 + v6); + computationBufferSpan[row + 6] = (short)(v1 - v6); + computationBufferSpan[row + 2] = (short)(v2 + v5); + computationBufferSpan[row + 5] = (short)(v2 - v5); + computationBufferSpan[row + 3] = (short)(v3 + v4); + computationBufferSpan[row + 4] = (short)(v3 - v4); } // inverse DCT on columns for (int col = 0; col < 8; ++col) { - p0 = computationBuffer[col]; - p1 = computationBuffer[col + 8]; - p2 = computationBuffer[col + 16]; - p3 = computationBuffer[col + 24]; - p4 = computationBuffer[col + 32]; - p5 = computationBuffer[col + 40]; - p6 = computationBuffer[col + 48]; - p7 = computationBuffer[col + 56]; + p0 = computationBufferSpan[col]; + p1 = computationBufferSpan[col + 8]; + p2 = computationBufferSpan[col + 16]; + p3 = computationBufferSpan[col + 24]; + p4 = computationBufferSpan[col + 32]; + p5 = computationBufferSpan[col + 40]; + p6 = computationBufferSpan[col + 48]; + p7 = computationBufferSpan[col + 56]; // check for all-zero AC coefficients if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs index eafc6c33c..33cc44df4 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs @@ -72,17 +72,19 @@ namespace ImageSharp.Formats.Jpeg.Port.Components float scaleX = this.imageWidth / (float)width; float scaleY = this.imageHeight / (float)height; this.componentData = new Buffer(width * height * numberOfComponents); + Span componentDataSpan = this.componentData; const uint Mask3Lsb = 0xFFFFFFF8; // Used to clear the 3 LSBs using (var xScaleBlockOffset = new Buffer(width)) { + Span xScaleBlockOffsetSpan = xScaleBlockOffset; for (int i = 0; i < numberOfComponents; i++) { ref Component component = ref components.Components[i]; float componentScaleX = component.ScaleX * scaleX; float componentScaleY = component.ScaleY * scaleY; int offset = i; - Buffer output = component.Output; + Span output = component.Output; int blocksPerScanline = (component.BlocksPerLine + 1) << 3; // Precalculate the xScaleBlockOffset @@ -90,7 +92,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components for (int x = 0; x < width; x++) { j = 0 | (int)(x * componentScaleX); - xScaleBlockOffset[x] = (int)((j & Mask3Lsb) << 3) | (j & 7); + xScaleBlockOffsetSpan[x] = (int)((j & Mask3Lsb) << 3) | (j & 7); } // Linearize the blocks of the component @@ -100,7 +102,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components int index = blocksPerScanline * (int)(j & Mask3Lsb) | ((j & 7) << 3); for (int x = 0; x < width; x++) { - this.componentData[offset] = (byte)output[index + xScaleBlockOffset[x]]; + componentDataSpan[offset] = (byte)output[index + xScaleBlockOffsetSpan[x]]; offset += numberOfComponents; } } diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 13a63c5c3..5a31fd89f 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -723,6 +723,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components return; } + var componentBlockDataSpan = component.BlockData.Span; int k = this.specStart; int e = this.specEnd; while (k <= e) @@ -750,7 +751,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components k += r; byte z = QuantizationTables.DctZigZag[k]; - component.BlockData[offset + z] = (short)(this.ReceiveAndExtend(s, stream) * (1 << this.successiveState)); + componentBlockDataSpan[offset + z] = (short)(this.ReceiveAndExtend(s, stream) * (1 << this.successiveState)); k++; } } @@ -761,6 +762,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components int k = this.specStart; int e = this.specEnd; int r = 0; + var componentBlockDataSpan = component.BlockData.Span; while (k <= e) { byte z = QuantizationTables.DctZigZag[k]; @@ -802,7 +804,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components continue; case 1: // Skipping r zero items case 2: - if (component.BlockData[offset + z] != 0) + if (componentBlockDataSpan[offset + z] != 0) { int bit = this.ReadBit(stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) @@ -810,7 +812,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components return; } - component.BlockData[offset + z] += (short)(bit << this.successiveState); + componentBlockDataSpan[offset + z] += (short)(bit << this.successiveState); } else { @@ -823,7 +825,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components break; case 3: // Set value for a zero item - if (component.BlockData[offset + z] != 0) + if (componentBlockDataSpan[offset + z] != 0) { int bit = this.ReadBit(stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) @@ -831,17 +833,17 @@ namespace ImageSharp.Formats.Jpeg.Port.Components return; } - component.BlockData[offset + z] += (short)(bit << this.successiveState); + componentBlockDataSpan[offset + z] += (short)(bit << this.successiveState); } else { - component.BlockData[offset + z] = (short)(this.successiveACNextValue << this.successiveState); + componentBlockDataSpan[offset + z] = (short)(this.successiveACNextValue << this.successiveState); this.successiveACState = 0; } break; case 4: // Eob - if (component.BlockData[offset + z] != 0) + if (componentBlockDataSpan[offset + z] != 0) { int bit = this.ReadBit(stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) @@ -849,7 +851,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components return; } - component.BlockData[offset + z] += (short)(bit << this.successiveState); + componentBlockDataSpan[offset + z] += (short)(bit << this.successiveState); } break; From 047875686348b360a2a489d4edcd6abbd33192bf Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 28 Jun 2017 22:27:43 +0200 Subject: [PATCH 24/43] fixed Sandbox46 execution --- tests/ImageSharp.Sandbox46/Program.cs | 4 ++-- tests/ImageSharp.Tests/TestFile.cs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index 6f93df16e..23b48caec 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -39,10 +39,10 @@ namespace ImageSharp.Sandbox46 /// public static void Main(string[] args) { - // RunDecodeJpegProfilingTests(); + RunDecodeJpegProfilingTests(); // RunToVector4ProfilingTest(); - RunResizeProfilingTest(); + //RunResizeProfilingTest(); Console.ReadLine(); } diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index bcfb6cb13..698ad8313 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -162,6 +162,7 @@ namespace ImageSharp.Tests "TestImages/Formats/", // Here for code coverage tests. "tests/ImageSharp.Tests/TestImages/Formats/", // From travis/build script "../../../../../ImageSharp.Tests/TestImages/Formats/", // From Sandbox46 + "../ImageSharp.Tests/TestImages/Formats/", // From Sandbox46 Program.cs "../../../../TestImages/Formats/", "../../../TestImages/Formats/" }; From bf8085e9ab2d4b96c8d1effade14d4bdc06d8a7b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 30 Jun 2017 09:12:29 +1000 Subject: [PATCH 25/43] Rough working better Huffman --- .../Jpeg/Port/Components/HuffmanTable.cs | 136 ++++++++++++++++++ .../Jpeg/Port/Components/HuffmanTables.cs | 4 +- .../Jpeg/Port/Components/ScanDecoder.cs | 37 +++-- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 63 ++------ 4 files changed, 170 insertions(+), 70 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs new file mode 100644 index 000000000..995fd550c --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs @@ -0,0 +1,136 @@ +namespace ImageSharp.Formats.Jpeg.Port.Components +{ + using System; + using System.Runtime.CompilerServices; + + /// + /// Represents a HUffman Table + /// + internal sealed class HuffmanTable + { + private short[] huffcode = new short[257]; + private short[] huffsize = new short[257]; + private short[] valOffset = new short[18]; + private long[] maxcode = new long[18]; + + private byte[] huffval; + private byte[] bits; + + /// + /// Initializes a new instance of the class. + /// + /// The code lengths + /// The huffman values + public HuffmanTable(byte[] lengths, byte[] values) + { + this.huffval = new byte[values.Length]; + Buffer.BlockCopy(values, 0, this.huffval, 0, values.Length); + this.bits = new byte[lengths.Length]; + Buffer.BlockCopy(lengths, 0, this.bits, 0, lengths.Length); + + this.GenerateSizeTable(); + this.GenerateCodeTable(); + this.GenerateDecoderTables(); + } + + /// + /// Gets the Huffman value code at the given index + /// + /// The index + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short GetHuffVal(int i) + { + return this.huffval[i]; + } + + /// + /// Gets the max code at the given index + /// + /// The index + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long GetMaxCode(int i) + { + return this.maxcode[i]; + } + + /// + /// Gets the index to the locatation of the huffman value + /// + /// The index + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetValPtr(int i) + { + return this.valOffset[i]; + } + + /// + /// Figure C.1: make table of Huffman code length for each symbol + /// + private void GenerateSizeTable() + { + short index = 0; + for (short l = 1; l <= 16; l++) + { + byte i = this.bits[l]; + for (short j = 0; j < i; j++) + { + this.huffsize[index] = l; + index++; + } + } + + this.huffsize[index] = 0; + } + + /// + /// Figure C.2: generate the codes themselves + /// + private void GenerateCodeTable() + { + short k = 0; + short si = this.huffsize[0]; + short code = 0; + for (short i = 0; i < this.huffsize.Length; i++) + { + while (this.huffsize[k] == si) + { + this.huffcode[k] = code; + code++; + k++; + } + + code <<= 1; + si++; + } + } + + /// + /// Figure F.15: generate decoding tables for bit-sequential decoding + /// + private void GenerateDecoderTables() + { + short bitcount = 0; + for (int i = 1; i <= 16; i++) + { + if (this.bits[i] != 0) + { + // valoffset[l] = huffval[] index of 1st symbol of code length i, + // minus the minimum code of length i + this.valOffset[i] = (short)(bitcount - this.huffcode[bitcount]); + bitcount += this.bits[i]; + this.maxcode[i] = this.huffcode[bitcount - 1]; // maximum code of length i + } + else + { + this.maxcode[i] = -1; // -1 if no codes of this length + } + } + + this.valOffset[17] = 0; + this.maxcode[17] = 0xFFFFFL; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs index a8644d645..8aeafd7db 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs @@ -13,14 +13,14 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// internal class HuffmanTables { - private readonly HuffmanBranch[][] tables = new HuffmanBranch[4][]; + private readonly HuffmanTable[] tables = new HuffmanTable[4]; /// /// Gets or sets the table at the given index. /// /// The index /// The - public HuffmanBranch[] this[int index] + public HuffmanTable this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 5a31fd89f..1d588301f 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -574,6 +574,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components #if DEBUG Debug.WriteLine($"DecodeScan - Unexpected marker {(this.bitsData << 8) | nextByte:X} at {stream.Position}"); #endif + // We've encountered an unexpected marker. Reverse the stream and exit. this.unexpectedMarkerReached = true; stream.Position -= 2; @@ -587,27 +588,31 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private short DecodeHuffman(HuffmanBranch[] tree, Stream stream) + private short DecodeHuffman(HuffmanTable tree, Stream stream) { - // TODO: This is our bottleneck. We should use a faster algorithm with a LUT. - HuffmanBranch[] node = tree; - while (true) + // "DECODE", section F.2.2.3, figure F.16, page 109 of T.81 + int i = 1; + short code = (short)this.ReadBit(stream); + if (this.endOfStreamReached || this.unexpectedMarkerReached) { - int index = this.ReadBit(stream); - if (this.endOfStreamReached || this.unexpectedMarkerReached) - { - return -1; - } + return -1; + } - HuffmanBranch branch = node[index]; + while (code > tree.GetMaxCode(i)) + { + code <<= 1; + code |= (short)this.ReadBit(stream); - if (branch.Value > -1) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { - return branch.Value; + return -1; } - node = branch.Children; + i++; } + + int j = tree.GetValPtr(i); + return tree.GetHuffVal((j + code) & 0xFF); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -682,6 +687,12 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } k += r; + + if (k > 63) + { + break; + } + byte z = QuantizationTables.DctZigZag[k]; short re = (short)this.ReceiveAndExtend(s, stream); component.BlockData[offset + z] = re; diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 6a1d6311c..7bd71048c 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Formats.Jpeg.Port { using System; using System.Collections.Generic; + using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; @@ -233,8 +234,6 @@ namespace ImageSharp.Formats.Jpeg.Port case JpegConstants.Markers.APP15: case JpegConstants.Markers.COM: - - // TODO: Read data block this.InputStream.Skip(remaining); break; @@ -676,28 +675,28 @@ namespace ImageSharp.Formats.Jpeg.Port throw new ImageFormatException($"DHT has wrong length: {remaining}"); } - using (var huffmanData = Buffer.CreateClean(16)) + using (var huffmanData = Buffer.CreateClean(256)) { for (int i = 2; i < remaining;) { byte huffmanTableSpec = (byte)this.InputStream.ReadByte(); this.InputStream.Read(huffmanData.Array, 0, 16); - using (var codeLengths = Buffer.CreateClean(16)) + using (var codeLengths = Buffer.CreateClean(17)) { int codeLengthSum = 0; - for (int j = 0; j < 16; j++) + for (int j = 1; j < 17; j++) { - codeLengthSum += codeLengths[j] = huffmanData[j]; + codeLengthSum += codeLengths[j] = huffmanData[j - 1]; } - using (var huffmanValues = Buffer.CreateClean(codeLengthSum)) + using (var huffmanValues = Buffer.CreateClean(256)) { this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum); i += 17 + codeLengthSum; - + Debug.WriteLine(huffmanTableSpec >> 4 == 0 ? "this.dcHuffmanTables" : "this.acHuffmanTables"); this.BuildHuffmanTable( huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables, huffmanTableSpec & 15, @@ -812,53 +811,7 @@ namespace ImageSharp.Formats.Jpeg.Port /// The values private void BuildHuffmanTable(HuffmanTables tables, int index, byte[] codeLengths, byte[] values) { - int length = 16; - while (length > 0 && codeLengths[length - 1] == 0) - { - length--; - } - - // TODO: Check the branch children capacity here. Seems to max at 2 - var code = new List { new HuffmanBranch(-1) }; - HuffmanBranch p = code[0]; - int k = 0; - - for (int i = 0; i < length; i++) - { - HuffmanBranch q; - for (int j = 0; j < codeLengths[i]; j++) - { - p = code.Pop(); - p.Children[p.Index] = new HuffmanBranch(values[k]); - while (p.Index > 0) - { - p = code.Pop(); - } - - p.Index++; - code.Add(p); - while (code.Count <= i) - { - q = new HuffmanBranch(-1); - code.Add(q); - p.Children[p.Index] = new HuffmanBranch(q.Children); - p = q; - } - - k++; - } - - if (i + 1 < length) - { - // p here points to last code - q = new HuffmanBranch(-1); - code.Add(q); - p.Children[p.Index] = new HuffmanBranch(q.Children); - p = q; - } - } - - tables[index] = code[0].Children; + tables[index] = new HuffmanTable(codeLengths, values); } /// From c42322db988eb725d3424e857640c51859a54476 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 30 Jun 2017 09:54:21 +1000 Subject: [PATCH 26/43] Better Huffman decoding --- .../Formats/Jpeg/Port/Components/Component.cs | 1 + .../Jpeg/Port/Components/FrameComponent.cs | 1 + .../Jpeg/Port/Components/HuffmanBranch.cs | 54 ----------------- .../Jpeg/Port/Components/HuffmanTable.cs | 59 ++++++++++++++----- .../Jpeg/Port/Components/HuffmanTables.cs | 12 +++- .../Jpeg/Port/Components/ScanDecoder.cs | 4 +- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 6 +- 7 files changed, 65 insertions(+), 72 deletions(-) delete mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanBranch.cs diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs index db3170613..120bfb58d 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs @@ -43,6 +43,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components public void Dispose() { this.Output?.Dispose(); + this.Output = null; } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs index 0cb9bbb1c..b386a86f3 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs @@ -68,6 +68,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components public void Dispose() { this.BlockData?.Dispose(); + this.BlockData = null; } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanBranch.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanBranch.cs deleted file mode 100644 index d716355ad..000000000 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanBranch.cs +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats.Jpeg.Port.Components -{ - using System.Runtime.CompilerServices; - - /// - /// Represents a branch in the huffman tree - /// - internal struct HuffmanBranch - { - /// - /// The index - /// - public int Index; - - /// - /// The value - /// - public short Value; - - /// - /// The children. - /// - public HuffmanBranch[] Children; - - /// - /// Initializes a new instance of the struct. - /// - /// The value - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public HuffmanBranch(short value) - { - this.Index = 0; - this.Value = value; - this.Children = new HuffmanBranch[2]; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The branch children - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public HuffmanBranch(HuffmanBranch[] children) - { - this.Index = 0; - this.Value = -1; - this.Children = children; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs index 995fd550c..bc3162978 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs @@ -1,32 +1,45 @@ -namespace ImageSharp.Formats.Jpeg.Port.Components +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpeg.Port.Components { using System; using System.Runtime.CompilerServices; + using ImageSharp.Memory; + /// - /// Represents a HUffman Table + /// Represents a Huffman Table /// - internal sealed class HuffmanTable + internal struct HuffmanTable : IDisposable { - private short[] huffcode = new short[257]; - private short[] huffsize = new short[257]; - private short[] valOffset = new short[18]; - private long[] maxcode = new long[18]; + private Buffer huffcode; + private Buffer huffsize; + private Buffer valOffset; + private Buffer maxcode; - private byte[] huffval; - private byte[] bits; + private Buffer huffval; + private Buffer bits; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// /// The code lengths /// The huffman values public HuffmanTable(byte[] lengths, byte[] values) { - this.huffval = new byte[values.Length]; - Buffer.BlockCopy(values, 0, this.huffval, 0, values.Length); - this.bits = new byte[lengths.Length]; - Buffer.BlockCopy(lengths, 0, this.bits, 0, lengths.Length); + this.huffcode = Buffer.CreateClean(257); + this.huffsize = Buffer.CreateClean(257); + this.valOffset = Buffer.CreateClean(18); + this.maxcode = Buffer.CreateClean(18); + + this.huffval = Buffer.CreateClean(values.Length); + Buffer.BlockCopy(values, 0, this.huffval.Array, 0, values.Length); + + this.bits = Buffer.CreateClean(lengths.Length); + Buffer.BlockCopy(lengths, 0, this.bits.Array, 0, lengths.Length); this.GenerateSizeTable(); this.GenerateCodeTable(); @@ -66,6 +79,24 @@ return this.valOffset[i]; } + /// + public void Dispose() + { + this.huffcode?.Dispose(); + this.huffsize?.Dispose(); + this.valOffset?.Dispose(); + this.maxcode?.Dispose(); + this.huffval?.Dispose(); + this.bits?.Dispose(); + + this.huffcode = null; + this.huffsize = null; + this.valOffset = null; + this.maxcode = null; + this.huffval = null; + this.bits = null; + } + /// /// Figure C.1: make table of Huffman code length for each symbol /// diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs index 8aeafd7db..6de8c441d 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs @@ -5,13 +5,14 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { + using System; using System.Collections.Generic; using System.Runtime.CompilerServices; /// /// Defines a pair of huffman tables /// - internal class HuffmanTables + internal sealed class HuffmanTables : IDisposable { private readonly HuffmanTable[] tables = new HuffmanTable[4]; @@ -34,5 +35,14 @@ namespace ImageSharp.Formats.Jpeg.Port.Components this.tables[index] = value; } } + + /// + public void Dispose() + { + for (int i = 0; i < this.tables.Length; i++) + { + this.tables[i].Dispose(); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 1d588301f..62b0a82e4 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -734,7 +734,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components return; } - var componentBlockDataSpan = component.BlockData.Span; + Span componentBlockDataSpan = component.BlockData.Span; int k = this.specStart; int e = this.specEnd; while (k <= e) @@ -773,7 +773,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components int k = this.specStart; int e = this.specEnd; int r = 0; - var componentBlockDataSpan = component.BlockData.Span; + Span componentBlockDataSpan = component.BlockData.Span; while (k <= e) { byte z = QuantizationTables.DctZigZag[k]; diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 7bd71048c..ca93e96dd 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -160,12 +160,16 @@ namespace ImageSharp.Formats.Jpeg.Port this.frame?.Dispose(); this.components?.Dispose(); this.quantizationTables?.Dispose(); + this.dcHuffmanTables?.Dispose(); + this.acHuffmanTables?.Dispose(); this.pixelArea.Dispose(); // Set large fields to null. this.frame = null; this.components = null; this.quantizationTables = null; + this.dcHuffmanTables = null; + this.acHuffmanTables = null; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -696,7 +700,7 @@ namespace ImageSharp.Formats.Jpeg.Port this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum); i += 17 + codeLengthSum; - Debug.WriteLine(huffmanTableSpec >> 4 == 0 ? "this.dcHuffmanTables" : "this.acHuffmanTables"); + this.BuildHuffmanTable( huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables, huffmanTableSpec & 15, From 174475926bb76dc0e7751c2831b6fa39a669dee1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 30 Jun 2017 16:30:05 +1000 Subject: [PATCH 27/43] Almost got Huffman LUT working --- .../Jpeg/Port/Components/HuffmanTable.cs | 50 ++++++++++++++++++- .../Jpeg/Port/Components/ScanDecoder.cs | 22 ++++++-- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs index bc3162978..0119f272c 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs @@ -15,6 +15,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// internal struct HuffmanTable : IDisposable { + private Buffer lookahead; private Buffer huffcode; private Buffer huffsize; private Buffer valOffset; @@ -30,6 +31,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// The huffman values public HuffmanTable(byte[] lengths, byte[] values) { + this.lookahead = Buffer.CreateClean(256); this.huffcode = Buffer.CreateClean(257); this.huffsize = Buffer.CreateClean(257); this.valOffset = Buffer.CreateClean(18); @@ -44,6 +46,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components this.GenerateSizeTable(); this.GenerateCodeTable(); this.GenerateDecoderTables(); + this.GenerateLookaheadTables(); } /// @@ -74,14 +77,26 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// The index /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetValPtr(int i) + public int GetValOffset(int i) { return this.valOffset[i]; } + /// + /// Gets the look ahead table balue + /// + /// The index + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetLookAhead(int i) + { + return this.lookahead[i]; + } + /// public void Dispose() { + this.lookahead?.Dispose(); this.huffcode?.Dispose(); this.huffsize?.Dispose(); this.valOffset?.Dispose(); @@ -89,6 +104,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components this.huffval?.Dispose(); this.bits?.Dispose(); + this.lookahead = null; this.huffcode = null; this.huffsize = null; this.valOffset = null; @@ -163,5 +179,37 @@ namespace ImageSharp.Formats.Jpeg.Port.Components this.valOffset[17] = 0; this.maxcode[17] = 0xFFFFFL; } + + /// + /// Generates lookup tables to speed up decoding + /// + private void GenerateLookaheadTables() + { + int x = 0, code = 0; + + for (int i = 0; i < 8; i++) + { + code <<= 1; + + for (int j = 0; j < this.bits[i + 1]; j++) + { + // The codeLength is 1+i, so shift code by 8-(1+i) to + // calculate the high bits for every 8-bit sequence + // whose codeLength's high bits matches code. + // The high 8 bits of lutValue are the encoded value. + // The low 8 bits are 1 plus the codeLength. + int base2 = code << (7 - i); + int lutValue = (this.huffval[x] << 8) | (2 + i); + + for (int k = 0; k < 1 << (7 - i); k++) + { + this.lookahead[base2 | k] = (short)lutValue; + } + + code++; + x++; + } + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 62b0a82e4..140f47449 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -23,6 +23,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private int bitsCount; + private int accumulator; + private int specStart; private int specEnd; @@ -584,20 +586,34 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } this.bitsCount = 7; + + // TODO: This line is incorrect. + this.accumulator = (this.accumulator << 8) | this.bitsData; + return this.bitsData >> 7; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private short DecodeHuffman(HuffmanTable tree, Stream stream) { - // "DECODE", section F.2.2.3, figure F.16, page 109 of T.81 - int i = 1; short code = (short)this.ReadBit(stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) { return -1; } + // TODO: If the following is enabled the decoder breaks. + // if (this.bitsCount > 0) + // { + // int lutIndex = (this.accumulator >> (this.bitsCount - 7)) & 0xFF; + // int v = tree.GetLookAhead(lutIndex); + // if (v != 0) + // { + // return (short)(v >> 8); + // } + // } + // "DECODE", section F.2.2.3, figure F.16, page 109 of T.81 + int i = 1; while (code > tree.GetMaxCode(i)) { code <<= 1; @@ -611,7 +627,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components i++; } - int j = tree.GetValPtr(i); + int j = tree.GetValOffset(i); return tree.GetHuffVal((j + code) & 0xFF); } From 5857b81e293da06ec12e4e8e84161abadd4fa642 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 30 Jun 2017 20:02:43 +1000 Subject: [PATCH 28/43] Better Huffman indexing. --- .../Jpeg/Port/Components/HuffmanTables.cs | 10 +-- .../Jpeg/Port/Components/ScanDecoder.cs | 86 +++++++++++-------- 2 files changed, 54 insertions(+), 42 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs index 6de8c441d..d076c0b03 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs @@ -21,18 +21,12 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// /// The index /// The - public HuffmanTable this[int index] + public ref HuffmanTable this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - return this.tables[index]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.tables[index] = value; + return ref this.tables[index]; } } diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 140f47449..9648dde82 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -205,6 +205,9 @@ namespace ImageSharp.Formats.Jpeg.Port.Components if (componentsLength == 1) { ref FrameComponent component = ref components[this.compIndex]; + ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + for (int n = 0; n < mcuToRead; n++) { if (this.endOfStreamReached || this.unexpectedMarkerReached) @@ -212,7 +215,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components continue; } - this.DecodeBlockBaseline(dcHuffmanTables, acHuffmanTables, ref component, mcu, stream); + this.DecodeBlockBaseline(ref dcHuffmanTable, ref acHuffmanTable, ref component, mcu, stream); mcu++; } } @@ -223,8 +226,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components for (int i = 0; i < componentsLength; i++) { ref FrameComponent component = ref components[i]; + ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; int h = component.HorizontalFactor; int v = component.VerticalFactor; + for (int j = 0; j < v; j++) { for (int k = 0; k < h; k++) @@ -234,7 +240,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components continue; } - this.DecodeMcuBaseline(dcHuffmanTables, acHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); + this.DecodeMcuBaseline(ref dcHuffmanTable, ref acHuffmanTable, ref component, mcusPerLine, mcu, j, k, stream); } } } @@ -257,6 +263,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components if (componentsLength == 1) { ref FrameComponent component = ref components[this.compIndex]; + ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + for (int n = 0; n < mcuToRead; n++) { if (this.endOfStreamReached || this.unexpectedMarkerReached) @@ -264,7 +272,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components continue; } - this.DecodeBlockDCFirst(dcHuffmanTables, ref component, mcu, stream); + this.DecodeBlockDCFirst(ref dcHuffmanTable, ref component, mcu, stream); mcu++; } } @@ -275,8 +283,10 @@ namespace ImageSharp.Formats.Jpeg.Port.Components for (int i = 0; i < componentsLength; i++) { ref FrameComponent component = ref components[i]; + ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; int h = component.HorizontalFactor; int v = component.VerticalFactor; + for (int j = 0; j < v; j++) { for (int k = 0; k < h; k++) @@ -286,7 +296,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components continue; } - this.DecodeMcuDCFirst(dcHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); + this.DecodeMcuDCFirst(ref dcHuffmanTable, ref component, mcusPerLine, mcu, j, k, stream); } } } @@ -360,6 +370,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components if (componentsLength == 1) { ref FrameComponent component = ref components[this.compIndex]; + ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + for (int n = 0; n < mcuToRead; n++) { if (this.endOfStreamReached || this.unexpectedMarkerReached) @@ -367,7 +379,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components continue; } - this.DecodeBlockACFirst(acHuffmanTables, ref component, mcu, stream); + this.DecodeBlockACFirst(ref acHuffmanTable, ref component, mcu, stream); mcu++; } } @@ -378,8 +390,10 @@ namespace ImageSharp.Formats.Jpeg.Port.Components for (int i = 0; i < componentsLength; i++) { ref FrameComponent component = ref components[i]; + ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; int h = component.HorizontalFactor; int v = component.VerticalFactor; + for (int j = 0; j < v; j++) { for (int k = 0; k < h; k++) @@ -389,7 +403,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components continue; } - this.DecodeMcuACFirst(acHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); + this.DecodeMcuACFirst(ref acHuffmanTable, ref component, mcusPerLine, mcu, j, k, stream); } } } @@ -412,6 +426,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components if (componentsLength == 1) { ref FrameComponent component = ref components[this.compIndex]; + ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + for (int n = 0; n < mcuToRead; n++) { if (this.endOfStreamReached || this.unexpectedMarkerReached) @@ -419,7 +435,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components continue; } - this.DecodeBlockACSuccessive(acHuffmanTables, ref component, mcu, stream); + this.DecodeBlockACSuccessive(ref acHuffmanTable, ref component, mcu, stream); mcu++; } } @@ -430,8 +446,10 @@ namespace ImageSharp.Formats.Jpeg.Port.Components for (int i = 0; i < componentsLength; i++) { ref FrameComponent component = ref components[i]; + ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; int h = component.HorizontalFactor; int v = component.VerticalFactor; + for (int j = 0; j < v; j++) { for (int k = 0; k < h; k++) @@ -441,7 +459,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components continue; } - this.DecodeMcuACSuccessive(acHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); + this.DecodeMcuACSuccessive(ref acHuffmanTable, ref component, mcusPerLine, mcu, j, k, stream); } } } @@ -452,43 +470,43 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBlockBaseline(HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, ref FrameComponent component, int mcu, Stream stream) + private void DecodeBlockBaseline(ref HuffmanTable dcHuffmanTable, ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcu, Stream stream) { int blockRow = (mcu / component.BlocksPerLine) | 0; int blockCol = mcu % component.BlocksPerLine; int offset = GetBlockBufferOffset(component, blockRow, blockCol); - this.DecodeBaseline(ref component, offset, dcHuffmanTables, acHuffmanTables, stream); + this.DecodeBaseline(ref component, offset, ref dcHuffmanTable, ref acHuffmanTable, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeMcuBaseline(HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) + private void DecodeMcuBaseline(ref HuffmanTable dcHuffmanTable, ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { int mcuRow = (mcu / mcusPerLine) | 0; int mcuCol = mcu % mcusPerLine; int blockRow = (mcuRow * component.VerticalFactor) + row; int blockCol = (mcuCol * component.HorizontalFactor) + col; int offset = GetBlockBufferOffset(component, blockRow, blockCol); - this.DecodeBaseline(ref component, offset, dcHuffmanTables, acHuffmanTables, stream); + this.DecodeBaseline(ref component, offset, ref dcHuffmanTable, ref acHuffmanTable, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBlockDCFirst(HuffmanTables dcHuffmanTables, ref FrameComponent component, int mcu, Stream stream) + private void DecodeBlockDCFirst(ref HuffmanTable dcHuffmanTable, ref FrameComponent component, int mcu, Stream stream) { int blockRow = (mcu / component.BlocksPerLine) | 0; int blockCol = mcu % component.BlocksPerLine; int offset = GetBlockBufferOffset(component, blockRow, blockCol); - this.DecodeDCFirst(ref component, offset, dcHuffmanTables, stream); + this.DecodeDCFirst(ref component, offset, ref dcHuffmanTable, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeMcuDCFirst(HuffmanTables dcHuffmanTables, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) + private void DecodeMcuDCFirst(ref HuffmanTable dcHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { int mcuRow = (mcu / mcusPerLine) | 0; int mcuCol = mcu % mcusPerLine; int blockRow = (mcuRow * component.VerticalFactor) + row; int blockCol = (mcuCol * component.HorizontalFactor) + col; int offset = GetBlockBufferOffset(component, blockRow, blockCol); - this.DecodeDCFirst(ref component, offset, dcHuffmanTables, stream); + this.DecodeDCFirst(ref component, offset, ref dcHuffmanTable, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -512,43 +530,43 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBlockACFirst(HuffmanTables acHuffmanTables, ref FrameComponent component, int mcu, Stream stream) + private void DecodeBlockACFirst(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcu, Stream stream) { int blockRow = (mcu / component.BlocksPerLine) | 0; int blockCol = mcu % component.BlocksPerLine; int offset = GetBlockBufferOffset(component, blockRow, blockCol); - this.DecodeACFirst(ref component, offset, acHuffmanTables, stream); + this.DecodeACFirst(ref component, offset, ref acHuffmanTable, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeMcuACFirst(HuffmanTables acHuffmanTables, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) + private void DecodeMcuACFirst(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { int mcuRow = (mcu / mcusPerLine) | 0; int mcuCol = mcu % mcusPerLine; int blockRow = (mcuRow * component.VerticalFactor) + row; int blockCol = (mcuCol * component.HorizontalFactor) + col; int offset = GetBlockBufferOffset(component, blockRow, blockCol); - this.DecodeACFirst(ref component, offset, acHuffmanTables, stream); + this.DecodeACFirst(ref component, offset, ref acHuffmanTable, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBlockACSuccessive(HuffmanTables acHuffmanTables, ref FrameComponent component, int mcu, Stream stream) + private void DecodeBlockACSuccessive(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcu, Stream stream) { int blockRow = (mcu / component.BlocksPerLine) | 0; int blockCol = mcu % component.BlocksPerLine; int offset = GetBlockBufferOffset(component, blockRow, blockCol); - this.DecodeACSuccessive(ref component, offset, acHuffmanTables, stream); + this.DecodeACSuccessive(ref component, offset, ref acHuffmanTable, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeMcuACSuccessive(HuffmanTables acHuffmanTables, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) + private void DecodeMcuACSuccessive(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { int mcuRow = (mcu / mcusPerLine) | 0; int mcuCol = mcu % mcusPerLine; int blockRow = (mcuRow * component.VerticalFactor) + row; int blockCol = (mcuCol * component.HorizontalFactor) + col; int offset = GetBlockBufferOffset(component, blockRow, blockCol); - this.DecodeACSuccessive(ref component, offset, acHuffmanTables, stream); + this.DecodeACSuccessive(ref component, offset, ref acHuffmanTable, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -594,7 +612,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private short DecodeHuffman(HuffmanTable tree, Stream stream) + private short DecodeHuffman(ref HuffmanTable tree, Stream stream) { short code = (short)this.ReadBit(stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) @@ -668,9 +686,9 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBaseline(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) + private void DecodeBaseline(ref FrameComponent component, int offset, ref HuffmanTable dcHuffmanTable, ref HuffmanTable acHuffmanTable, Stream stream) { - int t = this.DecodeHuffman(dcHuffmanTables[component.DCHuffmanTableId], stream); + int t = this.DecodeHuffman(ref dcHuffmanTable, stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; @@ -682,7 +700,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components int k = 1; while (k < 64) { - int rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream); + int rs = this.DecodeHuffman(ref acHuffmanTable, stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; @@ -717,9 +735,9 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeDCFirst(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, Stream stream) + private void DecodeDCFirst(ref FrameComponent component, int offset, ref HuffmanTable dcHuffmanTable, Stream stream) { - int t = this.DecodeHuffman(dcHuffmanTables[component.DCHuffmanTableId], stream); + int t = this.DecodeHuffman(ref dcHuffmanTable, stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; @@ -742,7 +760,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeACFirst(ref FrameComponent component, int offset, HuffmanTables acHuffmanTables, Stream stream) + private void DecodeACFirst(ref FrameComponent component, int offset, ref HuffmanTable acHuffmanTable, Stream stream) { if (this.eobrun > 0) { @@ -755,7 +773,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components int e = this.specEnd; while (k <= e) { - short rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream); + short rs = this.DecodeHuffman(ref acHuffmanTable, stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; @@ -784,7 +802,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeACSuccessive(ref FrameComponent component, int offset, HuffmanTables acHuffmanTables, Stream stream) + private void DecodeACSuccessive(ref FrameComponent component, int offset, ref HuffmanTable acHuffmanTable, Stream stream) { int k = this.specStart; int e = this.specEnd; @@ -796,7 +814,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components switch (this.successiveACState) { case 0: // Initial state - short rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream); + short rs = this.DecodeHuffman(ref acHuffmanTable, stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; From b8680799589b369b864023f10bf37c0f35549e3b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 1 Jul 2017 13:34:50 +1000 Subject: [PATCH 29/43] Getting close with that lookup now. --- .../Jpeg/Port/Components/HuffmanTable.cs | 6 +- .../Jpeg/Port/Components/ScanDecoder.cs | 57 +++++++++++++------ 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs index 0119f272c..85bcb5dfe 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs @@ -198,12 +198,12 @@ namespace ImageSharp.Formats.Jpeg.Port.Components // whose codeLength's high bits matches code. // The high 8 bits of lutValue are the encoded value. // The low 8 bits are 1 plus the codeLength. - int base2 = code << (7 - i); - int lutValue = (this.huffval[x] << 8) | (2 + i); + byte base2 = (byte)(code << (7 - i)); + short lutValue = (short)((short)(this.huffval[x] << 8) | (short)(2 + i)); for (int k = 0; k < 1 << (7 - i); k++) { - this.lookahead[base2 | k] = (short)lutValue; + this.lookahead[base2 | k] = lutValue; } code++; diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 9648dde82..19ffcd778 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -23,6 +23,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private int bitsCount; + private int bitsUnRead; + private int accumulator; private int specStart; @@ -139,6 +141,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components // Find marker this.bitsCount = 0; + this.accumulator = 0; + this.bitsUnRead = 0; fileMarker = JpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream); // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past @@ -572,6 +576,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private int ReadBit(Stream stream) { + // TODO: I wonder if we can do this two bytes at a time; libjpeg turbo seems to do that? if (this.bitsCount > 0) { this.bitsCount--; @@ -586,7 +591,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components this.endOfStreamReached = true; } - if (this.bitsData == 0xFF) + if (this.bitsData == JpegConstants.Markers.Prefix) { int nextByte = stream.ReadByte(); if (nextByte != 0) @@ -605,33 +610,51 @@ namespace ImageSharp.Formats.Jpeg.Port.Components this.bitsCount = 7; - // TODO: This line is incorrect. - this.accumulator = (this.accumulator << 8) | this.bitsData; - return this.bitsData >> 7; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private short DecodeHuffman(ref HuffmanTable tree, Stream stream) { - short code = (short)this.ReadBit(stream); - if (this.endOfStreamReached || this.unexpectedMarkerReached) - { - return -1; - } + short code = -1; - // TODO: If the following is enabled the decoder breaks. - // if (this.bitsCount > 0) + // TODO: Adding this code introduces error into the decoder. + // It doesn't appear to speed anything up either. + // if (this.bitsUnRead < 8) // { - // int lutIndex = (this.accumulator >> (this.bitsCount - 7)) & 0xFF; - // int v = tree.GetLookAhead(lutIndex); - // if (v != 0) - // { - // return (short)(v >> 8); - // } + // if (this.bitsCount <= 0) + // { + // code = (short)this.ReadBit(stream); + // this.bitsUnRead += 8; + // } + // if (this.endOfStreamReached || this.unexpectedMarkerReached) + // { + // return -1; + // } + // this.accumulator = (this.accumulator << 8) | this.bitsData; + // int lutIndex = (this.accumulator >> (this.bitsUnRead - 8)) & 0xFF; + // int v = tree.GetLookAhead(lutIndex); + // if (v != 0) + // { + // int nb = (v & 0xFF) - 1; + // this.bitsCount -= nb - 1; + // this.bitsUnRead -= nb; + // v = v >> 8; + // return (short)v; + // } // } + if (code == -1) + { + code = (short)this.ReadBit(stream); + if (this.endOfStreamReached || this.unexpectedMarkerReached) + { + return -1; + } + } + // "DECODE", section F.2.2.3, figure F.16, page 109 of T.81 int i = 1; + while (code > tree.GetMaxCode(i)) { code <<= 1; From de9559c542433de3587928d619bdf5a56d3e139b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 1 Jul 2017 13:38:32 +1000 Subject: [PATCH 30/43] Fix build --- src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 19ffcd778..98d46dae5 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -23,9 +23,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private int bitsCount; +#pragma warning disable 414 private int bitsUnRead; private int accumulator; +#pragma warning restore 414 private int specStart; From 8e4cdc732af5bfdc051d776e441d60ce4c314583 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 2 Jul 2017 17:15:02 +1000 Subject: [PATCH 31/43] Use properties --- .../Jpeg/Port/Components/HuffmanTable.cs | 125 +++++++++--------- .../Jpeg/Port/Components/ScanDecoder.cs | 8 +- 2 files changed, 65 insertions(+), 68 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs index 85bcb5dfe..4c475450b 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs @@ -16,13 +16,9 @@ namespace ImageSharp.Formats.Jpeg.Port.Components internal struct HuffmanTable : IDisposable { private Buffer lookahead; - private Buffer huffcode; - private Buffer huffsize; private Buffer valOffset; private Buffer maxcode; - private Buffer huffval; - private Buffer bits; /// /// Initializes a new instance of the struct. @@ -32,119 +28,113 @@ namespace ImageSharp.Formats.Jpeg.Port.Components public HuffmanTable(byte[] lengths, byte[] values) { this.lookahead = Buffer.CreateClean(256); - this.huffcode = Buffer.CreateClean(257); - this.huffsize = Buffer.CreateClean(257); this.valOffset = Buffer.CreateClean(18); this.maxcode = Buffer.CreateClean(18); + using (var huffsize = Buffer.CreateClean(257)) + using (var huffcode = Buffer.CreateClean(257)) + { + GenerateSizeTable(lengths, huffsize); + GenerateCodeTable(huffsize, huffcode); + GenerateDecoderTables(lengths, huffcode, this.valOffset, this.maxcode); + GenerateLookaheadTables(lengths, values, this.lookahead); + } + this.huffval = Buffer.CreateClean(values.Length); Buffer.BlockCopy(values, 0, this.huffval.Array, 0, values.Length); - this.bits = Buffer.CreateClean(lengths.Length); - Buffer.BlockCopy(lengths, 0, this.bits.Array, 0, lengths.Length); - - this.GenerateSizeTable(); - this.GenerateCodeTable(); - this.GenerateDecoderTables(); - this.GenerateLookaheadTables(); + this.MaxCode = this.maxcode.Array; + this.ValOffset = this.valOffset.Array; + this.HuffVal = this.huffval.Array; + this.Lookahead = this.lookahead.Array; } /// - /// Gets the Huffman value code at the given index + /// Gets the max code array /// - /// The index - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short GetHuffVal(int i) + public long[] MaxCode { - return this.huffval[i]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; } /// - /// Gets the max code at the given index + /// Gets the value offset array /// - /// The index - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long GetMaxCode(int i) + public short[] ValOffset { - return this.maxcode[i]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; } /// - /// Gets the index to the locatation of the huffman value + /// Gets the huffman value array /// - /// The index - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetValOffset(int i) + public byte[] HuffVal { - return this.valOffset[i]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; } /// - /// Gets the look ahead table balue + /// Gets the lookahead array /// - /// The index - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetLookAhead(int i) + public short[] Lookahead { - return this.lookahead[i]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; } /// public void Dispose() { this.lookahead?.Dispose(); - this.huffcode?.Dispose(); - this.huffsize?.Dispose(); this.valOffset?.Dispose(); this.maxcode?.Dispose(); this.huffval?.Dispose(); - this.bits?.Dispose(); this.lookahead = null; - this.huffcode = null; - this.huffsize = null; this.valOffset = null; this.maxcode = null; this.huffval = null; - this.bits = null; } /// /// Figure C.1: make table of Huffman code length for each symbol /// - private void GenerateSizeTable() + /// The code lengths + /// The huffman size span + private static void GenerateSizeTable(byte[] lengths, Span huffsize) { short index = 0; for (short l = 1; l <= 16; l++) { - byte i = this.bits[l]; + byte i = lengths[l]; for (short j = 0; j < i; j++) { - this.huffsize[index] = l; + huffsize[index] = l; index++; } } - this.huffsize[index] = 0; + huffsize[index] = 0; } /// /// Figure C.2: generate the codes themselves /// - private void GenerateCodeTable() + /// The huffman size span + /// The huffman code span + private static void GenerateCodeTable(Span huffsize, Span huffcode) { short k = 0; - short si = this.huffsize[0]; + short si = huffsize[0]; short code = 0; - for (short i = 0; i < this.huffsize.Length; i++) + for (short i = 0; i < huffsize.Length; i++) { - while (this.huffsize[k] == si) + while (huffsize[k] == si) { - this.huffcode[k] = code; + huffcode[k] = code; code++; k++; } @@ -157,33 +147,40 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// /// Figure F.15: generate decoding tables for bit-sequential decoding /// - private void GenerateDecoderTables() + /// The code lengths + /// The huffman code span + /// The value offset span + /// The max code span + private static void GenerateDecoderTables(byte[] lengths, Span huffcode, Span valOffset, Span maxcode) { short bitcount = 0; for (int i = 1; i <= 16; i++) { - if (this.bits[i] != 0) + if (lengths[i] != 0) { // valoffset[l] = huffval[] index of 1st symbol of code length i, // minus the minimum code of length i - this.valOffset[i] = (short)(bitcount - this.huffcode[bitcount]); - bitcount += this.bits[i]; - this.maxcode[i] = this.huffcode[bitcount - 1]; // maximum code of length i + valOffset[i] = (short)(bitcount - huffcode[bitcount]); + bitcount += lengths[i]; + maxcode[i] = huffcode[bitcount - 1]; // maximum code of length i } else { - this.maxcode[i] = -1; // -1 if no codes of this length + maxcode[i] = -1; // -1 if no codes of this length } } - this.valOffset[17] = 0; - this.maxcode[17] = 0xFFFFFL; + valOffset[17] = 0; + maxcode[17] = 0xFFFFFL; } /// /// Generates lookup tables to speed up decoding /// - private void GenerateLookaheadTables() + /// The code lengths + /// The huffman value array + /// The lookahead span + private static void GenerateLookaheadTables(byte[] lengths, byte[] huffval, Span lookahead) { int x = 0, code = 0; @@ -191,7 +188,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { code <<= 1; - for (int j = 0; j < this.bits[i + 1]; j++) + for (int j = 0; j < lengths[i + 1]; j++) { // The codeLength is 1+i, so shift code by 8-(1+i) to // calculate the high bits for every 8-bit sequence @@ -199,11 +196,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components // The high 8 bits of lutValue are the encoded value. // The low 8 bits are 1 plus the codeLength. byte base2 = (byte)(code << (7 - i)); - short lutValue = (short)((short)(this.huffval[x] << 8) | (short)(2 + i)); + short lutValue = (short)((short)(huffval[x] << 8) | (short)(2 + i)); for (int k = 0; k < 1 << (7 - i); k++) { - this.lookahead[base2 | k] = lutValue; + lookahead[base2 | k] = lutValue; } code++; diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 98d46dae5..23288f036 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -635,7 +635,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components // } // this.accumulator = (this.accumulator << 8) | this.bitsData; // int lutIndex = (this.accumulator >> (this.bitsUnRead - 8)) & 0xFF; - // int v = tree.GetLookAhead(lutIndex); + // int v = tree.Lookahead[lutIndex]; // if (v != 0) // { // int nb = (v & 0xFF) - 1; @@ -657,7 +657,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components // "DECODE", section F.2.2.3, figure F.16, page 109 of T.81 int i = 1; - while (code > tree.GetMaxCode(i)) + while (code > tree.MaxCode[i]) { code <<= 1; code |= (short)this.ReadBit(stream); @@ -670,8 +670,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components i++; } - int j = tree.GetValOffset(i); - return tree.GetHuffVal((j + code) & 0xFF); + int j = tree.ValOffset[i]; + return tree.HuffVal[(j + code) & 0xFF]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] From 83412932d5fc63df18adbe523464ebeb3bde702d Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 2 Jul 2017 20:05:27 +0100 Subject: [PATCH 32/43] store scale as vector + remove unneeded bit or operations `var t = blah | 0;` in js is for trimming floats into ints c# doesn't need it --- .../Formats/Jpeg/Port/Components/Component.cs | 11 +++------- .../Jpeg/Port/Components/JpegPixelArea.cs | 11 +++++----- .../Jpeg/Port/Components/ScanDecoder.cs | 20 +++++++++---------- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 5 +++-- 4 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs index 120bfb58d..a21cb6620 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs @@ -6,7 +6,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { using System; - + using System.Numerics; using ImageSharp.Memory; /// @@ -20,14 +20,9 @@ namespace ImageSharp.Formats.Jpeg.Port.Components public Buffer Output; /// - /// Gets or sets the horizontal scaling factor - /// - public float ScaleX; - - /// - /// Gets or sets the vertical scaling factor + /// Gets or sets the scaling factors /// - public float ScaleY; + public Vector2 Scale; /// /// Gets or sets the number of blocks per line diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs index 33cc44df4..e88e39617 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { using System; using System.Diagnostics; + using System.Numerics; using System.Runtime.CompilerServices; using ImageSharp.Memory; @@ -68,9 +69,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components this.Height = height; int numberOfComponents = this.NumberOfComponents; this.rowStride = width * numberOfComponents; + var scale = new Vector2(this.imageWidth / (float)width, this.imageHeight / (float)height); - float scaleX = this.imageWidth / (float)width; - float scaleY = this.imageHeight / (float)height; this.componentData = new Buffer(width * height * numberOfComponents); Span componentDataSpan = this.componentData; const uint Mask3Lsb = 0xFFFFFFF8; // Used to clear the 3 LSBs @@ -81,8 +81,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components for (int i = 0; i < numberOfComponents; i++) { ref Component component = ref components.Components[i]; - float componentScaleX = component.ScaleX * scaleX; - float componentScaleY = component.ScaleY * scaleY; + Vector2 componentScale = component.Scale * scale; int offset = i; Span output = component.Output; int blocksPerScanline = (component.BlocksPerLine + 1) << 3; @@ -91,14 +90,14 @@ namespace ImageSharp.Formats.Jpeg.Port.Components int j; for (int x = 0; x < width; x++) { - j = 0 | (int)(x * componentScaleX); + j = (int)(x * componentScale.X); xScaleBlockOffsetSpan[x] = (int)((j & Mask3Lsb) << 3) | (j & 7); } // Linearize the blocks of the component for (int y = 0; y < height; y++) { - j = 0 | (int)(y * componentScaleY); + j = (int)(y * componentScale.Y); int index = blocksPerScanline * (int)(j & Mask3Lsb) | ((j & 7) << 3); for (int x = 0; x < width; x++) { diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 23288f036..47ec50d71 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -478,7 +478,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeBlockBaseline(ref HuffmanTable dcHuffmanTable, ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcu, Stream stream) { - int blockRow = (mcu / component.BlocksPerLine) | 0; + int blockRow = mcu / component.BlocksPerLine; int blockCol = mcu % component.BlocksPerLine; int offset = GetBlockBufferOffset(component, blockRow, blockCol); this.DecodeBaseline(ref component, offset, ref dcHuffmanTable, ref acHuffmanTable, stream); @@ -487,7 +487,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeMcuBaseline(ref HuffmanTable dcHuffmanTable, ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { - int mcuRow = (mcu / mcusPerLine) | 0; + int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; int blockRow = (mcuRow * component.VerticalFactor) + row; int blockCol = (mcuCol * component.HorizontalFactor) + col; @@ -498,7 +498,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeBlockDCFirst(ref HuffmanTable dcHuffmanTable, ref FrameComponent component, int mcu, Stream stream) { - int blockRow = (mcu / component.BlocksPerLine) | 0; + int blockRow = mcu / component.BlocksPerLine; int blockCol = mcu % component.BlocksPerLine; int offset = GetBlockBufferOffset(component, blockRow, blockCol); this.DecodeDCFirst(ref component, offset, ref dcHuffmanTable, stream); @@ -507,7 +507,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeMcuDCFirst(ref HuffmanTable dcHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { - int mcuRow = (mcu / mcusPerLine) | 0; + int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; int blockRow = (mcuRow * component.VerticalFactor) + row; int blockCol = (mcuCol * component.HorizontalFactor) + col; @@ -518,7 +518,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeBlockDCSuccessive(ref FrameComponent component, int mcu, Stream stream) { - int blockRow = (mcu / component.BlocksPerLine) | 0; + int blockRow = mcu / component.BlocksPerLine; int blockCol = mcu % component.BlocksPerLine; int offset = GetBlockBufferOffset(component, blockRow, blockCol); this.DecodeDCSuccessive(ref component, offset, stream); @@ -527,7 +527,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeMcuDCSuccessive(ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { - int mcuRow = (mcu / mcusPerLine) | 0; + int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; int blockRow = (mcuRow * component.VerticalFactor) + row; int blockCol = (mcuCol * component.HorizontalFactor) + col; @@ -538,7 +538,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeBlockACFirst(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcu, Stream stream) { - int blockRow = (mcu / component.BlocksPerLine) | 0; + int blockRow = mcu / component.BlocksPerLine; int blockCol = mcu % component.BlocksPerLine; int offset = GetBlockBufferOffset(component, blockRow, blockCol); this.DecodeACFirst(ref component, offset, ref acHuffmanTable, stream); @@ -547,7 +547,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeMcuACFirst(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { - int mcuRow = (mcu / mcusPerLine) | 0; + int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; int blockRow = (mcuRow * component.VerticalFactor) + row; int blockCol = (mcuCol * component.HorizontalFactor) + col; @@ -558,7 +558,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeBlockACSuccessive(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcu, Stream stream) { - int blockRow = (mcu / component.BlocksPerLine) | 0; + int blockRow = mcu / component.BlocksPerLine; int blockCol = mcu % component.BlocksPerLine; int offset = GetBlockBufferOffset(component, blockRow, blockCol); this.DecodeACSuccessive(ref component, offset, ref acHuffmanTable, stream); @@ -567,7 +567,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeMcuACSuccessive(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { - int mcuRow = (mcu / mcusPerLine) | 0; + int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; int blockRow = (mcuRow * component.VerticalFactor) + row; int blockCol = (mcuCol * component.HorizontalFactor) + col; diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index f0e05fabd..d7f8a3ae7 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -277,8 +277,9 @@ namespace ImageSharp.Formats.Jpeg.Port ref var frameComponent = ref this.frame.Components[i]; var component = new Component { - ScaleX = frameComponent.HorizontalFactor / (float)this.frame.MaxHorizontalFactor, - ScaleY = frameComponent.VerticalFactor / (float)this.frame.MaxVerticalFactor, + Scale = new System.Numerics.Vector2( + frameComponent.HorizontalFactor / (float)this.frame.MaxHorizontalFactor, + frameComponent.VerticalFactor / (float)this.frame.MaxVerticalFactor), BlocksPerLine = frameComponent.BlocksPerLine, BlocksPerColumn = frameComponent.BlocksPerColumn }; From 937aad7a41f7fa27b6cb5d390a28c032288bd3cd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 6 Jul 2017 23:29:36 +1000 Subject: [PATCH 33/43] Add WIP faster IDCT method This is killing me! --- .../Formats/Jpeg/Port/Components/IDCT.cs | 248 +++++++++++++++++- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 2 +- 2 files changed, 248 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs index f931c3d6b..6b77dddb1 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs @@ -1,6 +1,9 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { using System; + using System.Numerics; + using System.Runtime.CompilerServices; + using ImageSharp.Memory; /// @@ -17,6 +20,47 @@ private const int DctSqrt2 = 5793; // sqrt(2) private const int DctSqrt1D2 = 2896; // sqrt(2) / 2 +#pragma warning disable SA1310 // Field names must not contain underscore + private const int FIX_1_082392200 = 277; /* FIX(1.082392200) */ + private const int FIX_1_414213562 = 362; /* FIX(1.414213562) */ + private const int FIX_1_847759065 = 473; /* FIX(1.847759065) */ + private const int FIX_2_613125930 = 669; /* FIX(2.613125930) */ +#pragma warning restore SA1310 // Field names must not contain underscore + + private const int ScaleBits = 2; /* fractional bits in scale factors */ + + /* + * Each IDCT routine is responsible for range-limiting its results and + * converting them to unsigned form (0..255). The raw outputs could + * be quite far out of range if the input data is corrupt, so a bulletproof + * range-limiting step is required. We use a mask-and-table-lookup method + * to do the combined operations quickly, assuming that 255+1 + * is a power of 2. See the comments with prepare_range_limit_table for more info. + */ + private const int RangeMask = (255 * 4) + 3; /* 2 bits wider than legal samples */ + + private static readonly byte[] Limit = new byte[5 * (255 + 1)]; + + static IDCT() + { + // First segment of range limit table: limit[x] = 0 for x < 0 + // allow negative subscripts of simple table */ + int tableOffset = 2 * (255 + 1); + + // Main part of range limit table: limit[x] = x + int i; + for (i = 0; i <= 255; i++) + { + Limit[tableOffset + i] = (byte)i; + } + + /* End of range limit table: limit[x] = MAXJSAMPLE for x > MAXJSAMPLE */ + for (; i < 3 * (255 + 1); i++) + { + Limit[tableOffset + i] = 255; + } + } + /// /// A port of Poppler's IDCT method which in turn is taken from: /// Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz, @@ -219,5 +263,207 @@ blockData[col + 56] = (short)p7; } } + + /// + /// A port of + /// TODO: This does not work!! + /// A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + /// on each row(or vice versa, but it's more convenient to emit a row at + /// a time). Direct algorithms are also available, but they are much more + /// complex and seem not to be any faster when reduced to code. + /// + /// This implementation is based on Arai, Agui, and Nakajima's algorithm for + /// scaled DCT.Their original paper (Trans.IEICE E-71(11):1095) is in + /// Japanese, but the algorithm is described in the Pennebaker & Mitchell + /// JPEG textbook(see REFERENCES section in file README.ijg). The following + /// code is based directly on figure 4-8 in P&M. + /// While an 8-point DCT cannot be done in less than 11 multiplies, it is + /// possible to arrange the computation so that many of the multiplies are + /// simple scalings of the final outputs.These multiplies can then be + /// folded into the multiplications or divisions by the JPEG quantization + /// table entries. The AA&N method leaves only 5 multiplies and 29 adds + /// to be done in the DCT itself. + /// The primary disadvantage of this method is that with fixed-point math, + /// accuracy is lost due to imprecise representation of the scaled + /// quantization values.The smaller the quantization table entry, the less + /// precise the scaled value, so this implementation does worse with high - + /// quality - setting files than with low - quality ones. + /// + /// The quantization tables + /// The fram component + /// The block buffer offset + /// The computational buffer for holding temp values + public static void QuantizeAndInverseAlt(QuantizationTables quantizationTables, ref FrameComponent component, int blockBufferOffset, Buffer computationBuffer) + { + Span qt = quantizationTables.Tables.GetRowSpan(component.QuantizationIdentifier); + Span blockData = component.BlockData.Slice(blockBufferOffset); + Span computationBufferSpan = computationBuffer; + + int p0, p1, p2, p3, p4, p5, p6, p7; + + for (int col = 0; col < 8; col++) + { + // Gather block data + p0 = blockData[col]; + p1 = blockData[col + 8]; + p2 = blockData[col + 16]; + p3 = blockData[col + 24]; + p4 = blockData[col + 32]; + p5 = blockData[col + 40]; + p6 = blockData[col + 48]; + p7 = blockData[col + 56]; + + int tmp0 = p0 * qt[col]; + + // Check for all-zero AC coefficients + if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) + { + short dcval = (short)tmp0; + + computationBufferSpan[col] = dcval; + computationBufferSpan[col + 8] = dcval; + computationBufferSpan[col + 16] = dcval; + computationBufferSpan[col + 24] = dcval; + computationBufferSpan[col + 32] = dcval; + computationBufferSpan[col + 40] = dcval; + computationBufferSpan[col + 48] = dcval; + computationBufferSpan[col + 56] = dcval; + continue; + } + + // Even part + int tmp1 = p2 * qt[col + 16]; + int tmp2 = p4 * qt[col + 32]; + int tmp3 = p6 * qt[col + 48]; + + int tmp10 = tmp0 + tmp2; // Phase 3 + int tmp11 = tmp0 - tmp2; + + int tmp13 = tmp1 + tmp3; // Phases 5-3 + int tmp12 = Multiply(tmp1 - tmp3, FIX_1_414213562) - tmp13; // 2*c4 + + tmp0 = tmp10 + tmp13; // Phase 2 + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + // Odd Part + int tmp4 = p1 * qt[col + 8]; + int tmp5 = p3 * qt[col + 24]; + int tmp6 = p5 * qt[col + 40]; + int tmp7 = p7 * qt[col + 56]; + + int z13 = tmp6 + tmp5; // Phase 6 + int z10 = tmp6 - tmp5; + int z11 = tmp4 + tmp7; + int z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; // Phase 5 + tmp11 = Multiply(z11 - z13, FIX_1_414213562); // 2*c4 + + int z5 = Multiply(z10 + z12, FIX_1_847759065); // 2*c2 + tmp10 = Multiply(z12, FIX_1_082392200) - z5; // 2*(c2-c6) + tmp12 = Multiply(z10, FIX_2_613125930) + z5; // 2*(c2+c6) + + tmp6 = tmp12 - tmp7; // Phase 2 + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 - tmp5; + + computationBufferSpan[col] = (short)(tmp0 + tmp7); + computationBufferSpan[col + 56] = (short)(tmp0 - tmp7); + computationBufferSpan[col + 8] = (short)(tmp1 + tmp6); + computationBufferSpan[col + 48] = (short)(tmp1 - tmp6); + computationBufferSpan[col + 16] = (short)(tmp2 + tmp5); + computationBufferSpan[col + 40] = (short)(tmp2 - tmp5); + computationBufferSpan[col + 32] = (short)(tmp3 + tmp4); + computationBufferSpan[col + 24] = (short)(tmp3 - tmp4); + } + + // Pass 2: process rows from work array, store into output array. + // Note that we must descale the results by a factor of 8 == 2**3, + // and also undo the pass 1 bits scaling. + for (int row = 0; row < 64; row += 8) + { + p0 = computationBufferSpan[row]; + p1 = computationBufferSpan[row + 1]; + p2 = computationBufferSpan[row + 2]; + p3 = computationBufferSpan[row + 3]; + p4 = computationBufferSpan[row + 4]; + p5 = computationBufferSpan[row + 5]; + p6 = computationBufferSpan[row + 6]; + p7 = computationBufferSpan[row + 7]; + + // Check for all-zero AC coefficients + if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) + { + byte dcval = Limit[Descale(p0, ScaleBits + 3) & RangeMask]; + + blockData[row] = dcval; + blockData[row + 1] = dcval; + blockData[row + 2] = dcval; + blockData[row + 3] = dcval; + blockData[row + 4] = dcval; + blockData[row + 5] = dcval; + blockData[row + 6] = dcval; + blockData[row + 7] = dcval; + + continue; + } + + // Even part + int tmp10 = p0 + p4; + int tmp11 = p0 - p4; + + int tmp13 = p2 + p6; + int tmp12 = Multiply(p2 - p6, FIX_1_414213562) - tmp13; /* 2*c4 */ + + int tmp0 = tmp10 + tmp13; + int tmp3 = tmp10 - tmp13; + int tmp1 = tmp11 + tmp12; + int tmp2 = tmp11 - tmp12; + + // Odd part + int z13 = p5 + p3; + int z10 = p5 - p3; + int z11 = p1 + p7; + int z12 = p1 - p7; + + int tmp7 = z11 + z13; // Phase 5 + tmp11 = Multiply(z11 - z13, FIX_1_414213562); // 2*c4 + + int z5 = Multiply(z10 + z12, FIX_1_847759065); // 2*c2 + tmp10 = Multiply(z12, FIX_1_082392200) - z5; // 2*(c2-c6) + tmp12 = Multiply(z10, FIX_2_613125930) + z5; // -2*(c2+c6) + + int tmp6 = tmp12 - tmp7; // Phase 2 + int tmp5 = tmp11 - tmp6; + int tmp4 = tmp10 - tmp5; + + // Final output stage: scale down by a factor of 8 and range-limit + blockData[row] = Limit[Descale(tmp0 + tmp7, ScaleBits + 3) & RangeMask]; + blockData[row + 7] = Limit[Descale(tmp0 - tmp7, ScaleBits + 3) & RangeMask]; + blockData[row + 1] = Limit[Descale(tmp1 + tmp6, ScaleBits + 3) & RangeMask]; + blockData[row + 6] = Limit[Descale(tmp1 - tmp6, ScaleBits + 3) & RangeMask]; + blockData[row + 2] = Limit[Descale(tmp2 + tmp5, ScaleBits + 3) & RangeMask]; + blockData[row + 5] = Limit[Descale(tmp2 - tmp5, ScaleBits + 3) & RangeMask]; + blockData[row + 3] = Limit[Descale(tmp3 + tmp4, ScaleBits + 3) & RangeMask]; + blockData[row + 4] = Limit[Descale(tmp3 - tmp4, ScaleBits + 3) & RangeMask]; + } + } + + private static int Multiply(int val, int c) + { + return Descale(val * c, 8); + } + + private static int RightShift(int x, int shft) + { + return x >> shft; + } + + private static int Descale(int x, int n) + { + return RightShift(x + (1 << (n - 1)), n); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index d7f8a3ae7..ef49dfaf0 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -798,7 +798,7 @@ namespace ImageSharp.Formats.Jpeg.Port for (int blockCol = 0; blockCol < blocksPerLine; blockCol++) { int offset = GetBlockBufferOffset(ref component, blockRow, blockCol); - IDCT.QuantizeAndInverse(this.quantizationTables, ref frameComponent, offset, computationBuffer); + IDCT.QuantizeAndInverseAlt(this.quantizationTables, ref frameComponent, offset, computationBuffer); } } } From e8674e05985449a4d0a9539c7d93beb3e5863292 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 9 Jul 2017 10:54:27 +1000 Subject: [PATCH 34/43] This works. High allocations though --- ImageSharp.sln | 5 +- .../Formats/Jpeg/Port/Components/IDCT.cs | 415 ++++++++++-------- 2 files changed, 244 insertions(+), 176 deletions(-) diff --git a/ImageSharp.sln b/ImageSharp.sln index a584c5686..aedfcc345 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -49,9 +49,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{7CC6 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvatarWithRoundedCorner", "samples\AvatarWithRoundedCorner\AvatarWithRoundedCorner.csproj", "{844FC582-4E78-4371-847D-EFD4D1103578}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChangeDefaultEncoderOptions", "samples\ChangeDefaultEncoderOptions\ChangeDefaultEncoderOptions.csproj", "{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChangeDefaultEncoderOptions", "samples\ChangeDefaultEncoderOptions\ChangeDefaultEncoderOptions.csproj", "{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}" EndProject Global + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs index 6b77dddb1..22d787269 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs @@ -27,37 +27,52 @@ private const int FIX_2_613125930 = 669; /* FIX(2.613125930) */ #pragma warning restore SA1310 // Field names must not contain underscore - private const int ScaleBits = 2; /* fractional bits in scale factors */ - - /* - * Each IDCT routine is responsible for range-limiting its results and - * converting them to unsigned form (0..255). The raw outputs could - * be quite far out of range if the input data is corrupt, so a bulletproof - * range-limiting step is required. We use a mask-and-table-lookup method - * to do the combined operations quickly, assuming that 255+1 - * is a power of 2. See the comments with prepare_range_limit_table for more info. - */ - private const int RangeMask = (255 * 4) + 3; /* 2 bits wider than legal samples */ + private const int ConstBits = 8; + private const int Pass1Bits = 2; // Factional bits in scale factors + private const int MaxJSample = 255; + private const int CenterJSample = 128; + private const int RangeCenter = (MaxJSample * 2) + 2; + + // First segment of range limit table: limit[x] = 0 for x < 0 + // allow negative subscripts of simple table + private const int TableOffset = 2 * (MaxJSample + 1); + private const int LimitOffset = TableOffset - (RangeCenter - CenterJSample); + + // Each IDCT routine is responsible for range-limiting its results and + // converting them to unsigned form (0..MaxJSample). The raw outputs could + // be quite far out of range if the input data is corrupt, so a bulletproof + // range-limiting step is required. We use a mask-and-table-lookup method + // to do the combined operations quickly, assuming that MaxJSample+1 + // is a power of 2. See the comments with prepare_range_limit_table for more info. + private const int RangeMask = (MaxJSample * 4) + 3; // 2 bits wider than legal samples + + // Precomputed values scaled up by 14 bits + private static readonly short[] Aanscales = + { + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, 22725, 31521, 29692, 26722, 22725, 17855, + 12299, 6270, 21407, 29692, 27969, 25172, 21407, 16819, 11585, + 5906, 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, 12873, + 17855, 16819, 15137, 12873, 10114, 6967, 3552, 8867, 12299, + 11585, 10426, 8867, 6967, 4799, 2446, 4520, 6270, 5906, 5315, + 4520, 3552, 2446, 1247 + }; - private static readonly byte[] Limit = new byte[5 * (255 + 1)]; + private static readonly byte[] Limit = new byte[5 * (MaxJSample + 1)]; static IDCT() { - // First segment of range limit table: limit[x] = 0 for x < 0 - // allow negative subscripts of simple table */ - int tableOffset = 2 * (255 + 1); - // Main part of range limit table: limit[x] = x int i; - for (i = 0; i <= 255; i++) + for (i = 0; i <= MaxJSample; i++) { - Limit[tableOffset + i] = (byte)i; + Limit[TableOffset + i] = (byte)i; } - /* End of range limit table: limit[x] = MAXJSAMPLE for x > MAXJSAMPLE */ - for (; i < 3 * (255 + 1); i++) + // End of range limit table: limit[x] = MaxJSample for x > MaxJSample + for (; i < 3 * (MaxJSample + 1); i++) { - Limit[tableOffset + i] = 255; + Limit[TableOffset + i] = MaxJSample; } } @@ -183,7 +198,7 @@ t = ((DctSqrt2 * p0) + 8192) >> 14; // convert to 8 bit - t = (t < -2040) ? 0 : (t >= 2024) ? 255 : (t + 2056) >> 4; + t = (t < -2040) ? 0 : (t >= 2024) ? MaxJSample : (t + 2056) >> 4; short st = (short)t; blockData[col] = st; @@ -243,14 +258,14 @@ p4 = v3 - v4; // convert to 8-bit integers - p0 = (p0 < 16) ? 0 : (p0 >= 4080) ? 255 : p0 >> 4; - p1 = (p1 < 16) ? 0 : (p1 >= 4080) ? 255 : p1 >> 4; - p2 = (p2 < 16) ? 0 : (p2 >= 4080) ? 255 : p2 >> 4; - p3 = (p3 < 16) ? 0 : (p3 >= 4080) ? 255 : p3 >> 4; - p4 = (p4 < 16) ? 0 : (p4 >= 4080) ? 255 : p4 >> 4; - p5 = (p5 < 16) ? 0 : (p5 >= 4080) ? 255 : p5 >> 4; - p6 = (p6 < 16) ? 0 : (p6 >= 4080) ? 255 : p6 >> 4; - p7 = (p7 < 16) ? 0 : (p7 >= 4080) ? 255 : p7 >> 4; + p0 = (p0 < 16) ? 0 : (p0 >= 4080) ? MaxJSample : p0 >> 4; + p1 = (p1 < 16) ? 0 : (p1 >= 4080) ? MaxJSample : p1 >> 4; + p2 = (p2 < 16) ? 0 : (p2 >= 4080) ? MaxJSample : p2 >> 4; + p3 = (p3 < 16) ? 0 : (p3 >= 4080) ? MaxJSample : p3 >> 4; + p4 = (p4 < 16) ? 0 : (p4 >= 4080) ? MaxJSample : p4 >> 4; + p5 = (p5 < 16) ? 0 : (p5 >= 4080) ? MaxJSample : p5 >> 4; + p6 = (p6 < 16) ? 0 : (p6 >= 4080) ? MaxJSample : p6 >> 4; + p7 = (p7 < 16) ? 0 : (p7 >= 4080) ? MaxJSample : p7 >> 4; // store block data blockData[col] = (short)p0; @@ -266,7 +281,6 @@ /// /// A port of - /// TODO: This does not work!! /// A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT /// on each row(or vice versa, but it's more convenient to emit a row at /// a time). Direct algorithms are also available, but they are much more @@ -274,7 +288,7 @@ /// /// This implementation is based on Arai, Agui, and Nakajima's algorithm for /// scaled DCT.Their original paper (Trans.IEICE E-71(11):1095) is in - /// Japanese, but the algorithm is described in the Pennebaker & Mitchell + /// Japanese, but the algorithm is described in the Pennebaker & Mitchell /// JPEG textbook(see REFERENCES section in file README.ijg). The following /// code is based directly on figure 4-8 in P&M. /// While an 8-point DCT cannot be done in less than 11 multiplies, it is @@ -293,177 +307,228 @@ /// The fram component /// The block buffer offset /// The computational buffer for holding temp values - public static void QuantizeAndInverseAlt(QuantizationTables quantizationTables, ref FrameComponent component, int blockBufferOffset, Buffer computationBuffer) + public static void QuantizeAndInverseAlt( + QuantizationTables quantizationTables, + ref FrameComponent component, + int blockBufferOffset, + Buffer computationBuffer) { Span qt = quantizationTables.Tables.GetRowSpan(component.QuantizationIdentifier); Span blockData = component.BlockData.Slice(blockBufferOffset); Span computationBufferSpan = computationBuffer; - int p0, p1, p2, p3, p4, p5, p6, p7; - - for (int col = 0; col < 8; col++) + // For AA&N IDCT method, multiplier are equal to quantization + // coefficients scaled by scalefactor[row]*scalefactor[col], where + // scalefactor[0] = 1 + // scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + // For integer operation, the multiplier table is to be scaled by 14. + using (var multiplier = new Buffer(64)) { - // Gather block data - p0 = blockData[col]; - p1 = blockData[col + 8]; - p2 = blockData[col + 16]; - p3 = blockData[col + 24]; - p4 = blockData[col + 32]; - p5 = blockData[col + 40]; - p6 = blockData[col + 48]; - p7 = blockData[col + 56]; - - int tmp0 = p0 * qt[col]; - - // Check for all-zero AC coefficients - if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) + Span multiplierSpan = multiplier; + for (int i = 0; i < 64; i++) { - short dcval = (short)tmp0; - - computationBufferSpan[col] = dcval; - computationBufferSpan[col + 8] = dcval; - computationBufferSpan[col + 16] = dcval; - computationBufferSpan[col + 24] = dcval; - computationBufferSpan[col + 32] = dcval; - computationBufferSpan[col + 40] = dcval; - computationBufferSpan[col + 48] = dcval; - computationBufferSpan[col + 56] = dcval; - continue; + multiplierSpan[i] = (short)Descale(qt[i] * Aanscales[i], 14 - Pass1Bits); } - // Even part - int tmp1 = p2 * qt[col + 16]; - int tmp2 = p4 * qt[col + 32]; - int tmp3 = p6 * qt[col + 48]; - - int tmp10 = tmp0 + tmp2; // Phase 3 - int tmp11 = tmp0 - tmp2; - - int tmp13 = tmp1 + tmp3; // Phases 5-3 - int tmp12 = Multiply(tmp1 - tmp3, FIX_1_414213562) - tmp13; // 2*c4 - - tmp0 = tmp10 + tmp13; // Phase 2 - tmp3 = tmp10 - tmp13; - tmp1 = tmp11 + tmp12; - tmp2 = tmp11 - tmp12; - - // Odd Part - int tmp4 = p1 * qt[col + 8]; - int tmp5 = p3 * qt[col + 24]; - int tmp6 = p5 * qt[col + 40]; - int tmp7 = p7 * qt[col + 56]; - - int z13 = tmp6 + tmp5; // Phase 6 - int z10 = tmp6 - tmp5; - int z11 = tmp4 + tmp7; - int z12 = tmp4 - tmp7; - - tmp7 = z11 + z13; // Phase 5 - tmp11 = Multiply(z11 - z13, FIX_1_414213562); // 2*c4 - - int z5 = Multiply(z10 + z12, FIX_1_847759065); // 2*c2 - tmp10 = Multiply(z12, FIX_1_082392200) - z5; // 2*(c2-c6) - tmp12 = Multiply(z10, FIX_2_613125930) + z5; // 2*(c2+c6) - - tmp6 = tmp12 - tmp7; // Phase 2 - tmp5 = tmp11 - tmp6; - tmp4 = tmp10 - tmp5; - - computationBufferSpan[col] = (short)(tmp0 + tmp7); - computationBufferSpan[col + 56] = (short)(tmp0 - tmp7); - computationBufferSpan[col + 8] = (short)(tmp1 + tmp6); - computationBufferSpan[col + 48] = (short)(tmp1 - tmp6); - computationBufferSpan[col + 16] = (short)(tmp2 + tmp5); - computationBufferSpan[col + 40] = (short)(tmp2 - tmp5); - computationBufferSpan[col + 32] = (short)(tmp3 + tmp4); - computationBufferSpan[col + 24] = (short)(tmp3 - tmp4); - } + int p0, p1, p2, p3, p4, p5, p6, p7; - // Pass 2: process rows from work array, store into output array. - // Note that we must descale the results by a factor of 8 == 2**3, - // and also undo the pass 1 bits scaling. - for (int row = 0; row < 64; row += 8) - { - p0 = computationBufferSpan[row]; - p1 = computationBufferSpan[row + 1]; - p2 = computationBufferSpan[row + 2]; - p3 = computationBufferSpan[row + 3]; - p4 = computationBufferSpan[row + 4]; - p5 = computationBufferSpan[row + 5]; - p6 = computationBufferSpan[row + 6]; - p7 = computationBufferSpan[row + 7]; - - // Check for all-zero AC coefficients - if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) + int coefBlockIndex = 0; + int workspaceIndex = 0; + int quantTableIndex = 0; + for (int col = 8; col > 0; col--) { - byte dcval = Limit[Descale(p0, ScaleBits + 3) & RangeMask]; - - blockData[row] = dcval; - blockData[row + 1] = dcval; - blockData[row + 2] = dcval; - blockData[row + 3] = dcval; - blockData[row + 4] = dcval; - blockData[row + 5] = dcval; - blockData[row + 6] = dcval; - blockData[row + 7] = dcval; - - continue; + // Gather block data + p0 = blockData[coefBlockIndex]; + p1 = blockData[coefBlockIndex + 8]; + p2 = blockData[coefBlockIndex + 16]; + p3 = blockData[coefBlockIndex + 24]; + p4 = blockData[coefBlockIndex + 32]; + p5 = blockData[coefBlockIndex + 40]; + p6 = blockData[coefBlockIndex + 48]; + p7 = blockData[coefBlockIndex + 56]; + + int tmp0 = p0 * multiplierSpan[quantTableIndex]; + + // Due to quantization, we will usually find that many of the input + // coefficients are zero, especially the AC terms. We can exploit this + // by short-circuiting the IDCT calculation for any column in which all + // the AC terms are zero. In that case each output is equal to the + // DC coefficient (with scale factor as needed). + // With typical images and quantization tables, half or more of the + // column DCT calculations can be simplified this way. + if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) + { + short dcval = (short)tmp0; + + computationBufferSpan[workspaceIndex] = dcval; + computationBufferSpan[workspaceIndex + 8] = dcval; + computationBufferSpan[workspaceIndex + 16] = dcval; + computationBufferSpan[workspaceIndex + 24] = dcval; + computationBufferSpan[workspaceIndex + 32] = dcval; + computationBufferSpan[workspaceIndex + 40] = dcval; + computationBufferSpan[workspaceIndex + 48] = dcval; + computationBufferSpan[workspaceIndex + 56] = dcval; + + coefBlockIndex++; + quantTableIndex++; + workspaceIndex++; + continue; + } + + // Even part + int tmp1 = p2 * multiplierSpan[quantTableIndex + 16]; + int tmp2 = p4 * multiplierSpan[quantTableIndex + 32]; + int tmp3 = p6 * multiplierSpan[quantTableIndex + 48]; + + int tmp10 = tmp0 + tmp2; // Phase 3 + int tmp11 = tmp0 - tmp2; + + int tmp13 = tmp1 + tmp3; // Phases 5-3 + int tmp12 = Multiply(tmp1 - tmp3, FIX_1_414213562) - tmp13; // 2*c4 + + tmp0 = tmp10 + tmp13; // Phase 2 + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + // Odd Part + int tmp4 = p1 * multiplierSpan[quantTableIndex + 8]; + int tmp5 = p3 * multiplierSpan[quantTableIndex + 24]; + int tmp6 = p5 * multiplierSpan[quantTableIndex + 40]; + int tmp7 = p7 * multiplierSpan[quantTableIndex + 56]; + + int z13 = tmp6 + tmp5; // Phase 6 + int z10 = tmp6 - tmp5; + int z11 = tmp4 + tmp7; + int z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; // Phase 5 + tmp11 = Multiply(z11 - z13, FIX_1_414213562); // 2*c4 + + int z5 = Multiply(z10 + z12, FIX_1_847759065); // 2*c2 + tmp10 = z5 - Multiply(z12, FIX_1_082392200); // 2*(c2-c6) + tmp12 = z5 - Multiply(z10, FIX_2_613125930); // 2*(c2+c6) + + tmp6 = tmp12 - tmp7; // Phase 2 + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 - tmp5; + + computationBufferSpan[workspaceIndex] = (short)(tmp0 + tmp7); + computationBufferSpan[workspaceIndex + 56] = (short)(tmp0 - tmp7); + computationBufferSpan[workspaceIndex + 8] = (short)(tmp1 + tmp6); + computationBufferSpan[workspaceIndex + 48] = (short)(tmp1 - tmp6); + computationBufferSpan[workspaceIndex + 16] = (short)(tmp2 + tmp5); + computationBufferSpan[workspaceIndex + 40] = (short)(tmp2 - tmp5); + computationBufferSpan[workspaceIndex + 24] = (short)(tmp3 + tmp4); + computationBufferSpan[workspaceIndex + 32] = (short)(tmp3 - tmp4); + + coefBlockIndex++; + quantTableIndex++; + workspaceIndex++; } - // Even part - int tmp10 = p0 + p4; - int tmp11 = p0 - p4; - - int tmp13 = p2 + p6; - int tmp12 = Multiply(p2 - p6, FIX_1_414213562) - tmp13; /* 2*c4 */ - - int tmp0 = tmp10 + tmp13; - int tmp3 = tmp10 - tmp13; - int tmp1 = tmp11 + tmp12; - int tmp2 = tmp11 - tmp12; - - // Odd part - int z13 = p5 + p3; - int z10 = p5 - p3; - int z11 = p1 + p7; - int z12 = p1 - p7; - - int tmp7 = z11 + z13; // Phase 5 - tmp11 = Multiply(z11 - z13, FIX_1_414213562); // 2*c4 - - int z5 = Multiply(z10 + z12, FIX_1_847759065); // 2*c2 - tmp10 = Multiply(z12, FIX_1_082392200) - z5; // 2*(c2-c6) - tmp12 = Multiply(z10, FIX_2_613125930) + z5; // -2*(c2+c6) - - int tmp6 = tmp12 - tmp7; // Phase 2 - int tmp5 = tmp11 - tmp6; - int tmp4 = tmp10 - tmp5; - - // Final output stage: scale down by a factor of 8 and range-limit - blockData[row] = Limit[Descale(tmp0 + tmp7, ScaleBits + 3) & RangeMask]; - blockData[row + 7] = Limit[Descale(tmp0 - tmp7, ScaleBits + 3) & RangeMask]; - blockData[row + 1] = Limit[Descale(tmp1 + tmp6, ScaleBits + 3) & RangeMask]; - blockData[row + 6] = Limit[Descale(tmp1 - tmp6, ScaleBits + 3) & RangeMask]; - blockData[row + 2] = Limit[Descale(tmp2 + tmp5, ScaleBits + 3) & RangeMask]; - blockData[row + 5] = Limit[Descale(tmp2 - tmp5, ScaleBits + 3) & RangeMask]; - blockData[row + 3] = Limit[Descale(tmp3 + tmp4, ScaleBits + 3) & RangeMask]; - blockData[row + 4] = Limit[Descale(tmp3 - tmp4, ScaleBits + 3) & RangeMask]; + // Pass 2: process rows from work array, store into output array. + // Note that we must descale the results by a factor of 8 == 2**3, + // and also undo the pass 1 bits scaling. + for (int row = 0; row < 64; row += 8) + { + p1 = computationBufferSpan[row + 1]; + p2 = computationBufferSpan[row + 2]; + p3 = computationBufferSpan[row + 3]; + p4 = computationBufferSpan[row + 4]; + p5 = computationBufferSpan[row + 5]; + p6 = computationBufferSpan[row + 6]; + p7 = computationBufferSpan[row + 7]; + + // Add range center and fudge factor for final descale and range-limit. + int z5 = computationBufferSpan[row] + (RangeCenter << (Pass1Bits + 3)) + (1 << (Pass1Bits + 2)); + + // Check for all-zero AC coefficients + if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) + { + byte dcval = DoLimit(RightShift(z5, Pass1Bits + 3) & RangeMask); + + blockData[row] = dcval; + blockData[row + 1] = dcval; + blockData[row + 2] = dcval; + blockData[row + 3] = dcval; + blockData[row + 4] = dcval; + blockData[row + 5] = dcval; + blockData[row + 6] = dcval; + blockData[row + 7] = dcval; + + continue; + } + + // Even part + int tmp10 = z5 + p4; + int tmp11 = z5 - p4; + + int tmp13 = p2 + p6; + int tmp12 = Multiply(p2 - p6, FIX_1_414213562) - tmp13; /* 2*c4 */ + + int tmp0 = tmp10 + tmp13; + int tmp3 = tmp10 - tmp13; + int tmp1 = tmp11 + tmp12; + int tmp2 = tmp11 - tmp12; + + // Odd part + int z13 = p5 + p3; + int z10 = p5 - p3; + int z11 = p1 + p7; + int z12 = p1 - p7; + + int tmp7 = z11 + z13; // Phase 5 + tmp11 = Multiply(z11 - z13, FIX_1_414213562); // 2*c4 + + z5 = Multiply(z10 + z12, FIX_1_847759065); // 2*c2 + tmp10 = z5 - Multiply(z12, FIX_1_082392200); // 2*(c2-c6) + tmp12 = z5 - Multiply(z10, FIX_2_613125930); // 2*(c2+c6) + + int tmp6 = tmp12 - tmp7; // Phase 2 + int tmp5 = tmp11 - tmp6; + int tmp4 = tmp10 - tmp5; + + // Final output stage: scale down by a factor of 8 and range-limit + blockData[row] = DoLimit(Descale(tmp0 + tmp7, Pass1Bits + 3) & RangeMask); + blockData[row + 7] = DoLimit(Descale(tmp0 - tmp7, Pass1Bits + 3) & RangeMask); + blockData[row + 1] = DoLimit(Descale(tmp1 + tmp6, Pass1Bits + 3) & RangeMask); + blockData[row + 6] = DoLimit(Descale(tmp1 - tmp6, Pass1Bits + 3) & RangeMask); + blockData[row + 2] = DoLimit(Descale(tmp2 + tmp5, Pass1Bits + 3) & RangeMask); + blockData[row + 5] = DoLimit(Descale(tmp2 - tmp5, Pass1Bits + 3) & RangeMask); + blockData[row + 3] = DoLimit(Descale(tmp3 + tmp4, Pass1Bits + 3) & RangeMask); + blockData[row + 4] = DoLimit(Descale(tmp3 - tmp4, Pass1Bits + 3) & RangeMask); + } } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Multiply(int val, int c) { - return Descale(val * c, 8); + return Descale(val * c, ConstBits); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int RightShift(int x, int shft) { return x >> shft; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Descale(int x, int n) { return RightShift(x + (1 << (n - 1)), n); } + + /// + /// Offsets the value by 128 and limits it to the 0..255 range + /// + /// The value + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte DoLimit(int value) + { + return Limit[value + LimitOffset]; + } } } \ No newline at end of file From 757d5bbda74c9ec7e3c367035721711b214a0c5b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 9 Jul 2017 11:21:02 +1000 Subject: [PATCH 35/43] A little cleanup --- .../Formats/Jpeg/Port/Components/IDCT.cs | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs index 22d787269..cae1e8434 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs @@ -1,7 +1,6 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { using System; - using System.Numerics; using System.Runtime.CompilerServices; using ImageSharp.Memory; @@ -69,7 +68,7 @@ Limit[TableOffset + i] = (byte)i; } - // End of range limit table: limit[x] = MaxJSample for x > MaxJSample + // End of range limit table: Limit[x] = MaxJSample for x > MaxJSample for (; i < 3 * (MaxJSample + 1); i++) { Limit[TableOffset + i] = MaxJSample; @@ -446,7 +445,7 @@ // Check for all-zero AC coefficients if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) { - byte dcval = DoLimit(RightShift(z5, Pass1Bits + 3) & RangeMask); + byte dcval = Limit[LimitOffset + (RightShift(z5, Pass1Bits + 3) & RangeMask)]; blockData[row] = dcval; blockData[row + 1] = dcval; @@ -489,46 +488,55 @@ int tmp5 = tmp11 - tmp6; int tmp4 = tmp10 - tmp5; - // Final output stage: scale down by a factor of 8 and range-limit - blockData[row] = DoLimit(Descale(tmp0 + tmp7, Pass1Bits + 3) & RangeMask); - blockData[row + 7] = DoLimit(Descale(tmp0 - tmp7, Pass1Bits + 3) & RangeMask); - blockData[row + 1] = DoLimit(Descale(tmp1 + tmp6, Pass1Bits + 3) & RangeMask); - blockData[row + 6] = DoLimit(Descale(tmp1 - tmp6, Pass1Bits + 3) & RangeMask); - blockData[row + 2] = DoLimit(Descale(tmp2 + tmp5, Pass1Bits + 3) & RangeMask); - blockData[row + 5] = DoLimit(Descale(tmp2 - tmp5, Pass1Bits + 3) & RangeMask); - blockData[row + 3] = DoLimit(Descale(tmp3 + tmp4, Pass1Bits + 3) & RangeMask); - blockData[row + 4] = DoLimit(Descale(tmp3 - tmp4, Pass1Bits + 3) & RangeMask); + // Final output stage: scale down by a factor of 8, offset, and range-limit + blockData[row] = Limit[LimitOffset + (RightShift(tmp0 + tmp7, Pass1Bits + 3) & RangeMask)]; + blockData[row + 7] = Limit[LimitOffset + (RightShift(tmp0 - tmp7, Pass1Bits + 3) & RangeMask)]; + blockData[row + 1] = Limit[LimitOffset + (RightShift(tmp1 + tmp6, Pass1Bits + 3) & RangeMask)]; + blockData[row + 6] = Limit[LimitOffset + (RightShift(tmp1 - tmp6, Pass1Bits + 3) & RangeMask)]; + blockData[row + 2] = Limit[LimitOffset + (RightShift(tmp2 + tmp5, Pass1Bits + 3) & RangeMask)]; + blockData[row + 5] = Limit[LimitOffset + (RightShift(tmp2 - tmp5, Pass1Bits + 3) & RangeMask)]; + blockData[row + 3] = Limit[LimitOffset + (RightShift(tmp3 + tmp4, Pass1Bits + 3) & RangeMask)]; + blockData[row + 4] = Limit[LimitOffset + (RightShift(tmp3 - tmp4, Pass1Bits + 3) & RangeMask)]; } } } + /// + /// Multiply a variable by an int constant, and immediately descale. + /// + /// The value + /// The multiplier + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Multiply(int val, int c) { return Descale(val * c, ConstBits); } + /// + /// Right-shifts the value by the given amount + /// + /// The value + /// The amount to shift by + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int RightShift(int x, int shft) - { - return x >> shft; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Descale(int x, int n) + private static int RightShift(int value, int shift) { - return RightShift(x + (1 << (n - 1)), n); + return value >> shift; } /// - /// Offsets the value by 128 and limits it to the 0..255 range + /// Descale and correctly round an int value that's scaled by bits. + /// We assume rounds towards minus infinity, so adding + /// the fudge factor is correct for either sign of . /// /// The value - /// The + /// The number of bits + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte DoLimit(int value) + private static int Descale(int value, int n) { - return Limit[value + LimitOffset]; + return RightShift(value + (1 << (n - 1)), n); } } } \ No newline at end of file From 1514a449c46df4264cffc763428b0daaaaa729cd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 9 Jul 2017 11:35:04 +1000 Subject: [PATCH 36/43] Simplify indexing TODO: Allocations! --- .../Formats/Jpeg/Port/Components/IDCT.cs | 80 ++++++++----------- 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs index cae1e8434..65c2bbde6 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs @@ -331,22 +331,19 @@ int p0, p1, p2, p3, p4, p5, p6, p7; - int coefBlockIndex = 0; - int workspaceIndex = 0; - int quantTableIndex = 0; - for (int col = 8; col > 0; col--) + for (int col = 0; col < 8; col++) { // Gather block data - p0 = blockData[coefBlockIndex]; - p1 = blockData[coefBlockIndex + 8]; - p2 = blockData[coefBlockIndex + 16]; - p3 = blockData[coefBlockIndex + 24]; - p4 = blockData[coefBlockIndex + 32]; - p5 = blockData[coefBlockIndex + 40]; - p6 = blockData[coefBlockIndex + 48]; - p7 = blockData[coefBlockIndex + 56]; + p0 = blockData[col]; + p1 = blockData[col + 8]; + p2 = blockData[col + 16]; + p3 = blockData[col + 24]; + p4 = blockData[col + 32]; + p5 = blockData[col + 40]; + p6 = blockData[col + 48]; + p7 = blockData[col + 56]; - int tmp0 = p0 * multiplierSpan[quantTableIndex]; + int tmp0 = p0 * multiplierSpan[col]; // Due to quantization, we will usually find that many of the input // coefficients are zero, especially the AC terms. We can exploit this @@ -359,25 +356,22 @@ { short dcval = (short)tmp0; - computationBufferSpan[workspaceIndex] = dcval; - computationBufferSpan[workspaceIndex + 8] = dcval; - computationBufferSpan[workspaceIndex + 16] = dcval; - computationBufferSpan[workspaceIndex + 24] = dcval; - computationBufferSpan[workspaceIndex + 32] = dcval; - computationBufferSpan[workspaceIndex + 40] = dcval; - computationBufferSpan[workspaceIndex + 48] = dcval; - computationBufferSpan[workspaceIndex + 56] = dcval; - - coefBlockIndex++; - quantTableIndex++; - workspaceIndex++; + computationBufferSpan[col] = dcval; + computationBufferSpan[col + 8] = dcval; + computationBufferSpan[col + 16] = dcval; + computationBufferSpan[col + 24] = dcval; + computationBufferSpan[col + 32] = dcval; + computationBufferSpan[col + 40] = dcval; + computationBufferSpan[col + 48] = dcval; + computationBufferSpan[col + 56] = dcval; + continue; } // Even part - int tmp1 = p2 * multiplierSpan[quantTableIndex + 16]; - int tmp2 = p4 * multiplierSpan[quantTableIndex + 32]; - int tmp3 = p6 * multiplierSpan[quantTableIndex + 48]; + int tmp1 = p2 * multiplierSpan[col + 16]; + int tmp2 = p4 * multiplierSpan[col + 32]; + int tmp3 = p6 * multiplierSpan[col + 48]; int tmp10 = tmp0 + tmp2; // Phase 3 int tmp11 = tmp0 - tmp2; @@ -391,10 +385,10 @@ tmp2 = tmp11 - tmp12; // Odd Part - int tmp4 = p1 * multiplierSpan[quantTableIndex + 8]; - int tmp5 = p3 * multiplierSpan[quantTableIndex + 24]; - int tmp6 = p5 * multiplierSpan[quantTableIndex + 40]; - int tmp7 = p7 * multiplierSpan[quantTableIndex + 56]; + int tmp4 = p1 * multiplierSpan[col + 8]; + int tmp5 = p3 * multiplierSpan[col + 24]; + int tmp6 = p5 * multiplierSpan[col + 40]; + int tmp7 = p7 * multiplierSpan[col + 56]; int z13 = tmp6 + tmp5; // Phase 6 int z10 = tmp6 - tmp5; @@ -412,18 +406,14 @@ tmp5 = tmp11 - tmp6; tmp4 = tmp10 - tmp5; - computationBufferSpan[workspaceIndex] = (short)(tmp0 + tmp7); - computationBufferSpan[workspaceIndex + 56] = (short)(tmp0 - tmp7); - computationBufferSpan[workspaceIndex + 8] = (short)(tmp1 + tmp6); - computationBufferSpan[workspaceIndex + 48] = (short)(tmp1 - tmp6); - computationBufferSpan[workspaceIndex + 16] = (short)(tmp2 + tmp5); - computationBufferSpan[workspaceIndex + 40] = (short)(tmp2 - tmp5); - computationBufferSpan[workspaceIndex + 24] = (short)(tmp3 + tmp4); - computationBufferSpan[workspaceIndex + 32] = (short)(tmp3 - tmp4); - - coefBlockIndex++; - quantTableIndex++; - workspaceIndex++; + computationBufferSpan[col] = (short)(tmp0 + tmp7); + computationBufferSpan[col + 56] = (short)(tmp0 - tmp7); + computationBufferSpan[col + 8] = (short)(tmp1 + tmp6); + computationBufferSpan[col + 48] = (short)(tmp1 - tmp6); + computationBufferSpan[col + 16] = (short)(tmp2 + tmp5); + computationBufferSpan[col + 40] = (short)(tmp2 - tmp5); + computationBufferSpan[col + 24] = (short)(tmp3 + tmp4); + computationBufferSpan[col + 32] = (short)(tmp3 - tmp4); } // Pass 2: process rows from work array, store into output array. @@ -464,7 +454,7 @@ int tmp11 = z5 - p4; int tmp13 = p2 + p6; - int tmp12 = Multiply(p2 - p6, FIX_1_414213562) - tmp13; /* 2*c4 */ + int tmp12 = Multiply(p2 - p6, FIX_1_414213562) - tmp13; // 2*c4 int tmp0 = tmp10 + tmp13; int tmp3 = tmp10 - tmp13; From 656c45436490c62970f547906b1dfab22c777785 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 9 Jul 2017 17:33:26 +1000 Subject: [PATCH 37/43] Reduce allocations --- .../Formats/Jpeg/Port/Components/IDCT.cs | 477 +++++++++--------- .../Formats/Jpeg/Port/JpegDecoderCore.cs | 20 +- 2 files changed, 245 insertions(+), 252 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs index 65c2bbde6..064b3bea3 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs @@ -6,24 +6,38 @@ using ImageSharp.Memory; /// - /// Performa the invers + /// Performs the inverse Descrete Cosine Transform on each frame component. /// internal static class IDCT { - private const int DctCos1 = 4017; // cos(pi/16) - private const int DctSin1 = 799; // sin(pi/16) - private const int DctCos3 = 3406; // cos(3*pi/16) - private const int DctSin3 = 2276; // sin(3*pi/16) - private const int DctCos6 = 1567; // cos(6*pi/16) - private const int DctSin6 = 3784; // sin(6*pi/16) - private const int DctSqrt2 = 5793; // sqrt(2) + /// + /// Precomputed values scaled up by 14 bits + /// + public static readonly short[] Aanscales = + { + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, 22725, 31521, 29692, 26722, 22725, 17855, + 12299, 6270, 21407, 29692, 27969, 25172, 21407, 16819, 11585, + 5906, 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, 12873, + 17855, 16819, 15137, 12873, 10114, 6967, 3552, 8867, 12299, + 11585, 10426, 8867, 6967, 4799, 2446, 4520, 6270, 5906, 5315, + 4520, 3552, 2446, 1247 + }; + + private const int DctCos1 = 4017; // cos(pi/16) + private const int DctSin1 = 799; // sin(pi/16) + private const int DctCos3 = 3406; // cos(3*pi/16) + private const int DctSin3 = 2276; // sin(3*pi/16) + private const int DctCos6 = 1567; // cos(6*pi/16) + private const int DctSin6 = 3784; // sin(6*pi/16) + private const int DctSqrt2 = 5793; // sqrt(2) private const int DctSqrt1D2 = 2896; // sqrt(2) / 2 #pragma warning disable SA1310 // Field names must not contain underscore - private const int FIX_1_082392200 = 277; /* FIX(1.082392200) */ - private const int FIX_1_414213562 = 362; /* FIX(1.414213562) */ - private const int FIX_1_847759065 = 473; /* FIX(1.847759065) */ - private const int FIX_2_613125930 = 669; /* FIX(2.613125930) */ + private const int FIX_1_082392200 = 277; // FIX(1.082392200) + private const int FIX_1_414213562 = 362; // FIX(1.414213562) + private const int FIX_1_847759065 = 473; // FIX(1.847759065) + private const int FIX_2_613125930 = 669; // FIX(2.613125930) #pragma warning restore SA1310 // Field names must not contain underscore private const int ConstBits = 8; @@ -42,21 +56,9 @@ // be quite far out of range if the input data is corrupt, so a bulletproof // range-limiting step is required. We use a mask-and-table-lookup method // to do the combined operations quickly, assuming that MaxJSample+1 - // is a power of 2. See the comments with prepare_range_limit_table for more info. + // is a power of 2. private const int RangeMask = (MaxJSample * 4) + 3; // 2 bits wider than legal samples - // Precomputed values scaled up by 14 bits - private static readonly short[] Aanscales = - { - 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, 22725, 31521, 29692, 26722, 22725, 17855, - 12299, 6270, 21407, 29692, 27969, 25172, 21407, 16819, 11585, - 5906, 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, - 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, 12873, - 17855, 16819, 15137, 12873, 10114, 6967, 3552, 8867, 12299, - 11585, 10426, 8867, 6967, 4799, 2446, 4520, 6270, 5906, 5315, - 4520, 3552, 2446, 1247 - }; - private static readonly byte[] Limit = new byte[5 * (MaxJSample + 1)]; static IDCT() @@ -81,15 +83,13 @@ /// 'Practical Fast 1-D DCT Algorithms with 11 Multiplications', /// IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, 988-991. /// - /// The quantization tables /// The fram component /// The block buffer offset /// The computational buffer for holding temp values - public static void QuantizeAndInverse(QuantizationTables quantizationTables, ref FrameComponent component, int blockBufferOffset, Buffer computationBuffer) + /// The quantization table + public static void QuantizeAndInverse(ref FrameComponent component, int blockBufferOffset, ref Span computationBuffer, ref Span quantizationTable) { - Span qt = quantizationTables.Tables.GetRowSpan(component.QuantizationIdentifier); Span blockData = component.BlockData.Slice(blockBufferOffset); - Span computationBufferSpan = computationBuffer; int v0, v1, v2, v3, v4, v5, v6, v7; int p0, p1, p2, p3, p4, p5, p6, p7; int t; @@ -108,32 +108,32 @@ p7 = blockData[row + 7]; // dequant p0 - p0 *= qt[row]; + p0 *= quantizationTable[row]; // check for all-zero AC coefficients if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) { t = ((DctSqrt2 * p0) + 512) >> 10; short st = (short)t; - computationBufferSpan[row] = st; - computationBufferSpan[row + 1] = st; - computationBufferSpan[row + 2] = st; - computationBufferSpan[row + 3] = st; - computationBufferSpan[row + 4] = st; - computationBufferSpan[row + 5] = st; - computationBufferSpan[row + 6] = st; - computationBufferSpan[row + 7] = st; + computationBuffer[row] = st; + computationBuffer[row + 1] = st; + computationBuffer[row + 2] = st; + computationBuffer[row + 3] = st; + computationBuffer[row + 4] = st; + computationBuffer[row + 5] = st; + computationBuffer[row + 6] = st; + computationBuffer[row + 7] = st; continue; } // dequant p1 ... p7 - p1 *= qt[row + 1]; - p2 *= qt[row + 2]; - p3 *= qt[row + 3]; - p4 *= qt[row + 4]; - p5 *= qt[row + 5]; - p6 *= qt[row + 6]; - p7 *= qt[row + 7]; + p1 *= quantizationTable[row + 1]; + p2 *= quantizationTable[row + 2]; + p3 *= quantizationTable[row + 3]; + p4 *= quantizationTable[row + 4]; + p5 *= quantizationTable[row + 5]; + p6 *= quantizationTable[row + 6]; + p7 *= quantizationTable[row + 7]; // stage 4 v0 = ((DctSqrt2 * p0) + 128) >> 8; @@ -169,27 +169,27 @@ v6 = t; // stage 1 - computationBufferSpan[row] = (short)(v0 + v7); - computationBufferSpan[row + 7] = (short)(v0 - v7); - computationBufferSpan[row + 1] = (short)(v1 + v6); - computationBufferSpan[row + 6] = (short)(v1 - v6); - computationBufferSpan[row + 2] = (short)(v2 + v5); - computationBufferSpan[row + 5] = (short)(v2 - v5); - computationBufferSpan[row + 3] = (short)(v3 + v4); - computationBufferSpan[row + 4] = (short)(v3 - v4); + computationBuffer[row] = (short)(v0 + v7); + computationBuffer[row + 7] = (short)(v0 - v7); + computationBuffer[row + 1] = (short)(v1 + v6); + computationBuffer[row + 6] = (short)(v1 - v6); + computationBuffer[row + 2] = (short)(v2 + v5); + computationBuffer[row + 5] = (short)(v2 - v5); + computationBuffer[row + 3] = (short)(v3 + v4); + computationBuffer[row + 4] = (short)(v3 - v4); } // inverse DCT on columns for (int col = 0; col < 8; ++col) { - p0 = computationBufferSpan[col]; - p1 = computationBufferSpan[col + 8]; - p2 = computationBufferSpan[col + 16]; - p3 = computationBufferSpan[col + 24]; - p4 = computationBufferSpan[col + 32]; - p5 = computationBufferSpan[col + 40]; - p6 = computationBufferSpan[col + 48]; - p7 = computationBufferSpan[col + 56]; + p0 = computationBuffer[col]; + p1 = computationBuffer[col + 8]; + p2 = computationBuffer[col + 16]; + p3 = computationBuffer[col + 24]; + p4 = computationBuffer[col + 32]; + p5 = computationBuffer[col + 40]; + p6 = computationBuffer[col + 48]; + p7 = computationBuffer[col + 56]; // check for all-zero AC coefficients if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) @@ -302,195 +302,188 @@ /// precise the scaled value, so this implementation does worse with high - /// quality - setting files than with low - quality ones. /// - /// The quantization tables - /// The fram component + /// The frame component /// The block buffer offset /// The computational buffer for holding temp values - public static void QuantizeAndInverseAlt( - QuantizationTables quantizationTables, - ref FrameComponent component, - int blockBufferOffset, - Buffer computationBuffer) + /// The multiplier table + public static void QuantizeAndInverseFast(ref FrameComponent component, int blockBufferOffset, ref Span computationBuffer, ref Span multiplierTable) { - Span qt = quantizationTables.Tables.GetRowSpan(component.QuantizationIdentifier); Span blockData = component.BlockData.Slice(blockBufferOffset); - Span computationBufferSpan = computationBuffer; - - // For AA&N IDCT method, multiplier are equal to quantization - // coefficients scaled by scalefactor[row]*scalefactor[col], where - // scalefactor[0] = 1 - // scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 - // For integer operation, the multiplier table is to be scaled by 14. - using (var multiplier = new Buffer(64)) + int p0, p1, p2, p3, p4, p5, p6, p7; + + for (int col = 0; col < 8; col++) { - Span multiplierSpan = multiplier; - for (int i = 0; i < 64; i++) + // Gather block data + p0 = blockData[col]; + p1 = blockData[col + 8]; + p2 = blockData[col + 16]; + p3 = blockData[col + 24]; + p4 = blockData[col + 32]; + p5 = blockData[col + 40]; + p6 = blockData[col + 48]; + p7 = blockData[col + 56]; + + int tmp0 = p0 * multiplierTable[col]; + + // Due to quantization, we will usually find that many of the input + // coefficients are zero, especially the AC terms. We can exploit this + // by short-circuiting the IDCT calculation for any column in which all + // the AC terms are zero. In that case each output is equal to the + // DC coefficient (with scale factor as needed). + // With typical images and quantization tables, half or more of the + // column DCT calculations can be simplified this way. + if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) { - multiplierSpan[i] = (short)Descale(qt[i] * Aanscales[i], 14 - Pass1Bits); - } + short dcval = (short)tmp0; - int p0, p1, p2, p3, p4, p5, p6, p7; + computationBuffer[col] = dcval; + computationBuffer[col + 8] = dcval; + computationBuffer[col + 16] = dcval; + computationBuffer[col + 24] = dcval; + computationBuffer[col + 32] = dcval; + computationBuffer[col + 40] = dcval; + computationBuffer[col + 48] = dcval; + computationBuffer[col + 56] = dcval; - for (int col = 0; col < 8; col++) - { - // Gather block data - p0 = blockData[col]; - p1 = blockData[col + 8]; - p2 = blockData[col + 16]; - p3 = blockData[col + 24]; - p4 = blockData[col + 32]; - p5 = blockData[col + 40]; - p6 = blockData[col + 48]; - p7 = blockData[col + 56]; - - int tmp0 = p0 * multiplierSpan[col]; - - // Due to quantization, we will usually find that many of the input - // coefficients are zero, especially the AC terms. We can exploit this - // by short-circuiting the IDCT calculation for any column in which all - // the AC terms are zero. In that case each output is equal to the - // DC coefficient (with scale factor as needed). - // With typical images and quantization tables, half or more of the - // column DCT calculations can be simplified this way. - if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) - { - short dcval = (short)tmp0; - - computationBufferSpan[col] = dcval; - computationBufferSpan[col + 8] = dcval; - computationBufferSpan[col + 16] = dcval; - computationBufferSpan[col + 24] = dcval; - computationBufferSpan[col + 32] = dcval; - computationBufferSpan[col + 40] = dcval; - computationBufferSpan[col + 48] = dcval; - computationBufferSpan[col + 56] = dcval; - - continue; - } - - // Even part - int tmp1 = p2 * multiplierSpan[col + 16]; - int tmp2 = p4 * multiplierSpan[col + 32]; - int tmp3 = p6 * multiplierSpan[col + 48]; - - int tmp10 = tmp0 + tmp2; // Phase 3 - int tmp11 = tmp0 - tmp2; - - int tmp13 = tmp1 + tmp3; // Phases 5-3 - int tmp12 = Multiply(tmp1 - tmp3, FIX_1_414213562) - tmp13; // 2*c4 - - tmp0 = tmp10 + tmp13; // Phase 2 - tmp3 = tmp10 - tmp13; - tmp1 = tmp11 + tmp12; - tmp2 = tmp11 - tmp12; - - // Odd Part - int tmp4 = p1 * multiplierSpan[col + 8]; - int tmp5 = p3 * multiplierSpan[col + 24]; - int tmp6 = p5 * multiplierSpan[col + 40]; - int tmp7 = p7 * multiplierSpan[col + 56]; - - int z13 = tmp6 + tmp5; // Phase 6 - int z10 = tmp6 - tmp5; - int z11 = tmp4 + tmp7; - int z12 = tmp4 - tmp7; - - tmp7 = z11 + z13; // Phase 5 - tmp11 = Multiply(z11 - z13, FIX_1_414213562); // 2*c4 - - int z5 = Multiply(z10 + z12, FIX_1_847759065); // 2*c2 - tmp10 = z5 - Multiply(z12, FIX_1_082392200); // 2*(c2-c6) - tmp12 = z5 - Multiply(z10, FIX_2_613125930); // 2*(c2+c6) - - tmp6 = tmp12 - tmp7; // Phase 2 - tmp5 = tmp11 - tmp6; - tmp4 = tmp10 - tmp5; - - computationBufferSpan[col] = (short)(tmp0 + tmp7); - computationBufferSpan[col + 56] = (short)(tmp0 - tmp7); - computationBufferSpan[col + 8] = (short)(tmp1 + tmp6); - computationBufferSpan[col + 48] = (short)(tmp1 - tmp6); - computationBufferSpan[col + 16] = (short)(tmp2 + tmp5); - computationBufferSpan[col + 40] = (short)(tmp2 - tmp5); - computationBufferSpan[col + 24] = (short)(tmp3 + tmp4); - computationBufferSpan[col + 32] = (short)(tmp3 - tmp4); + continue; } - // Pass 2: process rows from work array, store into output array. - // Note that we must descale the results by a factor of 8 == 2**3, - // and also undo the pass 1 bits scaling. - for (int row = 0; row < 64; row += 8) + // Even part + int tmp1 = p2 * multiplierTable[col + 16]; + int tmp2 = p4 * multiplierTable[col + 32]; + int tmp3 = p6 * multiplierTable[col + 48]; + + int tmp10 = tmp0 + tmp2; // Phase 3 + int tmp11 = tmp0 - tmp2; + + int tmp13 = tmp1 + tmp3; // Phases 5-3 + int tmp12 = Multiply(tmp1 - tmp3, FIX_1_414213562) - tmp13; // 2*c4 + + tmp0 = tmp10 + tmp13; // Phase 2 + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + // Odd Part + int tmp4 = p1 * multiplierTable[col + 8]; + int tmp5 = p3 * multiplierTable[col + 24]; + int tmp6 = p5 * multiplierTable[col + 40]; + int tmp7 = p7 * multiplierTable[col + 56]; + + int z13 = tmp6 + tmp5; // Phase 6 + int z10 = tmp6 - tmp5; + int z11 = tmp4 + tmp7; + int z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; // Phase 5 + tmp11 = Multiply(z11 - z13, FIX_1_414213562); // 2*c4 + + int z5 = Multiply(z10 + z12, FIX_1_847759065); // 2*c2 + tmp10 = z5 - Multiply(z12, FIX_1_082392200); // 2*(c2-c6) + tmp12 = z5 - Multiply(z10, FIX_2_613125930); // 2*(c2+c6) + + tmp6 = tmp12 - tmp7; // Phase 2 + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 - tmp5; + + computationBuffer[col] = (short)(tmp0 + tmp7); + computationBuffer[col + 56] = (short)(tmp0 - tmp7); + computationBuffer[col + 8] = (short)(tmp1 + tmp6); + computationBuffer[col + 48] = (short)(tmp1 - tmp6); + computationBuffer[col + 16] = (short)(tmp2 + tmp5); + computationBuffer[col + 40] = (short)(tmp2 - tmp5); + computationBuffer[col + 24] = (short)(tmp3 + tmp4); + computationBuffer[col + 32] = (short)(tmp3 - tmp4); + } + + // Pass 2: process rows from work array, store into output array. + // Note that we must descale the results by a factor of 8 == 2**3, + // and also undo the pass 1 bits scaling. + for (int row = 0; row < 64; row += 8) + { + p1 = computationBuffer[row + 1]; + p2 = computationBuffer[row + 2]; + p3 = computationBuffer[row + 3]; + p4 = computationBuffer[row + 4]; + p5 = computationBuffer[row + 5]; + p6 = computationBuffer[row + 6]; + p7 = computationBuffer[row + 7]; + + // Add range center and fudge factor for final descale and range-limit. + int z5 = computationBuffer[row] + (RangeCenter << (Pass1Bits + 3)) + (1 << (Pass1Bits + 2)); + + // Check for all-zero AC coefficients + if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) { - p1 = computationBufferSpan[row + 1]; - p2 = computationBufferSpan[row + 2]; - p3 = computationBufferSpan[row + 3]; - p4 = computationBufferSpan[row + 4]; - p5 = computationBufferSpan[row + 5]; - p6 = computationBufferSpan[row + 6]; - p7 = computationBufferSpan[row + 7]; - - // Add range center and fudge factor for final descale and range-limit. - int z5 = computationBufferSpan[row] + (RangeCenter << (Pass1Bits + 3)) + (1 << (Pass1Bits + 2)); - - // Check for all-zero AC coefficients - if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) - { - byte dcval = Limit[LimitOffset + (RightShift(z5, Pass1Bits + 3) & RangeMask)]; - - blockData[row] = dcval; - blockData[row + 1] = dcval; - blockData[row + 2] = dcval; - blockData[row + 3] = dcval; - blockData[row + 4] = dcval; - blockData[row + 5] = dcval; - blockData[row + 6] = dcval; - blockData[row + 7] = dcval; - - continue; - } - - // Even part - int tmp10 = z5 + p4; - int tmp11 = z5 - p4; - - int tmp13 = p2 + p6; - int tmp12 = Multiply(p2 - p6, FIX_1_414213562) - tmp13; // 2*c4 - - int tmp0 = tmp10 + tmp13; - int tmp3 = tmp10 - tmp13; - int tmp1 = tmp11 + tmp12; - int tmp2 = tmp11 - tmp12; - - // Odd part - int z13 = p5 + p3; - int z10 = p5 - p3; - int z11 = p1 + p7; - int z12 = p1 - p7; - - int tmp7 = z11 + z13; // Phase 5 - tmp11 = Multiply(z11 - z13, FIX_1_414213562); // 2*c4 - - z5 = Multiply(z10 + z12, FIX_1_847759065); // 2*c2 - tmp10 = z5 - Multiply(z12, FIX_1_082392200); // 2*(c2-c6) - tmp12 = z5 - Multiply(z10, FIX_2_613125930); // 2*(c2+c6) - - int tmp6 = tmp12 - tmp7; // Phase 2 - int tmp5 = tmp11 - tmp6; - int tmp4 = tmp10 - tmp5; - - // Final output stage: scale down by a factor of 8, offset, and range-limit - blockData[row] = Limit[LimitOffset + (RightShift(tmp0 + tmp7, Pass1Bits + 3) & RangeMask)]; - blockData[row + 7] = Limit[LimitOffset + (RightShift(tmp0 - tmp7, Pass1Bits + 3) & RangeMask)]; - blockData[row + 1] = Limit[LimitOffset + (RightShift(tmp1 + tmp6, Pass1Bits + 3) & RangeMask)]; - blockData[row + 6] = Limit[LimitOffset + (RightShift(tmp1 - tmp6, Pass1Bits + 3) & RangeMask)]; - blockData[row + 2] = Limit[LimitOffset + (RightShift(tmp2 + tmp5, Pass1Bits + 3) & RangeMask)]; - blockData[row + 5] = Limit[LimitOffset + (RightShift(tmp2 - tmp5, Pass1Bits + 3) & RangeMask)]; - blockData[row + 3] = Limit[LimitOffset + (RightShift(tmp3 + tmp4, Pass1Bits + 3) & RangeMask)]; - blockData[row + 4] = Limit[LimitOffset + (RightShift(tmp3 - tmp4, Pass1Bits + 3) & RangeMask)]; + byte dcval = Limit[LimitOffset + (RightShift(z5, Pass1Bits + 3) & RangeMask)]; + + blockData[row] = dcval; + blockData[row + 1] = dcval; + blockData[row + 2] = dcval; + blockData[row + 3] = dcval; + blockData[row + 4] = dcval; + blockData[row + 5] = dcval; + blockData[row + 6] = dcval; + blockData[row + 7] = dcval; + + continue; } + + // Even part + int tmp10 = z5 + p4; + int tmp11 = z5 - p4; + + int tmp13 = p2 + p6; + int tmp12 = Multiply(p2 - p6, FIX_1_414213562) - tmp13; // 2*c4 + + int tmp0 = tmp10 + tmp13; + int tmp3 = tmp10 - tmp13; + int tmp1 = tmp11 + tmp12; + int tmp2 = tmp11 - tmp12; + + // Odd part + int z13 = p5 + p3; + int z10 = p5 - p3; + int z11 = p1 + p7; + int z12 = p1 - p7; + + int tmp7 = z11 + z13; // Phase 5 + tmp11 = Multiply(z11 - z13, FIX_1_414213562); // 2*c4 + + z5 = Multiply(z10 + z12, FIX_1_847759065); // 2*c2 + tmp10 = z5 - Multiply(z12, FIX_1_082392200); // 2*(c2-c6) + tmp12 = z5 - Multiply(z10, FIX_2_613125930); // 2*(c2+c6) + + int tmp6 = tmp12 - tmp7; // Phase 2 + int tmp5 = tmp11 - tmp6; + int tmp4 = tmp10 - tmp5; + + // Final output stage: scale down by a factor of 8, offset, and range-limit + blockData[row] = Limit[LimitOffset + (RightShift(tmp0 + tmp7, Pass1Bits + 3) & RangeMask)]; + blockData[row + 7] = Limit[LimitOffset + (RightShift(tmp0 - tmp7, Pass1Bits + 3) & RangeMask)]; + blockData[row + 1] = Limit[LimitOffset + (RightShift(tmp1 + tmp6, Pass1Bits + 3) & RangeMask)]; + blockData[row + 6] = Limit[LimitOffset + (RightShift(tmp1 - tmp6, Pass1Bits + 3) & RangeMask)]; + blockData[row + 2] = Limit[LimitOffset + (RightShift(tmp2 + tmp5, Pass1Bits + 3) & RangeMask)]; + blockData[row + 5] = Limit[LimitOffset + (RightShift(tmp2 - tmp5, Pass1Bits + 3) & RangeMask)]; + blockData[row + 3] = Limit[LimitOffset + (RightShift(tmp3 + tmp4, Pass1Bits + 3) & RangeMask)]; + blockData[row + 4] = Limit[LimitOffset + (RightShift(tmp3 - tmp4, Pass1Bits + 3) & RangeMask)]; } } + /// + /// Descale and correctly round an int value that's scaled by bits. + /// We assume rounds towards minus infinity, so adding + /// the fudge factor is correct for either sign of . + /// + /// The value + /// The number of bits + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Descale(int value, int n) + { + return RightShift(value + (1 << (n - 1)), n); + } + /// /// Multiply a variable by an int constant, and immediately descale. /// @@ -514,19 +507,5 @@ { return value >> shift; } - - /// - /// Descale and correctly round an int value that's scaled by bits. - /// We assume rounds towards minus infinity, so adding - /// the fudge factor is correct for either sign of . - /// - /// The value - /// The number of bits - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Descale(int value, int n) - { - return RightShift(value + (1 << (n - 1)), n); - } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index ef49dfaf0..074ee3cfd 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -792,13 +792,28 @@ namespace ImageSharp.Formats.Jpeg.Port int blocksPerLine = component.BlocksPerLine; int blocksPerColumn = component.BlocksPerColumn; using (var computationBuffer = Buffer.CreateClean(64)) - { + using (var multiplicationBuffer = Buffer.CreateClean(64)) + { + Span quantizationTable = this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationIdentifier); + Span computationBufferSpan = computationBuffer; + + // For AA&N IDCT method, multiplier are equal to quantization + // coefficients scaled by scalefactor[row]*scalefactor[col], where + // scalefactor[0] = 1 + // scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + // For integer operation, the multiplier table is to be scaled by 12. + Span multiplierSpan = multiplicationBuffer; + for (int i = 0; i < 64; i++) + { + multiplierSpan[i] = (short)IDCT.Descale(quantizationTable[i] * IDCT.Aanscales[i], 12); + } + for (int blockRow = 0; blockRow < blocksPerColumn; blockRow++) { for (int blockCol = 0; blockCol < blocksPerLine; blockCol++) { int offset = GetBlockBufferOffset(ref component, blockRow, blockCol); - IDCT.QuantizeAndInverseAlt(this.quantizationTables, ref frameComponent, offset, computationBuffer); + IDCT.QuantizeAndInverseFast(ref frameComponent, offset, ref computationBufferSpan, ref multiplierSpan); } } } @@ -808,7 +823,6 @@ namespace ImageSharp.Formats.Jpeg.Port /// /// Builds the huffman tables - /// TODO: This is our bottleneck. We should use a faster algorithm with a LUT. /// /// The tables /// The table index From d6f20528ccd6b5460763016fb6941ccb56b449d6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 9 Jul 2017 19:48:01 +1000 Subject: [PATCH 38/43] Move check into if statement --- .../Jpeg/Port/Components/ScanDecoder.cs | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 47ec50d71..066dbf3ac 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -622,29 +622,31 @@ namespace ImageSharp.Formats.Jpeg.Port.Components // TODO: Adding this code introduces error into the decoder. // It doesn't appear to speed anything up either. - // if (this.bitsUnRead < 8) - // { - // if (this.bitsCount <= 0) - // { - // code = (short)this.ReadBit(stream); - // this.bitsUnRead += 8; - // } - // if (this.endOfStreamReached || this.unexpectedMarkerReached) - // { - // return -1; - // } - // this.accumulator = (this.accumulator << 8) | this.bitsData; - // int lutIndex = (this.accumulator >> (this.bitsUnRead - 8)) & 0xFF; - // int v = tree.Lookahead[lutIndex]; - // if (v != 0) - // { - // int nb = (v & 0xFF) - 1; - // this.bitsCount -= nb - 1; - // this.bitsUnRead -= nb; - // v = v >> 8; - // return (short)v; - // } - // } + // if (this.bitsUnRead < 8) + // { + // if (this.bitsCount <= 0) + // { + // code = (short)this.ReadBit(stream); + // if (this.endOfStreamReached || this.unexpectedMarkerReached) + // { + // return -1; + // } + // + // this.bitsUnRead += 8; + // } + // + // this.accumulator = (this.accumulator << 8) | this.bitsData; + // int lutIndex = (this.accumulator >> (8 - this.bitsUnRead)) & 0xFF; + // int v = tree.Lookahead[lutIndex]; + // if (v != 0) + // { + // int nb = (v & 0xFF) - 1; + // this.bitsCount -= nb - 1; + // this.bitsUnRead -= nb; + // v = v >> 8; + // return (short)v; + // } + // } if (code == -1) { code = (short)this.ReadBit(stream); From 2f48d33d2f68e6d3672c3a0670810acfa8d75082 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 10 Jul 2017 22:14:30 +1000 Subject: [PATCH 39/43] Fix tests --- .../Formats/Jpeg/Port/Components/ScanDecoder.cs | 2 ++ src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs | 9 ++------- tests/ImageSharp.Tests/FileTestBase.cs | 2 ++ tests/ImageSharp.Tests/TestFile.cs | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 066dbf3ac..8b711eaa8 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -621,6 +621,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components short code = -1; // TODO: Adding this code introduces error into the decoder. + // NOTES # During investigation of the libjpeg implementation it appears that they pull 32bits at a time and operate on those bits + // using 3 methods: FillBits, PeekBits, and ReadBits. We should attempt to do the same. // It doesn't appear to speed anything up either. // if (this.bitsUnRead < 8) // { diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 074ee3cfd..8bc43f0f7 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -542,11 +542,6 @@ namespace ImageSharp.Formats.Jpeg.Port remaining--; int quantizationTableSpec = this.InputStream.ReadByte(); - if (quantizationTableSpec > 3) - { - throw new ImageFormatException($"Bad Tq index value: {quantizationTableSpec}"); - } - switch (quantizationTableSpec >> 4) { case 0: @@ -561,7 +556,7 @@ namespace ImageSharp.Formats.Jpeg.Port this.InputStream.Read(this.temp, 0, 64); remaining -= 64; - Span tableSpan = this.quantizationTables.Tables.GetRowSpan(quantizationTableSpec); + Span tableSpan = this.quantizationTables.Tables.GetRowSpan(quantizationTableSpec & 15); for (int j = 0; j < 64; j++) { tableSpan[QuantizationTables.DctZigZag[j]] = this.temp[j]; @@ -581,7 +576,7 @@ namespace ImageSharp.Formats.Jpeg.Port this.InputStream.Read(this.temp, 0, 128); remaining -= 128; - Span tableSpan = this.quantizationTables.Tables.GetRowSpan(quantizationTableSpec); + Span tableSpan = this.quantizationTables.Tables.GetRowSpan(quantizationTableSpec & 15); for (int j = 0; j < 64; j++) { tableSpan[QuantizationTables.DctZigZag[j]] = (short)((this.temp[2 * j] << 8) | this.temp[(2 * j) + 1]); diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index 51a1562f5..1c3cb2d5b 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -70,7 +70,9 @@ namespace ImageSharp.Tests // TestFile.Create(TestImages.Jpeg.Baseline.Ycck), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Baseline.Cmyk), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Baseline.Floorplan), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Progressive.Festzug), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Baseline.Bad.MissingEOF), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Baseline.Bad.ExifUndefType), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Progressive.Fb), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Progressive.Progress), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Baseline.GammaDalaiLamaGray), // Perf: Enable for local testing only diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index b09aff78e..ffcd0b95b 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -116,7 +116,7 @@ namespace ImageSharp.Tests /// public string GetFileNameWithoutExtension(object value) { - return this.FileNameWithoutExtension + "-" + value; + return $"{this.FileNameWithoutExtension}-{value}"; } /// @@ -138,7 +138,7 @@ namespace ImageSharp.Tests /// public Image CreateImage(IImageDecoder decoder) { - return Image.Load(this.image.Configuration, this.Bytes, decoder); + return Image.Load(this.GetImage().Configuration, this.Bytes, decoder); } private Image GetImage() From 5a8474634ce2d75d4f835e62eecf5efcd85bb376 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 10 Jul 2017 22:53:07 +1000 Subject: [PATCH 40/43] Fix broken EXIF test --- src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs | 8 ++++---- src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index a7fd8fd6a..fc77765b1 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -5,6 +5,7 @@ namespace ImageSharp { + using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; @@ -82,10 +83,9 @@ namespace ImageSharp this.values.Add(new ExifValue(value)); } } - else - { - this.data = other.data; - } + + this.data = new byte[other.data.Length]; + Buffer.BlockCopy(other.data, 0, this.data, 0, other.data.Length); } /// diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs index 978d5bc24..d96bcb589 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs @@ -59,7 +59,8 @@ namespace ImageSharp Guard.NotNull(other, nameof(other)); // TODO: Do we need to copy anything else? - this.data = other.data; + this.data = new byte[other.data.Length]; + Buffer.BlockCopy(other.data, 0, this.data, 0, other.data.Length); } /// From da6cd47eb59a5852f0cff4e65096d2ab8a22fef9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 10 Jul 2017 23:17:36 +1000 Subject: [PATCH 41/43] Fix ImageMetaData test --- src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs | 9 ++++++--- src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs | 7 +++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index fc77765b1..be4201c72 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -84,8 +84,11 @@ namespace ImageSharp } } - this.data = new byte[other.data.Length]; - Buffer.BlockCopy(other.data, 0, this.data, 0, other.data.Length); + if (other.data != null) + { + this.data = new byte[other.data.Length]; + Buffer.BlockCopy(other.data, 0, this.data, 0, other.data.Length); + } } /// @@ -131,7 +134,7 @@ namespace ImageSharp return null; } - if (this.data.Length < (this.thumbnailOffset + this.thumbnailLength)) + if (this.data == null || this.data.Length < (this.thumbnailOffset + this.thumbnailLength)) { return null; } diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs index d96bcb589..1393afa62 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs @@ -59,8 +59,11 @@ namespace ImageSharp Guard.NotNull(other, nameof(other)); // TODO: Do we need to copy anything else? - this.data = new byte[other.data.Length]; - Buffer.BlockCopy(other.data, 0, this.data, 0, other.data.Length); + if (other.data != null) + { + this.data = new byte[other.data.Length]; + Buffer.BlockCopy(other.data, 0, this.data, 0, other.data.Length); + } } /// From 955a61752af911a6ad0b12dd4bea976ba7be26ec Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 11 Jul 2017 13:35:18 +0200 Subject: [PATCH 42/43] more benchmarks --- .../General/Vectorization/DivFloat.cs | 54 -------------- .../General/Vectorization/DivUInt32.cs | 54 -------------- .../General/Vectorization/Divide.cs | 69 ++++++++++++++++++ .../General/Vectorization/MulFloat.cs | 67 ------------------ .../General/Vectorization/MulUInt32.cs | 54 -------------- .../General/Vectorization/Multiply.cs | 50 +++++++++++++ .../Vectorization/SIMDBenchmarkBase.cs | 70 +++++++++++++++++++ .../Image/DecodeJpegMultiple.cs | 38 +++++++++- .../Image/MultiImageBenchmarkBase.cs | 2 +- 9 files changed, 226 insertions(+), 232 deletions(-) delete mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs delete mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs delete mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs delete mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs deleted file mode 100644 index caadaaa34..000000000 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; - - using BenchmarkDotNet.Attributes; - - public class DivFloat - { - private float[] input; - - private float[] result; - - [Params(32)] - public int InputSize { get; set; } - - private float testValue; - - [GlobalSetup] - public void Setup() - { - this.input = new float[this.InputSize]; - this.result = new float[this.InputSize]; - this.testValue = 42; - - for (int i = 0; i < this.InputSize; i++) - { - this.input[i] = (uint)i; - } - } - - [Benchmark(Baseline = true)] - public void Standard() - { - float v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = this.input[i] / v; - } - } - - [Benchmark] - public void Simd() - { - Vector v = new Vector(this.testValue); - - for (int i = 0; i < this.input.Length; i += Vector.Count) - { - Vector a = new Vector(this.input, i); - a = a / v; - a.CopyTo(this.result, i); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs deleted file mode 100644 index 6b3edb192..000000000 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; - - using BenchmarkDotNet.Attributes; - - public class DivUInt32 - { - private uint[] input; - - private uint[] result; - - [Params(32)] - public int InputSize { get; set; } - - private uint testValue; - - [GlobalSetup] - public void Setup() - { - this.input = new uint[this.InputSize]; - this.result = new uint[this.InputSize]; - this.testValue = 42; - - for (int i = 0; i < this.InputSize; i++) - { - this.input[i] = (uint)i; - } - } - - [Benchmark(Baseline = true)] - public void Standard() - { - uint v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = this.input[i] / v; - } - } - - [Benchmark] - public void Simd() - { - Vector v = new Vector(this.testValue); - - for (int i = 0; i < this.input.Length; i += Vector.Count) - { - Vector a = new Vector(this.input, i); - a = a / v; - a.CopyTo(this.result, i); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs new file mode 100644 index 000000000..b38429557 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs @@ -0,0 +1,69 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System; + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class DivFloat : SIMDBenchmarkBase.Divide + { + protected override float GetTestValue() => 42; + + [Benchmark(Baseline = true)] + public void Standard() + { + float v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] / v; + } + } + } + + public class Divide : SIMDBenchmarkBase.Divide + { + protected override uint GetTestValue() => 42; + + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] / v; + } + } + } + + public class DivInt32 : SIMDBenchmarkBase.Divide + { + protected override int GetTestValue() => 42; + + [Benchmark(Baseline = true)] + public void Standard() + { + int v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] / v; + } + } + } + + public class DivInt16 : SIMDBenchmarkBase.Divide + { + protected override short GetTestValue() => 42; + + protected override Vector GetTestVector() => new Vector(new short[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}); + + [Benchmark(Baseline = true)] + public void Standard() + { + short v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = (short)(this.input[i] / v); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs deleted file mode 100644 index e5ae49b2a..000000000 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; - - using BenchmarkDotNet.Attributes; - - public class MulFloat - { - private float[] input; - - private float[] result; - - [Params(32)] - public int InputSize { get; set; } - - private float testValue; - - [GlobalSetup] - public void Setup() - { - this.input = new float[this.InputSize]; - this.result = new float[this.InputSize]; - this.testValue = 42; - - for (int i = 0; i < this.InputSize; i++) - { - this.input[i] = i; - } - } - - [Benchmark(Baseline = true)] - public void Standard() - { - float v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = this.input[i] * v; - } - } - - [Benchmark] - public void SimdMultiplyByVector() - { - Vector v = new Vector(this.testValue); - - for (int i = 0; i < this.input.Length; i += Vector.Count) - { - Vector a = new Vector(this.input, i); - a = a * v; - a.CopyTo(this.result, i); - } - } - - [Benchmark] - public void SimdMultiplyByScalar() - { - float v = this.testValue; - - for (int i = 0; i < this.input.Length; i += Vector.Count) - { - Vector a = new Vector(this.input, i); - a = a * v; - a.CopyTo(this.result, i); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs deleted file mode 100644 index 532acc02d..000000000 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; - - using BenchmarkDotNet.Attributes; - - public class MulUInt32 - { - private uint[] input; - - private uint[] result; - - [Params(32)] - public int InputSize { get; set; } - - private uint testValue; - - [GlobalSetup] - public void Setup() - { - this.input = new uint[this.InputSize]; - this.result = new uint[this.InputSize]; - this.testValue = 42; - - for (int i = 0; i < this.InputSize; i++) - { - this.input[i] = (uint)i; - } - } - - [Benchmark(Baseline = true)] - public void Standard() - { - uint v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = this.input[i] * v; - } - } - - [Benchmark] - public void Simd() - { - Vector v = new Vector(this.testValue); - - for (int i = 0; i < this.input.Length; i += Vector.Count) - { - Vector a = new Vector(this.input, i); - a = a * v; - a.CopyTo(this.result, i); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs new file mode 100644 index 000000000..d1b70f21b --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs @@ -0,0 +1,50 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + using BenchmarkDotNet.Attributes; + + public class MulUInt32 : SIMDBenchmarkBase.Multiply + { + protected override uint GetTestValue() => 42u; + + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] * v; + } + } + } + + public class MulInt32 : SIMDBenchmarkBase.Multiply + { + [Benchmark(Baseline = true)] + public void Standard() + { + int v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] * v; + } + } + } + + public class MulInt16 : SIMDBenchmarkBase.Multiply + { + protected override short GetTestValue() => 42; + + protected override Vector GetTestVector() => new Vector(new short[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }); + + [Benchmark(Baseline = true)] + public void Standard() + { + short v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = (short)(this.input[i] * v); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs new file mode 100644 index 000000000..76987dbd2 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs @@ -0,0 +1,70 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + using BenchmarkDotNet.Attributes; + + public abstract class SIMDBenchmarkBase + where T : struct + { + protected T[] input; + + protected T[] result; + + protected T testValue; + + protected Vector testVector; + + protected virtual T GetTestValue() => default(T); + + protected virtual Vector GetTestVector() => new Vector(this.GetTestValue()); + + + [Params(32)] + public int InputSize { get; set; } + + [GlobalSetup] + public virtual void Setup() + { + this.input = new T[this.InputSize]; + this.result = new T[this.InputSize]; + this.testValue = this.GetTestValue(); + this.testVector = this.GetTestVector(); + } + + public abstract class Multiply : SIMDBenchmarkBase + { + [Benchmark] + public void Simd() + { + Vector v = this.testVector; + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = Unsafe.As>(ref this.input[i]); + a = a * v; + Unsafe.As>(ref this.result[i]) = a; + } + } + } + + public abstract class Divide : SIMDBenchmarkBase + { + [Benchmark] + public void Simd() + { + Vector v = this.testVector; + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = Unsafe.As>(ref this.input[i]); + a = a / v; + Unsafe.As>(ref this.result[i]) = a; + } + } + } + + + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs b/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs index 44c90d253..ebcdf972a 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs @@ -6,8 +6,13 @@ namespace ImageSharp.Benchmarks.Image { using System.Collections.Generic; + using System.IO; + using BenchmarkDotNet.Attributes; + using ImageSharp.Formats; + using ImageSharp.PixelFormats; + using CoreImage = ImageSharp.Image; [Config(typeof(Config.Short))] @@ -20,14 +25,23 @@ namespace ImageSharp.Benchmarks.Image protected override IEnumerable SearchPatterns => new[] { "*.jpg" }; - [Benchmark(Description = "DecodeJpegMultiple - ImageSharp")] - public void DecodeJpegImageSharp() + [Benchmark(Description = "DecodeJpegMultiple - ImageSharp NEW")] + public void DecodeJpegImageSharpNwq() { this.ForEachStream( ms => CoreImage.Load(ms) ); } + + [Benchmark(Description = "DecodeJpegMultiple - ImageSharp Original")] + public void DecodeJpegImageSharpOriginal() + { + this.ForEachStream( + ms => CoreImage.Load(ms, new OriginalJpegDecoder()) + ); + } + [Benchmark(Baseline = true, Description = "DecodeJpegMultiple - System.Drawing")] public void DecodeJpegSystemDrawing() { @@ -36,5 +50,25 @@ namespace ImageSharp.Benchmarks.Image ); } + + public sealed class OriginalJpegDecoder : IImageDecoder, IJpegDecoderOptions + { + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } + + /// + public Image Decode(Configuration configuration, Stream stream) + where TPixel : struct, IPixel + { + Guard.NotNull(stream, "stream"); + + using (var decoder = new JpegDecoderCore(configuration, this)) + { + return decoder.Decode(stream); + } + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs index e01a5951e..fcd72a9ba 100644 --- a/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs @@ -39,7 +39,7 @@ namespace ImageSharp.Benchmarks.Image [Params(InputImageCategory.AllImages, InputImageCategory.SmallImagesOnly, InputImageCategory.LargeImagesOnly)] public virtual InputImageCategory InputCategory { get; set; } - protected virtual string BaseFolder => "../ImageSharp.Tests/TestImages/Formats/"; + protected virtual string BaseFolder => "../../../../../../../../ImageSharp.Tests/TestImages/Formats/"; protected virtual IEnumerable SearchPatterns => new[] { "*.*" }; From 6fd052077cbcb57fdcc66dbceb5db9266ff23801 Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Thu, 17 Aug 2017 09:07:40 +1000 Subject: [PATCH 43/43] Use more accurate IDCT for now --- src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 8bc43f0f7..3a6e14f59 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -798,17 +798,17 @@ namespace ImageSharp.Formats.Jpeg.Port // scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 // For integer operation, the multiplier table is to be scaled by 12. Span multiplierSpan = multiplicationBuffer; - for (int i = 0; i < 64; i++) - { - multiplierSpan[i] = (short)IDCT.Descale(quantizationTable[i] * IDCT.Aanscales[i], 12); - } + // for (int i = 0; i < 64; i++) + // { + // multiplierSpan[i] = (short)IDCT.Descale(quantizationTable[i] * IDCT.Aanscales[i], 12); + // } for (int blockRow = 0; blockRow < blocksPerColumn; blockRow++) { for (int blockCol = 0; blockCol < blocksPerLine; blockCol++) { int offset = GetBlockBufferOffset(ref component, blockRow, blockCol); - IDCT.QuantizeAndInverseFast(ref frameComponent, offset, ref computationBufferSpan, ref multiplierSpan); + IDCT.QuantizeAndInverse(ref frameComponent, offset, ref computationBufferSpan, ref quantizationTable); } } }