diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs index 2442c3998..789673a4a 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs @@ -49,24 +49,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// public int VerticalSamplingFactor { get; } - Buffer2D IJpegComponent.SpectralBlocks => throw new NotImplementedException(); + /// + public Buffer2D SpectralBlocks { get; private set; } - // TODO: Should be derived from PdfJsComponent.Scale - public Size SubSamplingDivisors => throw new NotImplementedException(); + /// + public Size SubSamplingDivisors { get; private set; } /// public int QuantizationTableIndex { get; } - /// - /// Gets the block data - /// - public IBuffer BlockData { get; private set; } - /// public int Index { get; } + /// public Size SizeInBlocks => new Size(this.WidthInBlocks, this.HeightInBlocks); + /// public Size SamplingFactors => new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor); /// @@ -98,8 +96,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// public void Dispose() { - this.BlockData?.Dispose(); - this.BlockData = null; + this.SpectralBlocks?.Dispose(); + this.SpectralBlocks = null; } public void Init() @@ -113,10 +111,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components this.BlocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor; this.BlocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor; - int blocksBufferSize = 64 * this.BlocksPerColumnForMcu * (this.BlocksPerLineForMcu + 1); - - // Pooled. Disposed via frame disposal - this.BlockData = this.memoryManager.Allocate(blocksBufferSize, true); + // For 4-component images (either CMYK or YCbCrK), we only support two + // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22]. + // Theoretically, 4-component JPEG images could mix and match hv values + // but in practice, those two combinations are the only ones in use, + // and it simplifies the applyBlack code below if we can assume that: + // - for CMYK, the C and K channels have full samples, and if the M + // and Y channels subsample, they subsample both horizontally and + // vertically. + // - for YCbCrK, the Y and K channels have full samples. + if (this.Index == 0 || this.Index == 3) + { + this.SubSamplingDivisors = new Size(1, 1); + } + else + { + // TODO: Check division accuracy here. May need to divide by float + this.SubSamplingDivisors = this.SamplingFactors.DivideBy(new Size(this.Frame.MaxHorizontalFactor, this.Frame.MaxVerticalFactor)); + } + + this.SpectralBlocks = this.memoryManager.Allocate2D(this.SizeInBlocks.Width, this.SizeInBlocks.Height, true); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -124,11 +138,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { return 64 * (((this.WidthInBlocks + 1) * row) + col); } - - public Span GetBlockBuffer(int row, int col) - { - int offset = this.GetBlockBufferOffset(row, col); - return this.BlockData.Span.Slice(offset, 64); - } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs deleted file mode 100644 index 97c582dc0..000000000 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components -{ - /// - /// Performs the inverse Descrete Cosine Transform on each frame component. - /// - internal static class PdfJsIDCT - { - private const int DctCos1 = 4017; // cos(pi/16) - private const int DctSin1 = 799; // sin(pi/16) - private const int DctCos3 = 3406; // cos(3*pi/16) - private const int DctSin3 = 2276; // sin(3*pi/16) - private const int DctCos6 = 1567; // cos(6*pi/16) - private const int DctSin6 = 3784; // sin(6*pi/16) - private const int DctSqrt2 = 5793; // sqrt(2) - private const int DctSqrt1D2 = 2896; // sqrt(2) / 2 - private const int MaxJSample = 255; - private const int CenterJSample = 128; - - /// - /// A port of Poppler's IDCT method which in turn is taken from: - /// Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz, - /// 'Practical Fast 1-D DCT Algorithms with 11 Multiplications', - /// IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, 988-991. - /// - /// The frame component - /// The block buffer offset - /// The computational buffer for holding temp values ref - /// The quantization table ref - public static void QuantizeAndInverse(PdfJsFrameComponent component, int blockBufferOffset, ref short computationBufferRef, ref short quantizationTableRef) - { - ref short blockDataRef = ref MemoryMarshal.GetReference(component.BlockData.Slice(blockBufferOffset)); - int v0, v1, v2, v3, v4, v5, v6, v7; - int p0, p1, p2, p3, p4, p5, p6, p7; - int t; - - // inverse DCT on rows - for (int row = 0; row < 64; row += 8) - { - int r1 = row + 1; - int r2 = row + 2; - int r3 = row + 3; - int r4 = row + 4; - int r5 = row + 5; - int r6 = row + 6; - int r7 = row + 7; - - // gather block data - p0 = Unsafe.Add(ref blockDataRef, row); - p1 = Unsafe.Add(ref blockDataRef, r1); - p2 = Unsafe.Add(ref blockDataRef, r2); - p3 = Unsafe.Add(ref blockDataRef, r3); - p4 = Unsafe.Add(ref blockDataRef, r4); - p5 = Unsafe.Add(ref blockDataRef, r5); - p6 = Unsafe.Add(ref blockDataRef, r6); - p7 = Unsafe.Add(ref blockDataRef, r7); - - // dequant p0 - p0 *= Unsafe.Add(ref quantizationTableRef, row); - - // check for all-zero AC coefficients - if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) - { - t = ((DctSqrt2 * p0) + 512) >> 10; - short st = (short)t; - Unsafe.Add(ref computationBufferRef, row) = st; - Unsafe.Add(ref computationBufferRef, r1) = st; - Unsafe.Add(ref computationBufferRef, r2) = st; - Unsafe.Add(ref computationBufferRef, r3) = st; - Unsafe.Add(ref computationBufferRef, r4) = st; - Unsafe.Add(ref computationBufferRef, r5) = st; - Unsafe.Add(ref computationBufferRef, r6) = st; - Unsafe.Add(ref computationBufferRef, r7) = st; - continue; - } - - // dequant p1 ... p7 - p1 *= Unsafe.Add(ref quantizationTableRef, r1); - p2 *= Unsafe.Add(ref quantizationTableRef, r2); - p3 *= Unsafe.Add(ref quantizationTableRef, r3); - p4 *= Unsafe.Add(ref quantizationTableRef, r4); - p5 *= Unsafe.Add(ref quantizationTableRef, r5); - p6 *= Unsafe.Add(ref quantizationTableRef, r6); - p7 *= Unsafe.Add(ref quantizationTableRef, r7); - - // stage 4 - v0 = ((DctSqrt2 * p0) + CenterJSample) >> 8; - v1 = ((DctSqrt2 * p4) + CenterJSample) >> 8; - v2 = p2; - v3 = p6; - v4 = ((DctSqrt1D2 * (p1 - p7)) + CenterJSample) >> 8; - v7 = ((DctSqrt1D2 * (p1 + p7)) + CenterJSample) >> 8; - v5 = p3 << 4; - v6 = p5 << 4; - - // stage 3 - v0 = (v0 + v1 + 1) >> 1; - v1 = v0 - v1; - t = ((v2 * DctSin6) + (v3 * DctCos6) + CenterJSample) >> 8; - v2 = ((v2 * DctCos6) - (v3 * DctSin6) + CenterJSample) >> 8; - v3 = t; - v4 = (v4 + v6 + 1) >> 1; - v6 = v4 - v6; - v7 = (v7 + v5 + 1) >> 1; - v5 = v7 - v5; - - // stage 2 - v0 = (v0 + v3 + 1) >> 1; - v3 = v0 - v3; - v1 = (v1 + v2 + 1) >> 1; - v2 = v1 - v2; - t = ((v4 * DctSin3) + (v7 * DctCos3) + 2048) >> 12; - v4 = ((v4 * DctCos3) - (v7 * DctSin3) + 2048) >> 12; - v7 = t; - t = ((v5 * DctSin1) + (v6 * DctCos1) + 2048) >> 12; - v5 = ((v5 * DctCos1) - (v6 * DctSin1) + 2048) >> 12; - v6 = t; - - // stage 1 - Unsafe.Add(ref computationBufferRef, row) = (short)(v0 + v7); - Unsafe.Add(ref computationBufferRef, row + 7) = (short)(v0 - v7); - Unsafe.Add(ref computationBufferRef, row + 1) = (short)(v1 + v6); - Unsafe.Add(ref computationBufferRef, row + 6) = (short)(v1 - v6); - Unsafe.Add(ref computationBufferRef, row + 2) = (short)(v2 + v5); - Unsafe.Add(ref computationBufferRef, row + 5) = (short)(v2 - v5); - Unsafe.Add(ref computationBufferRef, row + 3) = (short)(v3 + v4); - Unsafe.Add(ref computationBufferRef, row + 4) = (short)(v3 - v4); - } - - // inverse DCT on columns - for (int col = 0; col < 8; ++col) - { - int c8 = col + 8; - int c16 = col + 16; - int c24 = col + 24; - int c32 = col + 32; - int c40 = col + 40; - int c48 = col + 48; - int c56 = col + 56; - - p0 = Unsafe.Add(ref computationBufferRef, col); - p1 = Unsafe.Add(ref computationBufferRef, c8); - p2 = Unsafe.Add(ref computationBufferRef, c16); - p3 = Unsafe.Add(ref computationBufferRef, c24); - p4 = Unsafe.Add(ref computationBufferRef, c32); - p5 = Unsafe.Add(ref computationBufferRef, c40); - p6 = Unsafe.Add(ref computationBufferRef, c48); - p7 = Unsafe.Add(ref computationBufferRef, c56); - - // check for all-zero AC coefficients - if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) - { - t = ((DctSqrt2 * p0) + 8192) >> 14; - - // convert to 8 bit - t = (t < -2040) ? 0 : (t >= 2024) ? MaxJSample : (t + 2056) >> 4; - short st = (short)t; - - Unsafe.Add(ref blockDataRef, col) = st; - Unsafe.Add(ref blockDataRef, c8) = st; - Unsafe.Add(ref blockDataRef, c16) = st; - Unsafe.Add(ref blockDataRef, c24) = st; - Unsafe.Add(ref blockDataRef, c32) = st; - Unsafe.Add(ref blockDataRef, c40) = st; - Unsafe.Add(ref blockDataRef, c48) = st; - Unsafe.Add(ref blockDataRef, c56) = st; - continue; - } - - // stage 4 - v0 = ((DctSqrt2 * p0) + 2048) >> 12; - v1 = ((DctSqrt2 * p4) + 2048) >> 12; - v2 = p2; - v3 = p6; - v4 = ((DctSqrt1D2 * (p1 - p7)) + 2048) >> 12; - v7 = ((DctSqrt1D2 * (p1 + p7)) + 2048) >> 12; - v5 = p3; - v6 = p5; - - // stage 3 - // Shift v0 by 128.5 << 5 here, so we don't need to shift p0...p7 when - // converting to UInt8 range later. - v0 = ((v0 + v1 + 1) >> 1) + 4112; - v1 = v0 - v1; - t = ((v2 * DctSin6) + (v3 * DctCos6) + 2048) >> 12; - v2 = ((v2 * DctCos6) - (v3 * DctSin6) + 2048) >> 12; - v3 = t; - v4 = (v4 + v6 + 1) >> 1; - v6 = v4 - v6; - v7 = (v7 + v5 + 1) >> 1; - v5 = v7 - v5; - - // stage 2 - v0 = (v0 + v3 + 1) >> 1; - v3 = v0 - v3; - v1 = (v1 + v2 + 1) >> 1; - v2 = v1 - v2; - t = ((v4 * DctSin3) + (v7 * DctCos3) + 2048) >> 12; - v4 = ((v4 * DctCos3) - (v7 * DctSin3) + 2048) >> 12; - v7 = t; - t = ((v5 * DctSin1) + (v6 * DctCos1) + 2048) >> 12; - v5 = ((v5 * DctCos1) - (v6 * DctSin1) + 2048) >> 12; - v6 = t; - - // stage 1 - p0 = v0 + v7; - p7 = v0 - v7; - p1 = v1 + v6; - p6 = v1 - v6; - p2 = v2 + v5; - p5 = v2 - v5; - p3 = v3 + v4; - p4 = v3 - v4; - - // convert to 8-bit integers - p0 = (p0 < 16) ? 0 : (p0 >= 4080) ? MaxJSample : p0 >> 4; - p1 = (p1 < 16) ? 0 : (p1 >= 4080) ? MaxJSample : p1 >> 4; - p2 = (p2 < 16) ? 0 : (p2 >= 4080) ? MaxJSample : p2 >> 4; - p3 = (p3 < 16) ? 0 : (p3 >= 4080) ? MaxJSample : p3 >> 4; - p4 = (p4 < 16) ? 0 : (p4 >= 4080) ? MaxJSample : p4 >> 4; - p5 = (p5 < 16) ? 0 : (p5 >= 4080) ? MaxJSample : p5 >> 4; - p6 = (p6 < 16) ? 0 : (p6 >= 4080) ? MaxJSample : p6 >> 4; - p7 = (p7 < 16) ? 0 : (p7 >= 4080) ? MaxJSample : p7 >> 4; - - // store block data - Unsafe.Add(ref blockDataRef, col) = Unsafe.As(ref Unsafe.AsRef(p0)); - Unsafe.Add(ref blockDataRef, c8) = Unsafe.As(ref Unsafe.AsRef(p1)); - Unsafe.Add(ref blockDataRef, c16) = Unsafe.As(ref Unsafe.AsRef(p2)); - Unsafe.Add(ref blockDataRef, c24) = Unsafe.As(ref Unsafe.AsRef(p3)); - Unsafe.Add(ref blockDataRef, c32) = Unsafe.As(ref Unsafe.AsRef(p4)); - Unsafe.Add(ref blockDataRef, c40) = Unsafe.As(ref Unsafe.AsRef(p5)); - Unsafe.Add(ref blockDataRef, c48) = Unsafe.As(ref Unsafe.AsRef(p6)); - Unsafe.Add(ref blockDataRef, c56) = Unsafe.As(ref Unsafe.AsRef(p7)); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs index fe80cbaf3..b2c80ce9a 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats.Jpeg.Common; namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { @@ -202,7 +203,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components if (componentsLength == 1) { PdfJsFrameComponent component = components[this.compIndex]; - ref short blockDataRef = ref MemoryMarshal.GetReference(component.BlockData.Span); + + // TODO: This is where our error is happening. + // We can't simply cast the span as I think the scan decoder expects data to be laid out in linear order + // rather than in the column major order expected by the Block8x8 struct and anything reading it down the pipeline. + // Ask Anton about this. It might be a lost cause. + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; @@ -224,7 +230,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components for (int i = 0; i < componentsLength; i++) { PdfJsFrameComponent component = components[i]; - ref short blockDataRef = ref MemoryMarshal.GetReference(component.BlockData.Span); + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; int h = component.HorizontalSamplingFactor; @@ -262,7 +268,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components if (componentsLength == 1) { PdfJsFrameComponent component = components[this.compIndex]; - ref short blockDataRef = ref MemoryMarshal.GetReference(component.BlockData.Span); + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; for (int n = 0; n < mcuToRead; n++) @@ -283,7 +289,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components for (int i = 0; i < componentsLength; i++) { PdfJsFrameComponent component = components[i]; - ref short blockDataRef = ref MemoryMarshal.GetReference(component.BlockData.Span); + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; @@ -319,7 +325,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components if (componentsLength == 1) { PdfJsFrameComponent component = components[this.compIndex]; - ref short blockDataRef = ref MemoryMarshal.GetReference(component.BlockData.Span); + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); for (int n = 0; n < mcuToRead; n++) { @@ -341,7 +347,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components PdfJsFrameComponent component = components[i]; int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; - ref short blockDataRef = ref MemoryMarshal.GetReference(component.BlockData.Span); + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); for (int j = 0; j < v; j++) { @@ -375,7 +381,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components if (componentsLength == 1) { PdfJsFrameComponent component = components[this.compIndex]; - ref short blockDataRef = ref MemoryMarshal.GetReference(component.BlockData.Span); + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; for (int n = 0; n < mcuToRead; n++) @@ -396,7 +402,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components for (int i = 0; i < componentsLength; i++) { PdfJsFrameComponent component = components[i]; - ref short blockDataRef = ref MemoryMarshal.GetReference(component.BlockData.Span); + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; @@ -433,7 +439,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components if (componentsLength == 1) { PdfJsFrameComponent component = components[this.compIndex]; - ref short blockDataRef = ref MemoryMarshal.GetReference(component.BlockData.Span); + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; for (int n = 0; n < mcuToRead; n++) @@ -454,7 +460,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components for (int i = 0; i < componentsLength; i++) { PdfJsFrameComponent component = components[i]; - ref short blockDataRef = ref MemoryMarshal.GetReference(component.BlockData.Span); + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index 9572b7b0e..37605d71f 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -3,10 +3,12 @@ using System; using System.Buffers.Binary; +using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Jpeg.Common; using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components; using SixLabors.ImageSharp.Memory; @@ -15,6 +17,7 @@ using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.MetaData.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; +using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort { @@ -22,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// Performs the jpeg decoding operation. /// Ported from with additional fixes to handle common encoding errors /// - internal sealed class PdfJsJpegDecoderCore : IDisposable + internal sealed class PdfJsJpegDecoderCore : IRawJpegData { /// /// The only supported precision @@ -42,8 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort private readonly byte[] markerBuffer = new byte[2]; - private PdfJsQuantizationTables quantizationTables; - + // private PdfJsQuantizationTables quantizationTables; private PdfJsHuffmanTables dcHuffmanTables; private PdfJsHuffmanTables acHuffmanTables; @@ -103,15 +105,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// public int ImageHeight { get; private set; } - /// - /// Gets the number of components - /// - public int NumberOfComponents { get; private set; } - /// /// Gets the color depth, in number of bits per pixel. /// - public int BitsPerPixel => this.NumberOfComponents * SupportedPrecision; + public int BitsPerPixel => this.ComponentCount * SupportedPrecision; /// /// Gets the input stream. @@ -128,6 +125,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// public ImageMetaData MetaData { get; private set; } + /// + public Size ImageSizeInPixels => new Size(this.ImageWidth, this.ImageHeight); + + /// + public int ComponentCount { get; private set; } + + /// + public JpegColorSpace ColorSpace { get; private set; } + + /// + public IEnumerable Components => this.Frame.Components; + + public Block8x8F[] QuantizationTables { get; private set; } + /// /// Finds the next file marker within the byte stream. /// @@ -174,10 +185,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort where TPixel : struct, IPixel { this.ParseStream(stream); - this.QuantizeAndInverseAllComponents(); - var image = new Image(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData); - this.FillPixelData(image.Frames.RootFrame); + Image image = this.PostProcessIntoImage(); + + // this.QuantizeAndInverseAllComponents(); + // var image = new Image(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData); + // this.FillPixelData(image.Frames.RootFrame); this.AssignResolution(); return image; } @@ -213,7 +226,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort ushort marker = this.ReadUint16(); fileMarker = new PdfJsFileMarker(marker, (int)this.InputStream.Position - 2); - this.quantizationTables = new PdfJsQuantizationTables(this.configuration.MemoryManager); + this.QuantizationTables = new Block8x8F[4]; + + // this.quantizationTables = new PdfJsQuantizationTables(this.configuration.MemoryManager); this.dcHuffmanTables = new PdfJsHuffmanTables(); this.acHuffmanTables = new PdfJsHuffmanTables(); @@ -339,7 +354,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort this.components.Components[i] = component; } - this.NumberOfComponents = this.components.Components.Length; + this.ComponentCount = this.components.Components.Length; } /// @@ -347,13 +362,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort { this.Frame?.Dispose(); this.components?.Dispose(); - this.quantizationTables?.Dispose(); + + // this.quantizationTables?.Dispose(); this.pixelArea.Dispose(); // Set large fields to null. this.Frame = null; this.components = null; - this.quantizationTables = null; + + // this.quantizationTables = null; this.dcHuffmanTables = null; this.acHuffmanTables = null; } @@ -377,21 +394,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort private void FillPixelData(ImageFrame image) where TPixel : struct, IPixel { - if (this.NumberOfComponents > 4) + if (this.ComponentCount > 4) { - throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.NumberOfComponents}"); + throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.ComponentCount}"); } - this.pixelArea = new PdfJsJpegPixelArea(this.configuration.MemoryManager, image.Width, image.Height, this.NumberOfComponents); + this.pixelArea = new PdfJsJpegPixelArea(this.configuration.MemoryManager, image.Width, image.Height, this.ComponentCount); this.pixelArea.LinearizeBlockData(this.components); - if (this.NumberOfComponents == 1) + if (this.ComponentCount == 1) { this.FillGrayScaleImage(image); return; } - if (this.NumberOfComponents == 3) + if (this.ComponentCount == 3) { if (this.adobe.Equals(default) || this.adobe.ColorTransform == PdfJsJpegConstants.Markers.Adobe.ColorTransformYCbCr) { @@ -403,7 +420,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort } } - if (this.NumberOfComponents == 4) + if (this.ComponentCount == 4) { if (this.adobe.ColorTransform == PdfJsJpegConstants.Markers.Adobe.ColorTransformYcck) { @@ -416,6 +433,40 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort } } + private JpegColorSpace DeduceJpegColorSpace() + { + if (this.ComponentCount == 1) + { + return JpegColorSpace.Grayscale; + } + + if (this.ComponentCount == 3) + { + if (this.adobe.Equals(default) || this.adobe.ColorTransform == PdfJsJpegConstants.Markers.Adobe.ColorTransformYCbCr) + { + return JpegColorSpace.YCbCr; + } + else if (this.adobe.ColorTransform == PdfJsJpegConstants.Markers.Adobe.ColorTransformUnknown) + { + return JpegColorSpace.RGB; + } + } + + if (this.ComponentCount == 4) + { + if (this.adobe.ColorTransform == PdfJsJpegConstants.Markers.Adobe.ColorTransformYcck) + { + return JpegColorSpace.Ycck; + } + else + { + return JpegColorSpace.Cmyk; + } + } + + throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.ComponentCount}"); + } + /// /// Assigns the horizontal and vertical resolution to the image if it has a JFIF header or EXIF metadata. /// @@ -602,10 +653,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort this.InputStream.Read(this.temp, 0, 64); remaining -= 64; - ref short tableRef = ref MemoryMarshal.GetReference(this.quantizationTables.Tables.GetRowSpan(quantizationTableSpec & 15)); + Block8x8F table = this.QuantizationTables[quantizationTableSpec & 15]; for (int j = 0; j < 64; j++) { - Unsafe.Add(ref tableRef, PdfJsQuantizationTables.DctZigZag[j]) = this.temp[j]; + table[j] = this.temp[j]; } } @@ -622,10 +673,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort this.InputStream.Read(this.temp, 0, 128); remaining -= 128; - ref short tableRef = ref MemoryMarshal.GetReference(this.quantizationTables.Tables.GetRowSpan(quantizationTableSpec & 15)); + Block8x8F table = this.QuantizationTables[quantizationTableSpec & 15]; for (int j = 0; j < 64; j++) { - Unsafe.Add(ref tableRef, PdfJsQuantizationTables.DctZigZag[j]) = (short)((this.temp[2 * j] << 8) | this.temp[(2 * j) + 1]); + table[j] = (this.temp[2 * j] << 8) | this.temp[(2 * j) + 1]; } } @@ -840,20 +891,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort int blocksPerColumn = component.BlocksPerColumn; using (IBuffer computationBuffer = this.configuration.MemoryManager.Allocate(64, true)) { - ref short quantizationTableRef = ref MemoryMarshal.GetReference(this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationTableIndex)); - ref short computationBufferSpan = ref MemoryMarshal.GetReference(computationBuffer.Span); - - for (int blockRow = 0; blockRow < blocksPerColumn; blockRow++) - { - for (int blockCol = 0; blockCol < blocksPerLine; blockCol++) - { - int offset = 64 * (((blocksPerLine + 1) * blockRow) + blockCol); - PdfJsIDCT.QuantizeAndInverse(frameComponent, offset, ref computationBufferSpan, ref quantizationTableRef); - } - } + // ref short quantizationTableRef = ref MemoryMarshal.GetReference(this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationTableIndex)); + // ref short computationBufferSpan = ref MemoryMarshal.GetReference(computationBuffer.Span); + // + // for (int blockRow = 0; blockRow < blocksPerColumn; blockRow++) + // { + // for (int blockCol = 0; blockCol < blocksPerLine; blockCol++) + // { + // int offset = 64 * (((blocksPerLine + 1) * blockRow) + blockCol); + // PdfJsIDCT.QuantizeAndInverse(frameComponent, offset, ref computationBufferSpan, ref quantizationTableRef); + // } + // } } - component.Output = frameComponent.BlockData; + // component.Output = frameComponent.BlockData; } /// @@ -980,5 +1031,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort this.InputStream.Read(this.markerBuffer, 0, 2); return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer); } + + private Image PostProcessIntoImage() + where TPixel : struct, IPixel + { + this.ColorSpace = this.DeduceJpegColorSpace(); + using (var postProcessor = new JpegImagePostProcessor(this.configuration.MemoryManager, this)) + { + var image = new Image(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData); + postProcessor.PostProcess(image.Frames.RootFrame); + return image; + } + } } } \ 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 30f008886..4cfaa6e61 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -39,9 +39,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils 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; } @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public short MinVal { get; private set; } = short.MaxValue; public short MaxVal { get; private set; } = short.MinValue; - + internal void MakeBlock(short[] data, int y, int x) { this.MinVal = Math.Min((short)this.MinVal, data.Min()); @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { for (int x = 0; x < result.WidthInBlocks; x++) { - short[] data = c.GetBlockBuffer(y, x).ToArray(); + short[] data = c.GetBlockReference(x, y).ToArray(); result.MakeBlock(data, y, x); } } @@ -100,7 +100,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public Image CreateGrayScaleImage() { Image result = new Image(this.WidthInBlocks * 8, this.HeightInBlocks * 8); - + for (int by = 0; by < this.HeightInBlocks; by++) { for (int bx = 0; bx < this.WidthInBlocks; bx++) @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils internal void WriteToImage(int bx, int by, Image image) { Block8x8 block = this.SpectralBlocks[bx, by]; - + for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++)