mirror of https://github.com/SixLabors/ImageSharp
967 changed files with 38735 additions and 5356 deletions
@ -1 +1 @@ |
|||||
Subproject commit 9b94ebc4be9b7a8d7620c257e6ee485455973332 |
Subproject commit a042aba176cdb840d800c6ed4cfe41a54fb7b1e3 |
||||
@ -0,0 +1,21 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Common.Helpers |
||||
|
{ |
||||
|
internal readonly struct ExifResolutionValues |
||||
|
{ |
||||
|
public ExifResolutionValues(ushort resolutionUnit, double? horizontalResolution, double? verticalResolution) |
||||
|
{ |
||||
|
this.ResolutionUnit = resolutionUnit; |
||||
|
this.HorizontalResolution = horizontalResolution; |
||||
|
this.VerticalResolution = verticalResolution; |
||||
|
} |
||||
|
|
||||
|
public ushort ResolutionUnit { get; } |
||||
|
|
||||
|
public double? HorizontalResolution { get; } |
||||
|
|
||||
|
public double? VerticalResolution { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,149 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
#if SUPPORTS_RUNTIME_INTRINSICS
|
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using System.Runtime.Intrinsics.X86; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components |
||||
|
{ |
||||
|
internal partial struct Block8x8F |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A number of rows of 8 scalar coefficients each in <see cref="Block8x8F"/>
|
||||
|
/// </summary>
|
||||
|
public const int RowCount = 8; |
||||
|
|
||||
|
[FieldOffset(0)] |
||||
|
public Vector256<float> V0; |
||||
|
[FieldOffset(32)] |
||||
|
public Vector256<float> V1; |
||||
|
[FieldOffset(64)] |
||||
|
public Vector256<float> V2; |
||||
|
[FieldOffset(96)] |
||||
|
public Vector256<float> V3; |
||||
|
[FieldOffset(128)] |
||||
|
public Vector256<float> V4; |
||||
|
[FieldOffset(160)] |
||||
|
public Vector256<float> V5; |
||||
|
[FieldOffset(192)] |
||||
|
public Vector256<float> V6; |
||||
|
[FieldOffset(224)] |
||||
|
public Vector256<float> V7; |
||||
|
|
||||
|
private static readonly Vector256<int> MultiplyIntoInt16ShuffleMask = Vector256.Create(0, 1, 4, 5, 2, 3, 6, 7); |
||||
|
|
||||
|
private static unsafe void MultiplyIntoInt16_Avx2(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(Avx2.IsSupported, "Avx2 support is required to run this operation!"); |
||||
|
|
||||
|
ref Vector256<float> aBase = ref a.V0; |
||||
|
ref Vector256<float> bBase = ref b.V0; |
||||
|
|
||||
|
ref Vector256<short> destRef = ref dest.V01; |
||||
|
|
||||
|
for (nint i = 0; i < 8; i += 2) |
||||
|
{ |
||||
|
Vector256<int> row0 = Avx.ConvertToVector256Int32(Avx.Multiply(Unsafe.Add(ref aBase, i + 0), Unsafe.Add(ref bBase, i + 0))); |
||||
|
Vector256<int> row1 = Avx.ConvertToVector256Int32(Avx.Multiply(Unsafe.Add(ref aBase, i + 1), Unsafe.Add(ref bBase, i + 1))); |
||||
|
|
||||
|
Vector256<short> row = Avx2.PackSignedSaturate(row0, row1); |
||||
|
row = Avx2.PermuteVar8x32(row.AsInt32(), MultiplyIntoInt16ShuffleMask).AsInt16(); |
||||
|
|
||||
|
Unsafe.Add(ref destRef, (IntPtr)((uint)i / 2)) = row; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void MultiplyIntoInt16_Sse2(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(Sse2.IsSupported, "Sse2 support is required to run this operation!"); |
||||
|
|
||||
|
ref Vector128<float> aBase = ref Unsafe.As<Block8x8F, Vector128<float>>(ref a); |
||||
|
ref Vector128<float> bBase = ref Unsafe.As<Block8x8F, Vector128<float>>(ref b); |
||||
|
|
||||
|
ref Vector128<short> destBase = ref Unsafe.As<Block8x8, Vector128<short>>(ref dest); |
||||
|
|
||||
|
for (int i = 0; i < 16; i += 2) |
||||
|
{ |
||||
|
Vector128<int> left = Sse2.ConvertToVector128Int32(Sse.Multiply(Unsafe.Add(ref aBase, i + 0), Unsafe.Add(ref bBase, i + 0))); |
||||
|
Vector128<int> right = Sse2.ConvertToVector128Int32(Sse.Multiply(Unsafe.Add(ref aBase, i + 1), Unsafe.Add(ref bBase, i + 1))); |
||||
|
|
||||
|
Vector128<short> row = Sse2.PackSignedSaturate(left, right); |
||||
|
Unsafe.Add(ref destBase, (IntPtr)((uint)i / 2)) = row; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void TransposeInplace_Avx() |
||||
|
{ |
||||
|
// https://stackoverflow.com/questions/25622745/transpose-an-8x8-float-using-avx-avx2/25627536#25627536
|
||||
|
Vector256<float> r0 = Avx.InsertVector128( |
||||
|
this.V0, |
||||
|
Unsafe.As<Vector4, Vector128<float>>(ref this.V4L), |
||||
|
1); |
||||
|
|
||||
|
Vector256<float> r1 = Avx.InsertVector128( |
||||
|
this.V1, |
||||
|
Unsafe.As<Vector4, Vector128<float>>(ref this.V5L), |
||||
|
1); |
||||
|
|
||||
|
Vector256<float> r2 = Avx.InsertVector128( |
||||
|
this.V2, |
||||
|
Unsafe.As<Vector4, Vector128<float>>(ref this.V6L), |
||||
|
1); |
||||
|
|
||||
|
Vector256<float> r3 = Avx.InsertVector128( |
||||
|
this.V3, |
||||
|
Unsafe.As<Vector4, Vector128<float>>(ref this.V7L), |
||||
|
1); |
||||
|
|
||||
|
Vector256<float> r4 = Avx.InsertVector128( |
||||
|
Unsafe.As<Vector4, Vector128<float>>(ref this.V0R).ToVector256(), |
||||
|
Unsafe.As<Vector4, Vector128<float>>(ref this.V4R), |
||||
|
1); |
||||
|
|
||||
|
Vector256<float> r5 = Avx.InsertVector128( |
||||
|
Unsafe.As<Vector4, Vector128<float>>(ref this.V1R).ToVector256(), |
||||
|
Unsafe.As<Vector4, Vector128<float>>(ref this.V5R), |
||||
|
1); |
||||
|
|
||||
|
Vector256<float> r6 = Avx.InsertVector128( |
||||
|
Unsafe.As<Vector4, Vector128<float>>(ref this.V2R).ToVector256(), |
||||
|
Unsafe.As<Vector4, Vector128<float>>(ref this.V6R), |
||||
|
1); |
||||
|
|
||||
|
Vector256<float> r7 = Avx.InsertVector128( |
||||
|
Unsafe.As<Vector4, Vector128<float>>(ref this.V3R).ToVector256(), |
||||
|
Unsafe.As<Vector4, Vector128<float>>(ref this.V7R), |
||||
|
1); |
||||
|
|
||||
|
Vector256<float> t0 = Avx.UnpackLow(r0, r1); |
||||
|
Vector256<float> t2 = Avx.UnpackLow(r2, r3); |
||||
|
Vector256<float> v = Avx.Shuffle(t0, t2, 0x4E); |
||||
|
this.V0 = Avx.Blend(t0, v, 0xCC); |
||||
|
this.V1 = Avx.Blend(t2, v, 0x33); |
||||
|
|
||||
|
Vector256<float> t4 = Avx.UnpackLow(r4, r5); |
||||
|
Vector256<float> t6 = Avx.UnpackLow(r6, r7); |
||||
|
v = Avx.Shuffle(t4, t6, 0x4E); |
||||
|
this.V4 = Avx.Blend(t4, v, 0xCC); |
||||
|
this.V5 = Avx.Blend(t6, v, 0x33); |
||||
|
|
||||
|
Vector256<float> t1 = Avx.UnpackHigh(r0, r1); |
||||
|
Vector256<float> t3 = Avx.UnpackHigh(r2, r3); |
||||
|
v = Avx.Shuffle(t1, t3, 0x4E); |
||||
|
this.V2 = Avx.Blend(t1, v, 0xCC); |
||||
|
this.V3 = Avx.Blend(t3, v, 0x33); |
||||
|
|
||||
|
Vector256<float> t5 = Avx.UnpackHigh(r4, r5); |
||||
|
Vector256<float> t7 = Avx.UnpackHigh(r6, r7); |
||||
|
v = Avx.Shuffle(t5, t7, 0x4E); |
||||
|
this.V6 = Avx.Blend(t5, v, 0xCC); |
||||
|
this.V7 = Avx.Blend(t7, v, 0x33); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
#endif
|
||||
@ -1,181 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Buffers; |
|
||||
using System.Numerics; |
|
||||
using System.Threading; |
|
||||
using SixLabors.ImageSharp.Advanced; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using JpegColorConverter = SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters.JpegColorConverter; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Encapsulates the execution od post-processing algorithms to be applied on a <see cref="IRawJpegData"/> to produce a valid <see cref="Image{TPixel}"/>: <br/>
|
|
||||
/// (1) Dequantization <br/>
|
|
||||
/// (2) IDCT <br/>
|
|
||||
/// (3) Color conversion form one of the <see cref="JpegColorSpace"/>-s into a <see cref="Vector4"/> buffer of RGBA values <br/>
|
|
||||
/// (4) Packing <see cref="Image{TPixel}"/> pixels from the <see cref="Vector4"/> buffer. <br/>
|
|
||||
/// These operations are executed in <see cref="NumberOfPostProcessorSteps"/> steps.
|
|
||||
/// <see cref="PixelRowsPerStep"/> image rows are converted in one step,
|
|
||||
/// which means that size of the allocated memory is limited (does not depend on <see cref="ImageFrame.Height"/>).
|
|
||||
/// </summary>
|
|
||||
internal class JpegImagePostProcessor : IDisposable |
|
||||
{ |
|
||||
private readonly Configuration configuration; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of block rows to be processed in one Step.
|
|
||||
/// </summary>
|
|
||||
public const int BlockRowsPerStep = 4; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of image pixel rows to be processed in one step.
|
|
||||
/// </summary>
|
|
||||
public const int PixelRowsPerStep = 4 * 8; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Temporal buffer to store a row of colors.
|
|
||||
/// </summary>
|
|
||||
private readonly IMemoryOwner<Vector4> rgbaBuffer; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The <see cref="JpegColorConverter"/> corresponding to the current <see cref="JpegColorSpace"/> determined by <see cref="IRawJpegData.ColorSpace"/>.
|
|
||||
/// </summary>
|
|
||||
private readonly JpegColorConverter colorConverter; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="JpegImagePostProcessor"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="configuration">The <see cref="Configuration"/> to configure internal operations.</param>
|
|
||||
/// <param name="rawJpeg">The <see cref="IRawJpegData"/> representing the uncompressed spectral Jpeg data</param>
|
|
||||
public JpegImagePostProcessor(Configuration configuration, IRawJpegData rawJpeg) |
|
||||
{ |
|
||||
this.configuration = configuration; |
|
||||
this.RawJpeg = rawJpeg; |
|
||||
IJpegComponent c0 = rawJpeg.Components[0]; |
|
||||
this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep; |
|
||||
this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep); |
|
||||
|
|
||||
MemoryAllocator memoryAllocator = configuration.MemoryAllocator; |
|
||||
|
|
||||
this.ComponentProcessors = new JpegComponentPostProcessor[rawJpeg.Components.Length]; |
|
||||
for (int i = 0; i < rawJpeg.Components.Length; i++) |
|
||||
{ |
|
||||
this.ComponentProcessors[i] = new JpegComponentPostProcessor(memoryAllocator, this, rawJpeg.Components[i]); |
|
||||
} |
|
||||
|
|
||||
this.rgbaBuffer = memoryAllocator.Allocate<Vector4>(rawJpeg.ImageSizeInPixels.Width); |
|
||||
this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace, rawJpeg.Precision); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the <see cref="JpegComponentPostProcessor"/> instances.
|
|
||||
/// </summary>
|
|
||||
public JpegComponentPostProcessor[] ComponentProcessors { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the <see cref="IRawJpegData"/> to be processed.
|
|
||||
/// </summary>
|
|
||||
public IRawJpegData RawJpeg { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the total number of post processor steps deduced from the height of the image and <see cref="PixelRowsPerStep"/>.
|
|
||||
/// </summary>
|
|
||||
public int NumberOfPostProcessorSteps { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the size of the temporary buffers we need to allocate into <see cref="JpegComponentPostProcessor.ColorBuffer"/>.
|
|
||||
/// </summary>
|
|
||||
public Size PostProcessorBufferSize { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the value of the counter that grows by each step by <see cref="PixelRowsPerStep"/>.
|
|
||||
/// </summary>
|
|
||||
public int PixelRowCounter { get; private set; } |
|
||||
|
|
||||
/// <inheritdoc />
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors) |
|
||||
{ |
|
||||
cpp.Dispose(); |
|
||||
} |
|
||||
|
|
||||
this.rgbaBuffer.Dispose(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Process all pixels into 'destination'. The image dimensions should match <see cref="RawJpeg"/>.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The pixel type</typeparam>
|
|
||||
/// <param name="destination">The destination image</param>
|
|
||||
/// <param name="cancellationToken">The token to request cancellation.</param>
|
|
||||
public void PostProcess<TPixel>(ImageFrame<TPixel> destination, CancellationToken cancellationToken) |
|
||||
where TPixel : unmanaged, IPixel<TPixel> |
|
||||
{ |
|
||||
this.PixelRowCounter = 0; |
|
||||
|
|
||||
if (this.RawJpeg.ImageSizeInPixels != destination.Size()) |
|
||||
{ |
|
||||
throw new ArgumentException("Input image is not of the size of the processed one!"); |
|
||||
} |
|
||||
|
|
||||
while (this.PixelRowCounter < this.RawJpeg.ImageSizeInPixels.Height) |
|
||||
{ |
|
||||
cancellationToken.ThrowIfCancellationRequested(); |
|
||||
this.DoPostProcessorStep(destination); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Execute one step processing <see cref="PixelRowsPerStep"/> pixel rows into 'destination'.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The pixel type</typeparam>
|
|
||||
/// <param name="destination">The destination image.</param>
|
|
||||
public void DoPostProcessorStep<TPixel>(ImageFrame<TPixel> destination) |
|
||||
where TPixel : unmanaged, IPixel<TPixel> |
|
||||
{ |
|
||||
foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors) |
|
||||
{ |
|
||||
cpp.CopyBlocksToColorBuffer(); |
|
||||
} |
|
||||
|
|
||||
this.ConvertColorsInto(destination); |
|
||||
|
|
||||
this.PixelRowCounter += PixelRowsPerStep; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Convert and copy <see cref="PixelRowsPerStep"/> row of colors into 'destination' starting at row <see cref="PixelRowCounter"/>.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The pixel type</typeparam>
|
|
||||
/// <param name="destination">The destination image</param>
|
|
||||
private void ConvertColorsInto<TPixel>(ImageFrame<TPixel> destination) |
|
||||
where TPixel : unmanaged, IPixel<TPixel> |
|
||||
{ |
|
||||
int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep); |
|
||||
|
|
||||
var buffers = new Buffer2D<float>[this.ComponentProcessors.Length]; |
|
||||
for (int i = 0; i < this.ComponentProcessors.Length; i++) |
|
||||
{ |
|
||||
buffers[i] = this.ComponentProcessors[i].ColorBuffer; |
|
||||
} |
|
||||
|
|
||||
for (int yy = this.PixelRowCounter; yy < maxY; yy++) |
|
||||
{ |
|
||||
int y = yy - this.PixelRowCounter; |
|
||||
|
|
||||
var values = new JpegColorConverter.ComponentValues(buffers, y); |
|
||||
this.colorConverter.ConvertToRgba(values, this.rgbaBuffer.GetSpan()); |
|
||||
|
|
||||
Span<TPixel> destRow = destination.GetPixelRowSpan(yy); |
|
||||
|
|
||||
// TODO: Investigate if slicing is actually necessary
|
|
||||
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,144 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Provides methods to evaluate the quality of an image.
|
|
||||
/// Ported from <see href="https://github.com/ImageMagick/ImageMagick/blob/f362c02083d27211b913c6e44794f0ac6edaf2bd/coders/jpeg.c#L855"/>
|
|
||||
/// </summary>
|
|
||||
internal static class QualityEvaluator |
|
||||
{ |
|
||||
private static readonly int[] Hash = new int[101] |
|
||||
{ |
|
||||
1020, 1015, 932, 848, 780, 735, 702, 679, 660, 645, |
|
||||
632, 623, 613, 607, 600, 594, 589, 585, 581, 571, |
|
||||
555, 542, 529, 514, 494, 474, 457, 439, 424, 410, |
|
||||
397, 386, 373, 364, 351, 341, 334, 324, 317, 309, |
|
||||
299, 294, 287, 279, 274, 267, 262, 257, 251, 247, |
|
||||
243, 237, 232, 227, 222, 217, 213, 207, 202, 198, |
|
||||
192, 188, 183, 177, 173, 168, 163, 157, 153, 148, |
|
||||
143, 139, 132, 128, 125, 119, 115, 108, 104, 99, |
|
||||
94, 90, 84, 79, 74, 70, 64, 59, 55, 49, |
|
||||
45, 40, 34, 30, 25, 20, 15, 11, 6, 4, |
|
||||
0 |
|
||||
}; |
|
||||
|
|
||||
private static readonly int[] Sums = new int[101] |
|
||||
{ |
|
||||
32640, 32635, 32266, 31495, 30665, 29804, 29146, 28599, 28104, |
|
||||
27670, 27225, 26725, 26210, 25716, 25240, 24789, 24373, 23946, |
|
||||
23572, 22846, 21801, 20842, 19949, 19121, 18386, 17651, 16998, |
|
||||
16349, 15800, 15247, 14783, 14321, 13859, 13535, 13081, 12702, |
|
||||
12423, 12056, 11779, 11513, 11135, 10955, 10676, 10392, 10208, |
|
||||
9928, 9747, 9564, 9369, 9193, 9017, 8822, 8639, 8458, |
|
||||
8270, 8084, 7896, 7710, 7527, 7347, 7156, 6977, 6788, |
|
||||
6607, 6422, 6236, 6054, 5867, 5684, 5495, 5305, 5128, |
|
||||
4945, 4751, 4638, 4442, 4248, 4065, 3888, 3698, 3509, |
|
||||
3326, 3139, 2957, 2775, 2586, 2405, 2216, 2037, 1846, |
|
||||
1666, 1483, 1297, 1109, 927, 735, 554, 375, 201, |
|
||||
128, 0 |
|
||||
}; |
|
||||
|
|
||||
private static readonly int[] Hash1 = new int[101] |
|
||||
{ |
|
||||
510, 505, 422, 380, 355, 338, 326, 318, 311, 305, |
|
||||
300, 297, 293, 291, 288, 286, 284, 283, 281, 280, |
|
||||
279, 278, 277, 273, 262, 251, 243, 233, 225, 218, |
|
||||
211, 205, 198, 193, 186, 181, 177, 172, 168, 164, |
|
||||
158, 156, 152, 148, 145, 142, 139, 136, 133, 131, |
|
||||
129, 126, 123, 120, 118, 115, 113, 110, 107, 105, |
|
||||
102, 100, 97, 94, 92, 89, 87, 83, 81, 79, |
|
||||
76, 74, 70, 68, 66, 63, 61, 57, 55, 52, |
|
||||
50, 48, 44, 42, 39, 37, 34, 31, 29, 26, |
|
||||
24, 21, 18, 16, 13, 11, 8, 6, 3, 2, |
|
||||
0 |
|
||||
}; |
|
||||
|
|
||||
private static readonly int[] Sums1 = new int[101] |
|
||||
{ |
|
||||
16320, 16315, 15946, 15277, 14655, 14073, 13623, 13230, 12859, |
|
||||
12560, 12240, 11861, 11456, 11081, 10714, 10360, 10027, 9679, |
|
||||
9368, 9056, 8680, 8331, 7995, 7668, 7376, 7084, 6823, |
|
||||
6562, 6345, 6125, 5939, 5756, 5571, 5421, 5240, 5086, |
|
||||
4976, 4829, 4719, 4616, 4463, 4393, 4280, 4166, 4092, |
|
||||
3980, 3909, 3835, 3755, 3688, 3621, 3541, 3467, 3396, |
|
||||
3323, 3247, 3170, 3096, 3021, 2952, 2874, 2804, 2727, |
|
||||
2657, 2583, 2509, 2437, 2362, 2290, 2211, 2136, 2068, |
|
||||
1996, 1915, 1858, 1773, 1692, 1620, 1552, 1477, 1398, |
|
||||
1326, 1251, 1179, 1109, 1031, 961, 884, 814, 736, |
|
||||
667, 592, 518, 441, 369, 292, 221, 151, 86, |
|
||||
64, 0 |
|
||||
}; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Returns an estimated quality of the image based on the quantization tables.
|
|
||||
/// </summary>
|
|
||||
/// <param name="quantizationTables">The quantization tables.</param>
|
|
||||
/// <returns>The <see cref="int"/>.</returns>
|
|
||||
public static int EstimateQuality(Block8x8F[] quantizationTables) |
|
||||
{ |
|
||||
int quality = 75; |
|
||||
float sum = 0; |
|
||||
|
|
||||
for (int i = 0; i < quantizationTables.Length; i++) |
|
||||
{ |
|
||||
ref Block8x8F qTable = ref quantizationTables[i]; |
|
||||
|
|
||||
if (!qTable.Equals(default)) |
|
||||
{ |
|
||||
for (int j = 0; j < Block8x8F.Size; j++) |
|
||||
{ |
|
||||
sum += qTable[j]; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ref Block8x8F qTable0 = ref quantizationTables[0]; |
|
||||
ref Block8x8F qTable1 = ref quantizationTables[1]; |
|
||||
|
|
||||
if (!qTable0.Equals(default)) |
|
||||
{ |
|
||||
if (!qTable1.Equals(default)) |
|
||||
{ |
|
||||
quality = (int)(qTable0[2] |
|
||||
+ qTable0[53] |
|
||||
+ qTable1[0] |
|
||||
+ qTable1[Block8x8F.Size - 1]); |
|
||||
|
|
||||
for (int i = 0; i < 100; i++) |
|
||||
{ |
|
||||
if (quality < Hash[i] && sum < Sums[i]) |
|
||||
{ |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
if (((quality <= Hash[i]) && (sum <= Sums[i])) || (i >= 50)) |
|
||||
{ |
|
||||
return i + 1; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
quality = (int)(qTable0[2] + qTable0[53]); |
|
||||
|
|
||||
for (int i = 0; i < 100; i++) |
|
||||
{ |
|
||||
if (quality < Hash1[i] && sum < Sums1[i]) |
|
||||
{ |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
if (((quality <= Hash1[i]) && (sum <= Sums1[i])) || (i >= 50)) |
|
||||
{ |
|
||||
return i + 1; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return quality; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,44 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Converter used to convert jpeg spectral data.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// This is tightly coupled with <see cref="HuffmanScanDecoder"/> and <see cref="JpegDecoderCore"/>.
|
||||
|
/// </remarks>
|
||||
|
internal abstract class SpectralConverter |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Injects jpeg image decoding metadata.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// This is guaranteed to be called only once at SOF marker by <see cref="HuffmanScanDecoder"/>.
|
||||
|
/// </remarks>
|
||||
|
/// <param name="frame"><see cref="JpegFrame"/> instance containing decoder-specific parameters.</param>
|
||||
|
/// <param name="jpegData"><see cref="IRawJpegData"/> instance containing decoder-specific parameters.</param>
|
||||
|
public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Called once per spectral stride for each component in <see cref="HuffmanScanDecoder"/>.
|
||||
|
/// This is called only for baseline interleaved jpegs.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Spectral 'stride' doesn't particularly mean 'single stride'.
|
||||
|
/// Actual stride height depends on the subsampling factor of the given component.
|
||||
|
/// </remarks>
|
||||
|
public abstract void ConvertStrideBaseline(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the color converter.
|
||||
|
/// </summary>
|
||||
|
/// <param name="frame">The jpeg frame with the color space to convert to.</param>
|
||||
|
/// <param name="jpegData">The raw JPEG data.</param>
|
||||
|
/// <returns>The color converter.</returns>
|
||||
|
protected virtual JpegColorConverter GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,172 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.Numerics; |
||||
|
using System.Threading; |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder |
||||
|
{ |
||||
|
internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
private readonly Configuration configuration; |
||||
|
|
||||
|
private readonly CancellationToken cancellationToken; |
||||
|
|
||||
|
private JpegComponentPostProcessor[] componentProcessors; |
||||
|
|
||||
|
private JpegColorConverter colorConverter; |
||||
|
|
||||
|
// private IMemoryOwner<Vector4> rgbaBuffer;
|
||||
|
private IMemoryOwner<byte> rgbBuffer; |
||||
|
|
||||
|
private IMemoryOwner<TPixel> paddedProxyPixelRow; |
||||
|
|
||||
|
private Buffer2D<TPixel> pixelBuffer; |
||||
|
|
||||
|
private int blockRowsPerStep; |
||||
|
|
||||
|
private int pixelRowsPerStep; |
||||
|
|
||||
|
private int pixelRowCounter; |
||||
|
|
||||
|
public SpectralConverter(Configuration configuration, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
this.configuration = configuration; |
||||
|
this.cancellationToken = cancellationToken; |
||||
|
} |
||||
|
|
||||
|
private bool Converted => this.pixelRowCounter >= this.pixelBuffer.Height; |
||||
|
|
||||
|
public Buffer2D<TPixel> GetPixelBuffer() |
||||
|
{ |
||||
|
if (!this.Converted) |
||||
|
{ |
||||
|
int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.pixelRowsPerStep); |
||||
|
|
||||
|
for (int step = 0; step < steps; step++) |
||||
|
{ |
||||
|
this.cancellationToken.ThrowIfCancellationRequested(); |
||||
|
this.ConvertNextStride(step); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return this.pixelBuffer; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) |
||||
|
{ |
||||
|
MemoryAllocator allocator = this.configuration.MemoryAllocator; |
||||
|
|
||||
|
// iteration data
|
||||
|
IJpegComponent c0 = frame.Components[0]; |
||||
|
|
||||
|
const int blockPixelHeight = 8; |
||||
|
this.blockRowsPerStep = c0.SamplingFactors.Height; |
||||
|
this.pixelRowsPerStep = this.blockRowsPerStep * blockPixelHeight; |
||||
|
|
||||
|
// pixel buffer for resulting image
|
||||
|
this.pixelBuffer = allocator.Allocate2D<TPixel>(frame.PixelWidth, frame.PixelHeight); |
||||
|
this.paddedProxyPixelRow = allocator.Allocate<TPixel>(frame.PixelWidth + 3); |
||||
|
|
||||
|
// component processors from spectral to Rgba32
|
||||
|
var postProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.pixelRowsPerStep); |
||||
|
this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length]; |
||||
|
for (int i = 0; i < this.componentProcessors.Length; i++) |
||||
|
{ |
||||
|
this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, frame, jpegData, postProcessorBufferSize, frame.Components[i]); |
||||
|
} |
||||
|
|
||||
|
// single 'stride' rgba32 buffer for conversion between spectral and TPixel
|
||||
|
// this.rgbaBuffer = allocator.Allocate<Vector4>(frame.PixelWidth);
|
||||
|
this.rgbBuffer = allocator.Allocate<byte>(frame.PixelWidth * 3); |
||||
|
|
||||
|
// color converter from Rgba32 to TPixel
|
||||
|
this.colorConverter = this.GetColorConverter(frame, jpegData); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void ConvertStrideBaseline() |
||||
|
{ |
||||
|
// Convert next pixel stride using single spectral `stride'
|
||||
|
// Note that zero passing eliminates the need of virtual call from JpegComponentPostProcessor
|
||||
|
this.ConvertNextStride(spectralStep: 0); |
||||
|
|
||||
|
// Clear spectral stride - this is VERY important as jpeg possibly won't fill entire buffer each stride
|
||||
|
// Which leads to decoding artifacts
|
||||
|
// Note that this code clears all buffers of the post processors, it's their responsibility to allocate only single stride
|
||||
|
foreach (JpegComponentPostProcessor cpp in this.componentProcessors) |
||||
|
{ |
||||
|
cpp.ClearSpectralBuffers(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
if (this.componentProcessors != null) |
||||
|
{ |
||||
|
foreach (JpegComponentPostProcessor cpp in this.componentProcessors) |
||||
|
{ |
||||
|
cpp.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.rgbBuffer?.Dispose(); |
||||
|
this.paddedProxyPixelRow?.Dispose(); |
||||
|
} |
||||
|
|
||||
|
private void ConvertNextStride(int spectralStep) |
||||
|
{ |
||||
|
int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); |
||||
|
|
||||
|
var buffers = new Buffer2D<float>[this.componentProcessors.Length]; |
||||
|
for (int i = 0; i < this.componentProcessors.Length; i++) |
||||
|
{ |
||||
|
this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep); |
||||
|
buffers[i] = this.componentProcessors[i].ColorBuffer; |
||||
|
} |
||||
|
|
||||
|
int width = this.pixelBuffer.Width; |
||||
|
|
||||
|
for (int yy = this.pixelRowCounter; yy < maxY; yy++) |
||||
|
{ |
||||
|
int y = yy - this.pixelRowCounter; |
||||
|
|
||||
|
var values = new JpegColorConverter.ComponentValues(buffers, y); |
||||
|
|
||||
|
this.colorConverter.ConvertToRgbInplace(values); |
||||
|
values = values.Slice(0, width); // slice away Jpeg padding
|
||||
|
|
||||
|
Span<byte> r = this.rgbBuffer.Slice(0, width); |
||||
|
Span<byte> g = this.rgbBuffer.Slice(width, width); |
||||
|
Span<byte> b = this.rgbBuffer.Slice(width * 2, width); |
||||
|
|
||||
|
SimdUtils.NormalizedFloatToByteSaturate(values.Component0, r); |
||||
|
SimdUtils.NormalizedFloatToByteSaturate(values.Component1, g); |
||||
|
SimdUtils.NormalizedFloatToByteSaturate(values.Component2, b); |
||||
|
|
||||
|
// PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row.
|
||||
|
// If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row,
|
||||
|
// pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row.
|
||||
|
if (this.pixelBuffer.TryGetPaddedRowSpan(yy, 3, out Span<TPixel> destRow)) |
||||
|
{ |
||||
|
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.configuration, r, g, b, destRow); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Span<TPixel> proxyRow = this.paddedProxyPixelRow.GetSpan(); |
||||
|
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.configuration, r, g, b, proxyRow); |
||||
|
proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.GetRowSpan(yy)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.pixelRowCounter += this.pixelRowsPerStep; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,114 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// On-stack worker struct to convert TPixel -> Rgb24 of 8x8 pixel blocks.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel type to work on.</typeparam>
|
||||
|
internal ref struct RgbForwardConverter<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel})"/> call
|
||||
|
/// </summary>
|
||||
|
private const int PixelsPerSample = 8 * 8; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Total byte size of processed pixels converted from TPixel to <see cref="Rgb24"/>
|
||||
|
/// </summary>
|
||||
|
private const int RgbSpanByteSize = PixelsPerSample * 3; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// <see cref="Size"/> of sampling area from given frame pixel buffer.
|
||||
|
/// </summary>
|
||||
|
private static readonly Size SampleSize = new Size(8, 8); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Red component.
|
||||
|
/// </summary>
|
||||
|
public Block8x8F R; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Green component.
|
||||
|
/// </summary>
|
||||
|
public Block8x8F G; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Blue component.
|
||||
|
/// </summary>
|
||||
|
public Block8x8F B; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Temporal 64-byte span to hold unconverted TPixel data.
|
||||
|
/// </summary>
|
||||
|
private readonly Span<TPixel> pixelSpan; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Temporal 64-byte span to hold converted Rgb24 data.
|
||||
|
/// </summary>
|
||||
|
private readonly Span<Rgb24> rgbSpan; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sampled pixel buffer size.
|
||||
|
/// </summary>
|
||||
|
private readonly Size samplingAreaSize; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// <see cref="Configuration"/> for internal operations.
|
||||
|
/// </summary>
|
||||
|
private readonly Configuration config; |
||||
|
|
||||
|
public RgbForwardConverter(ImageFrame<TPixel> frame) |
||||
|
{ |
||||
|
this.R = default; |
||||
|
this.G = default; |
||||
|
this.B = default; |
||||
|
|
||||
|
// temporal pixel buffers
|
||||
|
this.pixelSpan = new TPixel[PixelsPerSample].AsSpan(); |
||||
|
this.rgbSpan = MemoryMarshal.Cast<byte, Rgb24>(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan()); |
||||
|
|
||||
|
// frame data
|
||||
|
this.samplingAreaSize = new Size(frame.Width, frame.Height); |
||||
|
this.config = frame.GetConfiguration(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts a 8x8 image area inside 'pixels' at position (x, y) to Rgb24.
|
||||
|
/// </summary>
|
||||
|
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows) |
||||
|
{ |
||||
|
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan); |
||||
|
|
||||
|
ref Block8x8F redBlock = ref this.R; |
||||
|
ref Block8x8F greenBlock = ref this.G; |
||||
|
ref Block8x8F blueBlock = ref this.B; |
||||
|
|
||||
|
CopyToBlock(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock); |
||||
|
} |
||||
|
|
||||
|
private static void CopyToBlock(Span<Rgb24> rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock) |
||||
|
{ |
||||
|
ref Rgb24 rgbStart = ref MemoryMarshal.GetReference(rgbSpan); |
||||
|
|
||||
|
for (int i = 0; i < Block8x8F.Size; i++) |
||||
|
{ |
||||
|
Rgb24 c = Unsafe.Add(ref rgbStart, (nint)(uint)i); |
||||
|
|
||||
|
redBlock[i] = c.R; |
||||
|
greenBlock[i] = c.G; |
||||
|
blueBlock[i] = c.B; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,161 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
#if SUPPORTS_RUNTIME_INTRINSICS
|
||||
|
using System.Diagnostics; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using System.Runtime.Intrinsics.X86; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components |
||||
|
{ |
||||
|
internal static partial class FastFloatingPointDCT |
||||
|
{ |
||||
|
#pragma warning disable SA1310, SA1311, IDE1006 // naming rules violation warnings
|
||||
|
private static readonly Vector256<float> mm256_F_0_7071 = Vector256.Create(0.707106781f); |
||||
|
private static readonly Vector256<float> mm256_F_0_3826 = Vector256.Create(0.382683433f); |
||||
|
private static readonly Vector256<float> mm256_F_0_5411 = Vector256.Create(0.541196100f); |
||||
|
private static readonly Vector256<float> mm256_F_1_3065 = Vector256.Create(1.306562965f); |
||||
|
|
||||
|
private static readonly Vector256<float> mm256_F_1_1758 = Vector256.Create(1.175876f); |
||||
|
private static readonly Vector256<float> mm256_F_n1_9615 = Vector256.Create(-1.961570560f); |
||||
|
private static readonly Vector256<float> mm256_F_n0_3901 = Vector256.Create(-0.390180644f); |
||||
|
private static readonly Vector256<float> mm256_F_n0_8999 = Vector256.Create(-0.899976223f); |
||||
|
private static readonly Vector256<float> mm256_F_n2_5629 = Vector256.Create(-2.562915447f); |
||||
|
private static readonly Vector256<float> mm256_F_0_2986 = Vector256.Create(0.298631336f); |
||||
|
private static readonly Vector256<float> mm256_F_2_0531 = Vector256.Create(2.053119869f); |
||||
|
private static readonly Vector256<float> mm256_F_3_0727 = Vector256.Create(3.072711026f); |
||||
|
private static readonly Vector256<float> mm256_F_1_5013 = Vector256.Create(1.501321110f); |
||||
|
private static readonly Vector256<float> mm256_F_n1_8477 = Vector256.Create(-1.847759065f); |
||||
|
private static readonly Vector256<float> mm256_F_0_7653 = Vector256.Create(0.765366865f); |
||||
|
#pragma warning restore SA1310, SA1311, IDE1006
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Apply floating point FDCT inplace using simd operations.
|
||||
|
/// </summary>
|
||||
|
/// <param name="block">Input matrix.</param>
|
||||
|
private static void ForwardTransform_Avx(ref Block8x8F block) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(Avx.IsSupported, "Avx support is required to execute this operation."); |
||||
|
|
||||
|
// First pass - process rows
|
||||
|
block.TransposeInplace(); |
||||
|
FDCT8x8_Avx(ref block); |
||||
|
|
||||
|
// Second pass - process columns
|
||||
|
block.TransposeInplace(); |
||||
|
FDCT8x8_Avx(ref block); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Apply 1D floating point FDCT inplace using AVX operations on 8x8 matrix.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Requires Avx support.
|
||||
|
/// </remarks>
|
||||
|
/// <param name="block">Input matrix.</param>
|
||||
|
public static void FDCT8x8_Avx(ref Block8x8F block) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(Avx.IsSupported, "Avx support is required to execute this operation."); |
||||
|
|
||||
|
Vector256<float> tmp0 = Avx.Add(block.V0, block.V7); |
||||
|
Vector256<float> tmp7 = Avx.Subtract(block.V0, block.V7); |
||||
|
Vector256<float> tmp1 = Avx.Add(block.V1, block.V6); |
||||
|
Vector256<float> tmp6 = Avx.Subtract(block.V1, block.V6); |
||||
|
Vector256<float> tmp2 = Avx.Add(block.V2, block.V5); |
||||
|
Vector256<float> tmp5 = Avx.Subtract(block.V2, block.V5); |
||||
|
Vector256<float> tmp3 = Avx.Add(block.V3, block.V4); |
||||
|
Vector256<float> tmp4 = Avx.Subtract(block.V3, block.V4); |
||||
|
|
||||
|
// Even part
|
||||
|
Vector256<float> tmp10 = Avx.Add(tmp0, tmp3); |
||||
|
Vector256<float> tmp13 = Avx.Subtract(tmp0, tmp3); |
||||
|
Vector256<float> tmp11 = Avx.Add(tmp1, tmp2); |
||||
|
Vector256<float> tmp12 = Avx.Subtract(tmp1, tmp2); |
||||
|
|
||||
|
block.V0 = Avx.Add(tmp10, tmp11); |
||||
|
block.V4 = Avx.Subtract(tmp10, tmp11); |
||||
|
|
||||
|
Vector256<float> z1 = Avx.Multiply(Avx.Add(tmp12, tmp13), mm256_F_0_7071); |
||||
|
block.V2 = Avx.Add(tmp13, z1); |
||||
|
block.V6 = Avx.Subtract(tmp13, z1); |
||||
|
|
||||
|
// Odd part
|
||||
|
tmp10 = Avx.Add(tmp4, tmp5); |
||||
|
tmp11 = Avx.Add(tmp5, tmp6); |
||||
|
tmp12 = Avx.Add(tmp6, tmp7); |
||||
|
|
||||
|
Vector256<float> z5 = Avx.Multiply(Avx.Subtract(tmp10, tmp12), mm256_F_0_3826); |
||||
|
Vector256<float> z2 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, mm256_F_0_5411, tmp10); |
||||
|
Vector256<float> z4 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, mm256_F_1_3065, tmp12); |
||||
|
Vector256<float> z3 = Avx.Multiply(tmp11, mm256_F_0_7071); |
||||
|
|
||||
|
Vector256<float> z11 = Avx.Add(tmp7, z3); |
||||
|
Vector256<float> z13 = Avx.Subtract(tmp7, z3); |
||||
|
|
||||
|
block.V5 = Avx.Add(z13, z2); |
||||
|
block.V3 = Avx.Subtract(z13, z2); |
||||
|
block.V1 = Avx.Add(z11, z4); |
||||
|
block.V7 = Avx.Subtract(z11, z4); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Combined operation of <see cref="IDCT8x4_LeftPart(ref Block8x8F, ref Block8x8F)"/> and <see cref="IDCT8x4_RightPart(ref Block8x8F, ref Block8x8F)"/>
|
||||
|
/// using AVX commands.
|
||||
|
/// </summary>
|
||||
|
/// <param name="s">Source</param>
|
||||
|
/// <param name="d">Destination</param>
|
||||
|
public static void IDCT8x8_Avx(ref Block8x8F s, ref Block8x8F d) |
||||
|
{ |
||||
|
Debug.Assert(Avx.IsSupported, "AVX is required to execute this method"); |
||||
|
|
||||
|
Vector256<float> my1 = s.V1; |
||||
|
Vector256<float> my7 = s.V7; |
||||
|
Vector256<float> mz0 = Avx.Add(my1, my7); |
||||
|
|
||||
|
Vector256<float> my3 = s.V3; |
||||
|
Vector256<float> mz2 = Avx.Add(my3, my7); |
||||
|
Vector256<float> my5 = s.V5; |
||||
|
Vector256<float> mz1 = Avx.Add(my3, my5); |
||||
|
Vector256<float> mz3 = Avx.Add(my1, my5); |
||||
|
|
||||
|
Vector256<float> mz4 = Avx.Multiply(Avx.Add(mz0, mz1), mm256_F_1_1758); |
||||
|
|
||||
|
mz2 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, mz2, mm256_F_n1_9615); |
||||
|
mz3 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, mz3, mm256_F_n0_3901); |
||||
|
mz0 = Avx.Multiply(mz0, mm256_F_n0_8999); |
||||
|
mz1 = Avx.Multiply(mz1, mm256_F_n2_5629); |
||||
|
|
||||
|
Vector256<float> mb3 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz0, my7, mm256_F_0_2986), mz2); |
||||
|
Vector256<float> mb2 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz1, my5, mm256_F_2_0531), mz3); |
||||
|
Vector256<float> mb1 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz1, my3, mm256_F_3_0727), mz2); |
||||
|
Vector256<float> mb0 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz0, my1, mm256_F_1_5013), mz3); |
||||
|
|
||||
|
Vector256<float> my2 = s.V2; |
||||
|
Vector256<float> my6 = s.V6; |
||||
|
mz4 = Avx.Multiply(Avx.Add(my2, my6), mm256_F_0_5411); |
||||
|
Vector256<float> my0 = s.V0; |
||||
|
Vector256<float> my4 = s.V4; |
||||
|
mz0 = Avx.Add(my0, my4); |
||||
|
mz1 = Avx.Subtract(my0, my4); |
||||
|
mz2 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, my6, mm256_F_n1_8477); |
||||
|
mz3 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, my2, mm256_F_0_7653); |
||||
|
|
||||
|
my0 = Avx.Add(mz0, mz3); |
||||
|
my3 = Avx.Subtract(mz0, mz3); |
||||
|
my1 = Avx.Add(mz1, mz2); |
||||
|
my2 = Avx.Subtract(mz1, mz2); |
||||
|
|
||||
|
d.V0 = Avx.Add(my0, mb0); |
||||
|
d.V7 = Avx.Subtract(my0, mb0); |
||||
|
d.V1 = Avx.Add(my1, mb1); |
||||
|
d.V6 = Avx.Subtract(my1, mb1); |
||||
|
d.V2 = Avx.Add(my2, mb2); |
||||
|
d.V5 = Avx.Subtract(my2, mb2); |
||||
|
d.V3 = Avx.Add(my3, mb3); |
||||
|
d.V4 = Avx.Subtract(my3, mb3); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
#endif
|
||||
@ -0,0 +1,199 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides methods and properties related to jpeg quantization.
|
||||
|
/// </summary>
|
||||
|
internal static class Quantization |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Upper bound (inclusive) for jpeg quality setting.
|
||||
|
/// </summary>
|
||||
|
public const int MaxQualityFactor = 100; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Lower bound (inclusive) for jpeg quality setting.
|
||||
|
/// </summary>
|
||||
|
public const int MinQualityFactor = 1; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Default JPEG quality for both luminance and chominance tables.
|
||||
|
/// </summary>
|
||||
|
public const int DefaultQualityFactor = 75; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Represents lowest quality setting which can be estimated with enough confidence.
|
||||
|
/// Any quality below it results in a highly compressed jpeg image
|
||||
|
/// which shouldn't use standard itu quantization tables for re-encoding.
|
||||
|
/// </summary>
|
||||
|
public const int QualityEstimationConfidenceLowerThreshold = 25; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Represents highest quality setting which can be estimated with enough confidence.
|
||||
|
/// </summary>
|
||||
|
public const int QualityEstimationConfidenceUpperThreshold = 98; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets unscaled luminance quantization table.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// The values are derived from ITU section K.1.
|
||||
|
/// </remarks>
|
||||
|
// The C# compiler emits this as a compile-time constant embedded in the PE file.
|
||||
|
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
|
||||
|
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
|
||||
|
public static ReadOnlySpan<byte> LuminanceTable => new byte[] |
||||
|
{ |
||||
|
16, 11, 10, 16, 24, 40, 51, 61, |
||||
|
12, 12, 14, 19, 26, 58, 60, 55, |
||||
|
14, 13, 16, 24, 40, 57, 69, 56, |
||||
|
14, 17, 22, 29, 51, 87, 80, 62, |
||||
|
18, 22, 37, 56, 68, 109, 103, 77, |
||||
|
24, 35, 55, 64, 81, 104, 113, 92, |
||||
|
49, 64, 78, 87, 103, 121, 120, 101, |
||||
|
72, 92, 95, 98, 112, 100, 103, 99, |
||||
|
}; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets unscaled chrominance quantization table.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// The values are derived from ITU section K.1.
|
||||
|
/// </remarks>
|
||||
|
// The C# compiler emits this as a compile-time constant embedded in the PE file.
|
||||
|
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
|
||||
|
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
|
||||
|
public static ReadOnlySpan<byte> ChrominanceTable => new byte[] |
||||
|
{ |
||||
|
17, 18, 24, 47, 99, 99, 99, 99, |
||||
|
18, 21, 26, 66, 99, 99, 99, 99, |
||||
|
24, 26, 56, 99, 99, 99, 99, 99, |
||||
|
47, 66, 99, 99, 99, 99, 99, 99, |
||||
|
99, 99, 99, 99, 99, 99, 99, 99, |
||||
|
99, 99, 99, 99, 99, 99, 99, 99, |
||||
|
99, 99, 99, 99, 99, 99, 99, 99, |
||||
|
99, 99, 99, 99, 99, 99, 99, 99, |
||||
|
}; |
||||
|
|
||||
|
/// Ported from JPEGsnoop:
|
||||
|
/// https://github.com/ImpulseAdventure/JPEGsnoop/blob/9732ee0961f100eb69bbff4a0c47438d5997abee/source/JfifDecode.cpp#L4570-L4694
|
||||
|
/// <summary>
|
||||
|
/// Estimates jpeg quality based on standard quantization table.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Technically, this can be used with any given table but internal decoder code uses ITU spec tables:
|
||||
|
/// <see cref="LuminanceTable"/> and <see cref="ChrominanceTable"/>.
|
||||
|
/// </remarks>
|
||||
|
/// <param name="table">Input quantization table.</param>
|
||||
|
/// <param name="target">Natural order quantization table to estimate against.</param>
|
||||
|
/// <returns>Estimated quality.</returns>
|
||||
|
public static int EstimateQuality(ref Block8x8F table, ReadOnlySpan<byte> target) |
||||
|
{ |
||||
|
// This method can be SIMD'ified if standard table is injected as Block8x8F.
|
||||
|
// Or when we go to full-int16 spectral code implementation and inject both tables as Block8x8.
|
||||
|
double comparePercent; |
||||
|
double sumPercent = 0; |
||||
|
|
||||
|
// Corner case - all 1's => 100 quality
|
||||
|
// It would fail to deduce using algorithm below without this check
|
||||
|
if (table.EqualsToScalar(1)) |
||||
|
{ |
||||
|
// While this is a 100% to be 100 quality, any given table can be scaled to all 1's.
|
||||
|
// According to jpeg creators, top of the line quality is 99, 100 is just a technical 'limit' which will affect result filesize drastically.
|
||||
|
// Quality=100 shouldn't be used in usual use case.
|
||||
|
return 100; |
||||
|
} |
||||
|
|
||||
|
int quality; |
||||
|
for (int i = 0; i < Block8x8F.Size; i++) |
||||
|
{ |
||||
|
int coeff = (int)table[i]; |
||||
|
|
||||
|
// Coefficients are actually int16 casted to float numbers so there's no truncating error.
|
||||
|
if (coeff != 0) |
||||
|
{ |
||||
|
comparePercent = 100.0 * (table[i] / target[i]); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// No 'valid' quantization table should contain zero at any position
|
||||
|
// while this is okay to decode with, it will throw DivideByZeroException at encoding proces stage.
|
||||
|
// Not sure what to do here, we can't throw as this technically correct
|
||||
|
// but this will screw up the encoder.
|
||||
|
comparePercent = 999.99; |
||||
|
} |
||||
|
|
||||
|
sumPercent += comparePercent; |
||||
|
} |
||||
|
|
||||
|
// Perform some statistical analysis of the quality factor
|
||||
|
// to determine the likelihood of the current quantization
|
||||
|
// table being a scaled version of the "standard" tables.
|
||||
|
// If the variance is high, it is unlikely to be the case.
|
||||
|
sumPercent /= 64.0; |
||||
|
|
||||
|
// Generate the equivalent IJQ "quality" factor
|
||||
|
if (sumPercent <= 100.0) |
||||
|
{ |
||||
|
quality = (int)Math.Round((200 - sumPercent) / 2); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
quality = (int)Math.Round(5000.0 / sumPercent); |
||||
|
} |
||||
|
|
||||
|
return quality; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Estimates jpeg quality based on quantization table in zig-zag order.
|
||||
|
/// </summary>
|
||||
|
/// <param name="luminanceTable">Luminance quantization table.</param>
|
||||
|
/// <returns>Estimated quality</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static int EstimateLuminanceQuality(ref Block8x8F luminanceTable) |
||||
|
=> EstimateQuality(ref luminanceTable, LuminanceTable); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Estimates jpeg quality based on quantization table in zig-zag order.
|
||||
|
/// </summary>
|
||||
|
/// <param name="chrominanceTable">Chrominance quantization table.</param>
|
||||
|
/// <returns>Estimated quality</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static int EstimateChrominanceQuality(ref Block8x8F chrominanceTable) |
||||
|
=> EstimateQuality(ref chrominanceTable, ChrominanceTable); |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static int QualityToScale(int quality) |
||||
|
{ |
||||
|
DebugGuard.MustBeBetweenOrEqualTo(quality, MinQualityFactor, MaxQualityFactor, nameof(quality)); |
||||
|
|
||||
|
return quality < 50 ? (5000 / quality) : (200 - (quality * 2)); |
||||
|
} |
||||
|
|
||||
|
private static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan<byte> unscaledTable) |
||||
|
{ |
||||
|
Block8x8F table = default; |
||||
|
for (int j = 0; j < Block8x8F.Size; j++) |
||||
|
{ |
||||
|
int x = ((unscaledTable[j] * scale) + 50) / 100; |
||||
|
table[j] = Numerics.Clamp(x, 1, 255); |
||||
|
} |
||||
|
|
||||
|
return table; |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Block8x8F ScaleLuminanceTable(int quality) |
||||
|
=> ScaleQuantizationTable(scale: QualityToScale(quality), LuminanceTable); |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Block8x8F ScaleChrominanceTable(int quality) |
||||
|
=> ScaleQuantizationTable(scale: QualityToScale(quality), ChrominanceTable); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,300 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
#if SUPPORTS_RUNTIME_INTRINSICS
|
||||
|
using System; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using System.Runtime.Intrinsics.X86; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components |
||||
|
{ |
||||
|
internal static partial class ZigZag |
||||
|
{ |
||||
|
#pragma warning disable SA1309 // naming rules violation warnings
|
||||
|
/// <summary>
|
||||
|
/// Special byte value to zero out elements during Sse/Avx shuffle intrinsics.
|
||||
|
/// </summary>
|
||||
|
private const byte _ = 0xff; |
||||
|
#pragma warning restore SA1309
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets shuffle vectors for <see cref="ApplyZigZagOrderingSsse3"/>
|
||||
|
/// zig zag implementation.
|
||||
|
/// </summary>
|
||||
|
private static ReadOnlySpan<byte> SseShuffleMasks => new byte[] |
||||
|
{ |
||||
|
// row0
|
||||
|
0, 1, 2, 3, _, _, _, _, _, _, 4, 5, 6, 7, _, _, |
||||
|
_, _, _, _, 0, 1, _, _, 2, 3, _, _, _, _, 4, 5, |
||||
|
_, _, _, _, _, _, 0, 1, _, _, _, _, _, _, _, _, |
||||
|
|
||||
|
// row1
|
||||
|
_, _, _, _, _, _, _, _, _, _, _, _, 8, 9, 10, 11, |
||||
|
2, 3, _, _, _, _, _, _, 4, 5, _, _, _, _, _, _, |
||||
|
_, _, 0, 1, _, _, 2, 3, _, _, _, _, _, _, _, _, |
||||
|
|
||||
|
// row2
|
||||
|
_, _, _, _, _, _, 2, 3, _, _, _, _, _, _, 4, 5, |
||||
|
_, _, _, _, _, _, _, _, 0, 1, _, _, 2, 3, _, _, |
||||
|
|
||||
|
// row3
|
||||
|
_, _, _, _, _, _, 12, 13, 14, 15, _, _, _, _, _, _, |
||||
|
_, _, _, _, 10, 11, _, _, _, _, 12, 13, _, _, _, _, |
||||
|
_, _, 8, 9, _, _, _, _, _, _, _, _, 10, 11, _, _, |
||||
|
6, 7, _, _, _, _, _, _, _, _, _, _, _, _, 8, 9, |
||||
|
|
||||
|
// row4
|
||||
|
_, _, 4, 5, _, _, _, _, _, _, _, _, 6, 7, _, _, |
||||
|
_, _, _, _, 2, 3, _, _, _, _, 4, 5, _, _, _, _, |
||||
|
_, _, _, _, _, _, 0, 1, 2, 3, _, _, _, _, _, _, |
||||
|
|
||||
|
// row5
|
||||
|
_, _, 12, 13, _, _, 14, 15, _, _, _, _, _, _, _, _, |
||||
|
10, 11, _, _, _, _, _, _, 12, 13, _, _, _, _, _, _, |
||||
|
|
||||
|
// row6
|
||||
|
_, _, _, _, _, _, _, _, 12, 13, _, _, 14, 15, _, _, |
||||
|
_, _, _, _, _, _, 10, 11, _, _, _, _, _, _, 12, 13, |
||||
|
4, 5, 6, 7, _, _, _, _, _, _, _, _, _, _, _, _, |
||||
|
|
||||
|
// row7
|
||||
|
10, 11, _, _, _, _, 12, 13, _, _, 14, 15, _, _, _, _, |
||||
|
_, _, 8, 9, 10, 11, _, _, _, _, _, _, 12, 13, 14, 15 |
||||
|
}; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets shuffle vectors for <see cref="ApplyZigZagOrderingAvx2"/>
|
||||
|
/// zig zag implementation.
|
||||
|
/// </summary>
|
||||
|
private static ReadOnlySpan<byte> AvxShuffleMasks => new byte[] |
||||
|
{ |
||||
|
// 01_AB/01_EF/23_CD - cross-lane
|
||||
|
0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, |
||||
|
|
||||
|
// 01_AB - inner-lane
|
||||
|
0, 1, 2, 3, 8, 9, _, _, 10, 11, 4, 5, 6, 7, 12, 13, _, _, _, _, _, _, _, _, _, _, 10, 11, 4, 5, 6, 7, |
||||
|
|
||||
|
// 01_CD/23_GH - cross-lane
|
||||
|
0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, _, _, _, _, 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, _, _, _, _, |
||||
|
|
||||
|
// 01_CD - inner-lane
|
||||
|
_, _, _, _, _, _, 0, 1, _, _, _, _, _, _, _, _, 2, 3, 8, 9, _, _, 10, 11, 4, 5, _, _, _, _, _, _, |
||||
|
|
||||
|
// 01_EF - inner-lane
|
||||
|
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 0, 1, _, _, _, _, _, _, _, _, _, _, |
||||
|
|
||||
|
// 23_AB/45_CD/67_EF - cross-lane
|
||||
|
3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, _, _, _, _, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, _, _, _, _, |
||||
|
|
||||
|
// 23_AB - inner-lane
|
||||
|
4, 5, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 6, 7, 0, 1, 2, 3, 8, 9, _, _, _, _, |
||||
|
|
||||
|
// 23_CD - inner-lane
|
||||
|
_, _, 6, 7, 12, 13, _, _, _, _, _, _, _, _, _, _, 10, 11, 4, 5, _, _, _, _, _, _, _, _, 6, 7, 12, 13, |
||||
|
|
||||
|
// 23_EF - inner-lane
|
||||
|
_, _, _, _, _, _, 2, 3, 8, 9, _, _, 10, 11, 4, 5, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, |
||||
|
|
||||
|
// 23_GH - inner-lane
|
||||
|
_, _, _, _, _, _, _, _, _, _, 0, 1, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, |
||||
|
|
||||
|
// 45_AB - inner-lane
|
||||
|
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 10, 11, _, _, _, _, _, _, _, _, _, _, |
||||
|
|
||||
|
// 45_CD - inner-lane
|
||||
|
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 6, 7, 0, 1, _, _, 2, 3, 8, 9, _, _, _, _, _, _, |
||||
|
|
||||
|
// 45_EF - cross-lane
|
||||
|
1, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, _, _, _, _, 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, |
||||
|
|
||||
|
// 45_EF - inner-lane
|
||||
|
2, 3, 8, 9, _, _, _, _, _, _, _, _, 10, 11, 4, 5, _, _, _, _, _, _, _, _, _, _, 2, 3, 8, 9, _, _, |
||||
|
|
||||
|
// 45_GH - inner-lane
|
||||
|
_, _, _, _, 2, 3, 8, 9, 10, 11, 4, 5, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 6, 7, |
||||
|
|
||||
|
// 67_CD - inner-lane
|
||||
|
_, _, _, _, _, _, _, _, _, _, 10, 11, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, |
||||
|
|
||||
|
// 67_EF - inner-lane
|
||||
|
_, _, _, _, _, _, 6, 7, 0, 1, _, _, 2, 3, 8, 9, _, _, _, _, _, _, _, _, 10, 11, _, _, _, _, _, _, |
||||
|
|
||||
|
// 67_GH - inner-lane
|
||||
|
8, 9, 10, 11, 4, 5, _, _, _, _, _, _, _, _, _, _, 2, 3, 8, 9, 10, 11, 4, 5, _, _, 6, 7, 12, 13, 14, 15 |
||||
|
}; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies zig zag ordering for given 8x8 matrix using SSE cpu intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="block">Input matrix.</param>
|
||||
|
public static unsafe void ApplyZigZagOrderingSsse3(ref Block8x8 block) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(Ssse3.IsSupported, "Ssse3 support is required to run this operation!"); |
||||
|
|
||||
|
fixed (byte* maskPtr = SseShuffleMasks) |
||||
|
{ |
||||
|
Vector128<byte> rowA = block.V0.AsByte(); |
||||
|
Vector128<byte> rowB = block.V1.AsByte(); |
||||
|
Vector128<byte> rowC = block.V2.AsByte(); |
||||
|
Vector128<byte> rowD = block.V3.AsByte(); |
||||
|
Vector128<byte> rowE = block.V4.AsByte(); |
||||
|
Vector128<byte> rowF = block.V5.AsByte(); |
||||
|
Vector128<byte> rowG = block.V6.AsByte(); |
||||
|
Vector128<byte> rowH = block.V7.AsByte(); |
||||
|
|
||||
|
// row0 - A0 A1 B0 C0 B1 A2 A3 B2
|
||||
|
Vector128<short> rowA0 = Ssse3.Shuffle(rowA, Sse2.LoadVector128(maskPtr + (16 * 0))).AsInt16(); |
||||
|
Vector128<short> rowB0 = Ssse3.Shuffle(rowB, Sse2.LoadVector128(maskPtr + (16 * 1))).AsInt16(); |
||||
|
Vector128<short> row0 = Sse2.Or(rowA0, rowB0); |
||||
|
Vector128<short> rowC0 = Ssse3.Shuffle(rowC, Sse2.LoadVector128(maskPtr + (16 * 2))).AsInt16(); |
||||
|
row0 = Sse2.Or(row0, rowC0); |
||||
|
|
||||
|
// row1 - C1 D0 E0 D1 C2 B3 A4 A5
|
||||
|
Vector128<short> rowA1 = Ssse3.Shuffle(rowA, Sse2.LoadVector128(maskPtr + (16 * 3))).AsInt16(); |
||||
|
Vector128<short> rowC1 = Ssse3.Shuffle(rowC, Sse2.LoadVector128(maskPtr + (16 * 4))).AsInt16(); |
||||
|
Vector128<short> row1 = Sse2.Or(rowA1, rowC1); |
||||
|
Vector128<short> rowD1 = Ssse3.Shuffle(rowD, Sse2.LoadVector128(maskPtr + (16 * 5))).AsInt16(); |
||||
|
row1 = Sse2.Or(row1, rowD1); |
||||
|
row1 = Sse2.Insert(row1.AsUInt16(), Sse2.Extract(rowB.AsUInt16(), 3), 5).AsInt16(); |
||||
|
row1 = Sse2.Insert(row1.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 0), 2).AsInt16(); |
||||
|
|
||||
|
// row2
|
||||
|
Vector128<short> rowE2 = Ssse3.Shuffle(rowE, Sse2.LoadVector128(maskPtr + (16 * 6))).AsInt16(); |
||||
|
Vector128<short> rowF2 = Ssse3.Shuffle(rowF, Sse2.LoadVector128(maskPtr + (16 * 7))).AsInt16(); |
||||
|
Vector128<short> row2 = Sse2.Or(rowE2, rowF2); |
||||
|
row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowB.AsUInt16(), 4), 0).AsInt16(); |
||||
|
row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowC.AsUInt16(), 3), 1).AsInt16(); |
||||
|
row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 2), 2).AsInt16(); |
||||
|
row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowG.AsUInt16(), 0), 5).AsInt16(); |
||||
|
|
||||
|
// row3
|
||||
|
Vector128<short> rowA3 = Ssse3.Shuffle(rowA, Sse2.LoadVector128(maskPtr + (16 * 8))).AsInt16().AsInt16(); |
||||
|
Vector128<short> rowB3 = Ssse3.Shuffle(rowB, Sse2.LoadVector128(maskPtr + (16 * 9))).AsInt16().AsInt16(); |
||||
|
Vector128<short> row3 = Sse2.Or(rowA3, rowB3); |
||||
|
Vector128<short> rowC3 = Ssse3.Shuffle(rowC, Sse2.LoadVector128(maskPtr + (16 * 10))).AsInt16(); |
||||
|
row3 = Sse2.Or(row3, rowC3); |
||||
|
Vector128<byte> shuffleRowD3EF = Sse2.LoadVector128(maskPtr + (16 * 11)); |
||||
|
Vector128<short> rowD3 = Ssse3.Shuffle(rowD, shuffleRowD3EF).AsInt16(); |
||||
|
row3 = Sse2.Or(row3, rowD3); |
||||
|
|
||||
|
// row4
|
||||
|
Vector128<short> rowE4 = Ssse3.Shuffle(rowE, shuffleRowD3EF).AsInt16(); |
||||
|
Vector128<short> rowF4 = Ssse3.Shuffle(rowF, Sse2.LoadVector128(maskPtr + (16 * 12))).AsInt16(); |
||||
|
Vector128<short> row4 = Sse2.Or(rowE4, rowF4); |
||||
|
Vector128<short> rowG4 = Ssse3.Shuffle(rowG, Sse2.LoadVector128(maskPtr + (16 * 13))).AsInt16(); |
||||
|
row4 = Sse2.Or(row4, rowG4); |
||||
|
Vector128<short> rowH4 = Ssse3.Shuffle(rowH, Sse2.LoadVector128(maskPtr + (16 * 14))).AsInt16(); |
||||
|
row4 = Sse2.Or(row4, rowH4); |
||||
|
|
||||
|
// row5
|
||||
|
Vector128<short> rowC5 = Ssse3.Shuffle(rowC, Sse2.LoadVector128(maskPtr + (16 * 15))).AsInt16(); |
||||
|
Vector128<short> rowD5 = Ssse3.Shuffle(rowD, Sse2.LoadVector128(maskPtr + (16 * 16))).AsInt16(); |
||||
|
Vector128<short> row5 = Sse2.Or(rowC5, rowD5); |
||||
|
row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowB.AsUInt16(), 7), 2).AsInt16(); |
||||
|
row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 5), 5).AsInt16(); |
||||
|
row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowF.AsUInt16(), 4), 6).AsInt16(); |
||||
|
row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowG.AsUInt16(), 3), 7).AsInt16(); |
||||
|
|
||||
|
// row6
|
||||
|
Vector128<short> rowE6 = Ssse3.Shuffle(rowE, Sse2.LoadVector128(maskPtr + (16 * 17))).AsInt16(); |
||||
|
Vector128<short> rowF6 = Ssse3.Shuffle(rowF, Sse2.LoadVector128(maskPtr + (16 * 18))).AsInt16(); |
||||
|
Vector128<short> row6 = Sse2.Or(rowE6, rowF6); |
||||
|
Vector128<short> rowH6 = Ssse3.Shuffle(rowH, Sse2.LoadVector128(maskPtr + (16 * 19))).AsInt16(); |
||||
|
row6 = Sse2.Or(row6, rowH6); |
||||
|
row6 = Sse2.Insert(row6.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 7), 5).AsInt16(); |
||||
|
row6 = Sse2.Insert(row6.AsUInt16(), Sse2.Extract(rowG.AsUInt16(), 4), 2).AsInt16(); |
||||
|
|
||||
|
// row7
|
||||
|
Vector128<short> rowG7 = Ssse3.Shuffle(rowG, Sse2.LoadVector128(maskPtr + (16 * 20))).AsInt16(); |
||||
|
Vector128<short> rowH7 = Ssse3.Shuffle(rowH, Sse2.LoadVector128(maskPtr + (16 * 21))).AsInt16(); |
||||
|
Vector128<short> row7 = Sse2.Or(rowG7, rowH7); |
||||
|
row7 = Sse2.Insert(row7.AsUInt16(), Sse2.Extract(rowF.AsUInt16(), 7), 4).AsInt16(); |
||||
|
|
||||
|
block.V0 = row0; |
||||
|
block.V1 = row1; |
||||
|
block.V2 = row2; |
||||
|
block.V3 = row3; |
||||
|
block.V4 = row4; |
||||
|
block.V5 = row5; |
||||
|
block.V6 = row6; |
||||
|
block.V7 = row7; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies zig zag ordering for given 8x8 matrix using AVX cpu intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="block">Input matrix.</param>
|
||||
|
public static unsafe void ApplyZigZagOrderingAvx2(ref Block8x8 block) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(Avx2.IsSupported, "Avx2 support is required to run this operation!"); |
||||
|
|
||||
|
fixed (byte* shuffleVectorsPtr = AvxShuffleMasks) |
||||
|
{ |
||||
|
Vector256<byte> rowsAB = block.V01.AsByte(); |
||||
|
Vector256<byte> rowsCD = block.V23.AsByte(); |
||||
|
Vector256<byte> rowsEF = block.V45.AsByte(); |
||||
|
Vector256<byte> rowsGH = block.V67.AsByte(); |
||||
|
|
||||
|
// rows 0 1
|
||||
|
Vector256<int> rows_AB01_EF01_CD23_shuffleMask = Avx.LoadVector256(shuffleVectorsPtr + (0 * 32)).AsInt32(); |
||||
|
Vector256<byte> row01_AB = Avx2.PermuteVar8x32(rowsAB.AsInt32(), rows_AB01_EF01_CD23_shuffleMask).AsByte(); |
||||
|
row01_AB = Avx2.Shuffle(row01_AB, Avx.LoadVector256(shuffleVectorsPtr + (1 * 32))).AsByte(); |
||||
|
|
||||
|
Vector256<int> rows_CD01_GH23_shuffleMask = Avx.LoadVector256(shuffleVectorsPtr + (2 * 32)).AsInt32(); |
||||
|
Vector256<byte> row01_CD = Avx2.PermuteVar8x32(rowsCD.AsInt32(), rows_CD01_GH23_shuffleMask).AsByte(); |
||||
|
row01_CD = Avx2.Shuffle(row01_CD, Avx.LoadVector256(shuffleVectorsPtr + (3 * 32))).AsByte(); |
||||
|
|
||||
|
Vector256<byte> row0123_EF = Avx2.PermuteVar8x32(rowsEF.AsInt32(), rows_AB01_EF01_CD23_shuffleMask).AsByte(); |
||||
|
Vector256<byte> row01_EF = Avx2.Shuffle(row0123_EF, Avx.LoadVector256(shuffleVectorsPtr + (4 * 32))).AsByte(); |
||||
|
|
||||
|
Vector256<byte> row01 = Avx2.Or(Avx2.Or(row01_AB, row01_CD), row01_EF); |
||||
|
|
||||
|
// rows 2 3
|
||||
|
Vector256<int> rows_AB23_CD45_EF67_shuffleMask = Avx.LoadVector256(shuffleVectorsPtr + (5 * 32)).AsInt32(); |
||||
|
Vector256<byte> row2345_AB = Avx2.PermuteVar8x32(rowsAB.AsInt32(), rows_AB23_CD45_EF67_shuffleMask).AsByte(); |
||||
|
Vector256<byte> row23_AB = Avx2.Shuffle(row2345_AB, Avx.LoadVector256(shuffleVectorsPtr + (6 * 32))).AsByte(); |
||||
|
|
||||
|
Vector256<byte> row23_CD = Avx2.PermuteVar8x32(rowsCD.AsInt32(), rows_AB01_EF01_CD23_shuffleMask).AsByte(); |
||||
|
row23_CD = Avx2.Shuffle(row23_CD, Avx.LoadVector256(shuffleVectorsPtr + (7 * 32))).AsByte(); |
||||
|
|
||||
|
Vector256<byte> row23_EF = Avx2.Shuffle(row0123_EF, Avx.LoadVector256(shuffleVectorsPtr + (8 * 32))).AsByte(); |
||||
|
|
||||
|
Vector256<byte> row2345_GH = Avx2.PermuteVar8x32(rowsGH.AsInt32(), rows_CD01_GH23_shuffleMask).AsByte(); |
||||
|
Vector256<byte> row23_GH = Avx2.Shuffle(row2345_GH, Avx.LoadVector256(shuffleVectorsPtr + (9 * 32)).AsByte()); |
||||
|
|
||||
|
Vector256<byte> row23 = Avx2.Or(Avx2.Or(row23_AB, row23_CD), Avx2.Or(row23_EF, row23_GH)); |
||||
|
|
||||
|
// rows 4 5
|
||||
|
Vector256<byte> row45_AB = Avx2.Shuffle(row2345_AB, Avx.LoadVector256(shuffleVectorsPtr + (10 * 32)).AsByte()); |
||||
|
Vector256<byte> row4567_CD = Avx2.PermuteVar8x32(rowsCD.AsInt32(), rows_AB23_CD45_EF67_shuffleMask).AsByte(); |
||||
|
Vector256<byte> row45_CD = Avx2.Shuffle(row4567_CD, Avx.LoadVector256(shuffleVectorsPtr + (11 * 32)).AsByte()); |
||||
|
|
||||
|
Vector256<int> rows_EF45_GH67_shuffleMask = Avx.LoadVector256(shuffleVectorsPtr + (12 * 32)).AsInt32(); |
||||
|
Vector256<byte> row45_EF = Avx2.PermuteVar8x32(rowsEF.AsInt32(), rows_EF45_GH67_shuffleMask).AsByte(); |
||||
|
row45_EF = Avx2.Shuffle(row45_EF, Avx.LoadVector256(shuffleVectorsPtr + (13 * 32)).AsByte()); |
||||
|
|
||||
|
Vector256<byte> row45_GH = Avx2.Shuffle(row2345_GH, Avx.LoadVector256(shuffleVectorsPtr + (14 * 32)).AsByte()); |
||||
|
|
||||
|
Vector256<byte> row45 = Avx2.Or(Avx2.Or(row45_AB, row45_CD), Avx2.Or(row45_EF, row45_GH)); |
||||
|
|
||||
|
// rows 6 7
|
||||
|
Vector256<byte> row67_CD = Avx2.Shuffle(row4567_CD, Avx.LoadVector256(shuffleVectorsPtr + (15 * 32)).AsByte()); |
||||
|
|
||||
|
Vector256<byte> row67_EF = Avx2.PermuteVar8x32(rowsEF.AsInt32(), rows_AB23_CD45_EF67_shuffleMask).AsByte(); |
||||
|
row67_EF = Avx2.Shuffle(row67_EF, Avx.LoadVector256(shuffleVectorsPtr + (16 * 32)).AsByte()); |
||||
|
|
||||
|
Vector256<byte> row67_GH = Avx2.PermuteVar8x32(rowsGH.AsInt32(), rows_EF45_GH67_shuffleMask).AsByte(); |
||||
|
row67_GH = Avx2.Shuffle(row67_GH, Avx.LoadVector256(shuffleVectorsPtr + (17 * 32)).AsByte()); |
||||
|
|
||||
|
Vector256<byte> row67 = Avx2.Or(Avx2.Or(row67_CD, row67_EF), row67_GH); |
||||
|
|
||||
|
block.V01 = row01.AsInt16(); |
||||
|
block.V23 = row23.AsInt16(); |
||||
|
block.V45 = row45.AsInt16(); |
||||
|
block.V67 = row67.AsInt16(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
#endif
|
||||
@ -1,23 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Enumerates the chroma subsampling method applied to the image.
|
|
||||
/// </summary>
|
|
||||
public enum JpegSubsample |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// High Quality - Each of the three Y'CbCr components have the same sample rate,
|
|
||||
/// thus there is no chroma subsampling.
|
|
||||
/// </summary>
|
|
||||
Ratio444, |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Medium Quality - The horizontal sampling is halved and the Cb and Cr channels are only
|
|
||||
/// sampled on each alternate line.
|
|
||||
/// </summary>
|
|
||||
Ratio420 |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,49 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors |
||||
|
{ |
||||
|
internal class TiffJpegCompressor : TiffBaseCompressor |
||||
|
{ |
||||
|
public TiffJpegCompressor(Stream output, MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) |
||||
|
: base(output, memoryAllocator, width, bitsPerPixel, predictor) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override TiffCompression Method => TiffCompression.Jpeg; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void Initialize(int rowsPerStrip) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void CompressStrip(Span<byte> rows, int height) |
||||
|
{ |
||||
|
int pixelCount = rows.Length / 3; |
||||
|
int width = pixelCount / height; |
||||
|
|
||||
|
using var memoryStream = new MemoryStream(); |
||||
|
var image = Image.LoadPixelData<Rgb24>(rows, width, height); |
||||
|
image.Save(memoryStream, new JpegEncoder() |
||||
|
{ |
||||
|
ColorType = JpegColorType.Rgb |
||||
|
}); |
||||
|
memoryStream.Position = 0; |
||||
|
memoryStream.WriteTo(this.Output); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Dispose(bool disposing) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,154 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a reference scan line for CCITT 2D decoding.
|
||||
|
/// </summary>
|
||||
|
internal readonly ref struct CcittReferenceScanline |
||||
|
{ |
||||
|
private readonly ReadOnlySpan<byte> scanLine; |
||||
|
private readonly int width; |
||||
|
private readonly byte whiteByte; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="CcittReferenceScanline"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="whiteIsZero">Indicates, if white is zero, otherwise black is zero.</param>
|
||||
|
/// <param name="scanLine">The scan line.</param>
|
||||
|
public CcittReferenceScanline(bool whiteIsZero, ReadOnlySpan<byte> scanLine) |
||||
|
{ |
||||
|
this.scanLine = scanLine; |
||||
|
this.width = scanLine.Length; |
||||
|
this.whiteByte = whiteIsZero ? (byte)0 : (byte)255; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="CcittReferenceScanline"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="whiteIsZero">Indicates, if white is zero, otherwise black is zero.</param>
|
||||
|
/// <param name="width">The width of the scanline.</param>
|
||||
|
public CcittReferenceScanline(bool whiteIsZero, int width) |
||||
|
{ |
||||
|
this.scanLine = default; |
||||
|
this.width = width; |
||||
|
this.whiteByte = whiteIsZero ? (byte)0 : (byte)255; |
||||
|
} |
||||
|
|
||||
|
public bool IsEmpty => this.scanLine.IsEmpty; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Finds b1: The first changing element on the reference line to the right of a0 and of opposite color to a0.
|
||||
|
/// </summary>
|
||||
|
/// <param name="a0">The reference or starting element om the coding line.</param>
|
||||
|
/// <param name="a0Byte">Fill byte.</param>
|
||||
|
/// <returns>Position of b1.</returns>
|
||||
|
public int FindB1(int a0, byte a0Byte) |
||||
|
{ |
||||
|
if (this.IsEmpty) |
||||
|
{ |
||||
|
return this.FindB1ForImaginaryWhiteLine(a0, a0Byte); |
||||
|
} |
||||
|
|
||||
|
return this.FindB1ForNormalLine(a0, a0Byte); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Finds b2: The next changing element to the right of b1 on the reference line.
|
||||
|
/// </summary>
|
||||
|
/// <param name="b1">The first changing element on the reference line to the right of a0 and opposite of color to a0.</param>
|
||||
|
/// <returns>Position of b1.</returns>
|
||||
|
public int FindB2(int b1) |
||||
|
{ |
||||
|
if (this.IsEmpty) |
||||
|
{ |
||||
|
return this.FindB2ForImaginaryWhiteLine(); |
||||
|
} |
||||
|
|
||||
|
return this.FindB2ForNormalLine(b1); |
||||
|
} |
||||
|
|
||||
|
private int FindB1ForImaginaryWhiteLine(int a0, byte a0Byte) |
||||
|
{ |
||||
|
if (a0 < 0) |
||||
|
{ |
||||
|
if (a0Byte != this.whiteByte) |
||||
|
{ |
||||
|
return 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return this.width; |
||||
|
} |
||||
|
|
||||
|
private int FindB1ForNormalLine(int a0, byte a0Byte) |
||||
|
{ |
||||
|
int offset = 0; |
||||
|
if (a0 < 0) |
||||
|
{ |
||||
|
if (a0Byte != this.scanLine[0]) |
||||
|
{ |
||||
|
return 0; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
offset = a0; |
||||
|
} |
||||
|
|
||||
|
ReadOnlySpan<byte> searchSpace = this.scanLine.Slice(offset); |
||||
|
byte searchByte = (byte)~a0Byte; |
||||
|
int index = searchSpace.IndexOf(searchByte); |
||||
|
if (index < 0) |
||||
|
{ |
||||
|
return this.scanLine.Length; |
||||
|
} |
||||
|
|
||||
|
if (index != 0) |
||||
|
{ |
||||
|
return offset + index; |
||||
|
} |
||||
|
|
||||
|
searchByte = (byte)~searchSpace[0]; |
||||
|
index = searchSpace.IndexOf(searchByte); |
||||
|
if (index < 0) |
||||
|
{ |
||||
|
return this.scanLine.Length; |
||||
|
} |
||||
|
|
||||
|
searchSpace = searchSpace.Slice(index); |
||||
|
offset += index; |
||||
|
index = searchSpace.IndexOf((byte)~searchByte); |
||||
|
if (index < 0) |
||||
|
{ |
||||
|
return this.scanLine.Length; |
||||
|
} |
||||
|
|
||||
|
return index + offset; |
||||
|
} |
||||
|
|
||||
|
private int FindB2ForImaginaryWhiteLine() => this.width; |
||||
|
|
||||
|
private int FindB2ForNormalLine(int b1) |
||||
|
{ |
||||
|
if (b1 >= this.scanLine.Length) |
||||
|
{ |
||||
|
return this.scanLine.Length; |
||||
|
} |
||||
|
|
||||
|
byte searchByte = (byte)~this.scanLine[b1]; |
||||
|
int offset = b1 + 1; |
||||
|
ReadOnlySpan<byte> searchSpace = this.scanLine.Slice(offset); |
||||
|
int index = searchSpace.IndexOf(searchByte); |
||||
|
if (index == -1) |
||||
|
{ |
||||
|
return this.scanLine.Length; |
||||
|
} |
||||
|
|
||||
|
return offset + index; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Diagnostics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
[DebuggerDisplay("Type = {Type}")] |
||||
|
internal readonly struct CcittTwoDimensionalCode |
||||
|
{ |
||||
|
private readonly ushort value; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="CcittTwoDimensionalCode"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="type">The type.</param>
|
||||
|
/// <param name="bitsRequired">The bits required.</param>
|
||||
|
/// <param name="extensionBits">The extension bits.</param>
|
||||
|
public CcittTwoDimensionalCode(CcittTwoDimensionalCodeType type, int bitsRequired, int extensionBits = 0) |
||||
|
=> this.value = (ushort)((byte)type | ((bitsRequired & 0b1111) << 8) | ((extensionBits & 0b111) << 11)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the code type.
|
||||
|
/// </summary>
|
||||
|
public CcittTwoDimensionalCodeType Type => (CcittTwoDimensionalCodeType)(this.value & 0b11111111); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enum for the different two dimensional code words for the ccitt fax compression.
|
||||
|
/// </summary>
|
||||
|
internal enum CcittTwoDimensionalCodeType |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// No valid code word was read.
|
||||
|
/// </summary>
|
||||
|
None = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Pass mode: This mode is identified when the position of b2 lies to the left of a1.
|
||||
|
/// </summary>
|
||||
|
Pass = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicates horizontal mode.
|
||||
|
/// </summary>
|
||||
|
Horizontal = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Vertical 0 code word: relative distance between a1 and b1 is 0.
|
||||
|
/// </summary>
|
||||
|
Vertical0 = 3, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Vertical r1 code word: relative distance between a1 and b1 is 1, a1 is to the right of b1.
|
||||
|
/// </summary>
|
||||
|
VerticalR1 = 4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Vertical r2 code word: relative distance between a1 and b1 is 2, a1 is to the right of b1.
|
||||
|
/// </summary>
|
||||
|
VerticalR2 = 5, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Vertical r3 code word: relative distance between a1 and b1 is 3, a1 is to the right of b1.
|
||||
|
/// </summary>
|
||||
|
VerticalR3 = 6, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Vertical l1 code word: relative distance between a1 and b1 is 1, a1 is to the left of b1.
|
||||
|
/// </summary>
|
||||
|
VerticalL1 = 7, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Vertical l2 code word: relative distance between a1 and b1 is 2, a1 is to the left of b1.
|
||||
|
/// </summary>
|
||||
|
VerticalL2 = 8, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Vertical l3 code word: relative distance between a1 and b1 is 3, a1 is to the left of b1.
|
||||
|
/// </summary>
|
||||
|
VerticalL3 = 9, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 1d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme.
|
||||
|
/// Not supported.
|
||||
|
/// </summary>
|
||||
|
Extensions1D = 10, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 2d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme.
|
||||
|
/// Not supported.
|
||||
|
/// </summary>
|
||||
|
Extensions2D = 11, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,94 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Threading; |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg; |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.Metadata; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Class to handle cases where TIFF image data is compressed as a jpeg stream.
|
||||
|
/// </summary>
|
||||
|
internal sealed class JpegTiffCompression : TiffBaseDecompressor |
||||
|
{ |
||||
|
private readonly Configuration configuration; |
||||
|
|
||||
|
private readonly byte[] jpegTables; |
||||
|
|
||||
|
private readonly TiffPhotometricInterpretation photometricInterpretation; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="JpegTiffCompression"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="configuration">The configuration.</param>
|
||||
|
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
|
||||
|
/// <param name="width">The image width.</param>
|
||||
|
/// <param name="bitsPerPixel">The bits per pixel.</param>
|
||||
|
/// <param name="jpegTables">The JPEG tables containing the quantization and/or Huffman tables.</param>
|
||||
|
/// <param name="photometricInterpretation">The photometric interpretation.</param>
|
||||
|
public JpegTiffCompression( |
||||
|
Configuration configuration, |
||||
|
MemoryAllocator memoryAllocator, |
||||
|
int width, |
||||
|
int bitsPerPixel, |
||||
|
byte[] jpegTables, |
||||
|
TiffPhotometricInterpretation photometricInterpretation) |
||||
|
: base(memoryAllocator, width, bitsPerPixel) |
||||
|
{ |
||||
|
this.configuration = configuration; |
||||
|
this.jpegTables = jpegTables; |
||||
|
this.photometricInterpretation = photometricInterpretation; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer) |
||||
|
{ |
||||
|
if (this.jpegTables != null) |
||||
|
{ |
||||
|
using var jpegDecoder = new JpegDecoderCore(this.configuration, new JpegDecoder()); |
||||
|
|
||||
|
// TODO: Should we pass through the CancellationToken from the tiff decoder?
|
||||
|
// If the PhotometricInterpretation is YCbCr we explicitly assume the JPEG data is in RGB color space.
|
||||
|
// There seems no other way to determine that the JPEG data is RGB colorspace (no APP14 marker, componentId's are not RGB).
|
||||
|
using SpectralConverter<Rgb24> spectralConverter = this.photometricInterpretation == TiffPhotometricInterpretation.YCbCr ? |
||||
|
new RgbJpegSpectralConverter<Rgb24>(this.configuration, CancellationToken.None) : new SpectralConverter<Rgb24>(this.configuration, CancellationToken.None); |
||||
|
var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None); |
||||
|
jpegDecoder.LoadTables(this.jpegTables, scanDecoder); |
||||
|
scanDecoder.ResetInterval = 0; |
||||
|
jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None); |
||||
|
|
||||
|
CopyImageBytesToBuffer(buffer, spectralConverter.GetPixelBuffer()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
using var image = Image.Load<Rgb24>(stream); |
||||
|
CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void CopyImageBytesToBuffer(Span<byte> buffer, Buffer2D<Rgb24> pixelBuffer) |
||||
|
{ |
||||
|
int offset = 0; |
||||
|
for (int y = 0; y < pixelBuffer.Height; y++) |
||||
|
{ |
||||
|
Span<Rgb24> pixelRowSpan = pixelBuffer.GetRowSpan(y); |
||||
|
Span<byte> rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan); |
||||
|
rgbBytes.CopyTo(buffer.Slice(offset)); |
||||
|
offset += rgbBytes.Length; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Dispose(bool disposing) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Bit reader for data encoded with the modified huffman rle method.
|
||||
|
/// See TIFF 6.0 specification, section 10.
|
||||
|
/// </summary>
|
||||
|
internal sealed class ModifiedHuffmanBitReader : T4BitReader |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ModifiedHuffmanBitReader"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="input">The compressed input stream.</param>
|
||||
|
/// <param name="fillOrder">The logical order of bits within a byte.</param>
|
||||
|
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
|
||||
|
/// <param name="allocator">The memory allocator.</param>
|
||||
|
public ModifiedHuffmanBitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator) |
||||
|
: base(input, fillOrder, bytesToRead, allocator) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || ((uint)(this.BitsRead - 1) < (7 - 1)); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override bool IsEndOfScanLine |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (this.IsWhiteRun && this.CurValueBitsRead == 12 && this.Value == 1) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (this.CurValueBitsRead == 11 && this.Value == 0) |
||||
|
{ |
||||
|
// black run.
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void StartNewRow() |
||||
|
{ |
||||
|
base.StartNewRow(); |
||||
|
|
||||
|
int remainder = this.BitsRead & 7; // bit-hack for % 8
|
||||
|
if (remainder != 0) |
||||
|
{ |
||||
|
// Skip padding bits, move to next byte.
|
||||
|
this.Position++; |
||||
|
this.ResetBitsRead(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// No EOL is expected at the start of a run for the modified huffman encoding.
|
||||
|
/// </summary>
|
||||
|
protected override void ReadEolBeforeFirstData() |
||||
|
{ |
||||
|
// Nothing to do here.
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Threading; |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Spectral converter for YCbCr TIFF's which use the JPEG compression.
|
||||
|
/// The jpeg data should be always treated as RGB color space.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
|
||||
|
internal sealed class RgbJpegSpectralConverter<TPixel> : SpectralConverter<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="RgbJpegSpectralConverter{TPixel}"/> class.
|
||||
|
/// This Spectral converter will always convert the pixel data to RGB color.
|
||||
|
/// </summary>
|
||||
|
/// <param name="configuration">The configuration.</param>
|
||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
|
public RgbJpegSpectralConverter(Configuration configuration, CancellationToken cancellationToken) |
||||
|
: base(configuration, cancellationToken) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override JpegColorConverter GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(JpegColorSpace.RGB, frame.Precision); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,159 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Bit reader for reading CCITT T6 compressed fax data.
|
||||
|
/// See: Facsimile Coding Schemes and Coding Control Functions for Group 4 Facsimile Apparatus, itu-t recommendation t.6
|
||||
|
/// </summary>
|
||||
|
internal sealed class T6BitReader : T4BitReader |
||||
|
{ |
||||
|
private readonly int maxCodeLength = 12; |
||||
|
|
||||
|
private static readonly CcittTwoDimensionalCode None = new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.None, 0); |
||||
|
|
||||
|
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len1Codes = new Dictionary<uint, CcittTwoDimensionalCode>() |
||||
|
{ |
||||
|
{ 0b1, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Vertical0, 1) } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len3Codes = new Dictionary<uint, CcittTwoDimensionalCode>() |
||||
|
{ |
||||
|
{ 0b001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Horizontal, 3) }, |
||||
|
{ 0b010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL1, 3) }, |
||||
|
{ 0b011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR1, 3) } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len4Codes = new Dictionary<uint, CcittTwoDimensionalCode>() |
||||
|
{ |
||||
|
{ 0b0001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Pass, 4) } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len6Codes = new Dictionary<uint, CcittTwoDimensionalCode>() |
||||
|
{ |
||||
|
{ 0b000011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR2, 6) }, |
||||
|
{ 0b000010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL2, 6) } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len7Codes = new Dictionary<uint, CcittTwoDimensionalCode>() |
||||
|
{ |
||||
|
{ 0b0000011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR3, 7) }, |
||||
|
{ 0b0000010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL3, 7) }, |
||||
|
{ 0b0000001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Extensions2D, 7) }, |
||||
|
{ 0b0000000, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Extensions1D, 7) } |
||||
|
}; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="T6BitReader"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="input">The compressed input stream.</param>
|
||||
|
/// <param name="fillOrder">The logical order of bits within a byte.</param>
|
||||
|
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
|
||||
|
/// <param name="allocator">The memory allocator.</param>
|
||||
|
public T6BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator) |
||||
|
: base(input, fillOrder, bytesToRead, allocator) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || ((uint)(this.BitsRead - 1) < (7 - 1)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the two dimensional code.
|
||||
|
/// </summary>
|
||||
|
public CcittTwoDimensionalCode Code { get; internal set; } |
||||
|
|
||||
|
public bool ReadNextCodeWord() |
||||
|
{ |
||||
|
this.Code = None; |
||||
|
this.Reset(); |
||||
|
uint value = this.ReadValue(1); |
||||
|
|
||||
|
do |
||||
|
{ |
||||
|
if (this.CurValueBitsRead > this.maxCodeLength) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: invalid code length read"); |
||||
|
} |
||||
|
|
||||
|
switch (this.CurValueBitsRead) |
||||
|
{ |
||||
|
case 1: |
||||
|
if (Len1Codes.ContainsKey(value)) |
||||
|
{ |
||||
|
this.Code = Len1Codes[value]; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
|
||||
|
case 3: |
||||
|
if (Len3Codes.ContainsKey(value)) |
||||
|
{ |
||||
|
this.Code = Len3Codes[value]; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
|
||||
|
case 4: |
||||
|
if (Len4Codes.ContainsKey(value)) |
||||
|
{ |
||||
|
this.Code = Len4Codes[value]; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
|
||||
|
case 6: |
||||
|
if (Len6Codes.ContainsKey(value)) |
||||
|
{ |
||||
|
this.Code = Len6Codes[value]; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
|
||||
|
case 7: |
||||
|
if (Len7Codes.ContainsKey(value)) |
||||
|
{ |
||||
|
this.Code = Len7Codes[value]; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
uint currBit = this.ReadValue(1); |
||||
|
value = (value << 1) | currBit; |
||||
|
} |
||||
|
while (!this.IsEndOfScanLine); |
||||
|
|
||||
|
if (this.IsEndOfScanLine) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// No EOL is expected at the start of a run.
|
||||
|
/// </summary>
|
||||
|
protected override void ReadEolBeforeFirstData() |
||||
|
{ |
||||
|
// Nothing to do here.
|
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Swaps the white run to black run an vise versa.
|
||||
|
/// </summary>
|
||||
|
public void SwapColor() => this.IsWhiteRun = !this.IsWhiteRun; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,254 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Class to handle cases where TIFF image data is compressed using CCITT T6 compression.
|
||||
|
/// </summary>
|
||||
|
internal sealed class T6TiffCompression : TiffBaseDecompressor |
||||
|
{ |
||||
|
private readonly bool isWhiteZero; |
||||
|
|
||||
|
private readonly byte whiteValue; |
||||
|
|
||||
|
private readonly byte blackValue; |
||||
|
|
||||
|
private readonly int width; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="T6TiffCompression" /> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="allocator">The memory allocator.</param>
|
||||
|
/// <param name="fillOrder">The logical order of bits within a byte.</param>
|
||||
|
/// <param name="width">The image width.</param>
|
||||
|
/// <param name="bitsPerPixel">The number of bits per pixel.</param>
|
||||
|
/// <param name="photometricInterpretation">The photometric interpretation.</param>
|
||||
|
public T6TiffCompression( |
||||
|
MemoryAllocator allocator, |
||||
|
TiffFillOrder fillOrder, |
||||
|
int width, |
||||
|
int bitsPerPixel, |
||||
|
TiffPhotometricInterpretation photometricInterpretation) |
||||
|
: base(allocator, width, bitsPerPixel) |
||||
|
{ |
||||
|
this.FillOrder = fillOrder; |
||||
|
this.width = width; |
||||
|
this.isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; |
||||
|
this.whiteValue = (byte)(this.isWhiteZero ? 0 : 1); |
||||
|
this.blackValue = (byte)(this.isWhiteZero ? 1 : 0); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the logical order of bits within a byte.
|
||||
|
/// </summary>
|
||||
|
private TiffFillOrder FillOrder { get; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer) |
||||
|
{ |
||||
|
int height = stripHeight; |
||||
|
|
||||
|
using System.Buffers.IMemoryOwner<byte> scanLineBuffer = this.Allocator.Allocate<byte>(this.width * 2); |
||||
|
Span<byte> scanLine = scanLineBuffer.GetSpan().Slice(0, this.width); |
||||
|
Span<byte> referenceScanLineSpan = scanLineBuffer.GetSpan().Slice(this.width, this.width); |
||||
|
|
||||
|
using var bitReader = new T6BitReader(stream, this.FillOrder, byteCount, this.Allocator); |
||||
|
|
||||
|
var referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, this.width); |
||||
|
uint bitsWritten = 0; |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
scanLine.Fill(0); |
||||
|
Decode2DScanline(bitReader, this.isWhiteZero, referenceScanLine, scanLine); |
||||
|
|
||||
|
bitsWritten = this.WriteScanLine(buffer, scanLine, bitsWritten); |
||||
|
|
||||
|
scanLine.CopyTo(referenceScanLineSpan); |
||||
|
referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, referenceScanLineSpan); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private uint WriteScanLine(Span<byte> buffer, Span<byte> scanLine, uint bitsWritten) |
||||
|
{ |
||||
|
byte white = (byte)(this.isWhiteZero ? 0 : 255); |
||||
|
for (int i = 0; i < scanLine.Length; i++) |
||||
|
{ |
||||
|
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, 1, scanLine[i] == white ? this.whiteValue : this.blackValue); |
||||
|
bitsWritten++; |
||||
|
} |
||||
|
|
||||
|
// Write padding bytes, if necessary.
|
||||
|
uint remainder = bitsWritten % 8; |
||||
|
if (remainder != 0) |
||||
|
{ |
||||
|
uint padding = 8 - remainder; |
||||
|
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, padding, 0); |
||||
|
bitsWritten += padding; |
||||
|
} |
||||
|
|
||||
|
return bitsWritten; |
||||
|
} |
||||
|
|
||||
|
private static void Decode2DScanline(T6BitReader bitReader, bool whiteIsZero, CcittReferenceScanline referenceScanline, Span<byte> scanline) |
||||
|
{ |
||||
|
int width = scanline.Length; |
||||
|
bitReader.StartNewRow(); |
||||
|
|
||||
|
// 2D Encoding variables.
|
||||
|
int a0 = -1; |
||||
|
byte fillByte = whiteIsZero ? (byte)0 : (byte)255; |
||||
|
|
||||
|
// Process every code word in this scanline.
|
||||
|
int unpacked = 0; |
||||
|
while (true) |
||||
|
{ |
||||
|
// Read next code word and advance pass it.
|
||||
|
bool isEol = bitReader.ReadNextCodeWord(); |
||||
|
|
||||
|
// Special case handling for EOL.
|
||||
|
if (isEol) |
||||
|
{ |
||||
|
// If a TIFF reader encounters EOFB before the expected number of lines has been extracted,
|
||||
|
// it is appropriate to assume that the missing rows consist entirely of white pixels.
|
||||
|
scanline.Fill(whiteIsZero ? (byte)0 : (byte)255); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
// Update 2D Encoding variables.
|
||||
|
int b1 = referenceScanline.FindB1(a0, fillByte); |
||||
|
|
||||
|
// Switch on the code word.
|
||||
|
int a1; |
||||
|
switch (bitReader.Code.Type) |
||||
|
{ |
||||
|
case CcittTwoDimensionalCodeType.None: |
||||
|
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, could not read a valid code word."); |
||||
|
break; |
||||
|
|
||||
|
case CcittTwoDimensionalCodeType.Pass: |
||||
|
int b2 = referenceScanline.FindB2(b1); |
||||
|
scanline.Slice(unpacked, b2 - unpacked).Fill(fillByte); |
||||
|
unpacked = b2; |
||||
|
a0 = b2; |
||||
|
break; |
||||
|
case CcittTwoDimensionalCodeType.Horizontal: |
||||
|
// Decode M(a0a1)
|
||||
|
bitReader.ReadNextRun(); |
||||
|
int runLength = (int)bitReader.RunLength; |
||||
|
if (runLength > (uint)(scanline.Length - unpacked)) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error"); |
||||
|
} |
||||
|
|
||||
|
scanline.Slice(unpacked, runLength).Fill(fillByte); |
||||
|
unpacked += runLength; |
||||
|
fillByte = (byte)~fillByte; |
||||
|
|
||||
|
// Decode M(a1a2)
|
||||
|
bitReader.ReadNextRun(); |
||||
|
runLength = (int)bitReader.RunLength; |
||||
|
if (runLength > (uint)(scanline.Length - unpacked)) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error"); |
||||
|
} |
||||
|
|
||||
|
scanline.Slice(unpacked, runLength).Fill(fillByte); |
||||
|
unpacked += runLength; |
||||
|
fillByte = (byte)~fillByte; |
||||
|
|
||||
|
// Prepare next a0
|
||||
|
a0 = unpacked; |
||||
|
break; |
||||
|
|
||||
|
case CcittTwoDimensionalCodeType.Vertical0: |
||||
|
a1 = b1; |
||||
|
scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); |
||||
|
unpacked = a1; |
||||
|
a0 = a1; |
||||
|
fillByte = (byte)~fillByte; |
||||
|
bitReader.SwapColor(); |
||||
|
break; |
||||
|
|
||||
|
case CcittTwoDimensionalCodeType.VerticalR1: |
||||
|
a1 = b1 + 1; |
||||
|
scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); |
||||
|
unpacked = a1; |
||||
|
a0 = a1; |
||||
|
fillByte = (byte)~fillByte; |
||||
|
bitReader.SwapColor(); |
||||
|
break; |
||||
|
|
||||
|
case CcittTwoDimensionalCodeType.VerticalR2: |
||||
|
a1 = b1 + 2; |
||||
|
scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); |
||||
|
unpacked = a1; |
||||
|
a0 = a1; |
||||
|
fillByte = (byte)~fillByte; |
||||
|
bitReader.SwapColor(); |
||||
|
break; |
||||
|
|
||||
|
case CcittTwoDimensionalCodeType.VerticalR3: |
||||
|
a1 = b1 + 3; |
||||
|
scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); |
||||
|
unpacked = a1; |
||||
|
a0 = a1; |
||||
|
fillByte = (byte)~fillByte; |
||||
|
bitReader.SwapColor(); |
||||
|
break; |
||||
|
|
||||
|
case CcittTwoDimensionalCodeType.VerticalL1: |
||||
|
a1 = b1 - 1; |
||||
|
scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); |
||||
|
unpacked = a1; |
||||
|
a0 = a1; |
||||
|
fillByte = (byte)~fillByte; |
||||
|
bitReader.SwapColor(); |
||||
|
break; |
||||
|
|
||||
|
case CcittTwoDimensionalCodeType.VerticalL2: |
||||
|
a1 = b1 - 2; |
||||
|
scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); |
||||
|
unpacked = a1; |
||||
|
a0 = a1; |
||||
|
fillByte = (byte)~fillByte; |
||||
|
bitReader.SwapColor(); |
||||
|
break; |
||||
|
|
||||
|
case CcittTwoDimensionalCodeType.VerticalL3: |
||||
|
a1 = b1 - 3; |
||||
|
scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); |
||||
|
unpacked = a1; |
||||
|
a0 = a1; |
||||
|
fillByte = (byte)~fillByte; |
||||
|
bitReader.SwapColor(); |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
throw new NotSupportedException("ccitt extensions are not supported."); |
||||
|
} |
||||
|
|
||||
|
// This line is fully unpacked. Should exit and process next line.
|
||||
|
if (unpacked == width) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (unpacked > width) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, unpacked data > width"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Dispose(bool disposing) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue