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> where TPixel : unmanaged, IPixel<TPixel>
{ {
// DEBUG INITIALIZATION SETUP // DEBUG INITIALIZATION SETUP
frame.Init(1, 1);
frame.AllocateComponents(fullScan: false); frame.AllocateComponents(fullScan: false);
var spectralConverter = new SpectralConverter<TPixel>(configuration); 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); FastFloatingPointDCT.AdjustToFDCT(ref this.quantTable);
this.component = component; this.component = component;
this.blockAreaSize = this.component.SubSamplingDivisors * 8; this.blockAreaSize = component.SubSamplingDivisors * 8;
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>( this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
postProcessorBufferSize.Width, postProcessorBufferSize.Width,
postProcessorBufferSize.Height, postProcessorBufferSize.Height,
this.blockAreaSize.Height, 8 * component.SubSamplingDivisors.Height,
AllocationOptions.Clean); AllocationOptions.Clean);
} }
@ -42,32 +42,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
// but 12-bit jpegs are not supported currently // but 12-bit jpegs are not supported currently
float normalizationValue = -128f; 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 * this.component.SamplingFactors.Height;
int yBlockStart = spectralStep * blocksRowsPerStep;
Size subSamplingDivisors = this.component.SubSamplingDivisors;
Block8x8F workspaceBlock = default; 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; int yBuffer = y * this.blockAreaSize.Height;
for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++)
{ {
Span<float> colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); Span<float> colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer);
Span<Block8x8> blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); Span<Block8x8> blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y);
// load 8x8 block from 8 pixel strides // load 8x8 block from 8 pixel strides
int xColorBufferStart = xBlock * this.blockAreaSize.Width; int xColorBufferStart = xBlock * 8;
workspaceBlock.ScaledCopyFrom( workspaceBlock.ScaledCopyFrom(
ref colorBufferRow[xColorBufferStart], ref colorBufferRow[xColorBufferStart],
destAreaStride, destAreaStride);
subSamplingDivisors.Width,
subSamplingDivisors.Height);
// level shift via -128f // level shift via -128f
workspaceBlock.AddInPlace(normalizationValue); workspaceBlock.AddInPlace(normalizationValue);
@ -86,5 +82,61 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
public void Dispose() public void Dispose()
=> this.ColorBuffer.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.PixelWidth = image.Width;
this.PixelHeight = image.Height; this.PixelHeight = image.Height;
// int componentCount = 3; JpegComponentConfig[] componentConfigs = frameConfig.Components;
var componentConfigs = frameConfig.Components;
this.Components = new JpegComponent[componentConfigs.Length]; this.Components = new JpegComponent[componentConfigs.Length];
for (int i = 0; i < this.Components.Length; i++) 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) this.Components[i] = new JpegComponent(allocator, componentConfig.HorizontalSampleFactor, componentConfig.VerticalSampleFactor, componentConfig.QuantizatioTableIndex)
{ {
DcTableId = componentConfig.dcTableSelector, DcTableId = componentConfig.dcTableSelector,
AcTableId = componentConfig.acTableSelector, 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 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() 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) public void AllocateComponents(bool fullScan)
{ {
for (int i = 0; i < this.ComponentCount; i++) 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; return table;
} }
public static Block8x8F ScaleQuantizationTable(int scale, Block8x8 unscaledTable) public static Block8x8 ScaleQuantizationTable(int quality, Block8x8 unscaledTable)
{ {
Block8x8F table = default; int scale = QualityToScale(quality);
for (int j = 0; j < Block8x8F.Size; j++) Block8x8 table = default;
for (int j = 0; j < Block8x8.Size; j++)
{ {
int x = ((unscaledTable[j] * scale) + 50) / 100; 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; return table;

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

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

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

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