Browse Source

Add identifier tests and begin cleanup

pull/525/head
James Jackson-South 8 years ago
parent
commit
81d68e244d
  1. 43
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponent.cs
  2. 32
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponentBlocks.cs
  3. 98
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJpegPixelArea.cs
  4. 67
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsQuantizationTables.cs
  5. 35
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs
  6. 121
      src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
  7. 22
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  8. 6
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs

43
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponent.cs

@ -1,43 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
/// <summary>
/// Represents a component block
/// </summary>
internal class PdfJsComponent : IDisposable
{
#pragma warning disable SA1401
/// <summary>
/// Gets or sets the output
/// </summary>
public IBuffer<short> Output;
/// <summary>
/// Gets or sets the scaling factors
/// </summary>
public Vector2 Scale;
/// <summary>
/// Gets or sets the number of blocks per line
/// </summary>
public int BlocksPerLine;
/// <summary>
/// Gets or sets the number of blocks per column
/// </summary>
public int BlocksPerColumn;
/// <inheritdoc/>
public void Dispose()
{
this.Output?.Dispose();
this.Output = null;
}
}
}

32
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponentBlocks.cs

@ -1,32 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
/// <summary>
/// Contains all the decoded component blocks
/// </summary>
internal sealed class PdfJsComponentBlocks : IDisposable
{
/// <summary>
/// Gets or sets the component blocks
/// </summary>
public PdfJsComponent[] Components { get; set; }
/// <inheritdoc/>
public void Dispose()
{
if (this.Components != null)
{
for (int i = 0; i < this.Components.Length; i++)
{
this.Components[i].Dispose();
}
this.Components = null;
}
}
}
}

98
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJpegPixelArea.cs

@ -54,55 +54,55 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
this.componentData = this.memoryManager.Allocate<byte>(this.Width * this.Height * this.NumberOfComponents);
}
/// <summary>
/// Organsizes the decoded jpeg components into a linear array ordered by component.
/// This must be called before attempting to retrieve the data.
/// </summary>
/// <param name="components">The jpeg component blocks</param>
public void LinearizeBlockData(PdfJsComponentBlocks components)
{
ref byte componentDataRef = ref MemoryMarshal.GetReference(this.componentData.Span);
const uint Mask3Lsb = 0xFFFFFFF8; // Used to clear the 3 LSBs
using (IBuffer<int> xScaleBlockOffset = this.memoryManager.Allocate<int>(this.Width))
{
ref int xScaleBlockOffsetRef = ref MemoryMarshal.GetReference(xScaleBlockOffset.Span);
int numberOfComponents = this.NumberOfComponents;
int width = this.Width;
int height = this.Height;
for (int i = 0; i < numberOfComponents; i++)
{
ref PdfJsComponent component = ref components.Components[i];
ref short outputRef = ref MemoryMarshal.GetReference(component.Output.Span);
Vector2 componentScale = component.Scale;
float cX = componentScale.X;
float cY = componentScale.Y;
int blocksPerScanline = (component.BlocksPerLine + 1) << 3;
// Precalculate the xScaleBlockOffset
int j;
for (int x = 0; x < width; x++)
{
j = (int)(x * cX);
Unsafe.Add(ref xScaleBlockOffsetRef, x) = (int)((j & Mask3Lsb) << 3) | (j & 7);
}
// Linearize the blocks of the component
int offset = i;
for (int y = 0; y < height; y++)
{
j = (int)(y * cY);
int index = blocksPerScanline * (int)(j & Mask3Lsb) | ((j & 7) << 3);
for (int x = 0; x < width; x++)
{
Unsafe.Add(ref componentDataRef, offset) = (byte)Unsafe.Add(ref outputRef, index + Unsafe.Add(ref xScaleBlockOffsetRef, x));
offset += numberOfComponents;
}
}
}
}
}
//// <summary>
//// Organsizes the decoded jpeg components into a linear array ordered by component.
//// This must be called before attempting to retrieve the data.
//// </summary>
//// <param name="components">The jpeg component blocks</param>
// public void LinearizeBlockData(PdfJsComponentBlocks components)
// {
// ref byte componentDataRef = ref MemoryMarshal.GetReference(this.componentData.Span);
// const uint Mask3Lsb = 0xFFFFFFF8; // Used to clear the 3 LSBs
// using (IBuffer<int> xScaleBlockOffset = this.memoryManager.Allocate<int>(this.Width))
// {
// ref int xScaleBlockOffsetRef = ref MemoryMarshal.GetReference(xScaleBlockOffset.Span);
// int numberOfComponents = this.NumberOfComponents;
// int width = this.Width;
// int height = this.Height;
//
// for (int i = 0; i < numberOfComponents; i++)
// {
// ref PdfJsComponent component = ref components.Components[i];
// ref short outputRef = ref MemoryMarshal.GetReference(component.Output.Span);
// Vector2 componentScale = component.Scale;
// float cX = componentScale.X;
// float cY = componentScale.Y;
// int blocksPerScanline = (component.BlocksPerLine + 1) << 3;
//
// // Precalculate the xScaleBlockOffset
// int j;
// for (int x = 0; x < width; x++)
// {
// j = (int)(x * cX);
// Unsafe.Add(ref xScaleBlockOffsetRef, x) = (int)((j & Mask3Lsb) << 3) | (j & 7);
// }
//
// // Linearize the blocks of the component
// int offset = i;
// for (int y = 0; y < height; y++)
// {
// j = (int)(y * cY);
// int index = blocksPerScanline * (int)(j & Mask3Lsb) | ((j & 7) << 3);
// for (int x = 0; x < width; x++)
// {
// Unsafe.Add(ref componentDataRef, offset) = (byte)Unsafe.Add(ref outputRef, index + Unsafe.Add(ref xScaleBlockOffsetRef, x));
// offset += numberOfComponents;
// }
// }
// }
// }
// }
/// <summary>
/// Gets a <see cref="Span{Byte}"/> representing the row 'y' beginning from the the first byte on that row.

67
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsQuantizationTables.cs

@ -1,67 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
/// <summary>
/// Contains the quantization tables.
/// </summary>
internal sealed class PdfJsQuantizationTables : IDisposable
{
public PdfJsQuantizationTables(MemoryManager memoryManager)
{
this.Tables = memoryManager.Allocate2D<short>(64, 4);
}
/// <summary>
/// Gets the ZigZag scan table
/// </summary>
public static byte[] DctZigZag
{
[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
};
/// <summary>
/// Gets or sets the quantization tables.
/// </summary>
public Buffer2D<short> Tables
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get; set;
}
/// <inheritdoc/>
public void Dispose()
{
if (this.Tables != null)
{
this.Tables.Dispose();
this.Tables = null;
}
}
}
}

35
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs

@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0.
using System;
#if DEBUG
using System.Diagnostics;
#endif
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -15,6 +17,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
internal struct PdfJsScanDecoder
{
/// <summary>
/// Gets the ZigZag scan table
/// </summary>
private static readonly 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
};
private byte[] markerBuffer;
private int bitsData;
@ -203,11 +227,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
if (componentsLength == 1)
{
PdfJsFrameComponent component = components[this.compIndex];
// TODO: This is where our error is happening.
// We can't simply cast the span as I think the scan decoder expects data to be laid out in linear order
// rather than in the column major order expected by the Block8x8 struct and anything reading it down the pipeline.
// Ask Anton about this. It might be a lost cause.
ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<Block8x8, short>(component.SpectralBlocks.Span));
ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId];
ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId];
@ -765,7 +784,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
break;
}
ref byte z = ref PdfJsQuantizationTables.DctZigZag[k];
ref byte z = ref DctZigZag[k];
short re = (short)this.ReceiveAndExtend(s, stream);
Unsafe.Add(ref blockDataRef, offset + z) = re;
k++;
@ -833,7 +852,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
k += r;
ref byte z = ref PdfJsQuantizationTables.DctZigZag[k];
ref byte z = ref DctZigZag[k];
Unsafe.Add(ref blockDataRef, offset + z) = (short)(this.ReceiveAndExtend(s, stream) * (1 << this.successiveState));
k++;
}
@ -848,7 +867,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
while (k <= e)
{
int offsetZ = offset + PdfJsQuantizationTables.DctZigZag[k];
int offsetZ = offset + DctZigZag[k];
ref short blockOffsetZRef = ref Unsafe.Add(ref blockDataRef, offsetZ);
int sign = blockOffsetZRef < 0 ? -1 : 1;

121
src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs

@ -39,19 +39,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
private readonly Configuration configuration;
/// <summary>
/// Gets the temporary buffer used to store bytes read from the stream.
/// The buffer used to temporarily store bytes read from the stream.
/// </summary>
private readonly byte[] temp = new byte[2 * 16 * 4];
/// <summary>
/// The buffer used to read markers from the stream.
/// </summary>
private readonly byte[] markerBuffer = new byte[2];
// private PdfJsQuantizationTables quantizationTables;
/// <summary>
/// The DC HUffman tables
/// </summary>
private PdfJsHuffmanTables dcHuffmanTables;
/// <summary>
/// The AC HUffman tables
/// </summary>
private PdfJsHuffmanTables acHuffmanTables;
private PdfJsComponentBlocks components;
private PdfJsJpegPixelArea pixelArea;
private ushort resetInterval;
@ -185,14 +191,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
where TPixel : struct, IPixel<TPixel>
{
this.ParseStream(stream);
Image<TPixel> image = this.PostProcessIntoImage<TPixel>();
// this.QuantizeAndInverseAllComponents();
// var image = new Image<TPixel>(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData);
// this.FillPixelData(image.Frames.RootFrame);
this.AssignResolution();
return image;
return this.PostProcessIntoImage<TPixel>();
}
/// <summary>
@ -321,12 +320,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
break;
case PdfJsJpegConstants.Markers.SOS:
if (metadataOnly)
if (!metadataOnly)
{
return;
this.ProcessStartOfScanMarker();
}
this.ProcessStartOfScanMarker();
break;
}
@ -336,58 +334,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.ImageWidth = this.Frame.SamplesPerLine;
this.ImageHeight = this.Frame.Scanlines;
this.components = new PdfJsComponentBlocks { Components = new PdfJsComponent[this.Frame.ComponentCount] };
for (int i = 0; i < this.components.Components.Length; i++)
{
PdfJsFrameComponent frameComponent = this.Frame.Components[i];
var component = new PdfJsComponent
{
Scale = new System.Numerics.Vector2(
frameComponent.HorizontalSamplingFactor / (float)this.Frame.MaxHorizontalFactor,
frameComponent.VerticalSamplingFactor / (float)this.Frame.MaxVerticalFactor),
BlocksPerLine = frameComponent.WidthInBlocks,
BlocksPerColumn = frameComponent.HeightInBlocks
};
// this.QuantizeAndInverseComponentData(ref component, frameComponent);
this.components.Components[i] = component;
}
this.ComponentCount = this.components.Components.Length;
this.ComponentCount = this.Frame.ComponentCount;
}
/// <inheritdoc/>
public void Dispose()
{
this.Frame?.Dispose();
this.components?.Dispose();
// this.quantizationTables?.Dispose();
this.pixelArea.Dispose();
// Set large fields to null.
this.Frame = null;
this.components = null;
// this.quantizationTables = null;
this.dcHuffmanTables = null;
this.acHuffmanTables = null;
}
internal void QuantizeAndInverseAllComponents()
{
for (int i = 0; i < this.components.Components.Length; i++)
{
PdfJsFrameComponent frameComponent = this.Frame.Components[i];
PdfJsComponent component = this.components.Components[i];
this.QuantizeAndInverseComponentData(component, frameComponent);
}
}
/// <summary>
/// Fills the given image with the color data
/// Fills the given image with the color data. TODO: Delete ME!!
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image</param>
@ -400,8 +363,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
}
this.pixelArea = new PdfJsJpegPixelArea(this.configuration.MemoryManager, image.Width, image.Height, this.ComponentCount);
this.pixelArea.LinearizeBlockData(this.components);
// this.pixelArea.LinearizeBlockData(this.components);
if (this.ComponentCount == 1)
{
this.FillGrayScaleImage(image);
@ -433,6 +396,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
}
}
/// <summary>
/// Returns the correct colorspace based on the image component count
/// </summary>
/// <returns>The <see cref="JpegColorSpace"/></returns>
private JpegColorSpace DeduceJpegColorSpace()
{
if (this.ComponentCount == 1)
@ -536,12 +503,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
byte[] profile = new byte[remaining];
this.InputStream.Read(profile, 0, remaining);
if (profile[0] == PdfJsJpegConstants.Markers.Exif.E &&
profile[1] == PdfJsJpegConstants.Markers.Exif.X &&
profile[2] == PdfJsJpegConstants.Markers.Exif.I &&
profile[3] == PdfJsJpegConstants.Markers.Exif.F &&
profile[4] == PdfJsJpegConstants.Markers.Exif.Null &&
profile[5] == PdfJsJpegConstants.Markers.Exif.Null)
if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker))
{
this.isExif = true;
this.MetaData.ExifProfile = new ExifProfile(profile);
@ -566,18 +528,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.InputStream.Read(identifier, 0, Icclength);
remaining -= Icclength; // We have read it by this point
if (identifier[0] == PdfJsJpegConstants.Markers.ICC.I &&
identifier[1] == PdfJsJpegConstants.Markers.ICC.C &&
identifier[2] == PdfJsJpegConstants.Markers.ICC.C &&
identifier[3] == PdfJsJpegConstants.Markers.ICC.UnderScore &&
identifier[4] == PdfJsJpegConstants.Markers.ICC.P &&
identifier[5] == PdfJsJpegConstants.Markers.ICC.R &&
identifier[6] == PdfJsJpegConstants.Markers.ICC.O &&
identifier[7] == PdfJsJpegConstants.Markers.ICC.F &&
identifier[8] == PdfJsJpegConstants.Markers.ICC.I &&
identifier[9] == PdfJsJpegConstants.Markers.ICC.L &&
identifier[10] == PdfJsJpegConstants.Markers.ICC.E &&
identifier[11] == PdfJsJpegConstants.Markers.ICC.Null)
if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker))
{
byte[] profile = new byte[remaining];
this.InputStream.Read(profile, 0, remaining);
@ -880,33 +831,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
successiveApproximation & 15);
}
/// <summary>
/// Build the data for the given component
/// </summary>
/// <param name="component">The component</param>
/// <param name="frameComponent">The frame component</param>
private void QuantizeAndInverseComponentData(PdfJsComponent component, PdfJsFrameComponent frameComponent)
{
int blocksPerLine = component.BlocksPerLine;
int blocksPerColumn = component.BlocksPerColumn;
using (IBuffer<short> computationBuffer = this.configuration.MemoryManager.Allocate<short>(64, true))
{
// ref short quantizationTableRef = ref MemoryMarshal.GetReference(this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationTableIndex));
// ref short computationBufferSpan = ref MemoryMarshal.GetReference(computationBuffer.Span);
//
// for (int blockRow = 0; blockRow < blocksPerColumn; blockRow++)
// {
// for (int blockCol = 0; blockCol < blocksPerLine; blockCol++)
// {
// int offset = 64 * (((blocksPerLine + 1) * blockRow) + blockCol);
// PdfJsIDCT.QuantizeAndInverse(frameComponent, offset, ref computationBufferSpan, ref quantizationTableRef);
// }
// }
}
// component.Output = frameComponent.BlockData;
}
/// <summary>
/// Builds the huffman tables
/// </summary>
@ -1029,6 +953,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
where TPixel : struct, IPixel<TPixel>
{
this.ColorSpace = this.DeduceJpegColorSpace();
this.AssignResolution();
using (var postProcessor = new JpegImagePostProcessor(this.configuration.MemoryManager, this))
{
var image = new Image<TPixel>(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData);

22
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -453,12 +453,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[InlineData(TestImages.Jpeg.Baseline.Ycck, 32)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg400, 8)]
[InlineData(TestImages.Jpeg.Baseline.Snake, 24)]
public void DetectPixelSize(string imagePath, int expectedPixelSize)
public void DetectPixelSizeGolang(string imagePath, int expectedPixelSize)
{
TestFile testFile = TestFile.Create(imagePath);
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel);
Assert.Equal(expectedPixelSize, ((IImageInfoDetector)OrigJpegDecoder).Identify(Configuration.Default, stream)?.PixelType?.BitsPerPixel);
}
}
[Theory]
[InlineData(TestImages.Jpeg.Progressive.Progress, 24)]
[InlineData(TestImages.Jpeg.Progressive.Fb, 24)]
[InlineData(TestImages.Jpeg.Baseline.Cmyk, 32)]
[InlineData(TestImages.Jpeg.Baseline.Ycck, 32)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg400, 8)]
[InlineData(TestImages.Jpeg.Baseline.Snake, 24)]
public void DetectPixelSizePdfJs(string imagePath, int expectedPixelSize)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
Assert.Equal(expectedPixelSize, ((IImageInfoDetector)PdfJsJpegDecoder).Identify(Configuration.Default, stream)?.PixelType?.BitsPerPixel);
}
}
}

6
tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs

@ -117,10 +117,10 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
IEnumerable<ImageSimilarityReport<TPixelA, TPixelB>> reports = comparer.CompareImages(expected, actual);
if (reports.Any())
{
List<ImageSimilarityReport<TPixelA, TPixelB>> cleanedReports = new List<ImageSimilarityReport<TPixelA, TPixelB>>(reports.Count());
foreach (var r in reports)
var cleanedReports = new List<ImageSimilarityReport<TPixelA, TPixelB>>(reports.Count());
foreach (ImageSimilarityReport<TPixelA, TPixelB> r in reports)
{
var outsideChanges = r.Differences.Where(x => !(
IEnumerable<PixelDifference> outsideChanges = r.Differences.Where(x => !(
ignoredRegion.X <= x.Position.X &&
x.Position.X <= ignoredRegion.Right &&
ignoredRegion.Y <= x.Position.Y &&

Loading…
Cancel
Save