diff --git a/Directory.Build.props b/Directory.Build.props
index 14c9da79d6..0f9c5bdde2 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -30,17 +30,17 @@
@@ -52,6 +52,7 @@
$(DefineConstants);SUPPORTS_RUNTIME_INTRINSICS
$(DefineConstants);SUPPORTS_CODECOVERAGE
$(DefineConstants);SUPPORTS_HOTPATH
+ $(DefineConstants);SUPPORTS_CREATESPAN
$(DefineConstants);SUPPORTS_MATHF
@@ -60,10 +61,12 @@
$(DefineConstants);SUPPORTS_SPAN_STREAM
$(DefineConstants);SUPPORTS_ENCODING_STRING
$(DefineConstants);SUPPORTS_CODECOVERAGE
+ $(DefineConstants);SUPPORTS_CREATESPAN
$(DefineConstants);SUPPORTS_MATHF
$(DefineConstants);SUPPORTS_CODECOVERAGE
+ $(DefineConstants);SUPPORTS_CREATESPAN
$(DefineConstants);SUPPORTS_MATHF
@@ -71,6 +74,7 @@
$(DefineConstants);SUPPORTS_SPAN_STREAM
$(DefineConstants);SUPPORTS_ENCODING_STRING
$(DefineConstants);SUPPORTS_CODECOVERAGE
+ $(DefineConstants);SUPPORTS_CREATESPAN
$(DefineConstants);SUPPORTS_CODECOVERAGE
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
index 8809f890fd..b7835d6706 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
@@ -376,22 +376,19 @@ public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr,
/// Source block
/// Destination block
/// The quantization table
- /// Pointer to elements of
+ /// The 8x8 Unzig block.
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);
}
///
@@ -399,14 +396,12 @@ public static unsafe void Quantize(
///
/// The destination block.
/// The source block.
- public static unsafe void Scale16X16To8X8(Block8x8F* destination, Block8x8F* source)
+ public static unsafe void Scale16X16To8X8(ref Block8x8F destination, ReadOnlySpan 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 @@ public static unsafe void Scale16X16To8X8(Block8x8F* destination, Block8x8F* sou
{
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 @@ private static Vector NormalizeAndRound(Vector row, Vector
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);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/BlockQuad.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/BlockQuad.cs
deleted file mode 100644
index cda149d29e..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/BlockQuad.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
-{
- ///
- /// Poor man's stackalloc: Contains a value-type buffer sized for 4 instances.
- /// Useful for decoder/encoder operations allocating a block for each Jpeg component.
- ///
- internal unsafe struct BlockQuad
- {
- ///
- /// The value-type buffer sized for 4 instances.
- ///
- public fixed float Data[4 * Block8x8F.Size];
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs
index 3c234ccb1f..236eff27cc 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs
+++ b/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 @@ public static RgbToYCbCrTables Create()
/// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values.
///
[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(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);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
index 52d8a5107c..4d6186e22f 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
@@ -62,9 +62,9 @@ public void Convert(ImageFrame frame, int x, int y, in RowOctet
Span rgbSpan = this.rgbBlock.AsSpanUnsafe();
PixelOperations.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan);
- ref float yBlockStart = ref Unsafe.As(ref this.Y);
- ref float cbBlockStart = ref Unsafe.As(ref this.Cb);
- ref float crBlockStart = ref Unsafe.As(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 @@ public void Convert(ImageFrame frame, int x, int y, in RowOctet
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);
}
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
index 7fe7d869cd..92ba1afd35 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
+++ b/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
///
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct GenericBlock8x8
- where T : struct
+ where T : unmanaged
{
public const int Size = 64;
@@ -110,6 +109,14 @@ public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY, in
///
/// Only for on-stack instances!
///
- public Span AsSpanUnsafe() => new Span(Unsafe.AsPointer(ref this), Size);
+ public Span AsSpanUnsafe()
+ {
+#if SUPPORTS_CREATESPAN
+ Span> s = MemoryMarshal.CreateSpan(ref this, 1);
+ return MemoryMarshal.Cast, T>(s);
+#else
+ return new Span(Unsafe.AsPointer(ref this), Size);
+#endif
+ }
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index 593fe92776..c4ff1c0360 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -7,7 +7,6 @@
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 @@ private void Encode444(Image pixels, CancellationToken cancellat
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 @@ private void WriteApplicationHeader(ImageMetadata meta)
/// Temporal block to be used as FDCT Destination
/// Temporal block 2
/// Quantization table
- /// The 8x8 Unzig block pointer
+ /// The 8x8 Unzig block.
///
/// The
///
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 @@ private int WriteBlock(
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 @@ private void Encode420(Image pixels, CancellationToken cancellat
{
// TODO: Need a JpegScanEncoder 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 cb = stackalloc Block8x8F[4];
+ Span cr = stackalloc Block8x8F[4];
Block8x8F temp1 = default;
Block8x8F temp2 = default;
@@ -1018,38 +1013,38 @@ private void Encode420(Image pixels, CancellationToken cancellat
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);
}
}
}
diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs
index 60cbccded1..a50c18d42c 100644
--- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs
+++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs
@@ -201,7 +201,17 @@ public void FromLa32(La32 source)
///
[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
+ }
///
[MethodImpl(InliningOptions.ShortMethod)]
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
index 0a1fe977f8..4c326fb3ac 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
+++ b/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 @@ public void ReadImages()
{
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(this.bmpStream);
this.bmpStream.Position = 0;
this.bmpDrawing = Image.FromStream(this.bmpStream);
@@ -58,3 +60,17 @@ public void JpegCore()
}
}
}
+
+/*
+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 |
+*/
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
index 3482662698..722521f98d 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
@@ -282,7 +282,7 @@ public unsafe void Quantize(int seed)
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++)
{