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)