From 1ccca34ce4fae2b86aa3c1b6372a3ccbed034656 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 20 Jun 2017 12:13:11 +1000 Subject: [PATCH] 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; } - } -}