/// <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="tempDest1">Temporal block to be used as FDCT Destination</param> /// <param name="tempDest2">Temporal block 2</param> /// <param name="quant">Quantization table</param> /// <param name="unZig">The 8x8 Unzig block.</param> /// <param name="emitBufferBase">The reference to the emit buffer.</param> /// <returns>The <see cref="int"/>.</returns> private int WriteBlock( QuantIndex index, int prevDC, ref Block8x8F src, ref Block8x8F tempDest1, ref Block8x8F tempDest2, ref Block8x8F quant, ref ZigZag unZig, ref byte emitBufferBase) { FastFloatingPointDCT.TransformFDCT(ref src, ref tempDest1, ref tempDest2); Block8x8F.Quantize(ref tempDest1, ref tempDest2, ref quant, ref unZig); int dc = (int)tempDest2[0]; // Emit the DC delta. this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC, ref emitBufferBase); // Emit the AC components. var h = (HuffIndex)((2 * (int)index) + 1); int runLength = 0; for (int zig = 1; zig < Block8x8F.Size; zig++) { int ac = (int)tempDest2[zig]; if (ac == 0) { runLength++; } else { while (runLength > 15) { this.EmitHuff(h, 0xf0, ref emitBufferBase); runLength -= 16; } this.EmitHuffRLE(h, runLength, ac, ref emitBufferBase); runLength = 0; } } if (runLength > 0) { this.EmitHuff(h, 0x00, ref emitBufferBase); } return(dc); }
/// <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="tempDest1">Temporal block to be used as FDCT Destination</param> /// <param name="tempDest2">Temporal block 2</param> /// <param name="quant">Quantization table</param> /// <param name="unzigPtr">The 8x8 Unzig block pointer</param> /// <returns> /// The <see cref="int"/> /// </returns> private int WriteBlock( QuantIndex index, int prevDC, Block8x8F *src, Block8x8F *tempDest1, Block8x8F *tempDest2, Block8x8F *quant, byte *unzigPtr) { FastFloatingPointDCT.TransformFDCT(ref *src, ref *tempDest1, ref *tempDest2); Block8x8F.Quantize(tempDest1, tempDest2, quant, unzigPtr); float *unziggedDestPtr = (float *)tempDest2; int dc = (int)unziggedDestPtr[0]; // Emit the DC delta. this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC); // Emit the AC components. var h = (HuffIndex)((2 * (int)index) + 1); int runLength = 0; for (int zig = 1; zig < Block8x8F.Size; zig++) { int ac = (int)unziggedDestPtr[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> /// 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> private void Encode444 <TPixel>(Image <TPixel> pixels) where TPixel : struct, IPixel <TPixel> { // TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // (Partially done with YCbCrForwardConverter<TPixel>) Block8x8F temp1 = default; Block8x8F temp2 = default; Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; var unzig = ZigZag.CreateUnzigTable(); // ReSharper disable once InconsistentNaming int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; var pixelConverter = YCbCrForwardConverter <TPixel> .Create(); for (int y = 0; y < pixels.Height; y += 8) { for (int x = 0; x < pixels.Width; x += 8) { pixelConverter.Convert(pixels.Frames.RootFrame, x, y); prevDCY = this.WriteBlock( QuantIndex.Luminance, prevDCY, &pixelConverter.Y, &temp1, &temp2, &onStackLuminanceQuantTable, unzig.Data); prevDCCb = this.WriteBlock( QuantIndex.Chrominance, prevDCCb, &pixelConverter.Cb, &temp1, &temp2, &onStackChrominanceQuantTable, unzig.Data); prevDCCr = this.WriteBlock( QuantIndex.Chrominance, prevDCCr, &pixelConverter.Cr, &temp1, &temp2, &onStackChrominanceQuantTable, unzig.Data); } } }
public RgbForwardConverter(ImageFrame <TPixel> frame) { this.R = default; this.G = default; this.B = default; // temporal pixel buffers this.pixelSpan = new TPixel[PixelsPerSample].AsSpan(); this.rgbSpan = MemoryMarshal.Cast <byte, Rgb24>(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan()); // frame data this.samplingAreaSize = new Size(frame.Width, frame.Height); this.config = frame.GetConfiguration(); }
public void MultiplyInplace_ByOtherBlock() { Block8x8F original = CreateRandomFloatBlock(-500, 500, 42); Block8x8F m = CreateRandomFloatBlock(-500, 500, 42); Block8x8F actual = original; actual.MultiplyInplace(ref m); for (int i = 0; i < Block8x8F.Size; i++) { Assert.Equal(original[i] * m[i], actual[i]); } }
public void LLM_TransformIDCT_CompareToAccurate(int seed) { float[] sourceArray = Create8x8RoundedRandomFloatData(-1000, 1000, seed); var srcBlock = Block8x8F.Load(sourceArray); Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref srcBlock); var temp = default(Block8x8F); FastFloatingPointDCT.TransformIDCT(ref srcBlock, ref temp); this.CompareBlocks(expected, srcBlock, 1f); }
public static Block8x8F TransformFDCT_UpscaleBy8(ref Block8x8F source) { var s = new float[64]; source.CopyTo(s); var d = new float[64]; var temp = new float[64]; fDCT2D_llm(s, d, temp); Block8x8F result = default; result.LoadFrom(d); return(result); }
private static Block8x8F Create8x8FloatData() { Block8x8F block = default; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { block[(i * 8) + j] = (i * 10) + j; } } return(block); }
public unsafe void DequantizeBlock(int seed) { Block8x8F original = CreateRandomFloatBlock(-500, 500, seed); Block8x8F qt = CreateRandomFloatBlock(0, 10, seed + 42); var unzig = ZigZag.CreateUnzigTable(); Block8x8F expected = original; Block8x8F actual = original; ReferenceImplementations.DequantizeBlock(&expected, &qt, unzig.Data); Block8x8F.DequantizeBlock(&actual, &qt, unzig.Data); this.CompareBlocks(expected, actual, 0); }
public void TestConverterLut444() { int dataSize = 8 * 8; Rgb24[] data = CreateTestData(dataSize); var target = RgbToYCbCrConverterLut.Create(); Block8x8F y = default; Block8x8F cb = default; Block8x8F cr = default; target.Convert444(data.AsSpan(), ref y, ref cb, ref cr); Verify444(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(1F)); }
public void TestConverterLut420() { int dataSize = 16 * 16; Span <Rgb24> data = CreateTestData(dataSize).AsSpan(); var target = RgbToYCbCrConverterLut.Create(); var yBlocks = new Block8x8F[4]; var cb = default(Block8x8F); var cr = default(Block8x8F); target.Convert420(data, ref yBlocks[0], ref yBlocks[1], ref cb, ref cr, 0); target.Convert420(data.Slice(16 * 8), ref yBlocks[2], ref yBlocks[3], ref cb, ref cr, 1); Verify420(data, yBlocks, ref cb, ref cr, new ApproximateFloatComparer(1F)); }
public void QualityEstimationFromStandardEncoderTables_Chrominance() { int firstIndex = JpegQuantization.QualityEstimationConfidenceLowerThreshold; int lastIndex = JpegQuantization.QualityEstimationConfidenceUpperThreshold; for (int quality = firstIndex; quality <= lastIndex; quality++) { Block8x8F table = JpegQuantization.ScaleChrominanceTable(quality); int estimatedQuality = JpegQuantization.EstimateChrominanceQuality(ref table); Assert.True( quality.Equals(estimatedQuality), $"Failed to estimate chrominance quality for standard table at quality level {quality}"); } }
private static void Verify(ReadOnlySpan <Rgb24> data, ref Block8x8F yResult, ref Block8x8F cbResult, ref Block8x8F crResult, ApproximateColorSpaceComparer comparer) { for (int i = 0; i < data.Length; i++) { int r = data[i].R; int g = data[i].G; int b = data[i].B; float y = (0.299F * r) + (0.587F * g) + (0.114F * b); float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); Assert.True(comparer.Equals(new YCbCr(y, cb, cr), new YCbCr(yResult[i], cbResult[i], crResult[i])), $"Pos {i}, Expected {y} == {yResult[i]}, {cb} == {cbResult[i]}, {cr} == {crResult[i]}"); } }
public void LLM_FDCT_IsEquivalentTo_AccurateImplementation(int seed) { float[] floatData = JpegFixture.Create8x8RandomFloatData(-1000, 1000); Block8x8F source = default; source.LoadFrom(floatData); Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); Block8x8F actual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformFDCT_UpscaleBy8(ref source); actual /= 8; this.CompareBlocks(expected, actual, 1f); }
public void LLM_TransformIDCT_CompareToNonOptimized(int seed) { float[] sourceArray = Create8x8RoundedRandomFloatData(-1000, 1000, seed); var source = Block8x8F.Load(sourceArray); Block8x8F expected = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref source); var temp = default(Block8x8F); var actual = default(Block8x8F); FastFloatingPointDCT.TransformIDCT(ref source, ref actual, ref temp); this.CompareBlocks(expected, actual, 1f); }
/// <summary> /// Convert raw spectral DCT data to color data and copy it to the color buffer <see cref="ColorBuffer"/>. /// </summary> public void CopyBlocksToColorBuffer(int spectralStep) { Buffer2D <Block8x8> spectralBuffer = this.component.SpectralBlocks; float maximumValue = this.frame.MaxColorChannelValue; int destAreaStride = this.ColorBuffer.Width; int yBlockStart = spectralStep * this.blockRowsPerStep; Size subSamplingDivisors = this.component.SubSamplingDivisors; Block8x8F dequantTable = this.rawJpeg.QuantizationTables[this.component.QuantizationTableIndex]; Block8x8F workspaceBlock = default; for (int y = 0; y < this.blockRowsPerStep; y++) { int yBuffer = y * this.blockAreaSize.Height; Span <float> colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); Span <Block8x8> blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) { // Integer to float workspaceBlock.LoadFrom(ref blockRow[xBlock]); // Dequantize workspaceBlock.MultiplyInPlace(ref dequantTable); // Convert from spectral to color FastFloatingPointDCT.TransformIDCT(ref workspaceBlock); // 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! workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue); // Write to color buffer acording to sampling factors int xColorBufferStart = xBlock * this.blockAreaSize.Width; workspaceBlock.ScaledCopyTo( ref colorBufferRow[xColorBufferStart], destAreaStride, subSamplingDivisors.Width, subSamplingDivisors.Height); } } }
public unsafe void ZigZag_CreateDequantizationTable_MultiplicationShouldQuantize(int seed) { Block8x8F original = CreateRandomFloatBlock(-500, 500, seed); Block8x8F qt = CreateRandomFloatBlock(0, 10, seed + 42); var unzig = ZigZag.CreateUnzigTable(); Block8x8F zigQt = ZigZag.CreateDequantizationTable(ref qt); Block8x8F expected = original; Block8x8F actual = original; ReferenceImplementations.DequantizeBlock(&expected, &qt, unzig.Data); actual.MultiplyInplace(ref zigQt); this.CompareBlocks(expected, actual, 0); }
public static void Convert(ReadOnlySpan <Rgb24> rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) { Debug.Assert(IsSupported, "AVX2 is required to run this converter"); #if SUPPORTS_RUNTIME_INTRINSICS var f0299 = Vector256.Create(0.299f); var f0587 = Vector256.Create(0.587f); var f0114 = Vector256.Create(0.114f); var fn0168736 = Vector256.Create(-0.168736f); var fn0331264 = Vector256.Create(-0.331264f); var f128 = Vector256.Create(128f); var fn0418688 = Vector256.Create(-0.418688f); var fn0081312F = Vector256.Create(-0.081312F); var f05 = Vector256.Create(0.5f); var zero = Vector256.Create(0).AsByte(); ref Vector256 <byte> inRef = ref Unsafe.As <Rgb24, Vector256 <byte> >(ref MemoryMarshal.GetReference(rgbSpan));
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); }
public void Copy1x1Scale() { Block8x8F block = CreateRandomFloatBlock(0, 100); using (Buffer2D <float> buffer = Configuration.Default.MemoryAllocator.Allocate2D <float>(20, 20, AllocationOptions.Clean)) { Buffer2DRegion <float> region = buffer.GetRegion(5, 10, 8, 8); block.Copy1x1Scale(ref region.GetReferenceToOrigin(), region.Stride); Assert.Equal(block[0, 0], buffer[5, 10]); Assert.Equal(block[1, 0], buffer[6, 10]); Assert.Equal(block[0, 1], buffer[5, 11]); Assert.Equal(block[0, 7], buffer[5, 17]); Assert.Equal(block[63], buffer[12, 17]); VerifyAllZeroOutsideSubArea(buffer, 5, 10); } }
public void TestVectorizedConverter() { if (!RgbToYCbCrConverterVectorized.IsSupported) { this.Output.WriteLine("No AVX and/or FMA present, skipping test!"); return; } Rgb24[] data = CreateTestData(); Block8x8F y = default; Block8x8F cb = default; Block8x8F cr = default; RgbToYCbCrConverterVectorized.Convert(data.AsSpan(), ref y, ref cb, ref cr); Verify(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(0.0001F)); }
private void ConvertPixelInto( int r, int g, int b, ref Block8x8F yResult, ref Block8x8F cbResult, ref Block8x8F crResult, int i) { // float y = (0.299F * r) + (0.587F * g) + (0.114F * b); yResult[i] = (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits; // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); cbResult[i] = (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits; // float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); crResult[i] = (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits; }
public void Copy1x1Scale() { Block8x8F block = CreateRandomFloatBlock(0, 100); using (Buffer2D <float> buffer = Configuration.Default.MemoryAllocator.Allocate2D <float>(20, 20, AllocationOptions.Clean)) { BufferArea <float> area = buffer.GetArea(5, 10, 8, 8); block.Copy1x1Scale(area); Assert.Equal(block[0, 0], buffer[5, 10]); Assert.Equal(block[1, 0], buffer[6, 10]); Assert.Equal(block[0, 1], buffer[5, 11]); Assert.Equal(block[0, 7], buffer[5, 17]); Assert.Equal(block[63], buffer[12, 17]); VerifyAllZeroOutsideSubArea(buffer, 5, 10); } }
public void TransposeInto() { float[] expected = Create8x8FloatData(); ReferenceImplementations.Transpose8x8(expected); var source = new Block8x8F(); source.LoadFrom(Create8x8FloatData()); var dest = new Block8x8F(); source.TransposeInto(ref dest); float[] actual = new float[64]; dest.CopyTo(actual); Assert.Equal(expected, actual); }
//[Fact] public void Unscaled() { Block8x8F block = CreateRandomFloatBlock(0, 100); using (var buffer = Configuration.Default.MemoryManager.Allocate2D <float>(20, 20)) { BufferArea <float> area = buffer.GetArea(5, 10, 8, 8); block.CopyTo(area); Assert.Equal(block[0, 0], buffer[5, 10]); Assert.Equal(block[1, 0], buffer[6, 10]); Assert.Equal(block[0, 1], buffer[5, 11]); Assert.Equal(block[0, 7], buffer[5, 17]); Assert.Equal(block[63], buffer[12, 17]); VerifyAllZeroOutsideSubArea(buffer, 5, 10); } }
public void ConvertPixelInto( int r, int g, int b, ref Block8x8F yResult, ref Block8x8F cbResult, ref Block8x8F crResult, int i) { // float y = (0.299F * r) + (0.587F * g) + (0.114F * b); yResult[i] = (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits; // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); cbResult[i] = (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits; // float cr = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); crResult[i] = (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits; }
private static void DivideRoundAll(ref Block8x8F a, ref Block8x8F b) { a.V0L = DivideRound(a.V0L, b.V0L); a.V0R = DivideRound(a.V0R, b.V0R); a.V1L = DivideRound(a.V1L, b.V1L); a.V1R = DivideRound(a.V1R, b.V1R); a.V2L = DivideRound(a.V2L, b.V2L); a.V2R = DivideRound(a.V2R, b.V2R); a.V3L = DivideRound(a.V3L, b.V3L); a.V3R = DivideRound(a.V3R, b.V3R); a.V4L = DivideRound(a.V4L, b.V4L); a.V4R = DivideRound(a.V4R, b.V4R); a.V5L = DivideRound(a.V5L, b.V5L); a.V5R = DivideRound(a.V5R, b.V5R); a.V6L = DivideRound(a.V6L, b.V6L); a.V6R = DivideRound(a.V6R, b.V6R); a.V7L = DivideRound(a.V7L, b.V7L); a.V7R = DivideRound(a.V7R, b.V7R); }
public int BySimdMagic() { int sum = 0; Block8x8F bDividend = this.inputDividend; Block8x8F bDivisor = this.inputDivisior; float * pDividend = (float *)&bDividend; for (int cnt = 0; cnt < ExecutionCount; cnt++) { sum = 0; DivideRoundAll(ref bDividend, ref bDivisor); for (int i = 0; i < Block8x8F.ScalarCount; i++) { sum += (int)pDividend[i]; } } return(sum); }
/// <summary> /// Initializes quantization table. /// </summary> /// <param name="i">The quantization index.</param> /// <param name="scale">The scaling factor.</param> /// <param name="quant">The quantization table.</param> private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant) { for (int j = 0; j < Block8x8F.Size; j++) { int x = UnscaledQuant[i, j]; x = ((x * scale) + 50) / 100; if (x < 1) { x = 1; } if (x > 255) { x = 255; } quant[j] = x; } }
public void RoundInplaceSlow(int seed) { Block8x8F s = CreateRandomFloatBlock(-500, 500, seed); Block8x8F d = s; d.RoundInplace(); this.Output.WriteLine(s.ToString()); this.Output.WriteLine(d.ToString()); for (int i = 0; i < 64; i++) { float expected = (float)Math.Round(s[i]); float actual = d[i]; Assert.Equal(expected, actual); } }