mirror of https://github.com/SixLabors/ImageSharp
Browse Source
# Conflicts: # tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cspull/1552/head
219 changed files with 3257 additions and 2117 deletions
@ -1 +1 @@ |
|||
Subproject commit 48e73f455f15eafefbe3175efc7433e5f277e506 |
|||
Subproject commit 9b94ebc4be9b7a8d7620c257e6ee485455973332 |
|||
@ -0,0 +1,427 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Threading; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder |
|||
{ |
|||
internal class HuffmanScanEncoder |
|||
{ |
|||
/// <summary>
|
|||
/// Number of bytes cached before being written to target stream via Stream.Write(byte[], offest, count).
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This is subject to change, 1024 seems to be the best value in terms of performance.
|
|||
/// <see cref="Emit(int, int)"/> expects it to be at least 8 (see comments in method body).
|
|||
/// </remarks>
|
|||
private const int EmitBufferSizeInBytes = 1024; |
|||
|
|||
/// <summary>
|
|||
/// A buffer for reducing the number of stream writes when emitting Huffman tables.
|
|||
/// </summary>
|
|||
private readonly byte[] emitBuffer = new byte[EmitBufferSizeInBytes]; |
|||
|
|||
/// <summary>
|
|||
/// Number of filled bytes in <see cref="emitBuffer"/> buffer
|
|||
/// </summary>
|
|||
private int emitLen = 0; |
|||
|
|||
/// <summary>
|
|||
/// Emmited bits 'micro buffer' before being transfered to the <see cref="emitBuffer"/>.
|
|||
/// </summary>
|
|||
private int accumulatedBits; |
|||
|
|||
/// <summary>
|
|||
/// Number of jagged bits stored in <see cref="accumulatedBits"/>
|
|||
/// </summary>
|
|||
private int bitCount; |
|||
|
|||
private Block8x8F temporalBlock1; |
|||
private Block8x8F temporalBlock2; |
|||
|
|||
/// <summary>
|
|||
/// The output stream. All attempted writes after the first error become no-ops.
|
|||
/// </summary>
|
|||
private readonly Stream target; |
|||
|
|||
public HuffmanScanEncoder(Stream outputStream) |
|||
{ |
|||
this.target = outputStream; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes the image with no subsampling.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
|
|||
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee</param>
|
|||
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee</param>
|
|||
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
|
|||
public void Encode444<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
var unzig = ZigZag.CreateUnzigTable(); |
|||
|
|||
// ReSharper disable once InconsistentNaming
|
|||
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; |
|||
|
|||
ImageFrame<TPixel> frame = pixels.Frames.RootFrame; |
|||
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer; |
|||
RowOctet<TPixel> currentRows = default; |
|||
|
|||
var pixelConverter = new YCbCrForwardConverter444<TPixel>(frame); |
|||
|
|||
for (int y = 0; y < pixels.Height; y += 8) |
|||
{ |
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
currentRows.Update(pixelBuffer, y); |
|||
|
|||
for (int x = 0; x < pixels.Width; x += 8) |
|||
{ |
|||
pixelConverter.Convert(x, y, ref currentRows); |
|||
|
|||
prevDCY = this.WriteBlock( |
|||
QuantIndex.Luminance, |
|||
prevDCY, |
|||
ref pixelConverter.Y, |
|||
ref luminanceQuantTable, |
|||
ref unzig); |
|||
|
|||
prevDCCb = this.WriteBlock( |
|||
QuantIndex.Chrominance, |
|||
prevDCCb, |
|||
ref pixelConverter.Cb, |
|||
ref chrominanceQuantTable, |
|||
ref unzig); |
|||
|
|||
prevDCCr = this.WriteBlock( |
|||
QuantIndex.Chrominance, |
|||
prevDCCr, |
|||
ref pixelConverter.Cr, |
|||
ref chrominanceQuantTable, |
|||
ref unzig); |
|||
} |
|||
} |
|||
|
|||
this.FlushInternalBuffer(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes the image with subsampling. The Cb and Cr components are each subsampled
|
|||
/// at a factor of 2 both horizontally and vertically.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
|
|||
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee</param>
|
|||
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee</param>
|
|||
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
|
|||
public void Encode420<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
var unzig = ZigZag.CreateUnzigTable(); |
|||
|
|||
// ReSharper disable once InconsistentNaming
|
|||
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; |
|||
ImageFrame<TPixel> frame = pixels.Frames.RootFrame; |
|||
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer; |
|||
RowOctet<TPixel> currentRows = default; |
|||
|
|||
var pixelConverter = new YCbCrForwardConverter420<TPixel>(frame); |
|||
|
|||
for (int y = 0; y < pixels.Height; y += 16) |
|||
{ |
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
for (int x = 0; x < pixels.Width; x += 16) |
|||
{ |
|||
for (int i = 0; i < 2; i++) |
|||
{ |
|||
int yOff = i * 8; |
|||
currentRows.Update(pixelBuffer, y + yOff); |
|||
pixelConverter.Convert(x, y, ref currentRows, i); |
|||
|
|||
prevDCY = this.WriteBlock( |
|||
QuantIndex.Luminance, |
|||
prevDCY, |
|||
ref pixelConverter.YLeft, |
|||
ref luminanceQuantTable, |
|||
ref unzig); |
|||
|
|||
prevDCY = this.WriteBlock( |
|||
QuantIndex.Luminance, |
|||
prevDCY, |
|||
ref pixelConverter.YRight, |
|||
ref luminanceQuantTable, |
|||
ref unzig); |
|||
} |
|||
|
|||
prevDCCb = this.WriteBlock( |
|||
QuantIndex.Chrominance, |
|||
prevDCCb, |
|||
ref pixelConverter.Cb, |
|||
ref chrominanceQuantTable, |
|||
ref unzig); |
|||
|
|||
prevDCCr = this.WriteBlock( |
|||
QuantIndex.Chrominance, |
|||
prevDCCr, |
|||
ref pixelConverter.Cr, |
|||
ref chrominanceQuantTable, |
|||
ref unzig); |
|||
} |
|||
} |
|||
|
|||
this.FlushInternalBuffer(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes the image with no chroma, just luminance.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
|
|||
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee</param>
|
|||
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
|
|||
public void EncodeGrayscale<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
var unzig = ZigZag.CreateUnzigTable(); |
|||
|
|||
// ReSharper disable once InconsistentNaming
|
|||
int prevDCY = 0; |
|||
|
|||
var pixelConverter = LuminanceForwardConverter<TPixel>.Create(); |
|||
ImageFrame<TPixel> frame = pixels.Frames.RootFrame; |
|||
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer; |
|||
RowOctet<TPixel> currentRows = default; |
|||
|
|||
for (int y = 0; y < pixels.Height; y += 8) |
|||
{ |
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
currentRows.Update(pixelBuffer, y); |
|||
|
|||
for (int x = 0; x < pixels.Width; x += 8) |
|||
{ |
|||
pixelConverter.Convert(frame, x, y, ref currentRows); |
|||
|
|||
prevDCY = this.WriteBlock( |
|||
QuantIndex.Luminance, |
|||
prevDCY, |
|||
ref pixelConverter.Y, |
|||
ref luminanceQuantTable, |
|||
ref unzig); |
|||
} |
|||
} |
|||
|
|||
this.FlushInternalBuffer(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a block of pixel data using the given quantization table,
|
|||
/// returning the post-quantized DC value of the DCT-transformed block.
|
|||
/// The block is in natural (not zig-zag) order.
|
|||
/// </summary>
|
|||
/// <param name="index">The quantization table index.</param>
|
|||
/// <param name="prevDC">The previous DC value.</param>
|
|||
/// <param name="src">Source block</param>
|
|||
/// <param name="quant">Quantization table</param>
|
|||
/// <param name="unZig">The 8x8 Unzig block.</param>
|
|||
/// <returns>The <see cref="int"/>.</returns>
|
|||
private int WriteBlock( |
|||
QuantIndex index, |
|||
int prevDC, |
|||
ref Block8x8F src, |
|||
ref Block8x8F quant, |
|||
ref ZigZag unZig) |
|||
{ |
|||
ref Block8x8F refTemp1 = ref this.temporalBlock1; |
|||
ref Block8x8F refTemp2 = ref this.temporalBlock2; |
|||
|
|||
FastFloatingPointDCT.TransformFDCT(ref src, ref refTemp1, ref refTemp2); |
|||
|
|||
Block8x8F.Quantize(ref refTemp1, ref refTemp2, ref quant, ref unZig); |
|||
|
|||
int dc = (int)refTemp2[0]; |
|||
|
|||
// Emit the DC delta.
|
|||
this.EmitHuffRLE((2 * (int)index) + 0, 0, dc - prevDC); |
|||
|
|||
// Emit the AC components.
|
|||
int h = (2 * (int)index) + 1; |
|||
int runLength = 0; |
|||
|
|||
for (int zig = 1; zig < Block8x8F.Size; zig++) |
|||
{ |
|||
int ac = (int)refTemp2[zig]; |
|||
|
|||
if (ac == 0) |
|||
{ |
|||
runLength++; |
|||
} |
|||
else |
|||
{ |
|||
while (runLength > 15) |
|||
{ |
|||
this.EmitHuff(h, 0xf0); |
|||
runLength -= 16; |
|||
} |
|||
|
|||
this.EmitHuffRLE(h, runLength, ac); |
|||
runLength = 0; |
|||
} |
|||
} |
|||
|
|||
if (runLength > 0) |
|||
{ |
|||
this.EmitHuff(h, 0x00); |
|||
} |
|||
|
|||
return dc; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Emits the least significant count of bits to the stream write buffer.
|
|||
/// The precondition is bits
|
|||
/// <example>
|
|||
/// < 1<<nBits && nBits <= 16
|
|||
/// </example>
|
|||
/// .
|
|||
/// </summary>
|
|||
/// <param name="bits">The packed bits.</param>
|
|||
/// <param name="count">The number of bits</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private void Emit(int bits, int count) |
|||
{ |
|||
count += this.bitCount; |
|||
bits <<= 32 - count; |
|||
bits |= this.accumulatedBits; |
|||
|
|||
// Only write if more than 8 bits.
|
|||
if (count >= 8) |
|||
{ |
|||
// Track length
|
|||
while (count >= 8) |
|||
{ |
|||
byte b = (byte)(bits >> 24); |
|||
this.emitBuffer[this.emitLen++] = b; |
|||
if (b == byte.MaxValue) |
|||
{ |
|||
this.emitBuffer[this.emitLen++] = byte.MinValue; |
|||
} |
|||
|
|||
bits <<= 8; |
|||
count -= 8; |
|||
} |
|||
|
|||
// This can emit 4 times of:
|
|||
// 1 byte guaranteed
|
|||
// 1 extra byte.MinValue byte if previous one was byte.MaxValue
|
|||
// Thus writing (1 + 1) * 4 = 8 bytes max
|
|||
// So we must check if emit buffer has extra 8 bytes, if not - call stream.Write
|
|||
if (this.emitLen > EmitBufferSizeInBytes - 8) |
|||
{ |
|||
this.target.Write(this.emitBuffer, 0, this.emitLen); |
|||
this.emitLen = 0; |
|||
} |
|||
} |
|||
|
|||
this.accumulatedBits = bits; |
|||
this.bitCount = count; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Emits the given value with the given Huffman encoder.
|
|||
/// </summary>
|
|||
/// <param name="index">The index of the Huffman encoder</param>
|
|||
/// <param name="value">The value to encode.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private void EmitHuff(int index, int value) |
|||
{ |
|||
int x = HuffmanLut.TheHuffmanLut[index].Values[value]; |
|||
this.Emit(x & ((1 << 24) - 1), x >> 24); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Emits a run of runLength copies of value encoded with the given Huffman encoder.
|
|||
/// </summary>
|
|||
/// <param name="index">The index of the Huffman encoder</param>
|
|||
/// <param name="runLength">The number of copies to encode.</param>
|
|||
/// <param name="value">The value to encode.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private void EmitHuffRLE(int index, int runLength, int value) |
|||
{ |
|||
int a = value; |
|||
int b = value; |
|||
if (a < 0) |
|||
{ |
|||
a = -value; |
|||
b = value - 1; |
|||
} |
|||
|
|||
int bt = GetHuffmanEncodingLength((uint)a); |
|||
|
|||
this.EmitHuff(index, (runLength << 4) | bt); |
|||
if (bt > 0) |
|||
{ |
|||
this.Emit(b & ((1 << bt) - 1), bt); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes remaining bytes from internal buffer to the target stream.
|
|||
/// </summary>
|
|||
/// <remarks>Pads last byte with 1's if necessary</remarks>
|
|||
private void FlushInternalBuffer() |
|||
{ |
|||
// pad last byte with 1's
|
|||
int padBitsCount = 8 - (this.bitCount % 8); |
|||
if (padBitsCount != 0) |
|||
{ |
|||
this.Emit((1 << padBitsCount) - 1, padBitsCount); |
|||
} |
|||
|
|||
// flush remaining bytes
|
|||
if (this.emitLen != 0) |
|||
{ |
|||
this.target.Write(this.emitBuffer, 0, this.emitLen); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates how many minimum bits needed to store given value for Huffman jpeg encoding.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method returns 0 for input value 0. This is done specificaly for huffman encoding
|
|||
/// </remarks>
|
|||
/// <param name="value">The value.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int GetHuffmanEncodingLength(uint value) |
|||
{ |
|||
DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max"); |
|||
#if SUPPORTS_BITOPERATIONS
|
|||
// This should have been implemented as (BitOperations.Log2(value) + 1) as in non-intrinsic implementation
|
|||
// But internal log2 is implementated like this: (31 - (int)Lzcnt.LeadingZeroCount(value))
|
|||
|
|||
// BitOperations.Log2 implementation also checks if input value is zero for the convention 0->0
|
|||
// Lzcnt would return 32 for input value of 0 - no need to check that with branching
|
|||
// Fallback code if Lzcnt is not supported still use if-check
|
|||
// But most modern CPUs support this instruction so this should not be a problem
|
|||
return 32 - System.Numerics.BitOperations.LeadingZeroCount(value); |
|||
#else
|
|||
// Ideally:
|
|||
// if 0 - return 0 in this case
|
|||
// else - return log2(value) + 1
|
|||
//
|
|||
// Hack based on input value constaint:
|
|||
// We know that input values are guaranteed to be maximum 16 bit large for huffman encoding
|
|||
// We can safely shift input value for one bit -> log2(value << 1)
|
|||
// Because of the 16 bit value constraint it won't overflow
|
|||
// With that input value change we no longer need to add 1 before returning
|
|||
// And this eliminates need to check if input value is zero - it is a standard convention which Log2SoftwareFallback adheres to
|
|||
return Numerics.Log2(value << 1); |
|||
#endif
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,121 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
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 efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
|
|||
internal ref struct YCbCrForwardConverter420<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
/// <summary>
|
|||
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel}, int)"/> call
|
|||
/// </summary>
|
|||
private const int PixelsPerSample = 16 * 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(16, 8); |
|||
|
|||
/// <summary>
|
|||
/// The left Y component
|
|||
/// </summary>
|
|||
public Block8x8F YLeft; |
|||
|
|||
/// <summary>
|
|||
/// The left Y component
|
|||
/// </summary>
|
|||
public Block8x8F YRight; |
|||
|
|||
/// <summary>
|
|||
/// The Cb component
|
|||
/// </summary>
|
|||
public Block8x8F Cb; |
|||
|
|||
/// <summary>
|
|||
/// The Cr component
|
|||
/// </summary>
|
|||
public Block8x8F Cr; |
|||
|
|||
/// <summary>
|
|||
/// The color conversion tables
|
|||
/// </summary>
|
|||
private RgbToYCbCrConverterLut colorTables; |
|||
|
|||
/// <summary>
|
|||
/// Temporal 16x8 block to hold TPixel data
|
|||
/// </summary>
|
|||
private Span<TPixel> pixelSpan; |
|||
|
|||
/// <summary>
|
|||
/// Temporal RGB block
|
|||
/// </summary>
|
|||
private Span<Rgb24> rgbSpan; |
|||
|
|||
/// <summary>
|
|||
/// Sampled pixel buffer size
|
|||
/// </summary>
|
|||
private Size samplingAreaSize; |
|||
|
|||
/// <summary>
|
|||
/// <see cref="Configuration"/> for internal operations
|
|||
/// </summary>
|
|||
private Configuration config; |
|||
|
|||
public YCbCrForwardConverter420(ImageFrame<TPixel> frame) |
|||
{ |
|||
// matrices would be filled during convert calls
|
|||
this.YLeft = default; |
|||
this.YRight = default; |
|||
this.Cb = default; |
|||
this.Cr = 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(); |
|||
|
|||
// conversion vector fallback data
|
|||
if (!RgbToYCbCrConverterVectorized.IsSupported) |
|||
{ |
|||
this.colorTables = RgbToYCbCrConverterLut.Create(); |
|||
} |
|||
else |
|||
{ |
|||
this.colorTables = default; |
|||
} |
|||
} |
|||
|
|||
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows, int idx) |
|||
{ |
|||
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); |
|||
|
|||
PixelOperations<TPixel>.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan); |
|||
|
|||
if (RgbToYCbCrConverterVectorized.IsSupported) |
|||
{ |
|||
RgbToYCbCrConverterVectorized.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx); |
|||
} |
|||
else |
|||
{ |
|||
this.colorTables.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,122 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
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 efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
|
|||
internal ref struct YCbCrForwardConverter444<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 Y component
|
|||
/// </summary>
|
|||
public Block8x8F Y; |
|||
|
|||
/// <summary>
|
|||
/// The Cb component
|
|||
/// </summary>
|
|||
public Block8x8F Cb; |
|||
|
|||
/// <summary>
|
|||
/// The Cr component
|
|||
/// </summary>
|
|||
public Block8x8F Cr; |
|||
|
|||
/// <summary>
|
|||
/// The color conversion tables
|
|||
/// </summary>
|
|||
private RgbToYCbCrConverterLut colorTables; |
|||
|
|||
/// <summary>
|
|||
/// Temporal 64-byte span to hold unconverted TPixel data
|
|||
/// </summary>
|
|||
private Span<TPixel> pixelSpan; |
|||
|
|||
/// <summary>
|
|||
/// Temporal 64-byte span to hold converted Rgb24 data
|
|||
/// </summary>
|
|||
private Span<Rgb24> rgbSpan; |
|||
|
|||
/// <summary>
|
|||
/// Sampled pixel buffer size
|
|||
/// </summary>
|
|||
private Size samplingAreaSize; |
|||
|
|||
/// <summary>
|
|||
/// <see cref="Configuration"/> for internal operations
|
|||
/// </summary>
|
|||
private Configuration config; |
|||
|
|||
public YCbCrForwardConverter444(ImageFrame<TPixel> frame) |
|||
{ |
|||
// matrices would be filled during convert calls
|
|||
this.Y = default; |
|||
this.Cb = default; |
|||
this.Cr = 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(); |
|||
|
|||
// conversion vector fallback data
|
|||
if (!RgbToYCbCrConverterVectorized.IsSupported) |
|||
{ |
|||
this.colorTables = RgbToYCbCrConverterLut.Create(); |
|||
} |
|||
else |
|||
{ |
|||
this.colorTables = default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>, <see cref="Cb"/>, <see cref="Cr"/>)
|
|||
/// </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 yBlock = ref this.Y; |
|||
ref Block8x8F cbBlock = ref this.Cb; |
|||
ref Block8x8F crBlock = ref this.Cr; |
|||
|
|||
if (RgbToYCbCrConverterVectorized.IsSupported) |
|||
{ |
|||
RgbToYCbCrConverterVectorized.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock); |
|||
} |
|||
else |
|||
{ |
|||
this.colorTables.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,56 +1,138 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// The number of bits per component.
|
|||
/// </summary>
|
|||
public enum TiffBitsPerSample |
|||
public readonly struct TiffBitsPerSample : IEquatable<TiffBitsPerSample> |
|||
{ |
|||
/// <summary>
|
|||
/// The Bits per samples is not known.
|
|||
/// The bits for the channel 0.
|
|||
/// </summary>
|
|||
Unknown = 0, |
|||
public readonly ushort Channel0; |
|||
|
|||
/// <summary>
|
|||
/// One bit per sample for bicolor images.
|
|||
/// The bits for the channel 1.
|
|||
/// </summary>
|
|||
Bit1 = 1, |
|||
public readonly ushort Channel1; |
|||
|
|||
/// <summary>
|
|||
/// Four bits per sample for grayscale images with 16 different levels of gray or paletted images with a palette of 16 colors.
|
|||
/// The bits for the channel 2.
|
|||
/// </summary>
|
|||
Bit4 = 4, |
|||
public readonly ushort Channel2; |
|||
|
|||
/// <summary>
|
|||
/// Eight bits per sample for grayscale images with 256 different levels of gray or paletted images with a palette of 256 colors.
|
|||
/// The number of channels.
|
|||
/// </summary>
|
|||
Bit8 = 8, |
|||
public readonly byte Channels; |
|||
|
|||
/// <summary>
|
|||
/// Six bits per sample, each channel has 2 bits.
|
|||
/// Initializes a new instance of the <see cref="TiffBitsPerSample"/> struct.
|
|||
/// </summary>
|
|||
Bit6 = 6, |
|||
/// <param name="channel0">The bits for the channel 0.</param>
|
|||
/// <param name="channel1">The bits for the channel 1.</param>
|
|||
/// <param name="channel2">The bits for the channel 2.</param>
|
|||
public TiffBitsPerSample(ushort channel0, ushort channel1, ushort channel2) |
|||
{ |
|||
this.Channel0 = (ushort)Numerics.Clamp(channel0, 0, 32); |
|||
this.Channel1 = (ushort)Numerics.Clamp(channel1, 0, 32); |
|||
this.Channel2 = (ushort)Numerics.Clamp(channel2, 0, 32); |
|||
|
|||
/// <summary>
|
|||
/// Twelve bits per sample, each channel has 4 bits.
|
|||
/// </summary>
|
|||
Bit12 = 12, |
|||
this.Channels = 0; |
|||
this.Channels += (byte)(this.Channel0 != 0 ? 1 : 0); |
|||
this.Channels += (byte)(this.Channel1 != 0 ? 1 : 0); |
|||
this.Channels += (byte)(this.Channel2 != 0 ? 1 : 0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 24 bits per sample, each color channel has 8 Bits.
|
|||
/// Tries to parse a ushort array and convert it into a TiffBitsPerSample struct.
|
|||
/// </summary>
|
|||
Bit24 = 24, |
|||
/// <param name="value">The value to parse.</param>
|
|||
/// <param name="sample">The tiff bits per sample.</param>
|
|||
/// <returns>True, if the value could be parsed.</returns>
|
|||
public static bool TryParse(ushort[] value, out TiffBitsPerSample sample) |
|||
{ |
|||
if (value is null || value.Length == 0) |
|||
{ |
|||
sample = default; |
|||
return false; |
|||
} |
|||
|
|||
ushort c2; |
|||
ushort c1; |
|||
ushort c0; |
|||
switch (value.Length) |
|||
{ |
|||
case 3: |
|||
c2 = value[2]; |
|||
c1 = value[1]; |
|||
c0 = value[0]; |
|||
break; |
|||
case 2: |
|||
c2 = 0; |
|||
c1 = value[1]; |
|||
c0 = value[0]; |
|||
break; |
|||
default: |
|||
c2 = 0; |
|||
c1 = 0; |
|||
c0 = value[0]; |
|||
break; |
|||
} |
|||
|
|||
sample = new TiffBitsPerSample(c0, c1, c2); |
|||
return true; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) |
|||
=> obj is TiffBitsPerSample sample && this.Equals(sample); |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(TiffBitsPerSample other) |
|||
=> this.Channel0 == other.Channel0 |
|||
&& this.Channel1 == other.Channel1 |
|||
&& this.Channel2 == other.Channel2; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
=> HashCode.Combine(this.Channel0, this.Channel1, this.Channel2); |
|||
|
|||
/// <summary>
|
|||
/// Thirty bits per sample, each channel has 10 bits.
|
|||
/// Converts the bits per sample struct to an ushort array.
|
|||
/// </summary>
|
|||
Bit30 = 30, |
|||
/// <returns>Bits per sample as ushort array.</returns>
|
|||
public ushort[] ToArray() |
|||
{ |
|||
if (this.Channel1 == 0) |
|||
{ |
|||
return new[] { this.Channel0 }; |
|||
} |
|||
|
|||
if (this.Channel2 == 0) |
|||
{ |
|||
return new[] { this.Channel0, this.Channel1 }; |
|||
} |
|||
|
|||
return new[] { this.Channel0, this.Channel1, this.Channel2 }; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Forty two bits per sample, each channel has 14 bits.
|
|||
/// Gets the bits per pixel for the given bits per sample.
|
|||
/// </summary>
|
|||
Bit42 = 42, |
|||
/// <returns>Bits per pixel.</returns>
|
|||
public TiffBitsPerPixel BitsPerPixel() |
|||
{ |
|||
int bitsPerPixel = this.Channel0 + this.Channel1 + this.Channel2; |
|||
return (TiffBitsPerPixel)bitsPerPixel; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
=> $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2})"; |
|||
} |
|||
} |
|||
|
|||
@ -1,111 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
internal static class TiffBitsPerSampleExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the bits per channel array for a given BitsPerSample value, e,g, for RGB888: [8, 8, 8]
|
|||
/// </summary>
|
|||
/// <param name="tiffBitsPerSample">The tiff bits per sample.</param>
|
|||
/// <returns>Bits per sample array.</returns>
|
|||
public static ushort[] Bits(this TiffBitsPerSample tiffBitsPerSample) |
|||
{ |
|||
switch (tiffBitsPerSample) |
|||
{ |
|||
case TiffBitsPerSample.Bit1: |
|||
return TiffConstants.BitsPerSample1Bit; |
|||
case TiffBitsPerSample.Bit4: |
|||
return TiffConstants.BitsPerSample4Bit; |
|||
case TiffBitsPerSample.Bit6: |
|||
return TiffConstants.BitsPerSampleRgb2Bit; |
|||
case TiffBitsPerSample.Bit8: |
|||
return TiffConstants.BitsPerSample8Bit; |
|||
case TiffBitsPerSample.Bit12: |
|||
return TiffConstants.BitsPerSampleRgb4Bit; |
|||
case TiffBitsPerSample.Bit24: |
|||
return TiffConstants.BitsPerSampleRgb8Bit; |
|||
case TiffBitsPerSample.Bit30: |
|||
return TiffConstants.BitsPerSampleRgb10Bit; |
|||
case TiffBitsPerSample.Bit42: |
|||
return TiffConstants.BitsPerSampleRgb14Bit; |
|||
|
|||
default: |
|||
return Array.Empty<ushort>(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Maps an array of bits per sample to a concrete enum value.
|
|||
/// </summary>
|
|||
/// <param name="bitsPerSample">The bits per sample array.</param>
|
|||
/// <returns>TiffBitsPerSample enum value.</returns>
|
|||
public static TiffBitsPerSample GetBitsPerSample(this ushort[] bitsPerSample) |
|||
{ |
|||
switch (bitsPerSample.Length) |
|||
{ |
|||
case 3: |
|||
if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb14Bit[2] && |
|||
bitsPerSample[1] == TiffConstants.BitsPerSampleRgb14Bit[1] && |
|||
bitsPerSample[0] == TiffConstants.BitsPerSampleRgb14Bit[0]) |
|||
{ |
|||
return TiffBitsPerSample.Bit42; |
|||
} |
|||
|
|||
if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb10Bit[2] && |
|||
bitsPerSample[1] == TiffConstants.BitsPerSampleRgb10Bit[1] && |
|||
bitsPerSample[0] == TiffConstants.BitsPerSampleRgb10Bit[0]) |
|||
{ |
|||
return TiffBitsPerSample.Bit30; |
|||
} |
|||
|
|||
if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb8Bit[2] && |
|||
bitsPerSample[1] == TiffConstants.BitsPerSampleRgb8Bit[1] && |
|||
bitsPerSample[0] == TiffConstants.BitsPerSampleRgb8Bit[0]) |
|||
{ |
|||
return TiffBitsPerSample.Bit24; |
|||
} |
|||
|
|||
if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb4Bit[2] && |
|||
bitsPerSample[1] == TiffConstants.BitsPerSampleRgb4Bit[1] && |
|||
bitsPerSample[0] == TiffConstants.BitsPerSampleRgb4Bit[0]) |
|||
{ |
|||
return TiffBitsPerSample.Bit12; |
|||
} |
|||
|
|||
if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb2Bit[2] && |
|||
bitsPerSample[1] == TiffConstants.BitsPerSampleRgb2Bit[1] && |
|||
bitsPerSample[0] == TiffConstants.BitsPerSampleRgb2Bit[0]) |
|||
{ |
|||
return TiffBitsPerSample.Bit6; |
|||
} |
|||
|
|||
break; |
|||
|
|||
case 1: |
|||
if (bitsPerSample[0] == TiffConstants.BitsPerSample1Bit[0]) |
|||
{ |
|||
return TiffBitsPerSample.Bit1; |
|||
} |
|||
|
|||
if (bitsPerSample[0] == TiffConstants.BitsPerSample4Bit[0]) |
|||
{ |
|||
return TiffBitsPerSample.Bit4; |
|||
} |
|||
|
|||
if (bitsPerSample[0] == TiffConstants.BitsPerSample8Bit[0]) |
|||
{ |
|||
return TiffBitsPerSample.Bit8; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
return TiffBitsPerSample.Unknown; |
|||
} |
|||
} |
|||
} |
|||
@ -1,38 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components |
|||
{ |
|||
[Config(typeof(Config.HwIntrinsics_SSE_AVX))] |
|||
public class Block8x8F_Scale16X16To8X8 |
|||
{ |
|||
private Block8x8F source; |
|||
private readonly Block8x8F[] target = new Block8x8F[4]; |
|||
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
var random = new Random(); |
|||
|
|||
float[] f = new float[8 * 8]; |
|||
for (int i = 0; i < f.Length; i++) |
|||
{ |
|||
f[i] = (float)random.NextDouble(); |
|||
} |
|||
|
|||
for (int i = 0; i < 4; i++) |
|||
{ |
|||
this.target[i] = Block8x8F.Load(f); |
|||
} |
|||
|
|||
this.source = Block8x8F.Load(f); |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void Scale16X16To8X8() => Block8x8F.Scale16X16To8X8(ref this.source, this.target); |
|||
} |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Common |
|||
{ |
|||
public class NumericsTests |
|||
{ |
|||
private ITestOutputHelper Output { get; } |
|||
|
|||
public NumericsTests(ITestOutputHelper output) |
|||
{ |
|||
this.Output = output; |
|||
} |
|||
|
|||
private static int Log2_ReferenceImplementation(uint value) |
|||
{ |
|||
int n = 0; |
|||
while ((value >>= 1) != 0) |
|||
{ |
|||
++n; |
|||
} |
|||
|
|||
return n; |
|||
} |
|||
|
|||
[Fact] |
|||
public void Log2_ZeroConvention() |
|||
{ |
|||
uint value = 0; |
|||
int expected = 0; |
|||
int actual = Numerics.Log2(value); |
|||
|
|||
Assert.True(expected == actual, $"Expected: {expected}, Actual: {actual}"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Log2_PowersOfTwo() |
|||
{ |
|||
for (int i = 0; i < sizeof(int) * 8; i++) |
|||
{ |
|||
// from 2^0 to 2^32
|
|||
uint value = (uint)(1 << i); |
|||
int expected = i; |
|||
int actual = Numerics.Log2(value); |
|||
|
|||
Assert.True(expected == actual, $"Expected: {expected}, Actual: {actual}"); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(1, 100)] |
|||
[InlineData(2, 100)] |
|||
public void Log2_RandomValues(int seed, int count) |
|||
{ |
|||
var rng = new Random(seed); |
|||
byte[] bytes = new byte[4]; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
rng.NextBytes(bytes); |
|||
uint value = BitConverter.ToUInt32(bytes, 0); |
|||
int expected = Log2_ReferenceImplementation(value); |
|||
int actual = Numerics.Log2(value); |
|||
|
|||
Assert.True(expected == actual, $"Expected: {expected}, Actual: {actual}"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:6dba331639d724f198d7d11af971156d34076b57bba0f2d0d45e699104a3a674 |
|||
oid sha256:11375b15df083d98335f4a4baf0717e7fdd6b21ab2132a6815cadc787ac17e7d |
|||
size 9270 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:9213e188b3a2f715ae21a5ab2fb2acedc23397207820c0999b06fa60e7052b85 |
|||
oid sha256:e063e97cd8a000de6830adcc3961a7dc41785d40cd4d83af10ca38d96e071362 |
|||
size 9270 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:1aa62e798c085eb7b0e8e5ce5e4cb2cccfe925dd8ac3e29659f9afd53fca977c |
|||
size 329912 |
|||
oid sha256:cafc426ac8e8d02a87f67c90e8c1976c5fae0e12b49deae52ad08476f7ed49a4 |
|||
size 266391 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:d0c8ccdfbf6b1c961f6531ae61207a7f89507f469c875677f1755ea3d6c8d900 |
|||
size 326504 |
|||
oid sha256:4d88eb2e50ca9dbed0e8dfe4ad278cd88ddb9d3408b30d9dfe59102b167f570b |
|||
size 262887 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:0216f1684430087035b387ab02d33b043c526bd8c7d78343c31e1bc410581bfb |
|||
size 727 |
|||
oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0 |
|||
size 720 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:0216f1684430087035b387ab02d33b043c526bd8c7d78343c31e1bc410581bfb |
|||
size 727 |
|||
oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0 |
|||
size 720 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:0216f1684430087035b387ab02d33b043c526bd8c7d78343c31e1bc410581bfb |
|||
size 727 |
|||
oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0 |
|||
size 720 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:24191da3ce18438edefa7a189d9beadaa3057b5e4d4c550254e3a81ed159c0f8 |
|||
size 723 |
|||
oid sha256:f63aebed17504ef50d96ac7e58dc41f5227a83a38810359ed8e9cecda137183b |
|||
size 720 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:087148425a048f33c6ae063064cfe374f7fb88f075d767e62c73675ec52a3e0a |
|||
size 100066 |
|||
oid sha256:471eaf2e532b40592c86dc816709d3ae4bbd64892006e00fd611ef6869d3b934 |
|||
size 52070 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:b2a90c8463632606b40461ad91d80d44826f7b468ba5f1a905acfc85ad0344c9 |
|||
size 114413 |
|||
oid sha256:91fb9966a4b3eaefd5533ddf0b98ec08fbf8cbc263e4ebd438895e6d4129dd03 |
|||
size 61447 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:192c742bfd53f3a74d96c79e92443a922ac60c354b73d7abf292f30d10131307 |
|||
size 114842 |
|||
oid sha256:d74faa8d188a2915739de64ba9d71b2132b53c8d154db22510c524ae757578a5 |
|||
size 61183 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:50c12659dc05b3ce8a6692cdbc72971bbc691cc7fd26c34df65b4bd71d190e5b |
|||
size 108799 |
|||
oid sha256:080cc89d1d6568a2c9b707bf05428ab5febd2951e37223f96e349cc6646d32aa |
|||
size 56070 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:6a1c408687899b57b96e9f01ea889bc6f16d9a479386346c6bd9babc45ef99d0 |
|||
size 109095 |
|||
oid sha256:c7589986c1a762d52fe8ffc252e9938ff0e3a9e00b91ea7f5e36d4335b2b7870 |
|||
size 58502 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:d3d8aead7f9f69dac7eec1b2d8e2200331bf28218c98b7fa3435c9610ff88264 |
|||
size 110221 |
|||
oid sha256:934042746c3a9b652069da26b479e2be7cbdb17ab20e41c5e271013a76e96e46 |
|||
size 58480 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:f1fed9d8f58e38cfa938d7735cbdfcbac0aa02f58eda0dddfe29a5ebed0e74eb |
|||
size 117802 |
|||
oid sha256:03d5d5cbf1b2c0be736aa2bf726ad4bb04fca77aff393edb9663a7915a794264 |
|||
size 62418 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:dbeeda3f4e505c46e1ec218001f0f1aade4eb64c3932e2c374a74c4b0702d7a8 |
|||
size 103735 |
|||
oid sha256:19a0d8667bfd01e18adbfca778e868ea7a6c43d427f9ae40eb4281d438ef509c |
|||
size 54464 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:8003014c90f6c3a722c75e9cafb397d1be3818bb84c3484e28ee79ae273d7d0b |
|||
size 109707 |
|||
oid sha256:11c1056e013292e0543598f5690625b9bac0420a15fd1f37f6484daa3b8326fa |
|||
size 60074 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:f02a9465aaaa62b6fc0e9e0578362ccf65ce57bf7a8e1e2899f254863e72807f |
|||
size 100060 |
|||
oid sha256:3fcf9b7e4ee34e80e8811f94940aff09a5392c21019fc86b145d16fd9c6b1cd2 |
|||
size 57501 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:f50c240c062cb66e820e8f632107e63bc0de85013143a81975e56f0b72499d8f |
|||
size 102871 |
|||
oid sha256:4f0d9a43d8a47e00f6e5932b57f99565370a7239496fdbe162fb774497c4ef2a |
|||
size 59377 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:aa6c2bb78faaf689cf979dd87b3a24b6405720755ce16c028cafab690ac7b318 |
|||
size 104334 |
|||
oid sha256:d4a64da29f144d4d4c525ea45e56819e02a46030ae09542be01fdd8ffc85a295 |
|||
size 60377 |
|||
|
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue