Browse Source

Fixed sampling factors (hopefully)

pull/2120/head
Dmitry Pentin 4 years ago
parent
commit
dd3c3ec601
  1. 162
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs
  2. 179
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs
  3. 1
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  4. 80
      src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs
  5. 36
      src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs
  6. 9
      src/ImageSharp/Formats/Jpeg/Components/Quantization.cs
  7. 7
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  8. 19
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  9. 97
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs

162
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs

@ -0,0 +1,162 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
// ReSharper disable UseObjectOrCollectionInitializer
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
public partial struct Block8x8F
{
[MethodImpl(InliningOptions.ShortMethod)]
public void ScaledCopyFrom(ref float areaOrigin, int areaStride) =>
CopyFrom1x1Scale(ref Unsafe.As<float, byte>(ref areaOrigin), ref Unsafe.As<Block8x8F, byte>(ref this), areaStride);
[MethodImpl(InliningOptions.ShortMethod)]
public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale)
{
if (horizontalScale == 1 && verticalScale == 1)
{
CopyTo1x1Scale(ref Unsafe.As<Block8x8F, byte>(ref this), ref Unsafe.As<float, byte>(ref areaOrigin), areaStride);
return;
}
if (horizontalScale == 2 && verticalScale == 2)
{
this.CopyTo2x2Scale(ref areaOrigin, areaStride);
return;
}
// TODO: Optimize: implement all cases with scale-specific, loopless code!
this.CopyArbitraryScale(ref areaOrigin, areaStride, horizontalScale, verticalScale);
}
private void CopyTo2x2Scale(ref float areaOrigin, int areaStride)
{
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref areaOrigin);
int destStride = areaStride / 2;
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 0, destStride);
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 1, destStride);
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 2, destStride);
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 3, destStride);
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 4, destStride);
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 5, destStride);
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 6, destStride);
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 7, destStride);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void WidenCopyRowImpl2x2(ref Vector4 selfBase, ref Vector2 destBase, int row, int destStride)
{
ref Vector4 sLeft = ref Unsafe.Add(ref selfBase, 2 * row);
ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1);
int offset = 2 * row * destStride;
ref Vector4 dTopLeft = ref Unsafe.As<Vector2, Vector4>(ref Unsafe.Add(ref destBase, offset));
ref Vector4 dBottomLeft = ref Unsafe.As<Vector2, Vector4>(ref Unsafe.Add(ref destBase, offset + destStride));
var xyLeft = new Vector4(sLeft.X);
xyLeft.Z = sLeft.Y;
xyLeft.W = sLeft.Y;
var zwLeft = new Vector4(sLeft.Z);
zwLeft.Z = sLeft.W;
zwLeft.W = sLeft.W;
var xyRight = new Vector4(sRight.X);
xyRight.Z = sRight.Y;
xyRight.W = sRight.Y;
var zwRight = new Vector4(sRight.Z);
zwRight.Z = sRight.W;
zwRight.W = sRight.W;
dTopLeft = xyLeft;
Unsafe.Add(ref dTopLeft, 1) = zwLeft;
Unsafe.Add(ref dTopLeft, 2) = xyRight;
Unsafe.Add(ref dTopLeft, 3) = zwRight;
dBottomLeft = xyLeft;
Unsafe.Add(ref dBottomLeft, 1) = zwLeft;
Unsafe.Add(ref dBottomLeft, 2) = xyRight;
Unsafe.Add(ref dBottomLeft, 3) = zwRight;
}
}
[MethodImpl(InliningOptions.ColdPath)]
private void CopyArbitraryScale(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale)
{
for (int y = 0; y < 8; y++)
{
int yy = y * verticalScale;
int y8 = y * 8;
for (int x = 0; x < 8; x++)
{
int xx = x * horizontalScale;
float value = this[y8 + x];
for (int i = 0; i < verticalScale; i++)
{
int baseIdx = ((yy + i) * areaStride) + xx;
for (int j = 0; j < horizontalScale; j++)
{
// area[xx + j, yy + i] = value;
Unsafe.Add(ref areaOrigin, baseIdx + j) = value;
}
}
}
}
}
private static void CopyTo1x1Scale(ref byte origin, ref byte dest, int areaStride)
{
int destStride = areaStride * sizeof(float);
CopyRowImpl(ref origin, ref dest, destStride, 0);
CopyRowImpl(ref origin, ref dest, destStride, 1);
CopyRowImpl(ref origin, ref dest, destStride, 2);
CopyRowImpl(ref origin, ref dest, destStride, 3);
CopyRowImpl(ref origin, ref dest, destStride, 4);
CopyRowImpl(ref origin, ref dest, destStride, 5);
CopyRowImpl(ref origin, ref dest, destStride, 6);
CopyRowImpl(ref origin, ref dest, destStride, 7);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void CopyRowImpl(ref byte origin, ref byte dest, int destStride, int row)
{
origin = ref Unsafe.Add(ref origin, row * 8 * sizeof(float));
dest = ref Unsafe.Add(ref dest, row * destStride);
Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float));
}
}
private static void CopyFrom1x1Scale(ref byte origin, ref byte dest, int areaStride)
{
int destStride = areaStride * sizeof(float);
CopyRowImpl(ref origin, ref dest, destStride, 0);
CopyRowImpl(ref origin, ref dest, destStride, 1);
CopyRowImpl(ref origin, ref dest, destStride, 2);
CopyRowImpl(ref origin, ref dest, destStride, 3);
CopyRowImpl(ref origin, ref dest, destStride, 4);
CopyRowImpl(ref origin, ref dest, destStride, 5);
CopyRowImpl(ref origin, ref dest, destStride, 6);
CopyRowImpl(ref origin, ref dest, destStride, 7);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void CopyRowImpl(ref byte origin, ref byte dest, int sourceStride, int row)
{
origin = ref Unsafe.Add(ref origin, row * sourceStride);
dest = ref Unsafe.Add(ref dest, row * 8 * sizeof(float));
Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float));
}
}
}
}

179
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs

@ -1,179 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
// ReSharper disable UseObjectOrCollectionInitializer
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
public partial struct Block8x8F
{
/// <summary>
/// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical scale factors.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void ScaledCopyTo(in Buffer2DRegion<float> region, int horizontalScale, int verticalScale)
{
ref float areaOrigin = ref region.GetReferenceToOrigin();
this.ScaledCopyTo(ref areaOrigin, region.Stride, horizontalScale, verticalScale);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void ScaledCopyFrom(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale)
{
if (horizontalScale == 1 && verticalScale == 1)
{
ref byte selfBase = ref Unsafe.As<Block8x8F, byte>(ref this);
ref byte destBase = ref Unsafe.As<float, byte>(ref areaOrigin);
int destStride = areaStride * sizeof(float);
CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 0);
CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 1);
CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 2);
CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 3);
CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 4);
CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 5);
CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 6);
CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 7);
return;
}
throw new NotImplementedException("This is a test setup!");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void CopyRowImplFromStrides(ref byte src, ref byte dst, int srcStride, int row)
{
ref byte s = ref Unsafe.Add(ref dst, row * srcStride);
ref byte d = ref Unsafe.Add(ref src, row * 8 * sizeof(float));
Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float));
}
}
[MethodImpl(InliningOptions.ShortMethod)]
public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale)
{
if (horizontalScale == 1 && verticalScale == 1)
{
this.Copy1x1Scale(ref areaOrigin, areaStride);
return;
}
if (horizontalScale == 2 && verticalScale == 2)
{
this.Copy2x2Scale(ref areaOrigin, areaStride);
return;
}
// TODO: Optimize: implement all cases with scale-specific, loopless code!
this.CopyArbitraryScale(ref areaOrigin, areaStride, horizontalScale, verticalScale);
}
public void Copy1x1Scale(ref float areaOrigin, int areaStride)
{
ref byte selfBase = ref Unsafe.As<Block8x8F, byte>(ref this);
ref byte destBase = ref Unsafe.As<float, byte>(ref areaOrigin);
int destStride = areaStride * sizeof(float);
CopyRowImpl(ref selfBase, ref destBase, destStride, 0);
CopyRowImpl(ref selfBase, ref destBase, destStride, 1);
CopyRowImpl(ref selfBase, ref destBase, destStride, 2);
CopyRowImpl(ref selfBase, ref destBase, destStride, 3);
CopyRowImpl(ref selfBase, ref destBase, destStride, 4);
CopyRowImpl(ref selfBase, ref destBase, destStride, 5);
CopyRowImpl(ref selfBase, ref destBase, destStride, 6);
CopyRowImpl(ref selfBase, ref destBase, destStride, 7);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row)
{
ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float));
ref byte d = ref Unsafe.Add(ref destBase, row * destStride);
Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float));
}
private void Copy2x2Scale(ref float areaOrigin, int areaStride)
{
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref areaOrigin);
int destStride = areaStride / 2;
this.WidenCopyRowImpl2x2(ref destBase, 0, destStride);
this.WidenCopyRowImpl2x2(ref destBase, 1, destStride);
this.WidenCopyRowImpl2x2(ref destBase, 2, destStride);
this.WidenCopyRowImpl2x2(ref destBase, 3, destStride);
this.WidenCopyRowImpl2x2(ref destBase, 4, destStride);
this.WidenCopyRowImpl2x2(ref destBase, 5, destStride);
this.WidenCopyRowImpl2x2(ref destBase, 6, destStride);
this.WidenCopyRowImpl2x2(ref destBase, 7, destStride);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WidenCopyRowImpl2x2(ref Vector2 destBase, int row, int destStride)
{
ref Vector4 sLeft = ref Unsafe.Add(ref this.V0L, 2 * row);
ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1);
int offset = 2 * row * destStride;
ref Vector4 dTopLeft = ref Unsafe.As<Vector2, Vector4>(ref Unsafe.Add(ref destBase, offset));
ref Vector4 dBottomLeft = ref Unsafe.As<Vector2, Vector4>(ref Unsafe.Add(ref destBase, offset + destStride));
var xyLeft = new Vector4(sLeft.X);
xyLeft.Z = sLeft.Y;
xyLeft.W = sLeft.Y;
var zwLeft = new Vector4(sLeft.Z);
zwLeft.Z = sLeft.W;
zwLeft.W = sLeft.W;
var xyRight = new Vector4(sRight.X);
xyRight.Z = sRight.Y;
xyRight.W = sRight.Y;
var zwRight = new Vector4(sRight.Z);
zwRight.Z = sRight.W;
zwRight.W = sRight.W;
dTopLeft = xyLeft;
Unsafe.Add(ref dTopLeft, 1) = zwLeft;
Unsafe.Add(ref dTopLeft, 2) = xyRight;
Unsafe.Add(ref dTopLeft, 3) = zwRight;
dBottomLeft = xyLeft;
Unsafe.Add(ref dBottomLeft, 1) = zwLeft;
Unsafe.Add(ref dBottomLeft, 2) = xyRight;
Unsafe.Add(ref dBottomLeft, 3) = zwRight;
}
[MethodImpl(InliningOptions.ColdPath)]
private void CopyArbitraryScale(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale)
{
for (int y = 0; y < 8; y++)
{
int yy = y * verticalScale;
int y8 = y * 8;
for (int x = 0; x < 8; x++)
{
int xx = x * horizontalScale;
float value = this[y8 + x];
for (int i = 0; i < verticalScale; i++)
{
int baseIdx = ((yy + i) * areaStride) + xx;
for (int j = 0; j < horizontalScale; j++)
{
// area[xx + j, yy + i] = value;
Unsafe.Add(ref areaOrigin, baseIdx + j) = value;
}
}
}
}
}
}
}

1
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs

@ -142,7 +142,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
where TPixel : unmanaged, IPixel<TPixel>
{
// DEBUG INITIALIZATION SETUP
frame.Init(1, 1);
frame.AllocateComponents(fullScan: false);
var spectralConverter = new SpectralConverter<TPixel>(configuration);

80
src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs

@ -21,11 +21,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
FastFloatingPointDCT.AdjustToFDCT(ref this.quantTable);
this.component = component;
this.blockAreaSize = this.component.SubSamplingDivisors * 8;
this.blockAreaSize = component.SubSamplingDivisors * 8;
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
postProcessorBufferSize.Width,
postProcessorBufferSize.Height,
this.blockAreaSize.Height,
8 * component.SubSamplingDivisors.Height,
AllocationOptions.Clean);
}
@ -42,32 +42,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
// but 12-bit jpegs are not supported currently
float normalizationValue = -128f;
int blocksRowsPerStep = this.component.SamplingFactors.Height;
int destAreaStride = this.ColorBuffer.Width * this.component.SubSamplingDivisors.Height;
int destAreaStride = this.ColorBuffer.Width;
int yBlockStart = spectralStep * blocksRowsPerStep;
Size subSamplingDivisors = this.component.SubSamplingDivisors;
int yBlockStart = spectralStep * this.component.SamplingFactors.Height;
Block8x8F workspaceBlock = default;
for (int y = 0; y < blocksRowsPerStep; y++)
// handle subsampling
this.PackColorBuffer();
for (int y = 0; y < spectralBuffer.Height; y++)
{
int yBuffer = y * this.blockAreaSize.Height;
for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++)
{
Span<float> colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer);
Span<Block8x8> blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y);
// load 8x8 block from 8 pixel strides
int xColorBufferStart = xBlock * this.blockAreaSize.Width;
int xColorBufferStart = xBlock * 8;
workspaceBlock.ScaledCopyFrom(
ref colorBufferRow[xColorBufferStart],
destAreaStride,
subSamplingDivisors.Width,
subSamplingDivisors.Height);
destAreaStride);
// level shift via -128f
workspaceBlock.AddInPlace(normalizationValue);
@ -86,5 +82,61 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
public void Dispose()
=> this.ColorBuffer.Dispose();
private void PackColorBuffer()
{
Size factors = this.component.SubSamplingDivisors;
if (factors.Width == 1 && factors.Height == 1)
{
return;
}
for (int i = 0; i < this.ColorBuffer.Height; i += factors.Height)
{
Span<float> targetBufferRow = this.ColorBuffer.DangerousGetRowSpan(i);
// vertical sum
for (int j = 1; j < factors.Height; j++)
{
SumVertical(targetBufferRow, this.ColorBuffer.DangerousGetRowSpan(i + j));
}
// horizontal sum
SumHorizontal(targetBufferRow, factors.Width);
// calculate average
float multiplier = 1f / (factors.Width * factors.Height);
MultiplyToAverage(targetBufferRow, multiplier);
}
static void SumVertical(Span<float> target, Span<float> source)
{
for (int i = 0; i < target.Length; i++)
{
target[i] += source[i];
}
}
static void SumHorizontal(Span<float> target, int factor)
{
for (int i = 0; i < target.Length / factor; i++)
{
target[i] = target[i * factor];
for (int j = 1; j < factor; j++)
{
target[i] += target[(i * factor) + j];
}
}
}
static void MultiplyToAverage(Span<float> target, float multiplier)
{
for (int i = 0; i < target.Length; i++)
{
target[i] *= multiplier;
}
}
}
}
}

36
src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs

@ -18,17 +18,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.PixelWidth = image.Width;
this.PixelHeight = image.Height;
// int componentCount = 3;
var componentConfigs = frameConfig.Components;
JpegComponentConfig[] componentConfigs = frameConfig.Components;
this.Components = new JpegComponent[componentConfigs.Length];
for (int i = 0; i < this.Components.Length; i++)
{
var componentConfig = componentConfigs[i];
JpegComponentConfig componentConfig = componentConfigs[i];
this.Components[i] = new JpegComponent(allocator, componentConfig.HorizontalSampleFactor, componentConfig.VerticalSampleFactor, componentConfig.QuantizatioTableIndex)
{
DcTableId = componentConfig.dcTableSelector,
AcTableId = componentConfig.acTableSelector,
};
this.BlocksPerMcu += componentConfig.HorizontalSampleFactor * componentConfig.VerticalSampleFactor;
}
int maxSubFactorH = frameConfig.MaxHorizontalSamplingFactor;
int maxSubFactorV = frameConfig.MaxVerticalSamplingFactor;
this.McusPerLine = (int)Numerics.DivideCeil((uint)image.Width, (uint)maxSubFactorH * 8);
this.McusPerColumn = (int)Numerics.DivideCeil((uint)image.Height, (uint)maxSubFactorV * 8);
for (int i = 0; i < this.ComponentCount; i++)
{
JpegComponent component = this.Components[i];
component.Init(this, maxSubFactorH, maxSubFactorV);
}
}
@ -42,9 +54,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
public JpegComponent[] Components { get; }
public int McusPerLine { get; set; }
public int McusPerLine { get; }
public int McusPerColumn { get; }
public int McusPerColumn { get; set; }
public int BlocksPerMcu { get; }
public void Dispose()
{
@ -54,18 +68,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
}
public void Init(int maxSubFactorH, int maxSubFactorV)
{
this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)maxSubFactorH * 8);
this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)maxSubFactorV * 8);
for (int i = 0; i < this.ComponentCount; i++)
{
JpegComponent component = this.Components[i];
component.Init(this, maxSubFactorH, maxSubFactorV);
}
}
public void AllocateComponents(bool fullScan)
{
for (int i = 0; i < this.ComponentCount; i++)

9
src/ImageSharp/Formats/Jpeg/Components/Quantization.cs

@ -188,13 +188,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return table;
}
public static Block8x8F ScaleQuantizationTable(int scale, Block8x8 unscaledTable)
public static Block8x8 ScaleQuantizationTable(int quality, Block8x8 unscaledTable)
{
Block8x8F table = default;
for (int j = 0; j < Block8x8F.Size; j++)
int scale = QualityToScale(quality);
Block8x8 table = default;
for (int j = 0; j < Block8x8.Size; j++)
{
int x = ((unscaledTable[j] * scale) + 50) / 100;
table[j] = Numerics.Clamp(x, 1, 255);
table[j] = (short)(uint)Numerics.Clamp(x, 1, 255);
}
return table;

7
src/ImageSharp/Formats/Jpeg/JpegEncoder.cs

@ -89,6 +89,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public JpegComponentConfig[] Components { get; }
public int MaxHorizontalSamplingFactor { get; set; } = 1;
public int MaxVerticalSamplingFactor { get; set; } = 1;
public JpegFrameConfig PopulateComponent(int index, byte id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex)
{
this.Components[index] = new JpegComponentConfig
@ -101,6 +105,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
acTableSelector = acIndex,
};
this.MaxHorizontalSamplingFactor = Math.Max(this.MaxHorizontalSamplingFactor, hsf);
this.MaxVerticalSamplingFactor = Math.Max(this.MaxVerticalSamplingFactor, vsf);
return this;
}
}

19
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -91,7 +91,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
cancellationToken.ThrowIfCancellationRequested();
this.scanEncoder = new HuffmanScanEncoder(3, stream);
var frame = new Components.Encoder.JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.ColorType));
this.scanEncoder = new HuffmanScanEncoder(frame.BlocksPerMcu, stream);
this.outputStream = stream;
ImageMetadata metadata = image.Metadata;
@ -127,7 +128,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Write the scan header.
this.WriteStartOfScan(this.frameConfig.Components.Length, this.frameConfig.Components);
var frame = new Components.Encoder.JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.ColorType));
this.scanEncoder.EncodeInterleavedScan(frame, image, this.QuantizationTables, Configuration.Default, cancellationToken);
// Write the End Of Image marker.
@ -649,7 +649,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Sampling factors
// 4 bits
int samplingFactors = components[i].HorizontalSampleFactor | (components[i].VerticalSampleFactor << 4);
int samplingFactors = (components[i].HorizontalSampleFactor << 4) | components[i].VerticalSampleFactor;
bufferSpan[1] = (byte)samplingFactors;
// Id
@ -748,7 +748,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="chrominanceQuantTable">Output chrominance quantization table.</param>
private void InitQuantizationTables(JpegQuantizationTableConfig[] configs, JpegMetadata metadata)
{
int dataLen = configs.Length * (1 + Block8x8F.Size);
int dataLen = configs.Length * (1 + Block8x8.Size);
// Marker + quantization table lengths.
int markerlen = 2 + dataLen;
@ -761,16 +761,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
JpegQuantizationTableConfig config = configs[i];
int quality = GetQualityForTable(config.DestinationIndex, this.quality, metadata);
Block8x8 scaledTable = Quantization.ScaleQuantizationTable(quality, config.Table);
// write to the output stream
buffer[offset++] = (byte)config.DestinationIndex;
for (int j = 0; j < Block8x8F.Size; j++)
for (int j = 0; j < Block8x8.Size; j++)
{
buffer[offset++] = (byte)(uint)config.Table[ZigZag.ZigZagOrder[j]];
buffer[offset++] = (byte)(uint)scaledTable[ZigZag.ZigZagOrder[j]];
}
// apply scaling and save into buffer
int quality = GetQualityForTable(config.DestinationIndex, this.quality, metadata);
this.QuantizationTables[config.DestinationIndex] = Quantization.ScaleQuantizationTable(quality, config.Table);
this.QuantizationTables[config.DestinationIndex].LoadFromInt16Scalar(ref scaledTable);
}
// write filled buffer to the stream

97
tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs

@ -1,97 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Uncomment this to turn unit tests into benchmarks:
// #define BENCHMARKING
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using Xunit;
using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
[Trait("Format", "Jpg")]
public partial class Block8x8FTests
{
public class CopyToBufferArea : JpegFixture
{
public CopyToBufferArea(ITestOutputHelper output)
: base(output)
{
}
private static void VerifyAllZeroOutsideSubArea(Buffer2D<float> buffer, int subX, int subY, int horizontalFactor = 1, int verticalFactor = 1)
{
for (int y = 0; y < 20; y++)
{
for (int x = 0; x < 20; x++)
{
if (x < subX || x >= subX + (8 * horizontalFactor) || y < subY || y >= subY + (8 * verticalFactor))
{
Assert.Equal(0, buffer[x, y]);
}
}
}
}
[Fact]
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);
}
}
[Theory]
[InlineData(1, 1)]
[InlineData(1, 2)]
[InlineData(2, 1)]
[InlineData(2, 2)]
[InlineData(4, 2)]
[InlineData(4, 4)]
public void CopyTo(int horizontalFactor, int verticalFactor)
{
Block8x8F block = CreateRandomFloatBlock(0, 100);
var start = new Point(50, 50);
using (Buffer2D<float> buffer = Configuration.Default.MemoryAllocator.Allocate2D<float>(100, 100, AllocationOptions.Clean))
{
Buffer2DRegion<float> region = buffer.GetRegion(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor);
block.ScaledCopyTo(region, horizontalFactor, verticalFactor);
for (int y = 0; y < 8 * verticalFactor; y++)
{
for (int x = 0; x < 8 * horizontalFactor; x++)
{
int yy = y / verticalFactor;
int xx = x / horizontalFactor;
float expected = block[xx, yy];
float actual = region[x, y];
Assert.Equal(expected, actual);
}
}
VerifyAllZeroOutsideSubArea(buffer, start.X, start.Y, horizontalFactor, verticalFactor);
}
}
}
}
}
Loading…
Cancel
Save