diff --git a/src/ImageSharp/Formats/Jpeg/Common/ComponentUtils.cs b/src/ImageSharp/Formats/Jpeg/Common/ComponentUtils.cs
index 7d38d1b507..12ca674287 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/ComponentUtils.cs
+++ b/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;
-
///
/// Various utilities for and .
///
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 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
///
/// Returns the height and width of the chroma components
+ /// TODO: Not needed by new JpegImagePostprocessor
///
/// The subsampling ratio.
/// The width.
/// The height.
/// The of the chrominance channel
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 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)
diff --git a/src/ImageSharp/Formats/Jpeg/Common/IJpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Common/IJpegComponent.cs
index 7161218815..dcd18f9098 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/IJpegComponent.cs
+++ b/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; }
///
- /// Gets the number of blocks per line
+ /// Gets the number of blocks in this component as
///
- int WidthInBlocks { get; }
+ Size SizeInBlocks { get; }
///
- /// Gets the number of blocks per column
+ /// Gets the horizontal and the vertical sampling factor as
///
- int HeightInBlocks { get; }
+ Size SamplingFactors { get; }
///
- /// Gets the horizontal sampling factor.
+ /// Gets the divisors needed to apply when calculating colors.
+ ///
+ /// https://en.wikipedia.org/wiki/Chroma_subsampling
+ ///
+ /// In case of 4:2:0 subsampling the values are: Luma.SubSamplingDivisors = (1,1) Chroma.SubSamplingDivisors = (2,2)
///
- int HorizontalSamplingFactor { get; }
-
- ///
- /// Gets the vertical sampling factor.
- ///
- int VerticalSamplingFactor { get; }
+ Size SubSamplingDivisors { get; }
///
/// Gets the index of the quantization table for this block.
diff --git a/src/ImageSharp/Formats/Jpeg/Common/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Common/IRawJpegData.cs
index 90540384e4..b3d1870d20 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/IRawJpegData.cs
+++ b/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; }
diff --git a/src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegComponentPostProcessor.cs
index bbad6b5776..16071b17cd 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegComponentPostProcessor.cs
+++ b/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(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 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;
diff --git a/src/ImageSharp/Formats/Jpeg/Common/SubsampleRatio.cs b/src/ImageSharp/Formats/Jpeg/Common/SubsampleRatio.cs
index f6f5fbd680..235c2352a3 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/SubsampleRatio.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/SubsampleRatio.cs
@@ -3,6 +3,7 @@
///
/// Provides enumeration of the various available subsample ratios.
/// https://en.wikipedia.org/wiki/Chroma_subsampling
+ /// TODO: Not needed by new JpegImagePostprocessor
///
internal enum SubsampleRatio
{
diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs
index a3f9e4938b..7baf545342 100644
--- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs
+++ b/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
///
private DataPointers pointers;
-
+
///
/// Initialize the instance on the stack.
///
@@ -43,9 +43,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// The component
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);
}
diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs
index 3b5265cfc4..e0694afb46 100644
--- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs
+++ b/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;
+
///
///
/// Represents a single color component
@@ -27,11 +29,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
///
public int Index { get; }
- ///
- public int HorizontalSamplingFactor { get; private set; }
+ public Size SizeInBlocks { get; private set; }
- ///
- 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;
///
public int QuantizationTableIndex { get; private set; }
@@ -45,28 +51,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
///
public Buffer2D SpectralBlocks { get; private set; }
- ///
- public int WidthInBlocks { get; private set; }
-
- ///
- public int HeightInBlocks { get; private set; }
-
///
/// Initializes
///
/// The instance
- 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.CreateClean(this.WidthInBlocks, this.HeightInBlocks);
+ this.SizeInBlocks = decoder.ImageSizeInBlocks.MultiplyBy(this.SamplingFactors);
+
+ this.SpectralBlocks = Buffer2D.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);
+ }
}
///
/// Initializes all component data except .
///
/// The instance
- 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()
diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs
index ec673b6d9b..660418eb0c 100644
--- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs
+++ b/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
diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
index 0643a11300..5f2306a7ea 100644
--- a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
+++ b/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;
-
///
/// Performs the jpeg decoding operation.
///
@@ -143,6 +141,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
public Size ImageSizeInBlocks { get; private set; }
+ public Size ImageSizeInMCU { get; private set; }
+
///
/// Gets the number of color components within the image.
///
@@ -178,12 +178,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
///
/// Gets the number of MCU-s (Minimum Coded Units) in the image along the X axis
///
- public int MCUCountX { get; private set; }
+ public int MCUCountX => this.ImageSizeInMCU.Width;
///
/// Gets the number of MCU-s (Minimum Coded Units) in the image along the Y axis
///
- public int MCUCountY { get; private set; }
+ public int MCUCountY => this.ImageSizeInMCU.Height;
///
/// 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);
- }
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs
index 7b8191458c..3320b8a8c4 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs
+++ b/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
///
public int Pred { get; set; }
- ///
+ ///
+ /// Gets the horizontal sampling factor.
+ ///
public int HorizontalSamplingFactor { get; }
- ///
+ ///
+ /// Gets the vertical sampling factor.
+ ///
public int VerticalSamplingFactor { get; }
Buffer2D IJpegComponent.SpectralBlocks => throw new NotImplementedException();
+ // TODO: Should be derived from PdfJsComponent.Scale
+ public Size SubSamplingDivisors => throw new NotImplementedException();
+
///
public int QuantizationTableIndex { get; }
@@ -54,10 +62,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
///
public int Index { get; }
- ///
+ public Size SizeInBlocks => new Size(this.WidthInBlocks, this.HeightInBlocks);
+
+ public Size SamplingFactors => new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor);
+
+ ///
+ /// Gets the number of blocks per line
+ ///
public int WidthInBlocks { get; private set; }
- ///
+ ///
+ /// Gets the number of blocks per column
+ ///
public int HeightInBlocks { get; private set; }
///
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
similarity index 100%
rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs
rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
diff --git a/src/ImageSharp/Memory/Buffer2D.cs b/src/ImageSharp/Memory/Buffer2D.cs
index 620c32bfcf..cacd3c9f6f 100644
--- a/src/ImageSharp/Memory/Buffer2D.cs
+++ b/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];
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ComponentUtilsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ComponentUtilsTests.cs
index cdaf5fa3b5..053eadf27e 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/ComponentUtilsTests.cs
+++ b/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);
- }
- }
- }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
index 7bfa39ddab..9247a1fdc4 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
+++ b/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]
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs
index ebed368f85..6bc087f9ed 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs
+++ b/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(TestImageProvider provider)
where TPixel : struct, IPixel
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs
index 58923d198e..fed28fda73 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs
+++ b/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);
}
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
new file mode 100644
index 0000000000..dd954c61a2
--- /dev/null
+++ b/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);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs
index 7784dcb17d..a8020ae34b 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs
+++ b/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 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
);
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs
index 7ceb013440..6d0c1ac7e0 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs
+++ b/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 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(TestImageProvider provider, LibJpegTools.SpectralData data, ITestOutputHelper output = null)