Browse Source

Merge pull request #1304 from SixLabors/js/simplify-jpeg-encoder

Simplify jpeg encoder and fix for Windows ARM
pull/1574/head
James Jackson-South 6 years ago
committed by GitHub
parent
commit
2ea36cea78
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      Directory.Build.props
  2. 27
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  3. 17
      src/ImageSharp/Formats/Jpeg/Components/Encoder/BlockQuad.cs
  4. 37
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs
  5. 13
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  6. 13
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
  7. 97
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  8. 12
      src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs
  9. 18
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
  10. 2
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs

26
Directory.Build.props

@ -30,17 +30,17 @@
<!--
https://apisof.net/
+===================+=======+==========+=====================+=============+=================+====================+==============+=========+
| SUPPORTS | MATHF | HASHCODE | EXTENDED_INTRINSICS | SPAN_STREAM | ENCODING_STRING | RUNTIME_INTRINSICS | CODECOVERAGE | HOTPATH |
+===================+=======+==========+=====================+=============+=================+====================+==============+=========|
| netcoreapp3.1 | Y | Y | Y | Y | Y | Y | Y | Y |
| netcoreapp2.1 | Y | Y | Y | Y | Y | N | Y | N |
| netcoreapp2.0 | Y | N | N | N | N | N | Y | N |
| netstandard2.1 | Y | Y | N | Y | Y | N | Y | N |
| netstandard2.0 | N | N | N | N | N | N | Y | N |
| netstandard1.3 | N | N | N | N | N | N | N | N |
| net472 | N | N | Y | N | N | N | Y | N |
+===================+=======+==========+=====================+=============+=================+====================+==============+=========|
+===================+=======+==========+=====================+=============+=================+====================+==============+=========+============|
| SUPPORTS | MATHF | HASHCODE | EXTENDED_INTRINSICS | SPAN_STREAM | ENCODING_STRING | RUNTIME_INTRINSICS | CODECOVERAGE | HOTPATH | CREATESPAN |
+===================+=======+==========+=====================+=============+=================+====================+==============+=========|============|
| netcoreapp3.1 | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| netcoreapp2.1 | Y | Y | Y | Y | Y | N | Y | N | Y |
| netcoreapp2.0 | Y | N | N | N | N | N | Y | N | Y |
| netstandard2.1 | Y | Y | N | Y | Y | N | Y | N | Y |
| netstandard2.0 | N | N | N | N | N | N | Y | N | N |
| netstandard1.3 | N | N | N | N | N | N | N | N | N |
| net472 | N | N | Y | N | N | N | Y | N | N |
+===================+=======+==========+=====================+=============+=================+====================+==============+=========|============|
-->
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
@ -52,6 +52,7 @@
<DefineConstants>$(DefineConstants);SUPPORTS_RUNTIME_INTRINSICS</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CODECOVERAGE</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_HOTPATH</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CREATESPAN</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<DefineConstants>$(DefineConstants);SUPPORTS_MATHF</DefineConstants>
@ -60,10 +61,12 @@
<DefineConstants>$(DefineConstants);SUPPORTS_SPAN_STREAM</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_ENCODING_STRING</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CODECOVERAGE</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CREATESPAN</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0'">
<DefineConstants>$(DefineConstants);SUPPORTS_MATHF</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CODECOVERAGE</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CREATESPAN</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
<DefineConstants>$(DefineConstants);SUPPORTS_MATHF</DefineConstants>
@ -71,6 +74,7 @@
<DefineConstants>$(DefineConstants);SUPPORTS_SPAN_STREAM</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_ENCODING_STRING</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CODECOVERAGE</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CREATESPAN</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<DefineConstants>$(DefineConstants);SUPPORTS_CODECOVERAGE</DefineConstants>

27
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

@ -376,22 +376,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <param name="block">Source block</param>
/// <param name="dest">Destination block</param>
/// <param name="qt">The quantization table</param>
/// <param name="unzigPtr">Pointer to elements of <see cref="ZigZag"/></param>
/// <param name="unZig">The 8x8 Unzig block.</param>
public static unsafe void Quantize(
Block8x8F* block,
Block8x8F* dest,
Block8x8F* qt,
byte* unzigPtr)
ref Block8x8F block,
ref Block8x8F dest,
ref Block8x8F qt,
ref ZigZag unZig)
{
float* s = (float*)block;
float* d = (float*)dest;
for (int zig = 0; zig < Size; zig++)
{
d[zig] = s[unzigPtr[zig]];
dest[zig] = block[unZig[zig]];
}
DivideRoundAll(ref *dest, ref *qt);
DivideRoundAll(ref dest, ref qt);
}
/// <summary>
@ -399,14 +396,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary>
/// <param name="destination">The destination block.</param>
/// <param name="source">The source block.</param>
public static unsafe void Scale16X16To8X8(Block8x8F* destination, Block8x8F* source)
public static unsafe void Scale16X16To8X8(ref Block8x8F destination, ReadOnlySpan<Block8x8F> source)
{
float* d = (float*)destination;
for (int i = 0; i < 4; i++)
{
int dstOff = ((i & 2) << 4) | ((i & 1) << 2);
float* iSource = (float*)(source + i);
Block8x8F iSource = source[i];
for (int y = 0; y < 4; y++)
{
@ -414,7 +409,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
int j = (16 * y) + (2 * x);
float sum = iSource[j] + iSource[j + 1] + iSource[j + 8] + iSource[j + 9];
d[(8 * y) + x + dstOff] = (sum + 2) / 4;
destination[(8 * y) + x + dstOff] = (sum + 2) * .25F;
}
}
}
@ -589,7 +584,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor)
{
// sign(dividend) = max(min(dividend, 1), -1)
var sign = Vector4Utilities.FastClamp(dividend, NegativeOne, Vector4.One);
Vector4 sign = Vector4Utilities.FastClamp(dividend, NegativeOne, Vector4.One);
// AlmostRound(dividend/divisor) = dividend/divisor + 0.5*sign(dividend)
return (dividend / divisor) + (sign * Offset);

17
src/ImageSharp/Formats/Jpeg/Components/Encoder/BlockQuad.cs

@ -1,17 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Poor man's stackalloc: Contains a value-type <see cref="float"/> buffer sized for 4 <see cref="Block8x8F"/> instances.
/// Useful for decoder/encoder operations allocating a block for each Jpeg component.
/// </summary>
internal unsafe struct BlockQuad
{
/// <summary>
/// The value-type <see cref="float"/> buffer sized for 4 <see cref="Block8x8F"/> instances.
/// </summary>
public fixed float Data[4 * Block8x8F.Size];
}
}

37
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
@ -96,30 +96,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ConvertPixelInto(int r, int g, int b, ref float yResult, ref float cbResult, ref float crResult)
public void ConvertPixelInto(
int r,
int g,
int b,
ref Block8x8F yResult,
ref Block8x8F cbResult,
ref Block8x8F crResult,
int i)
{
ref int start = ref Unsafe.As<RgbToYCbCrTables, int>(ref this);
// float y = (0.299F * r) + (0.587F * g) + (0.114F * b);
yResult[i] = (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits;
ref int yR = ref start;
ref int yG = ref Unsafe.Add(ref start, 256 * 1);
ref int yB = ref Unsafe.Add(ref start, 256 * 2);
// float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
cbResult[i] = (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits;
ref int cbR = ref Unsafe.Add(ref start, 256 * 3);
ref int cbG = ref Unsafe.Add(ref start, 256 * 4);
ref int cbB = ref Unsafe.Add(ref start, 256 * 5);
ref int crG = ref Unsafe.Add(ref start, 256 * 6);
ref int crB = ref Unsafe.Add(ref start, 256 * 7);
yResult = (Unsafe.Add(ref yR, r) + Unsafe.Add(ref yG, g) + Unsafe.Add(ref yB, b)) >> ScaleBits;
cbResult = (Unsafe.Add(ref cbR, r) + Unsafe.Add(ref cbG, g) + Unsafe.Add(ref cbB, b)) >> ScaleBits;
crResult = (Unsafe.Add(ref cbB, r) + Unsafe.Add(ref crG, g) + Unsafe.Add(ref crB, b)) >> ScaleBits;
// float cr = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero);
crResult[i] = (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Fix(float x)
{
return (int)((x * (1L << ScaleBits)) + 0.5F);
}
=> (int)((x * (1L << ScaleBits)) + 0.5F);
}
}
}

13
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs

@ -62,9 +62,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
Span<Rgb24> rgbSpan = this.rgbBlock.AsSpanUnsafe();
PixelOperations<TPixel>.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan);
ref float yBlockStart = ref Unsafe.As<Block8x8F, float>(ref this.Y);
ref float cbBlockStart = ref Unsafe.As<Block8x8F, float>(ref this.Cb);
ref float crBlockStart = ref Unsafe.As<Block8x8F, float>(ref this.Cr);
ref Block8x8F yBlock = ref this.Y;
ref Block8x8F cbBlock = ref this.Cb;
ref Block8x8F crBlock = ref this.Cr;
ref Rgb24 rgbStart = ref rgbSpan[0];
for (int i = 0; i < 64; i++)
@ -75,9 +75,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
c.R,
c.G,
c.B,
ref Unsafe.Add(ref yBlockStart, i),
ref Unsafe.Add(ref cbBlockStart, i),
ref Unsafe.Add(ref crBlockStart, i));
ref yBlock,
ref cbBlock,
ref crBlock,
i);
}
}
}

13
src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs

@ -4,7 +4,6 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -16,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct GenericBlock8x8<T>
where T : struct
where T : unmanaged
{
public const int Size = 64;
@ -110,6 +109,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <summary>
/// Only for on-stack instances!
/// </summary>
public Span<T> AsSpanUnsafe() => new Span<T>(Unsafe.AsPointer(ref this), Size);
public Span<T> AsSpanUnsafe()
{
#if SUPPORTS_CREATESPAN
Span<GenericBlock8x8<T>> s = MemoryMarshal.CreateSpan(ref this, 1);
return MemoryMarshal.Cast<GenericBlock8x8<T>, T>(s);
#else
return new Span<T>(Unsafe.AsPointer(ref this), Size);
#endif
}
}
}

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

@ -7,7 +7,6 @@ using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
@ -432,27 +431,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
&pixelConverter.Y,
&temp1,
&temp2,
&onStackLuminanceQuantTable,
unzig.Data);
ref pixelConverter.Y,
ref temp1,
ref temp2,
ref onStackLuminanceQuantTable,
ref unzig);
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
&pixelConverter.Cb,
&temp1,
&temp2,
&onStackChrominanceQuantTable,
unzig.Data);
ref pixelConverter.Cb,
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
&pixelConverter.Cr,
&temp1,
&temp2,
&onStackChrominanceQuantTable,
unzig.Data);
ref pixelConverter.Cr,
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig);
}
}
}
@ -517,25 +516,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <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>
/// <param name="unZig">The 8x8 Unzig block.</param>
/// <returns>
/// The <see cref="int"/>
/// </returns>
private int WriteBlock(
QuantIndex index,
int prevDC,
Block8x8F* src,
Block8x8F* tempDest1,
Block8x8F* tempDest2,
Block8x8F* quant,
byte* unzigPtr)
ref Block8x8F src,
ref Block8x8F tempDest1,
ref Block8x8F tempDest2,
ref Block8x8F quant,
ref ZigZag unZig)
{
FastFloatingPointDCT.TransformFDCT(ref *src, ref *tempDest1, ref *tempDest2);
FastFloatingPointDCT.TransformFDCT(ref src, ref tempDest1, ref tempDest2);
Block8x8F.Quantize(tempDest1, tempDest2, quant, unzigPtr);
float* unziggedDestPtr = (float*)tempDest2;
Block8x8F.Quantize(ref tempDest1, ref tempDest2, ref quant, ref unZig);
int dc = (int)unziggedDestPtr[0];
int dc = (int)tempDest2[0];
// Emit the DC delta.
this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC);
@ -546,7 +544,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
for (int zig = 1; zig < Block8x8F.Size; zig++)
{
int ac = (int)unziggedDestPtr[zig];
int ac = (int)tempDest2[zig];
if (ac == 0)
{
@ -982,11 +980,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
Block8x8F b = default;
BlockQuad cb = default;
BlockQuad cr = default;
var cbPtr = (Block8x8F*)cb.Data;
var crPtr = (Block8x8F*)cr.Data;
Span<Block8x8F> cb = stackalloc Block8x8F[4];
Span<Block8x8F> cr = stackalloc Block8x8F[4];
Block8x8F temp1 = default;
Block8x8F temp2 = default;
@ -1018,38 +1013,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
pixelConverter.Convert(frame, x + xOff, y + yOff, currentRows);
cbPtr[i] = pixelConverter.Cb;
crPtr[i] = pixelConverter.Cr;
cb[i] = pixelConverter.Cb;
cr[i] = pixelConverter.Cr;
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
&pixelConverter.Y,
&temp1,
&temp2,
&onStackLuminanceQuantTable,
unzig.Data);
ref pixelConverter.Y,
ref temp1,
ref temp2,
ref onStackLuminanceQuantTable,
ref unzig);
}
Block8x8F.Scale16X16To8X8(&b, cbPtr);
Block8x8F.Scale16X16To8X8(ref b, cb);
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
&b,
&temp1,
&temp2,
&onStackChrominanceQuantTable,
unzig.Data);
ref b,
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig);
Block8x8F.Scale16X16To8X8(&b, crPtr);
Block8x8F.Scale16X16To8X8(ref b, cr);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
&b,
&temp1,
&temp2,
&onStackChrominanceQuantTable,
unzig.Data);
ref b,
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig);
}
}
}

12
src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs

@ -201,7 +201,17 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void FromRgba32(Rgba32 source) => this = source.Rgb;
public void FromRgba32(Rgba32 source)
{
#if NETSTANDARD2_0
// See https://github.com/SixLabors/ImageSharp/issues/1275
this.R = source.R;
this.G = source.G;
this.B = source.B;
#else
this = source.Rgb;
#endif
}
/// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)]

18
tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs

@ -10,6 +10,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using SixLabors.ImageSharp.Tests;
using CoreImage = SixLabors.ImageSharp.Image;
public class EncodeJpeg : BenchmarkBase
@ -24,7 +25,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
if (this.bmpStream == null)
{
this.bmpStream = File.OpenRead("../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp");
const string TestImage = TestImages.Bmp.Car;
this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
this.bmpCore = CoreImage.Load<Rgba32>(this.bmpStream);
this.bmpStream.Position = 0;
this.bmpDrawing = Image.FromStream(this.bmpStream);
@ -58,3 +60,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
}
}
}
/*
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.959 (1909/November2018Update/19H2)
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.302
[Host] : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT
DefaultJob : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT
| Method | Mean | Error | StdDev | Ratio | RatioSD |
|---------------------- |---------:|----------:|----------:|------:|--------:|
| 'System.Drawing Jpeg' | 4.297 ms | 0.0244 ms | 0.0228 ms | 1.00 | 0.00 |
| 'ImageSharp Jpeg' | 5.286 ms | 0.1034 ms | 0.0967 ms | 1.23 | 0.02 |
*/

2
tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs

@ -282,7 +282,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var actualResults = default(Block8x8F);
Block8x8F.Quantize(&block, &actualResults, &qt, unzig.Data);
Block8x8F.Quantize(ref block, ref actualResults, ref qt, ref unzig);
for (int i = 0; i < Block8x8F.Size; i++)
{

Loading…
Cancel
Save