diff --git a/src/ImageSharp/Formats/Jpeg/Common/IJpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Common/IJpegComponent.cs new file mode 100644 index 0000000000..3dbd010223 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/IJpegComponent.cs @@ -0,0 +1,8 @@ +namespace SixLabors.ImageSharp.Formats.Jpeg.Common +{ + internal interface IJpegComponent + { + int WidthInBlocks { get; } + int HeightInBlocks { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockProcessor.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockProcessor.cs index e58e7997d8..4182e18a97 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockProcessor.cs @@ -49,9 +49,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder { OldComponent component = decoder.Components[this.componentIndex]; - for (int by = 0; by < component.BlockCountY; by++) + for (int by = 0; by < component.HeightInBlocks; by++) { - for (int bx = 0; bx < component.BlockCountX; bx++) + for (int bx = 0; bx < component.WidthInBlocks; bx++) { this.ProcessBlockColors(decoder, component, bx, by); } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldComponent.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldComponent.cs index 90f4c60ee8..bd3667e235 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldComponent.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// Represents a single color component /// - internal class OldComponent : IDisposable + internal class OldComponent : IDisposable, IJpegComponent { public OldComponent(byte identifier, int index) { @@ -55,12 +55,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// Gets the number of blocks for this component along the X axis /// - public int BlockCountX { get; private set; } + public int WidthInBlocks { get; private set; } /// /// Gets the number of blocks for this component along the Y axis /// - public int BlockCountY { get; private set; } + public int HeightInBlocks { get; private set; } public ref Block8x8 GetBlockReference(int bx, int by) { @@ -73,9 +73,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// The instance public void InitializeBlocks(OldJpegDecoderCore decoder) { - this.BlockCountX = decoder.MCUCountX * this.HorizontalFactor; - this.BlockCountY = decoder.MCUCountY * this.VerticalFactor; - this.SpectralBlocks = Buffer2D.CreateClean(this.BlockCountX, this.BlockCountY); + this.WidthInBlocks = decoder.MCUCountX * this.HorizontalFactor; + this.HeightInBlocks = decoder.MCUCountY * this.VerticalFactor; + this.SpectralBlocks = Buffer2D.CreateClean(this.WidthInBlocks, this.HeightInBlocks); } /// diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs index 8383f54549..8f050b6c63 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs @@ -7,10 +7,12 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { + using SixLabors.ImageSharp.Formats.Jpeg.Common; + /// /// Represents a single frame component /// - internal class FrameComponent : IDisposable + internal class FrameComponent : IDisposable, IJpegComponent { #pragma warning disable SA1401 // Fields should be private @@ -56,12 +58,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Gets the number of blocks per line /// - public int BlocksPerLine { get; private set; } + public int WidthInBlocks { get; private set; } /// /// Gets the number of blocks per column /// - public int BlocksPerColumn { get; private set; } + public int HeightInBlocks { get; private set; } /// /// Gets or sets the index for the DC Huffman table @@ -88,10 +90,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components public void Init() { - this.BlocksPerLine = (int)MathF.Ceiling( + this.WidthInBlocks = (int)MathF.Ceiling( MathF.Ceiling(this.Frame.SamplesPerLine / 8F) * this.HorizontalFactor / this.Frame.MaxHorizontalFactor); - this.BlocksPerColumn = (int)MathF.Ceiling( + this.HeightInBlocks = (int)MathF.Ceiling( MathF.Ceiling(this.Frame.Scanlines / 8F) * this.VerticalFactor / this.Frame.MaxVerticalFactor); this.BlocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalFactor; @@ -106,7 +108,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetBlockBufferOffset(int row, int col) { - return 64 * (((this.BlocksPerLine + 1) * row) + col); + return 64 * (((this.WidthInBlocks + 1) * row) + col); } public Span GetBlockBuffer(int row, int col) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index d8152c7b9b..42da5964fa 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components int mcuExpected; if (componentsLength == 1) { - mcuExpected = components[this.compIndex].BlocksPerLine * components[this.compIndex].BlocksPerColumn; + mcuExpected = components[this.compIndex].WidthInBlocks * components[this.compIndex].HeightInBlocks; } else { @@ -468,8 +468,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeBlockBaseline(ref HuffmanTable dcHuffmanTable, ref HuffmanTable acHuffmanTable, FrameComponent component, int mcu, Stream stream) { - int blockRow = mcu / component.BlocksPerLine; - int blockCol = mcu % component.BlocksPerLine; + int blockRow = mcu / component.WidthInBlocks; + int blockCol = mcu % component.WidthInBlocks; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeBaseline(component, offset, ref dcHuffmanTable, ref acHuffmanTable, stream); } @@ -488,8 +488,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeBlockDCFirst(ref HuffmanTable dcHuffmanTable, FrameComponent component, int mcu, Stream stream) { - int blockRow = mcu / component.BlocksPerLine; - int blockCol = mcu % component.BlocksPerLine; + int blockRow = mcu / component.WidthInBlocks; + int blockCol = mcu % component.WidthInBlocks; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeDCFirst(component, offset, ref dcHuffmanTable, stream); } @@ -508,8 +508,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeBlockDCSuccessive(FrameComponent component, int mcu, Stream stream) { - int blockRow = mcu / component.BlocksPerLine; - int blockCol = mcu % component.BlocksPerLine; + int blockRow = mcu / component.WidthInBlocks; + int blockCol = mcu % component.WidthInBlocks; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeDCSuccessive(component, offset, stream); } @@ -528,8 +528,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeBlockACFirst(ref HuffmanTable acHuffmanTable, FrameComponent component, int mcu, Stream stream) { - int blockRow = mcu / component.BlocksPerLine; - int blockCol = mcu % component.BlocksPerLine; + int blockRow = mcu / component.WidthInBlocks; + int blockCol = mcu % component.WidthInBlocks; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeACFirst(component, offset, ref acHuffmanTable, stream); } @@ -548,8 +548,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeBlockACSuccessive(ref HuffmanTable acHuffmanTable, FrameComponent component, int mcu, Stream stream) { - int blockRow = mcu / component.BlocksPerLine; - int blockCol = mcu % component.BlocksPerLine; + int blockRow = mcu / component.WidthInBlocks; + int blockCol = mcu % component.WidthInBlocks; 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 3357d03874..a9962a7b8e 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs @@ -298,8 +298,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort Scale = new System.Numerics.Vector2( frameComponent.HorizontalFactor / (float)this.Frame.MaxHorizontalFactor, frameComponent.VerticalFactor / (float)this.Frame.MaxVerticalFactor), - BlocksPerLine = frameComponent.BlocksPerLine, - BlocksPerColumn = frameComponent.BlocksPerColumn + BlocksPerLine = frameComponent.WidthInBlocks, + BlocksPerColumn = frameComponent.HeightInBlocks }; // this.QuantizeAndInverseComponentData(ref component, frameComponent); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index a8128ca80b..eedc96fb30 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -11,12 +11,15 @@ using Xunit; namespace SixLabors.ImageSharp.Tests { using System; + using System.Collections.Generic; using System.IO; using System.Linq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; + using SixLabors.ImageSharp.Formats.Jpeg.Common; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; + using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; @@ -62,7 +65,53 @@ namespace SixLabors.ImageSharp.Tests private static IImageDecoder OldJpegDecoder => new OldJpegDecoder(); private static IImageDecoder PdfJsJpegDecoder => new JpegDecoder(); - + + private static void VerifyJpegComponent(IJpegComponent component, int expectedBlocksX, int expectedBlocksY) + { + Assert.Equal(component.WidthInBlocks, expectedBlocksX); + Assert.Equal(component.HeightInBlocks, expectedBlocksY); + } + + private static void Verify3ComponentJpeg( + IEnumerable components, + int xBc0, int yBc0, + int xBc1, int yBc1, + int xBc2, int yBc2) + { + IJpegComponent[] c = components.ToArray(); + Assert.Equal(3, components.Count()); + + VerifyJpegComponent(c[0], xBc0, yBc0); + VerifyJpegComponent(c[1], xBc1, yBc1); + VerifyJpegComponent(c[2], xBc2, yBc2); + } + + [Fact] + public void ParseStream_BasicPropertiesAreCorrect1_Old() + { + byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes; + using (var ms = new MemoryStream(bytes)) + { + var decoder = new OldJpegDecoderCore(Configuration.Default, new JpegDecoder()); + decoder.ParseStream(ms); + + Verify3ComponentJpeg(decoder.Components, 43, 61, 22, 31, 22, 31); + } + } + + [Fact] + public void ParseStream_BasicPropertiesAreCorrect1_PdfJs() + { + byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes; + using (var ms = new MemoryStream(bytes)) + { + var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + decoder.ParseStream(ms); + + Verify3ComponentJpeg(decoder.Frame.Components, 43, 61, 22, 31, 22, 31); + } + } + [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] public void DecodeBaselineJpeg(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs index 851e600633..628e5966cb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs @@ -1,6 +1,8 @@ namespace SixLabors.ImageSharp.Tests { using System; + using System.Collections.Generic; + using System.Diagnostics; using System.IO; using System.Linq; using System.Numerics; @@ -8,6 +10,7 @@ namespace SixLabors.ImageSharp.Tests using BitMiracle.LibJpeg.Classic; + using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Common; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; @@ -47,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests } } - private SpectralData(ComponentData[] components) + internal SpectralData(ComponentData[] components) { this.ComponentCount = components.Length; this.Components = components; @@ -272,7 +275,7 @@ namespace SixLabors.ImageSharp.Tests } } - private void MakeBlock(short[] data, int y, int x) + internal void MakeBlock(short[] data, int y, int x) { this.MinVal = Math.Min(this.MinVal, data.Min()); this.MaxVal = Math.Max(this.MaxVal, data.Max()); @@ -302,8 +305,8 @@ namespace SixLabors.ImageSharp.Tests public static ComponentData Load(OldComponent c) { var result = new ComponentData( - c.BlockCountY, - c.BlockCountX, + c.HeightInBlocks, + c.WidthInBlocks, c.Index ); @@ -454,9 +457,69 @@ namespace SixLabors.ImageSharp.Tests return result / (count * Block8x8.Size); } + private static string DumpToolFullPath => Path.Combine( + TestEnvironment.ToolsDirectoryFullPath, + @"jpeg\dump-jpeg-coeffs.exe"); + public static void RunDumpJpegCoeffsTool(string sourceFile, string destFile) { - throw new NotImplementedException(); + string args = $@"""{sourceFile}"" ""{destFile}"""; + var process = Process.Start(DumpToolFullPath, args); + process.WaitForExit(); + } + + public static SpectralData ExtractSpectralData(string inputFile) + { + TestFile testFile = TestFile.Create(inputFile); + + string outDir = TestEnvironment.CreateOutputDirectory(".Temp", $"JpegCoeffs"); + string fn = $"{Path.GetFileName(inputFile)}-{new Random().Next(1000)}.dctcoeffs"; + string coeffFileFullPath = Path.Combine(outDir, fn); + + try + { + RunDumpJpegCoeffsTool(testFile.FullPath, coeffFileFullPath); + byte[] spectralBytes = File.ReadAllBytes(coeffFileFullPath); + File.Delete(coeffFileFullPath); + + using (var ms = new MemoryStream(testFile.Bytes)) + { + OldJpegDecoderCore decoder = new OldJpegDecoderCore(Configuration.Default, new JpegDecoder()); + decoder.ParseStream(ms); + + Span dump = new Span(spectralBytes).NonPortableCast(); + int counter = 0; + + OldComponent[] components = decoder.Components; + ComponentData[] result = new ComponentData[components.Length]; + + for (int i = 0; i < components.Length; i++) + { + OldComponent c = components[i]; + ComponentData resultComponent = new ComponentData(c.HeightInBlocks, c.WidthInBlocks, i); + result[i] = resultComponent; + + for (int y = 0; y < c.HeightInBlocks; y++) + { + for (int x = 0; x < c.WidthInBlocks; x++) + { + short[] block = dump.Slice(counter, 64).ToArray(); + resultComponent.MakeBlock(block, y, x); + counter += 64; + } + } + } + + return new SpectralData(result); + } + } + finally + { + if (File.Exists(coeffFileFullPath)) + { + File.Delete(coeffFileFullPath); + } + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index dfa1d91046..49495d4e1a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -47,10 +47,28 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void RunDumpJpegCoeffsTool() { + if (!TestEnvironment.IsWindows) return; + string inputFile = TestFile.GetInputFileFullPath(TestImages.Jpeg.Progressive.Progress); - + string outputDir = TestEnvironment.CreateOutputDirectory(nameof(SpectralJpegTests)); + string outputFile = Path.Combine(outputDir, "progress.dctdump"); + + LibJpegTools.RunDumpJpegCoeffsTool(inputFile, outputFile); + + Assert.True(File.Exists(outputFile)); } + [Theory] + [InlineData(TestImages.Jpeg.Baseline.Calliphora)] + [InlineData(TestImages.Jpeg.Progressive.Progress)] + public void ExtractSpectralData(string testImage) + { + LibJpegTools.SpectralData data = LibJpegTools.ExtractSpectralData(testImage); + + Assert.True(data.ComponentCount == 3); + Assert.True(data.Components.Length == 3); + } + [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] public void BuildLibJpegSpectralResult(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs index bc9c28898f..0e7dc5917e 100644 --- a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests private static (Size original, Size rotated) Rotate(int angle) { var file = TestFile.Create(TestImages.Bmp.Car); - using (var image = Image.Load(file.FilePath)) + using (var image = Image.Load(file.FullPath)) { Size original = image.Bounds().Size; image.Mutate(x => x.Rotate(angle)); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index fa19557dff..a9952f5c48 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests public void ConstructorFileSystem() { TestFile file = TestFile.Create(TestImages.Bmp.Car); - using (Image image = Image.Load(file.FilePath)) + using (Image image = Image.Load(file.FullPath)) { Assert.Equal(600, image.Width); Assert.Equal(450, image.Height); diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index d3c40f86aa..f56802e548 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -38,39 +38,34 @@ namespace SixLabors.ImageSharp.Tests /// private byte[] bytes; - /// - /// The file. - /// - private readonly string file; - /// /// Initializes a new instance of the class. /// /// The file. private TestFile(string file) { - this.file = file; + this.FullPath = file; } /// /// Gets the image bytes. /// - public byte[] Bytes => this.bytes ?? (this.bytes = File.ReadAllBytes(this.file)); + public byte[] Bytes => this.bytes ?? (this.bytes = File.ReadAllBytes(this.FullPath)); /// - /// The file name. + /// The full path to file. /// - public string FilePath => this.file; + public string FullPath { get; } /// /// The file name. /// - public string FileName => Path.GetFileName(this.file); + public string FileName => Path.GetFileName(this.FullPath); /// /// The file name without extension. /// - public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.file); + public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.FullPath); /// /// Gets the image with lazy initialization. @@ -116,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests /// public string GetFileName(object value) { - return $"{this.FileNameWithoutExtension}-{value}{Path.GetExtension(this.file)}"; + return $"{this.FileNameWithoutExtension}-{value}{Path.GetExtension(this.FullPath)}"; } /// diff --git a/tests/Images/External b/tests/Images/External index 086c854f00..fd428515bb 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 086c854f001e3bb1fa9085e76ba902171140dcc6 +Subproject commit fd428515bb3b125621c3b9518dfd07c6d919d3bf