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++) {