Browse Source

several refactors

af/merge-core
Anton Firszov 9 years ago
parent
commit
4974617e11
  1. 69
      src/ImageSharp/Formats/Jpeg/Common/ComponentUtils.cs
  2. 22
      src/ImageSharp/Formats/Jpeg/Common/IJpegComponent.cs
  3. 1
      src/ImageSharp/Formats/Jpeg/Common/IRawJpegData.cs
  4. 23
      src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegComponentPostProcessor.cs
  5. 1
      src/ImageSharp/Formats/Jpeg/Common/SubsampleRatio.cs
  6. 6
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs
  7. 56
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs
  8. 7
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs
  9. 38
      src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
  10. 24
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs
  11. 0
      src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
  12. 3
      src/ImageSharp/Memory/Buffer2D.cs
  13. 77
      tests/ImageSharp.Tests/Formats/Jpg/ComponentUtilsTests.cs
  14. 7
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  15. 1
      tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs
  16. 2
      tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs
  17. 84
      tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
  18. 16
      tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs
  19. 25
      tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs

69
src/ImageSharp/Formats/Jpeg/Common/ComponentUtils.cs

@ -1,17 +1,23 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
using System;
/// <summary>
/// Various utilities for <see cref="SubsampleRatio"/> and <see cref="IJpegComponent"/>.
/// </summary>
internal static class ComponentUtils
{
public static Size SizeInBlocks(this IJpegComponent component) => new Size(component.WidthInBlocks, component.HeightInBlocks);
//public static Size SizeInBlocks(this IJpegComponent component) => new Size(component.WidthInBlocks, component.HeightInBlocks);
// In Jpeg these are really useful operations:
public static Size MultiplyBy(this Size a, Size b) => new Size(a.Width * b.Width, a.Height * b.Height);
public static Size DivideBy(this Size a, Size b) => new Size(a.Width / b.Width, a.Height / b.Height);
public static ref Block8x8 GetBlockReference(this IJpegComponent component, int bx, int by)
{
@ -39,16 +45,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
return SubsampleRatio.Ratio444;
}
// https://en.wikipedia.org/wiki/Chroma_subsampling
public static SubsampleRatio GetSubsampleRatio(IEnumerable<IJpegComponent> components)
{
IJpegComponent[] componentArray = components.ToArray();
if (componentArray.Length == 3)
{
int h0 = componentArray[0].HorizontalSamplingFactor;
int v0 = componentArray[0].VerticalSamplingFactor;
int horizontalRatio = h0 / componentArray[1].HorizontalSamplingFactor;
int verticalRatio = v0 / componentArray[1].VerticalSamplingFactor;
return GetSubsampleRatio(horizontalRatio, verticalRatio);
Size s0 = componentArray[0].SamplingFactors;
Size ratio = s0.DivideBy(componentArray[1].SamplingFactors);
return GetSubsampleRatio(ratio.Width, ratio.Height);
}
else
{
@ -58,40 +64,57 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// <summary>
/// Returns the height and width of the chroma components
/// TODO: Not needed by new JpegImagePostprocessor
/// </summary>
/// <param name="ratio">The subsampling ratio.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <returns>The <see cref="Size"/> of the chrominance channel</returns>
public static Size CalculateChrominanceSize(this SubsampleRatio ratio, int width, int height)
{
(int divX, int divY) = ratio.GetChrominanceSubSampling();
var size = new Size(width, height);
return size.GetSubSampledSize(divX, divY);
}
// TODO: Find a better place for this method
public static Size GetSubSampledSize(this Size originalSize, int divX, int divY)
{
var sizeVect = (Vector2)(SizeF)originalSize;
sizeVect /= new Vector2(divX, divY);
sizeVect.X = MathF.Ceiling(sizeVect.X);
sizeVect.Y = MathF.Ceiling(sizeVect.Y);
return new Size((int)sizeVect.X, (int)sizeVect.Y);
}
public static Size GetSubSampledSize(this Size originalSize, int subsamplingDivisor) =>
GetSubSampledSize(originalSize, subsamplingDivisor, subsamplingDivisor);
// TODO: Not needed by new JpegImagePostprocessor
public static (int divX, int divY) GetChrominanceSubSampling(this SubsampleRatio ratio)
{
switch (ratio)
{
case SubsampleRatio.Ratio422:
return new Size((width + 1) / 2, height);
case SubsampleRatio.Ratio420:
return new Size((width + 1) / 2, (height + 1) / 2);
case SubsampleRatio.Ratio440:
return new Size(width, (height + 1) / 2);
case SubsampleRatio.Ratio411:
return new Size((width + 3) / 4, height);
case SubsampleRatio.Ratio410:
return new Size((width + 3) / 4, (height + 1) / 2);
default:
// Default to 4:4:4 subsampling.
return new Size(width, height);
case SubsampleRatio.Ratio422: return (2, 1);
case SubsampleRatio.Ratio420: return (2, 2);
case SubsampleRatio.Ratio440: return (1, 2);
case SubsampleRatio.Ratio411: return (4, 1);
case SubsampleRatio.Ratio410: return (4, 2);
default: return (1, 1);
}
}
public static bool IsChromaComponent(this IJpegComponent component) =>
component.Index > 0 && component.Index < 3;
// TODO: Not needed by new JpegImagePostprocessor
public static Size[] CalculateJpegChannelSizes(IEnumerable<IJpegComponent> components, SubsampleRatio ratio)
{
IJpegComponent[] c = components.ToArray();
Size[] sizes = new Size[c.Length];
Size s0 = new Size(c[0].WidthInBlocks, c[0].HeightInBlocks) * 8;
Size s0 = c[0].SizeInBlocks * 8;
sizes[0] = s0;
if (c.Length > 1)

22
src/ImageSharp/Formats/Jpeg/Common/IJpegComponent.cs

@ -1,4 +1,5 @@
using SixLabors.ImageSharp.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
@ -13,24 +14,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
int Index { get; }
/// <summary>
/// Gets the number of blocks per line
/// Gets the number of blocks in this component as <see cref="Size"/>
/// </summary>
int WidthInBlocks { get; }
Size SizeInBlocks { get; }
/// <summary>
/// Gets the number of blocks per column
/// Gets the horizontal and the vertical sampling factor as <see cref="Size"/>
/// </summary>
int HeightInBlocks { get; }
Size SamplingFactors { get; }
/// <summary>
/// Gets the horizontal sampling factor.
/// Gets the divisors needed to apply when calculating colors.
/// <see>
/// <cref>https://en.wikipedia.org/wiki/Chroma_subsampling</cref>
/// </see>
/// In case of 4:2:0 subsampling the values are: Luma.SubSamplingDivisors = (1,1) Chroma.SubSamplingDivisors = (2,2)
/// </summary>
int HorizontalSamplingFactor { get; }
/// <summary>
/// Gets the vertical sampling factor.
/// </summary>
int VerticalSamplingFactor { get; }
Size SubSamplingDivisors { get; }
/// <summary>
/// Gets the index of the quantization table for this block.

1
src/ImageSharp/Formats/Jpeg/Common/IRawJpegData.cs

@ -7,6 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
Size ImageSizeInPixels { get; }
// TODO: Kill this
Size ImageSizeInBlocks { get; }
int ComponentCount { get; }

23
src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegComponentPostProcessor.cs

@ -1,11 +1,10 @@
using System;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder;
using SixLabors.ImageSharp.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.PostProcessing
{
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder;
using SixLabors.Primitives;
internal class JpegComponentPostProcessor : IDisposable
{
private int currentComponentRowInBlocks;
@ -18,8 +17,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.PostProcessing
this.ImagePostProcessor = imagePostProcessor;
this.ColorBuffer = new Buffer2D<float>(imagePostProcessor.PostProcessorBufferSize);
this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.VerticalSamplingFactor;
this.blockAreaSize = new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor) * 8;
this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height;
this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
}
public JpegImagePostProcessor ImagePostProcessor { get; }
@ -28,14 +27,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.PostProcessing
public Buffer2D<float> ColorBuffer { get; }
public int BlocksPerRow => this.Component.WidthInBlocks;
public Size SizeInBlocks => this.Component.SizeInBlocks;
public int BlockRowsPerStep { get; }
private int HorizontalSamplingFactor => this.Component.HorizontalSamplingFactor;
private int VerticalSamplingFactor => this.Component.VerticalSamplingFactor;
public void Dispose()
{
this.ColorBuffer.Dispose();
@ -49,9 +44,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.PostProcessing
for (int y = 0; y < this.BlockRowsPerStep; y++)
{
int yBlock = this.currentComponentRowInBlocks + y;
if (yBlock >= this.SizeInBlocks.Height)
{
break;
}
int yBuffer = y * this.blockAreaSize.Height;
for (int x = 0; x < this.BlocksPerRow; x++)
for (int x = 0; x < this.SizeInBlocks.Width; x++)
{
int xBlock = x;
int xBuffer = x * this.blockAreaSize.Width;

1
src/ImageSharp/Formats/Jpeg/Common/SubsampleRatio.cs

@ -3,6 +3,7 @@
/// <summary>
/// Provides enumeration of the various available subsample ratios.
/// https://en.wikipedia.org/wiki/Chroma_subsampling
/// TODO: Not needed by new JpegImagePostprocessor
/// </summary>
internal enum SubsampleRatio
{

6
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs

@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// Pointers to elements of <see cref="data"/>
/// </summary>
private DataPointers pointers;
/// <summary>
/// Initialize the <see cref="JpegBlockPostProcessor"/> instance on the stack.
/// </summary>
@ -43,9 +43,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <param name="component">The component</param>
public void ProcessAllBlocks(OrigJpegDecoderCore decoder, IJpegComponent component)
{
for (int by = 0; by < component.HeightInBlocks; by++)
for (int by = 0; by < component.SizeInBlocks.Height; by++)
{
for (int bx = 0; bx < component.WidthInBlocks; bx++)
for (int bx = 0; bx < component.SizeInBlocks.Width; bx++)
{
this.ProcessBlockColors(decoder, component, bx, by);
}

56
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs

@ -7,6 +7,8 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
using SixLabors.Primitives;
/// <inheritdoc cref="IJpegComponent" />
/// <summary>
/// Represents a single color component
@ -27,11 +29,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <inheritdoc />
public int Index { get; }
/// <inheritdoc />
public int HorizontalSamplingFactor { get; private set; }
public Size SizeInBlocks { get; private set; }
/// <inheritdoc />
public int VerticalSamplingFactor { get; private set; }
public Size SamplingFactors { get; private set; }
public Size SubSamplingDivisors { get; private set; } = new Size(1, 1);
public int HorizontalSamplingFactor => this.SamplingFactors.Width;
public int VerticalSamplingFactor => this.SamplingFactors.Height;
/// <inheritdoc />
public int QuantizationTableIndex { get; private set; }
@ -45,28 +51,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary>
public Buffer2D<Block8x8> SpectralBlocks { get; private set; }
/// <inheritdoc />
public int WidthInBlocks { get; private set; }
/// <inheritdoc />
public int HeightInBlocks { get; private set; }
/// <summary>
/// Initializes <see cref="SpectralBlocks"/>
/// </summary>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param>
public void InitializeBlocks(OrigJpegDecoderCore decoder)
public void InitializeDerivedData(OrigJpegDecoderCore decoder)
{
this.WidthInBlocks = decoder.MCUCountX * this.HorizontalSamplingFactor;
this.HeightInBlocks = decoder.MCUCountY * this.VerticalSamplingFactor;
this.SpectralBlocks = Buffer2D<Block8x8>.CreateClean(this.WidthInBlocks, this.HeightInBlocks);
this.SizeInBlocks = decoder.ImageSizeInBlocks.MultiplyBy(this.SamplingFactors);
this.SpectralBlocks = Buffer2D<Block8x8>.CreateClean(this.SizeInBlocks);
if (decoder.ComponentCount > 1 && (this.Index == 1 || this.Index == 2))
{
Size s0 = decoder.Components[0].SamplingFactors;
this.SubSamplingDivisors = s0.DivideBy(this.SamplingFactors);
}
}
/// <summary>
/// Initializes all component data except <see cref="SpectralBlocks"/>.
/// </summary>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param>
public void InitializeData(OrigJpegDecoderCore decoder)
public void InitializeCoreData(OrigJpegDecoderCore decoder)
{
// Section B.2.2 states that "the value of C_i shall be different from
// the values of C_1 through C_(i-1)".
@ -146,8 +152,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
case 1:
{
// Cb.
if (decoder.Components[0].HorizontalSamplingFactor % h != 0
|| decoder.Components[0].VerticalSamplingFactor % v != 0)
Size s0 = decoder.Components[0].SamplingFactors;
if (s0.Width % h != 0 || s0.Height % v != 0)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
@ -158,8 +166,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
case 2:
{
// Cr.
if (decoder.Components[1].HorizontalSamplingFactor != h
|| decoder.Components[1].VerticalSamplingFactor != v)
Size s1 = decoder.Components[1].SamplingFactors;
if (s1.Width != h || s1.Height != v)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
@ -199,8 +209,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
break;
case 3:
if (decoder.Components[0].HorizontalSamplingFactor != h
|| decoder.Components[0].VerticalSamplingFactor != v)
Size s0 = decoder.Components[0].SamplingFactors;
if (s0.Width != h || s0.Height != v)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
@ -211,8 +222,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
break;
}
this.HorizontalSamplingFactor = h;
this.VerticalSamplingFactor = v;
this.SamplingFactors = new Size(h, v);
}
public void Dispose()

7
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs

@ -149,8 +149,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
for (int scanIndex = 0; scanIndex < this.componentScanCount; scanIndex++)
{
this.ComponentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex;
this.hi = decoder.Components[this.ComponentIndex].HorizontalSamplingFactor;
int vi = decoder.Components[this.ComponentIndex].VerticalSamplingFactor;
OrigComponent component = decoder.Components[this.ComponentIndex];
this.hi = component.HorizontalSamplingFactor;
int vi = component.VerticalSamplingFactor;
for (int j = 0; j < this.hi * vi; j++)
{
@ -172,7 +174,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
}
// Find the block at (bx,by) in the component's buffer:
OrigComponent component = decoder.Components[this.ComponentIndex];
ref Block8x8 blockRefOnHeap = ref component.GetBlockReference(this.bx, this.by);
// Copy block to stack

38
src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs

@ -2,8 +2,10 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
@ -14,13 +16,9 @@ using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.MetaData.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
using System.Collections.Generic;
using System.Numerics;
/// <summary>
/// Performs the jpeg decoding operation.
/// </summary>
@ -143,6 +141,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
public Size ImageSizeInBlocks { get; private set; }
public Size ImageSizeInMCU { get; private set; }
/// <summary>
/// Gets the number of color components within the image.
/// </summary>
@ -178,12 +178,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// <summary>
/// Gets the number of MCU-s (Minimum Coded Units) in the image along the X axis
/// </summary>
public int MCUCountX { get; private set; }
public int MCUCountX => this.ImageSizeInMCU.Width;
/// <summary>
/// Gets the number of MCU-s (Minimum Coded Units) in the image along the Y axis
/// </summary>
public int MCUCountY { get; private set; }
public int MCUCountY => this.ImageSizeInMCU.Height;
/// <summary>
/// Gets the the total number of MCU-s (Minimum Coded Units) in the image.
@ -1178,7 +1178,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
int height = (this.Temp[1] << 8) + this.Temp[2];
int width = (this.Temp[3] << 8) + this.Temp[4];
this.InitSizes(width, height);
this.ImageSizeInPixels = new Size(width, height);
if (this.Temp[5] != this.ComponentCount)
{
@ -1191,33 +1192,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
byte componentIdentifier = this.Temp[6 + (3 * i)];
var component = new OrigComponent(componentIdentifier, i);
component.InitializeData(this);
component.InitializeCoreData(this);
this.Components[i] = component;
}
int h0 = this.Components[0].HorizontalSamplingFactor;
int v0 = this.Components[0].VerticalSamplingFactor;
this.MCUCountX = (this.ImageWidth + (8 * h0) - 1) / (8 * h0);
this.MCUCountY = (this.ImageHeight + (8 * v0) - 1) / (8 * v0);
// As a preparation for parallelizing Scan decoder, we also allocate DecodedBlocks in the non-progressive case!
for (int i = 0; i < this.ComponentCount; i++)
this.ImageSizeInMCU = this.ImageSizeInPixels.GetSubSampledSize(8 * h0, 8 * v0);
foreach (OrigComponent component in this.Components)
{
this.Components[i].InitializeBlocks(this);
component.InitializeDerivedData(this);
}
this.ImageSizeInBlocks = this.Components[0].SizeInBlocks;
this.SubsampleRatio = ComponentUtils.GetSubsampleRatio(this.Components);
}
private void InitSizes(int width, int height)
{
this.ImageSizeInPixels = new Size(width, height);
var sizeInBlocks = (Vector2)(SizeF)this.ImageSizeInPixels;
sizeInBlocks /= 8;
sizeInBlocks.X = MathF.Ceiling(sizeInBlocks.X);
sizeInBlocks.Y = MathF.Ceiling(sizeInBlocks.Y);
this.ImageSizeInBlocks = new Size((int)sizeInBlocks.X, (int)sizeInBlocks.Y);
}
}
}

24
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs

@ -5,6 +5,7 @@ using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
@ -35,14 +36,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
public int Pred { get; set; }
/// <inheritdoc />
/// <summary>
/// Gets the horizontal sampling factor.
/// </summary>
public int HorizontalSamplingFactor { get; }
/// <inheritdoc />
/// <summary>
/// Gets the vertical sampling factor.
/// </summary>
public int VerticalSamplingFactor { get; }
Buffer2D<Block8x8> IJpegComponent.SpectralBlocks => throw new NotImplementedException();
// TODO: Should be derived from PdfJsComponent.Scale
public Size SubSamplingDivisors => throw new NotImplementedException();
/// <inheritdoc />
public int QuantizationTableIndex { get; }
@ -54,10 +62,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <inheritdoc />
public int Index { get; }
/// <inheritdoc />
public Size SizeInBlocks => new Size(this.WidthInBlocks, this.HeightInBlocks);
public Size SamplingFactors => new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor);
/// <summary>
/// Gets the number of blocks per line
/// </summary>
public int WidthInBlocks { get; private set; }
/// <inheritdoc />
/// <summary>
/// Gets the number of blocks per column
/// </summary>
public int HeightInBlocks { get; private set; }
/// <summary>

0
src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs → src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs

3
src/ImageSharp/Memory/Buffer2D.cs

@ -62,6 +62,9 @@ namespace SixLabors.ImageSharp.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
DebugGuard.MustBeLessThan(x, this.Width, nameof(x));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
return ref this.Array[(this.Width * y) + x];
}
}

77
tests/ImageSharp.Tests/Formats/Jpg/ComponentUtilsTests.cs

@ -5,9 +5,7 @@
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using SixLabors.Primitives;
using Xunit;
@ -41,6 +39,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(new Size(400 / expectedDivX, 400 / expectedDivY), size);
}
[Theory]
[InlineData(SubsampleRatio.Ratio410, 4, 2)]
[InlineData(SubsampleRatio.Ratio411, 4, 1)]
[InlineData(SubsampleRatio.Ratio420, 2, 2)]
[InlineData(SubsampleRatio.Ratio422, 2, 1)]
[InlineData(SubsampleRatio.Ratio440, 1, 2)]
[InlineData(SubsampleRatio.Ratio444, 1, 1)]
[InlineData(SubsampleRatio.Undefined, 1, 1)]
internal void GetChrominanceSubSampling(SubsampleRatio ratio, int expectedDivX, int expectedDivY)
{
(int divX, int divY) = ratio.GetChrominanceSubSampling();
Assert.Equal(expectedDivX, divX);
Assert.Equal(expectedDivY, divY);
}
[Theory]
[InlineData(SubsampleRatio.Ratio410, 4)]
[InlineData(SubsampleRatio.Ratio411, 4)]
@ -68,64 +82,5 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
this.Output.WriteLine($"{name}: Stride={channel.Stride}");
}
[Fact]
public void CalculateJpegChannelSizes_Grayscale()
{
using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(TestImages.Jpeg.Baseline.Jpeg400))
{
Size[] sizes = ComponentUtils.CalculateJpegChannelSizes(decoder.Components, decoder.SubsampleRatio);
Assert.Equal(1, sizes.Length);
Size expected = decoder.Components[0].SizeInBlocks() * 8;
Assert.Equal(expected, sizes[0]);
}
}
[Theory]
[InlineData(TestImages.Jpeg.Baseline.Jpeg444, 1, 1)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg420Exif, 2, 2)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, 2, 2)]
public void CalculateJpegChannelSizes_YCbCr(
string imageFile,
int hDiv,
int vDiv)
{
using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile))
{
Size[] s = ComponentUtils.CalculateJpegChannelSizes(decoder.Components, decoder.SubsampleRatio);
Assert.Equal(3, s.Length);
Size ySize = decoder.Components[0].SizeInBlocks() * 8;
Size cSize = ySize;
cSize.Width /= hDiv;
cSize.Height /= vDiv;
Assert.Equal(ySize, s[0]);
Assert.Equal(cSize, s[1]);
Assert.Equal(cSize, s[2]);
}
}
[Theory]
[InlineData(TestImages.Jpeg.Baseline.Ycck)]
[InlineData(TestImages.Jpeg.Baseline.Cmyk)]
public void CalculateJpegChannelSizes_4Chan(string imageFile)
{
using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile))
{
Size[] sizes = ComponentUtils.CalculateJpegChannelSizes(decoder.Components, decoder.SubsampleRatio);
Assert.Equal(4, sizes.Length);
Size expected = decoder.Components[0].SizeInBlocks() * 8;
foreach (Size s in sizes)
{
Assert.Equal(expected, s);
}
}
}
}
}

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

@ -16,6 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using Xunit;
using Xunit.Abstractions;
@ -68,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(TestImages.Jpeg.Progressive.Progress))
{
VerifyJpeg.Components3(decoder.Components, 43, 61, 22, 31, 22, 31);
VerifyJpeg.VerifyComponentSizes3(decoder.Components, 43, 61, 22, 31, 22, 31);
}
}
@ -81,10 +82,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var decoder = new PdfJsJpegDecoderCore(Configuration.Default, new JpegDecoder());
decoder.ParseStream(ms);
VerifyJpeg.Components3(decoder.Frame.Components, 43, 61, 22, 31, 22, 31);
VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 43, 61, 22, 31, 22, 31);
}
}
public const string DecodeBaselineJpegOutputName = "DecodeBaselineJpeg";
[Theory]

1
tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs

@ -55,6 +55,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)]
public void PostProcess<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>

2
tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs

@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// I knew this one well:
if (testImage == TestImages.Jpeg.Progressive.Progress)
{
VerifyJpeg.Components3(data.Components, 43, 61, 22, 31, 22, 31);
VerifyJpeg.VerifyComponentSizes3(data.Components, 43, 61, 22, 31, 22, 31);
}
}
}

84
tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs

@ -0,0 +1,84 @@
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using SixLabors.Primitives;
using Xunit;
using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
public class ParseStreamTests
{
private ITestOutputHelper Output { get; }
public ParseStreamTests(ITestOutputHelper output)
{
this.Output = output;
}
[Fact]
public void ComponentScalingIsCorrect_1ChannelJpeg()
{
using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(TestImages.Jpeg.Baseline.Jpeg400))
{
Assert.Equal(1, decoder.ComponentCount);
Assert.Equal(1, decoder.Components.Length);
Size sizeInBlocks = decoder.ImageSizeInBlocks;
Size expectedSizeInBlocks = decoder.ImageSizeInPixels.GetSubSampledSize(8);
Assert.Equal(expectedSizeInBlocks, sizeInBlocks);
Assert.Equal(sizeInBlocks, decoder.ImageSizeInMCU);
var uniform1 = new Size(1, 1);
OrigComponent c0 = decoder.Components[0];
VerifyJpeg.VerifyComponent(c0, expectedSizeInBlocks, uniform1, uniform1);
}
}
[Theory]
[InlineData(TestImages.Jpeg.Baseline.Jpeg444, 3, 1, 1)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg420Exif, 3, 2, 2)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, 3, 2, 2)]
[InlineData(TestImages.Jpeg.Baseline.Ycck, 4, 1, 1)] // TODO: Find Ycck or Cmyk images with different subsampling
[InlineData(TestImages.Jpeg.Baseline.Cmyk, 4, 1, 1)]
public void ComponentScalingIsCorrect_MultiChannelJpeg(
string imageFile,
int componentCount,
int hDiv,
int vDiv)
{
Size divisor = new Size(hDiv, vDiv);
using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile))
{
Assert.Equal(componentCount, decoder.ComponentCount);
Assert.Equal(componentCount, decoder.Components.Length);
OrigComponent c0 = decoder.Components[0];
OrigComponent c1 = decoder.Components[1];
OrigComponent c2 = decoder.Components[2];
var uniform1 = new Size(1, 1);
Size expectedLumaSizeInBlocks = decoder.ImageSizeInPixels.GetSubSampledSize(8);
Size expectedChromaSizeInBlocks = expectedLumaSizeInBlocks.DivideBy(divisor);
Size expectedLumaSamplingFactors = expectedLumaSizeInBlocks.DivideBy(decoder.ImageSizeInMCU);
Size expectedChromaSamplingFactors = expectedLumaSamplingFactors.DivideBy(divisor);
VerifyJpeg.VerifyComponent(c0, expectedLumaSizeInBlocks, expectedLumaSamplingFactors, uniform1);
VerifyJpeg.VerifyComponent(c1, expectedChromaSizeInBlocks, expectedChromaSamplingFactors, divisor);
VerifyJpeg.VerifyComponent(c2, expectedChromaSizeInBlocks, expectedChromaSamplingFactors, divisor);
if (componentCount == 4)
{
OrigComponent c3 = decoder.Components[2];
VerifyJpeg.VerifyComponent(c3, expectedLumaSizeInBlocks, expectedLumaSamplingFactors, uniform1);
}
}
}
}
}

16
tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs

@ -26,14 +26,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
public int Index { get; }
public int HeightInBlocks { get; }
public int WidthInBlocks { get; }
public Size SizeInBlocks => new Size(this.WidthInBlocks, this.HeightInBlocks);
public int HorizontalSamplingFactor => throw new NotSupportedException();
public Size SamplingFactors => throw new NotSupportedException();
public int VerticalSamplingFactor => throw new NotSupportedException();
public Size SubSamplingDivisors => throw new NotSupportedException();
public int HeightInBlocks { get; }
public int WidthInBlocks { get; }
public int QuantizationTableIndex => throw new NotSupportedException();
public Buffer2D<Block8x8> SpectralBlocks { get; private set; }
@ -72,8 +74,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
public static ComponentData Load(OrigComponent c)
{
var result = new ComponentData(
c.HeightInBlocks,
c.WidthInBlocks,
c.SizeInBlocks.Width,
c.SizeInBlocks.Height,
c.Index
);

25
tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs

@ -5,19 +5,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
using Xunit;
using Xunit.Abstractions;
internal static class VerifyJpeg
{
internal static void ComponentSize(IJpegComponent component, int expectedBlocksX, int expectedBlocksY)
internal static void VerifySize(IJpegComponent component, int expectedBlocksX, int expectedBlocksY)
{
Assert.Equal(component.WidthInBlocks, expectedBlocksX);
Assert.Equal(component.HeightInBlocks, expectedBlocksY);
Assert.Equal(new Size(expectedBlocksX, expectedBlocksY), component.SizeInBlocks);
}
internal static void Components3(
internal static void VerifyComponent(
IJpegComponent component,
Size expectedSizeInBlocks,
Size expectedSamplingFactors,
Size expectedSubsamplingDivisors)
{
Assert.Equal(expectedSizeInBlocks, component.SizeInBlocks);
Assert.Equal(expectedSamplingFactors, component.SamplingFactors);
Assert.Equal(expectedSubsamplingDivisors, component.SubSamplingDivisors);
}
internal static void VerifyComponentSizes3(
IEnumerable<IJpegComponent> components,
int xBc0, int yBc0,
int xBc1, int yBc1,
@ -26,9 +37,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
IJpegComponent[] c = components.ToArray();
Assert.Equal(3, components.Count());
ComponentSize(c[0], xBc0, yBc0);
ComponentSize(c[1], xBc1, yBc1);
ComponentSize(c[2], xBc2, yBc2);
VerifySize(c[0], xBc0, yBc0);
VerifySize(c[1], xBc1, yBc1);
VerifySize(c[2], xBc2, yBc2);
}
internal static void SaveSpectralImage<TPixel>(TestImageProvider<TPixel> provider, LibJpegTools.SpectralData data, ITestOutputHelper output = null)

Loading…
Cancel
Save