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.
|
// Copyright (c) Six Labors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Tiff |
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// The number of bits per component.
|
/// The number of bits per component.
|
||||
/// </summary>
|
/// </summary>
|
||||
public enum TiffBitsPerSample |
public readonly struct TiffBitsPerSample : IEquatable<TiffBitsPerSample> |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// The Bits per samples is not known.
|
/// The bits for the channel 0.
|
||||
/// </summary>
|
/// </summary>
|
||||
Unknown = 0, |
public readonly ushort Channel0; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// One bit per sample for bicolor images.
|
/// The bits for the channel 1.
|
||||
/// </summary>
|
/// </summary>
|
||||
Bit1 = 1, |
public readonly ushort Channel1; |
||||
|
|
||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||
Bit4 = 4, |
public readonly ushort Channel2; |
||||
|
|
||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||
Bit8 = 8, |
public readonly byte Channels; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Six bits per sample, each channel has 2 bits.
|
/// Initializes a new instance of the <see cref="TiffBitsPerSample"/> struct.
|
||||
/// </summary>
|
/// </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>
|
this.Channels = 0; |
||||
/// Twelve bits per sample, each channel has 4 bits.
|
this.Channels += (byte)(this.Channel0 != 0 ? 1 : 0); |
||||
/// </summary>
|
this.Channels += (byte)(this.Channel1 != 0 ? 1 : 0); |
||||
Bit12 = 12, |
this.Channels += (byte)(this.Channel2 != 0 ? 1 : 0); |
||||
|
} |
||||
|
|
||||
/// <summary>
|
/// <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>
|
/// </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>
|
/// <summary>
|
||||
/// Thirty bits per sample, each channel has 10 bits.
|
/// Converts the bits per sample struct to an ushort array.
|
||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||
/// Forty two bits per sample, each channel has 14 bits.
|
/// Gets the bits per pixel for the given bits per sample.
|
||||
/// </summary>
|
/// </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 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:6dba331639d724f198d7d11af971156d34076b57bba0f2d0d45e699104a3a674 |
oid sha256:11375b15df083d98335f4a4baf0717e7fdd6b21ab2132a6815cadc787ac17e7d |
||||
size 9270 |
size 9270 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:9213e188b3a2f715ae21a5ab2fb2acedc23397207820c0999b06fa60e7052b85 |
oid sha256:e063e97cd8a000de6830adcc3961a7dc41785d40cd4d83af10ca38d96e071362 |
||||
size 9270 |
size 9270 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:1aa62e798c085eb7b0e8e5ce5e4cb2cccfe925dd8ac3e29659f9afd53fca977c |
oid sha256:cafc426ac8e8d02a87f67c90e8c1976c5fae0e12b49deae52ad08476f7ed49a4 |
||||
size 329912 |
size 266391 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:d0c8ccdfbf6b1c961f6531ae61207a7f89507f469c875677f1755ea3d6c8d900 |
oid sha256:4d88eb2e50ca9dbed0e8dfe4ad278cd88ddb9d3408b30d9dfe59102b167f570b |
||||
size 326504 |
size 262887 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:0216f1684430087035b387ab02d33b043c526bd8c7d78343c31e1bc410581bfb |
oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0 |
||||
size 727 |
size 720 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:0216f1684430087035b387ab02d33b043c526bd8c7d78343c31e1bc410581bfb |
oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0 |
||||
size 727 |
size 720 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:0216f1684430087035b387ab02d33b043c526bd8c7d78343c31e1bc410581bfb |
oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0 |
||||
size 727 |
size 720 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:24191da3ce18438edefa7a189d9beadaa3057b5e4d4c550254e3a81ed159c0f8 |
oid sha256:f63aebed17504ef50d96ac7e58dc41f5227a83a38810359ed8e9cecda137183b |
||||
size 723 |
size 720 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:087148425a048f33c6ae063064cfe374f7fb88f075d767e62c73675ec52a3e0a |
oid sha256:471eaf2e532b40592c86dc816709d3ae4bbd64892006e00fd611ef6869d3b934 |
||||
size 100066 |
size 52070 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:b2a90c8463632606b40461ad91d80d44826f7b468ba5f1a905acfc85ad0344c9 |
oid sha256:91fb9966a4b3eaefd5533ddf0b98ec08fbf8cbc263e4ebd438895e6d4129dd03 |
||||
size 114413 |
size 61447 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:192c742bfd53f3a74d96c79e92443a922ac60c354b73d7abf292f30d10131307 |
oid sha256:d74faa8d188a2915739de64ba9d71b2132b53c8d154db22510c524ae757578a5 |
||||
size 114842 |
size 61183 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:50c12659dc05b3ce8a6692cdbc72971bbc691cc7fd26c34df65b4bd71d190e5b |
oid sha256:080cc89d1d6568a2c9b707bf05428ab5febd2951e37223f96e349cc6646d32aa |
||||
size 108799 |
size 56070 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:6a1c408687899b57b96e9f01ea889bc6f16d9a479386346c6bd9babc45ef99d0 |
oid sha256:c7589986c1a762d52fe8ffc252e9938ff0e3a9e00b91ea7f5e36d4335b2b7870 |
||||
size 109095 |
size 58502 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:d3d8aead7f9f69dac7eec1b2d8e2200331bf28218c98b7fa3435c9610ff88264 |
oid sha256:934042746c3a9b652069da26b479e2be7cbdb17ab20e41c5e271013a76e96e46 |
||||
size 110221 |
size 58480 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:f1fed9d8f58e38cfa938d7735cbdfcbac0aa02f58eda0dddfe29a5ebed0e74eb |
oid sha256:03d5d5cbf1b2c0be736aa2bf726ad4bb04fca77aff393edb9663a7915a794264 |
||||
size 117802 |
size 62418 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:dbeeda3f4e505c46e1ec218001f0f1aade4eb64c3932e2c374a74c4b0702d7a8 |
oid sha256:19a0d8667bfd01e18adbfca778e868ea7a6c43d427f9ae40eb4281d438ef509c |
||||
size 103735 |
size 54464 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:8003014c90f6c3a722c75e9cafb397d1be3818bb84c3484e28ee79ae273d7d0b |
oid sha256:11c1056e013292e0543598f5690625b9bac0420a15fd1f37f6484daa3b8326fa |
||||
size 109707 |
size 60074 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:f02a9465aaaa62b6fc0e9e0578362ccf65ce57bf7a8e1e2899f254863e72807f |
oid sha256:3fcf9b7e4ee34e80e8811f94940aff09a5392c21019fc86b145d16fd9c6b1cd2 |
||||
size 100060 |
size 57501 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:f50c240c062cb66e820e8f632107e63bc0de85013143a81975e56f0b72499d8f |
oid sha256:4f0d9a43d8a47e00f6e5932b57f99565370a7239496fdbe162fb774497c4ef2a |
||||
size 102871 |
size 59377 |
||||
|
|||||
@ -1,3 +1,3 @@ |
|||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:aa6c2bb78faaf689cf979dd87b3a24b6405720755ce16c028cafab690ac7b318 |
oid sha256:d4a64da29f144d4d4c525ea45e56819e02a46030ae09542be01fdd8ffc85a295 |
||||
size 104334 |
size 60377 |
||||
|
|||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue