mirror of https://github.com/SixLabors/ImageSharp
Browse Source
# Conflicts: # src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cspull/1730/head
40 changed files with 1199 additions and 953 deletions
@ -0,0 +1,39 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
#if SUPPORTS_RUNTIME_INTRINSICS
|
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components |
|||
{ |
|||
internal unsafe partial struct Block8x8 |
|||
{ |
|||
[FieldOffset(0)] |
|||
public Vector128<short> V0; |
|||
[FieldOffset(16)] |
|||
public Vector128<short> V1; |
|||
[FieldOffset(32)] |
|||
public Vector128<short> V2; |
|||
[FieldOffset(48)] |
|||
public Vector128<short> V3; |
|||
[FieldOffset(64)] |
|||
public Vector128<short> V4; |
|||
[FieldOffset(80)] |
|||
public Vector128<short> V5; |
|||
[FieldOffset(96)] |
|||
public Vector128<short> V6; |
|||
[FieldOffset(112)] |
|||
public Vector128<short> V7; |
|||
|
|||
[FieldOffset(0)] |
|||
public Vector256<short> V01; |
|||
[FieldOffset(32)] |
|||
public Vector256<short> V23; |
|||
[FieldOffset(64)] |
|||
public Vector256<short> V45; |
|||
[FieldOffset(96)] |
|||
public Vector256<short> V67; |
|||
} |
|||
} |
|||
#endif
|
|||
@ -1,82 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder |
|||
{ |
|||
/// <summary>
|
|||
/// Encapsulates the implementation of processing "raw" jpeg buffers into Jpeg image channels.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal struct JpegBlockPostProcessor |
|||
{ |
|||
/// <summary>
|
|||
/// Source block
|
|||
/// </summary>
|
|||
public Block8x8F SourceBlock; |
|||
|
|||
/// <summary>
|
|||
/// The quantization table as <see cref="Block8x8F"/>.
|
|||
/// </summary>
|
|||
public Block8x8F DequantiazationTable; |
|||
|
|||
/// <summary>
|
|||
/// Defines the horizontal and vertical scale we need to apply to the 8x8 sized block.
|
|||
/// </summary>
|
|||
private Size subSamplingDivisors; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JpegBlockPostProcessor"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="decoder">The raw jpeg data.</param>
|
|||
/// <param name="component">The raw component.</param>
|
|||
public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component) |
|||
{ |
|||
int qtIndex = component.QuantizationTableIndex; |
|||
this.DequantiazationTable = decoder.QuantizationTables[qtIndex]; |
|||
this.subSamplingDivisors = component.SubSamplingDivisors; |
|||
|
|||
this.SourceBlock = default; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes 'sourceBlock' producing Jpeg color channel values from spectral values:
|
|||
/// - Dequantize
|
|||
/// - Applying IDCT
|
|||
/// - Level shift by +maximumValue/2, clip to [0, maximumValue]
|
|||
/// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in <see cref="subSamplingDivisors"/>.
|
|||
/// </summary>
|
|||
/// <param name="sourceBlock">The source block.</param>
|
|||
/// <param name="destAreaOrigin">Reference to the origin of the destination pixel area.</param>
|
|||
/// <param name="destAreaStride">The width of the destination pixel buffer.</param>
|
|||
/// <param name="maximumValue">The maximum value derived from the bitdepth.</param>
|
|||
public void ProcessBlockColorsInto( |
|||
ref Block8x8 sourceBlock, |
|||
ref float destAreaOrigin, |
|||
int destAreaStride, |
|||
float maximumValue) |
|||
{ |
|||
ref Block8x8F block = ref this.SourceBlock; |
|||
block.LoadFrom(ref sourceBlock); |
|||
|
|||
// Dequantize:
|
|||
block.MultiplyInPlace(ref this.DequantiazationTable); |
|||
|
|||
FastFloatingPointDCT.TransformIDCT(ref block); |
|||
|
|||
// To conform better to libjpeg we actually NEED TO loose precision here.
|
|||
// This is because they store blocks as Int16 between all the operations.
|
|||
// To be "more accurate", we need to emulate this by rounding!
|
|||
block.NormalizeColorsAndRoundInPlace(maximumValue); |
|||
|
|||
block.ScaledCopyTo( |
|||
ref destAreaOrigin, |
|||
destAreaStride, |
|||
this.subSamplingDivisors.Width, |
|||
this.subSamplingDivisors.Height); |
|||
} |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// <auto-generated />
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components |
|||
{ |
|||
internal unsafe partial struct GenericBlock8x8<T> |
|||
{ |
|||
#pragma warning disable 169
|
|||
|
|||
// It's not allowed use fix-sized buffers with generics, need to place all the fields manually:
|
|||
private T _y0_x0, _y0_x1, _y0_x2, _y0_x3, _y0_x4, _y0_x5, _y0_x6, _y0_x7; |
|||
private T _y1_x0, _y1_x1, _y1_x2, _y1_x3, _y1_x4, _y1_x5, _y1_x6, _y1_x7; |
|||
private T _y2_x0, _y2_x1, _y2_x2, _y2_x3, _y2_x4, _y2_x5, _y2_x6, _y2_x7; |
|||
private T _y3_x0, _y3_x1, _y3_x2, _y3_x3, _y3_x4, _y3_x5, _y3_x6, _y3_x7; |
|||
private T _y4_x0, _y4_x1, _y4_x2, _y4_x3, _y4_x4, _y4_x5, _y4_x6, _y4_x7; |
|||
private T _y5_x0, _y5_x1, _y5_x2, _y5_x3, _y5_x4, _y5_x5, _y5_x6, _y5_x7; |
|||
private T _y6_x0, _y6_x1, _y6_x2, _y6_x3, _y6_x4, _y6_x5, _y6_x6, _y6_x7; |
|||
private T _y7_x0, _y7_x1, _y7_x2, _y7_x3, _y7_x4, _y7_x5, _y7_x6, _y7_x7; |
|||
|
|||
#pragma warning restore 169
|
|||
} |
|||
} |
|||
@ -1,122 +0,0 @@ |
|||
// 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.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components |
|||
{ |
|||
/// <summary>
|
|||
/// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal unsafe partial struct GenericBlock8x8<T> |
|||
where T : unmanaged |
|||
{ |
|||
public const int Size = 64; |
|||
|
|||
/// <summary>
|
|||
/// FOR TESTING ONLY!
|
|||
/// Gets or sets a <see cref="Rgb24"/> value at the given index
|
|||
/// </summary>
|
|||
/// <param name="idx">The index</param>
|
|||
/// <returns>The value</returns>
|
|||
public T this[int idx] |
|||
{ |
|||
get |
|||
{ |
|||
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this); |
|||
return Unsafe.Add(ref selfRef, idx); |
|||
} |
|||
|
|||
set |
|||
{ |
|||
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this); |
|||
Unsafe.Add(ref selfRef, idx) = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// FOR TESTING ONLY!
|
|||
/// Gets or sets a value in a row+column of the 8x8 block
|
|||
/// </summary>
|
|||
/// <param name="x">The x position index in the row</param>
|
|||
/// <param name="y">The column index</param>
|
|||
/// <returns>The value</returns>
|
|||
public T this[int x, int y] |
|||
{ |
|||
get => this[(y * 8) + x]; |
|||
set => this[(y * 8) + x] = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Load a 8x8 region of an image into the block.
|
|||
/// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image.
|
|||
/// </summary>
|
|||
public void LoadAndStretchEdges(Buffer2D<T> source, int sourceX, int sourceY, ref RowOctet<T> currentRows) |
|||
{ |
|||
int width = Math.Min(8, source.Width - sourceX); |
|||
int height = Math.Min(8, source.Height - sourceY); |
|||
|
|||
if (width <= 0 || height <= 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
uint byteWidth = (uint)width * (uint)Unsafe.SizeOf<T>(); |
|||
int remainderXCount = 8 - width; |
|||
|
|||
ref byte blockStart = ref Unsafe.As<GenericBlock8x8<T>, byte>(ref this); |
|||
int blockRowSizeInBytes = 8 * Unsafe.SizeOf<T>(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<T> row = currentRows[y]; |
|||
|
|||
ref byte s = ref Unsafe.As<T, byte>(ref row[sourceX]); |
|||
ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes); |
|||
|
|||
Unsafe.CopyBlock(ref d, ref s, byteWidth); |
|||
|
|||
ref T last = ref Unsafe.Add(ref Unsafe.As<byte, T>(ref d), width - 1); |
|||
|
|||
for (int x = 1; x <= remainderXCount; x++) |
|||
{ |
|||
Unsafe.Add(ref last, x) = last; |
|||
} |
|||
} |
|||
|
|||
int remainderYCount = 8 - height; |
|||
|
|||
if (remainderYCount == 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * blockRowSizeInBytes); |
|||
|
|||
for (int y = 1; y <= remainderYCount; y++) |
|||
{ |
|||
ref byte remStart = ref Unsafe.Add(ref lastRowStart, blockRowSizeInBytes * y); |
|||
Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)blockRowSizeInBytes); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Only for on-stack instances!
|
|||
/// </summary>
|
|||
public Span<T> AsSpanUnsafe() |
|||
{ |
|||
#if SUPPORTS_CREATESPAN
|
|||
Span<GenericBlock8x8<T>> s = MemoryMarshal.CreateSpan(ref this, 1); |
|||
return MemoryMarshal.Cast<GenericBlock8x8<T>, T>(s); |
|||
#else
|
|||
return new Span<T>(Unsafe.AsPointer(ref this), Size); |
|||
#endif
|
|||
} |
|||
} |
|||
} |
|||
@ -1,129 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Jpg |
|||
{ |
|||
[Trait("Format", "Jpg")] |
|||
public class GenericBlock8x8Tests |
|||
{ |
|||
public static Image<TPixel> CreateTestImage<TPixel>() |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
var image = new Image<TPixel>(10, 10); |
|||
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer(); |
|||
for (int i = 0; i < 10; i++) |
|||
{ |
|||
for (int j = 0; j < 10; j++) |
|||
{ |
|||
var rgba = new Rgba32((byte)(i + 1), (byte)(j + 1), 200, 255); |
|||
var color = default(TPixel); |
|||
color.FromRgba32(rgba); |
|||
|
|||
pixels[i, j] = color; |
|||
} |
|||
} |
|||
|
|||
return image; |
|||
} |
|||
|
|||
[Theory] |
|||
[WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32 /* | PixelTypes.Rgba32 | PixelTypes.Argb32*/)] |
|||
public void LoadAndStretchCorners_FromOrigo<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> s = provider.GetImage()) |
|||
{ |
|||
var d = default(GenericBlock8x8<TPixel>); |
|||
RowOctet<TPixel> rowOctet = default; |
|||
rowOctet.Update(s.GetRootFramePixelBuffer(), 0); |
|||
d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 0, 0, ref rowOctet); |
|||
|
|||
TPixel a = s.Frames.RootFrame[0, 0]; |
|||
TPixel b = d[0, 0]; |
|||
|
|||
Assert.Equal(s[0, 0], d[0, 0]); |
|||
Assert.Equal(s[1, 0], d[1, 0]); |
|||
Assert.Equal(s[7, 0], d[7, 0]); |
|||
Assert.Equal(s[0, 1], d[0, 1]); |
|||
Assert.Equal(s[1, 1], d[1, 1]); |
|||
Assert.Equal(s[7, 0], d[7, 0]); |
|||
Assert.Equal(s[0, 7], d[0, 7]); |
|||
Assert.Equal(s[7, 7], d[7, 7]); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32)] |
|||
public void LoadAndStretchCorners_WithOffset<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> s = provider.GetImage()) |
|||
{ |
|||
var d = default(GenericBlock8x8<TPixel>); |
|||
RowOctet<TPixel> rowOctet = default; |
|||
rowOctet.Update(s.GetRootFramePixelBuffer(), 7); |
|||
|
|||
d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 6, 7, ref rowOctet); |
|||
|
|||
Assert.Equal(s[6, 7], d[0, 0]); |
|||
Assert.Equal(s[6, 8], d[0, 1]); |
|||
Assert.Equal(s[7, 8], d[1, 1]); |
|||
|
|||
Assert.Equal(s[6, 9], d[0, 2]); |
|||
Assert.Equal(s[6, 9], d[0, 3]); |
|||
Assert.Equal(s[6, 9], d[0, 7]); |
|||
|
|||
Assert.Equal(s[7, 9], d[1, 2]); |
|||
Assert.Equal(s[7, 9], d[1, 3]); |
|||
Assert.Equal(s[7, 9], d[1, 7]); |
|||
|
|||
Assert.Equal(s[9, 9], d[3, 2]); |
|||
Assert.Equal(s[9, 9], d[3, 3]); |
|||
Assert.Equal(s[9, 9], d[3, 7]); |
|||
|
|||
Assert.Equal(s[9, 7], d[3, 0]); |
|||
Assert.Equal(s[9, 7], d[4, 0]); |
|||
Assert.Equal(s[9, 7], d[7, 0]); |
|||
|
|||
Assert.Equal(s[9, 9], d[3, 2]); |
|||
Assert.Equal(s[9, 9], d[4, 2]); |
|||
Assert.Equal(s[9, 9], d[7, 2]); |
|||
|
|||
Assert.Equal(s[9, 9], d[4, 3]); |
|||
Assert.Equal(s[9, 9], d[7, 7]); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Indexer() |
|||
{ |
|||
var block = default(GenericBlock8x8<Rgb24>); |
|||
Span<Rgb24> span = block.AsSpanUnsafe(); |
|||
Assert.Equal(64, span.Length); |
|||
|
|||
for (int i = 0; i < 64; i++) |
|||
{ |
|||
span[i] = new Rgb24((byte)i, (byte)(2 * i), (byte)(3 * i)); |
|||
} |
|||
|
|||
var expected00 = new Rgb24(0, 0, 0); |
|||
var expected07 = new Rgb24(7, 14, 21); |
|||
var expected11 = new Rgb24(9, 18, 27); |
|||
var expected77 = new Rgb24(63, 126, 189); |
|||
var expected67 = new Rgb24(62, 124, 186); |
|||
|
|||
Assert.Equal(expected00, block[0, 0]); |
|||
Assert.Equal(expected07, block[7, 0]); |
|||
Assert.Equal(expected11, block[1, 1]); |
|||
Assert.Equal(expected67, block[6, 7]); |
|||
Assert.Equal(expected77, block[7, 7]); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue