Browse Source

more jpeg testing

pull/322/head
Anton Firszov 9 years ago
parent
commit
eb2fd0e47c
  1. 8
      src/ImageSharp/Formats/Jpeg/Common/IJpegComponent.cs
  2. 4
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockProcessor.cs
  3. 12
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldComponent.cs
  4. 14
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs
  5. 22
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs
  6. 4
      src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs
  7. 51
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  8. 73
      tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs
  9. 20
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  10. 2
      tests/ImageSharp.Tests/Image/ImageRotationTests.cs
  11. 2
      tests/ImageSharp.Tests/Image/ImageTests.cs
  12. 19
      tests/ImageSharp.Tests/TestFile.cs
  13. 2
      tests/Images/External

8
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; }
}
}

4
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);
}

12
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldComponent.cs

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <summary>
/// Represents a single color component
/// </summary>
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
/// <summary>
/// Gets the number of blocks for this component along the X axis
/// </summary>
public int BlockCountX { get; private set; }
public int WidthInBlocks { get; private set; }
/// <summary>
/// Gets the number of blocks for this component along the Y axis
/// </summary>
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
/// <param name="decoder">The <see cref="OldJpegDecoderCore"/> instance</param>
public void InitializeBlocks(OldJpegDecoderCore decoder)
{
this.BlockCountX = decoder.MCUCountX * this.HorizontalFactor;
this.BlockCountY = decoder.MCUCountY * this.VerticalFactor;
this.SpectralBlocks = Buffer2D<Block8x8>.CreateClean(this.BlockCountX, this.BlockCountY);
this.WidthInBlocks = decoder.MCUCountX * this.HorizontalFactor;
this.HeightInBlocks = decoder.MCUCountY * this.VerticalFactor;
this.SpectralBlocks = Buffer2D<Block8x8>.CreateClean(this.WidthInBlocks, this.HeightInBlocks);
}
/// <summary>

14
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;
/// <summary>
/// Represents a single frame component
/// </summary>
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
/// <summary>
/// Gets the number of blocks per line
/// </summary>
public int BlocksPerLine { get; private set; }
public int WidthInBlocks { get; private set; }
/// <summary>
/// Gets the number of blocks per column
/// </summary>
public int BlocksPerColumn { get; private set; }
public int HeightInBlocks { get; private set; }
/// <summary>
/// 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<short> GetBlockBuffer(int row, int col)

22
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);
}

4
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);

51
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<IJpegComponent> 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<TPixel>(TestImageProvider<TPixel> provider)

73
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<short> dump = new Span<byte>(spectralBytes).NonPortableCast<byte, short>();
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);
}
}
}
}
}

20
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<TPixel>(TestImageProvider<TPixel> provider)

2
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<Rgba32>(file.FilePath))
using (var image = Image.Load<Rgba32>(file.FullPath))
{
Size original = image.Bounds().Size;
image.Mutate(x => x.Rotate(angle));

2
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<Rgba32> image = Image.Load<Rgba32>(file.FilePath))
using (Image<Rgba32> image = Image.Load<Rgba32>(file.FullPath))
{
Assert.Equal(600, image.Width);
Assert.Equal(450, image.Height);

19
tests/ImageSharp.Tests/TestFile.cs

@ -38,39 +38,34 @@ namespace SixLabors.ImageSharp.Tests
/// </summary>
private byte[] bytes;
/// <summary>
/// The file.
/// </summary>
private readonly string file;
/// <summary>
/// Initializes a new instance of the <see cref="TestFile"/> class.
/// </summary>
/// <param name="file">The file.</param>
private TestFile(string file)
{
this.file = file;
this.FullPath = file;
}
/// <summary>
/// Gets the image bytes.
/// </summary>
public byte[] Bytes => this.bytes ?? (this.bytes = File.ReadAllBytes(this.file));
public byte[] Bytes => this.bytes ?? (this.bytes = File.ReadAllBytes(this.FullPath));
/// <summary>
/// The file name.
/// The full path to file.
/// </summary>
public string FilePath => this.file;
public string FullPath { get; }
/// <summary>
/// The file name.
/// </summary>
public string FileName => Path.GetFileName(this.file);
public string FileName => Path.GetFileName(this.FullPath);
/// <summary>
/// The file name without extension.
/// </summary>
public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.file);
public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.FullPath);
/// <summary>
/// Gets the image with lazy initialization.
@ -116,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests
/// </returns>
public string GetFileName(object value)
{
return $"{this.FileNameWithoutExtension}-{value}{Path.GetExtension(this.file)}";
return $"{this.FileNameWithoutExtension}-{value}{Path.GetExtension(this.FullPath)}";
}
/// <summary>

2
tests/Images/External

@ -1 +1 @@
Subproject commit 086c854f001e3bb1fa9085e76ba902171140dcc6
Subproject commit fd428515bb3b125621c3b9518dfd07c6d919d3bf
Loading…
Cancel
Save