From 26e645d0eb9811495b412cd0e417475a7b23c249 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 20 Aug 2017 06:40:00 +0200 Subject: [PATCH] CompareSpectralResults --- .../Jpeg/PdfJsPort/Components/Component.cs | 2 +- .../Jpeg/PdfJsPort/Components/Frame.cs | 18 ++ .../PdfJsPort/Components/FrameComponent.cs | 62 +++- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 26 +- .../Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs | 57 ++-- .../Formats/Jpg/LibJpegTools.cs | 281 +++++++++++++++--- .../Formats/Jpg/SpectralJpegTests.cs | 39 ++- 7 files changed, 385 insertions(+), 100 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Component.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Component.cs index 2f167c251c..d49b1b4812 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Component.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Component.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Represents a component block /// - internal struct Component : IDisposable + internal class Component : IDisposable { /// /// Gets or sets the output diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Frame.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Frame.cs index e7eabcdc8b..4fdb5cd3b3 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Frame.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Frame.cs @@ -5,6 +5,8 @@ using System; namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { + using SixLabors.ImageSharp.Memory; + /// /// Represent a single jpeg frame /// @@ -83,5 +85,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components this.Components = null; } } + + /// + /// Allocates the frame component blocks + /// + public void InitComponents() + { + this.McusPerLine = (int)MathF.Ceiling(this.SamplesPerLine / 8F / this.MaxHorizontalFactor); + this.McusPerColumn = (int)MathF.Ceiling(this.Scanlines / 8F / this.MaxVerticalFactor); + + for (int i = 0; i < this.ComponentCount; i++) + { + FrameComponent component = this.Components[i]; + component.Init(); + } + } + } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs index 4bc66406bb..bfc5c7c8b4 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs @@ -6,6 +6,8 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { + using System.Runtime.CompilerServices; + /// /// Represents a single frame component /// @@ -13,9 +15,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { #pragma warning disable SA1401 // Fields should be private /// - /// Gets or sets the component Id + /// Gets the component Id /// - public byte Id; + public byte Id { get; } /// /// TODO: What does pred stand for? @@ -23,19 +25,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components public int Pred; /// - /// Gets or sets the horizontal sampling factor. + /// Gets the horizontal sampling factor. /// - public int HorizontalFactor; + public int HorizontalFactor { get; } /// - /// Gets or sets the vertical sampling factor. + /// Gets the vertical sampling factor. /// - public int VerticalFactor; + public int VerticalFactor { get; } /// - /// Gets or sets the identifier + /// Gets the identifier /// - public byte QuantizationIdentifier; + public byte QuantizationIdentifier { get; } /// /// Gets or sets the block data @@ -62,11 +64,55 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// public int ACHuffmanTableId; + internal int BlocksPerLineForMcu; + + internal int BlocksPerColumnForMcu; + + public Frame Frame { get; } + + public FrameComponent(Frame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationIdentifier) + { + this.Frame = frame; + this.Id = id; + this.HorizontalFactor = horizontalFactor; + this.VerticalFactor = verticalFactor; + this.QuantizationIdentifier = quantizationIdentifier; + } + /// public void Dispose() { this.BlockData?.Dispose(); this.BlockData = null; } + + public void Init() + { + this.BlocksPerLine = (int)MathF.Ceiling( + MathF.Ceiling(this.Frame.SamplesPerLine / 8F) * this.HorizontalFactor / this.Frame.MaxHorizontalFactor); + + this.BlocksPerColumn = (int)MathF.Ceiling( + MathF.Ceiling(this.Frame.Scanlines / 8F) * this.VerticalFactor / this.Frame.MaxVerticalFactor); + + this.BlocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalFactor; + this.BlocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalFactor; + + int blocksBufferSize = 64 * this.BlocksPerColumnForMcu * (this.BlocksPerLineForMcu + 1); + + // Pooled. Disposed via frame disposal + this.BlockData = Buffer.CreateClean(blocksBufferSize); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetBlockBufferOffset(int row, int col) + { + return 64 * (((this.BlocksPerLine + 1) * row) + col); + } + + public Span GetBlockBuffer(int row, int col) + { + int offset = this.GetBlockBufferOffset(row, col); + return this.BlockData.Span.Slice(offset, 64); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index c7f1513c8d..d8152c7b9b 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -187,12 +187,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components } } - [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, @@ -476,7 +470,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { int blockRow = mcu / component.BlocksPerLine; int blockCol = mcu % component.BlocksPerLine; - int offset = GetBlockBufferOffset(component, blockRow, blockCol); + int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeBaseline(component, offset, ref dcHuffmanTable, ref acHuffmanTable, stream); } @@ -487,7 +481,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.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 = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeBaseline(component, offset, ref dcHuffmanTable, ref acHuffmanTable, stream); } @@ -496,7 +490,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { int blockRow = mcu / component.BlocksPerLine; int blockCol = mcu % component.BlocksPerLine; - int offset = GetBlockBufferOffset(component, blockRow, blockCol); + int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeDCFirst(component, offset, ref dcHuffmanTable, stream); } @@ -507,7 +501,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.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 = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeDCFirst(component, offset, ref dcHuffmanTable, stream); } @@ -516,7 +510,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { int blockRow = mcu / component.BlocksPerLine; int blockCol = mcu % component.BlocksPerLine; - int offset = GetBlockBufferOffset(component, blockRow, blockCol); + int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeDCSuccessive(component, offset, stream); } @@ -527,7 +521,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.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 = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeDCSuccessive(component, offset, stream); } @@ -536,7 +530,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { int blockRow = mcu / component.BlocksPerLine; int blockCol = mcu % component.BlocksPerLine; - int offset = GetBlockBufferOffset(component, blockRow, blockCol); + int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeACFirst(component, offset, ref acHuffmanTable, stream); } @@ -547,7 +541,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.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 = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeACFirst(component, offset, ref acHuffmanTable, stream); } @@ -556,7 +550,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { int blockRow = mcu / component.BlocksPerLine; int blockCol = mcu % component.BlocksPerLine; - int offset = GetBlockBufferOffset(component, blockRow, blockCol); + int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeACSuccessive(component, offset, ref acHuffmanTable, stream); } @@ -567,7 +561,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.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 = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeACSuccessive(component, offset, ref acHuffmanTable, stream); } diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs index 6f01f6b442..dcdde1feb7 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs @@ -140,6 +140,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort { ImageMetaData metadata = this.ParseStream(stream); + this.QuantizeAndInverseAllComponents(); + var image = new Image(this.configuration, this.ImageWidth, this.ImageHeight, metadata); this.FillPixelData(image); this.AssignResolution(image); @@ -275,7 +277,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort for (int i = 0; i < this.components.Components.Length; i++) { - ref var frameComponent = ref this.Frame.Components[i]; + FrameComponent frameComponent = this.Frame.Components[i]; var component = new Component { Scale = new System.Numerics.Vector2( @@ -285,13 +287,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort BlocksPerColumn = frameComponent.BlocksPerColumn }; - this.BuildComponentData(ref component, ref frameComponent); + // this.QuantizeAndInverseComponentData(ref component, frameComponent); this.components.Components[i] = component; } this.NumberOfComponents = this.components.Components.Length; } + internal void QuantizeAndInverseAllComponents() + { + for (int i = 0; i < this.components.Components.Length; i++) + { + FrameComponent frameComponent = this.Frame.Components[i]; + Component component = this.components.Components[i]; + + this.QuantizeAndInverseComponentData(component, frameComponent); + } + } + /// /// Fills the given image with the color data /// @@ -648,13 +661,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort maxV = v; } - var component = new FrameComponent(); - this.Frame.Components[i] = component; - component.Id = this.temp[index]; - component.HorizontalFactor = h; - component.VerticalFactor = v; - component.QuantizationIdentifier = this.temp[index + 2]; + var component = new FrameComponent(this.Frame, this.temp[index], h, v, this.temp[index + 2]); + this.Frame.Components[i] = component; this.Frame.ComponentIds[i] = component.Id; index += 3; @@ -662,7 +671,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort this.Frame.MaxHorizontalFactor = maxH; this.Frame.MaxVerticalFactor = maxV; - this.PrepareComponents(); + this.Frame.InitComponents(); } /// @@ -784,7 +793,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// /// The component /// The frame component - private void BuildComponentData(ref Component component, ref FrameComponent frameComponent) + private void QuantizeAndInverseComponentData(Component component, FrameComponent frameComponent) { int blocksPerLine = component.BlocksPerLine; int blocksPerColumn = component.BlocksPerColumn; @@ -830,34 +839,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort tables[index] = new HuffmanTable(codeLengths, values); } - /// - /// Allocates the frame component blocks - /// - private void PrepareComponents() - { - 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)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 disposal - component.BlockData = Buffer.CreateClean(blocksBufferSize); - component.BlocksPerLine = blocksPerLine; - component.BlocksPerColumn = blocksPerColumn; - } - - this.Frame.McusPerLine = mcusPerLine; - this.Frame.McusPerColumn = mcusPerColumn; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private void FillGrayScaleImage(Image image) where TPixel : struct, IPixel diff --git a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs index a6efa3f2fb..04e755310f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs @@ -11,12 +11,13 @@ namespace SixLabors.ImageSharp.Tests using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components; using SixLabors.ImageSharp.PixelFormats; + using SixLabors.Primitives; using Xunit; internal static class LibJpegTools { - public unsafe struct Block + public unsafe struct Block : IEquatable { public Block(short[] data) { @@ -24,28 +25,45 @@ namespace SixLabors.ImageSharp.Tests } public short[] Data { get; } - - //public fixed short Data[64]; - - //public Block8x8(short[] data) - //{ - // fixed (short* p = Data) - // { - // for (int i = 0; i < 64; i++) - // { - // p[i] = data[i]; - // } - // } - //} - + public short this[int x, int y] { get => this.Data[y * 8 + x]; set => this.Data[y * 8 + x] = value; } + + public bool Equals(Block other) + { + for (int i = 0; i < 64; i++) + { + if (this.Data[i] != other.Data[i]) return false; + } + return true; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is Block && Equals((Block)obj); + } + + public override int GetHashCode() + { + return (this.Data != null ? this.Data.GetHashCode() : 0); + } + + public static bool operator ==(Block left, Block right) + { + return left.Equals(right); + } + + public static bool operator !=(Block left, Block right) + { + return !left.Equals(right); + } } - public class SpectralData + public class SpectralData : IEquatable { public int ComponentCount { get; private set; } @@ -132,13 +150,116 @@ namespace SixLabors.ImageSharp.Tests public static SpectralData LoadFromImageSharpDecoder(JpegDecoderCore decoder) { FrameComponent[] srcComponents = decoder.Frame.Components; - - ComponentData[] destComponents = new ComponentData[srcComponents.Length]; - throw new NotImplementedException(); + + ComponentData[] destComponents = srcComponents.Select(ComponentData.Load).ToArray(); + + return new SpectralData(destComponents); + } + + public Image TryCreateRGBSpectralImage() + { + if (this.ComponentCount != 3) return null; + + ComponentData c0 = this.Components[0]; + ComponentData c1 = this.Components[1]; + ComponentData c2 = this.Components[2]; + + if (c0.Size != c1.Size || c1.Size != c2.Size) + { + return null; + } + + Image result = new Image(c0.XCount * 8, c0.YCount * 8); + + for (int by = 0; by < c0.YCount; by++) + { + for (int bx = 0; bx < c0.XCount; bx++) + { + this.WriteToImage(bx, by, result); + } + } + return result; + } + + internal void WriteToImage(int bx, int by, Image image) + { + ComponentData c0 = this.Components[0]; + ComponentData c1 = this.Components[1]; + ComponentData c2 = this.Components[2]; + + Block block0 = c0.Blocks[by, bx]; + Block block1 = c1.Blocks[by, bx]; + Block block2 = c2.Blocks[by, bx]; + + float d0 = (c0.MaxVal - c0.MinVal); + float d1 = (c1.MaxVal - c1.MinVal); + float d2 = (c2.MaxVal - c2.MinVal); + + for (int y = 0; y < 8; y++) + { + for (int x = 0; x < 8; x++) + { + float val0 = c0.GetBlockValue(block0, x, y); + float val1 = c0.GetBlockValue(block1, x, y); + float val2 = c0.GetBlockValue(block2, x, y); + + Vector4 v = new Vector4(val0, val1, val2, 1); + Rgba32 color = default(Rgba32); + color.PackFromVector4(v); + + int yy = by * 8 + y; + int xx = bx * 8 + x; + image[xx, yy] = color; + } + } + } + + public bool Equals(SpectralData other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + if (this.ComponentCount != other.ComponentCount) + { + return false; + } + + for (int i = 0; i < this.ComponentCount; i++) + { + ComponentData a = this.Components[i]; + ComponentData b = other.Components[i]; + if (!a.Equals(b)) return false; + } + return true; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((SpectralData)obj); + } + + public override int GetHashCode() + { + unchecked + { + return (this.ComponentCount * 397) ^ (this.Components != null ? this.Components[0].GetHashCode() : 0); + } + } + + public static bool operator ==(SpectralData left, SpectralData right) + { + return Equals(left, right); + } + + public static bool operator !=(SpectralData left, SpectralData right) + { + return !Equals(left, right); } } - public class ComponentData + public class ComponentData : IEquatable { public ComponentData(int yCount, int xCount, int index) { @@ -148,6 +269,8 @@ namespace SixLabors.ImageSharp.Tests this.Blocks = new Block[this.YCount, this.XCount]; } + public Size Size => new Size(this.XCount, this.YCount); + public int Index { get; } public int YCount { get; } @@ -172,16 +295,44 @@ namespace SixLabors.ImageSharp.Tests private void Init(Array bloxSource) { - for (int i = 0; i < bloxSource.Length; i++) + for (int y = 0; y < bloxSource.Length; y++) { - Array row = (Array)bloxSource.GetValue(i); - for (int j = 0; j < row.Length; j++) + Array row = (Array)bloxSource.GetValue(y); + for (int x = 0; x < row.Length; x++) { - object jBlock = row.GetValue(j); + object jBlock = row.GetValue(x); short[] data = (short[])GetNonPublicMember(jBlock, "data"); - this.MinVal = Math.Min(this.MinVal, data.Min()); - this.MaxVal = Math.Max(this.MaxVal, data.Max()); - this.Blocks[i, j] = new Block(data); + this.MakeBlock(data, y, x); + } + } + } + + private void MakeBlock(short[] data, int y, int x) + { + this.MinVal = Math.Min(this.MinVal, data.Min()); + this.MaxVal = Math.Max(this.MaxVal, data.Max()); + this.Blocks[y, x] = new Block(data); + } + + public static ComponentData Load(FrameComponent sc, int index) + { + var result = new ComponentData( + sc.BlocksPerColumnForMcu, + sc.BlocksPerLineForMcu, + index + ); + result.Init(sc); + return result; + } + + private void Init(FrameComponent sc) + { + for (int y = 0; y < this.YCount; y++) + { + for (int x = 0; x < this.XCount; x++) + { + short[] data = sc.GetBlockBuffer(y, x).ToArray(); + this.MakeBlock(data, y, x); } } } @@ -194,33 +345,93 @@ namespace SixLabors.ImageSharp.Tests { for (int bx = 0; bx < this.XCount; bx++) { - WriteToImage(this.Blocks[by, bx], bx, by, result); + this.WriteToImage(bx, by, result); } } return result; } - private void WriteToImage(Block block, int bx, int by, Image image) + internal void WriteToImage(int bx, int by, Image image) { - float d = (this.MaxVal - this.MinVal); + Block block = this.Blocks[by, bx]; + for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { - int yy = by * 8 + y; - int xx = bx * 8 + x; - float val = block[x, y]; - val -= this.MinVal; - val /= d; + var val = this.GetBlockValue(block, x, y); Vector4 v = new Vector4(val, val, val, 1); Rgba32 color = default(Rgba32); color.PackFromVector4(v); + int yy = by * 8 + y; + int xx = bx * 8 + x; image[xx, yy] = color; } } } + + internal float GetBlockValue(Block block, int x, int y) + { + float d = (this.MaxVal - this.MinVal); + float val = block[x, y]; + val -= this.MinVal; + val /= d; + return val; + } + + public bool Equals(ComponentData other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + bool ok = this.Index == other.Index && this.YCount == other.YCount && this.XCount == other.XCount + && this.MinVal == other.MinVal + && this.MaxVal == other.MaxVal; + if (!ok) return false; + + for (int i = 0; i < this.YCount; i++) + { + for (int j = 0; j < this.XCount; j++) + { + Block a = this.Blocks[i, j]; + Block b = other.Blocks[i, j]; + if (!a.Equals(b)) return false; + } + } + return true; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ComponentData)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = this.Index; + hashCode = (hashCode * 397) ^ this.YCount; + hashCode = (hashCode * 397) ^ this.XCount; + hashCode = (hashCode * 397) ^ this.MinVal.GetHashCode(); + hashCode = (hashCode * 397) ^ this.MaxVal.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(ComponentData left, ComponentData right) + { + return Equals(left, right); + } + + public static bool operator !=(ComponentData left, ComponentData right) + { + return !Equals(left, right); + } } internal static FieldInfo GetNonPublicField(object obj, string fieldName) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index eeb879b8cc..51c996d85b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -1,11 +1,14 @@ // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { + using System.Collections.Generic; using System.IO; + using System.Linq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; using SixLabors.ImageSharp.PixelFormats; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using Xunit.Abstractions; @@ -34,6 +37,8 @@ namespace SixLabors.ImageSharp.Tests TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF }; + public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray(); + [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] public void BuildLibJpegSpectralResult(TestImageProvider provider) @@ -68,14 +73,36 @@ namespace SixLabors.ImageSharp.Tests this.SaveSpectralImage(provider, data); } } - + + [Theory] + [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] + public void CompareSpectralResults(TestImageProvider provider) + where TPixel : struct, IPixel + { + JpegDecoderCore decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + + byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; + + using (var ms = new MemoryStream(sourceBytes)) + { + decoder.ParseStream(ms); + var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); + + ms.Seek(0, SeekOrigin.Begin); + var libJpegData = LibJpegTools.SpectralData.Load(ms); + + Assert.Equal(libJpegData, imageSharpData); + } + } + + private void SaveSpectralImage(TestImageProvider provider, LibJpegTools.SpectralData data) where TPixel : struct, IPixel { foreach (LibJpegTools.ComponentData comp in data.Components) { this.Output.WriteLine("Min: " + comp.MinVal); - this.Output.WriteLine("MAx: " + comp.MaxVal); + this.Output.WriteLine("Max: " + comp.MaxVal); using (Image image = comp.CreateGrayScaleImage()) { @@ -83,6 +110,14 @@ namespace SixLabors.ImageSharp.Tests image.DebugSave(provider, details, appendPixelTypeToFileName: false); } } + + Image fullImage = data.TryCreateRGBSpectralImage(); + + if (fullImage != null) + { + fullImage.DebugSave(provider, "FULL", appendPixelTypeToFileName: false); + fullImage.Dispose(); + } } }