Browse Source

Merge pull request #1533 from SixLabors/js/jpeg-encoder-perf

JpegEncoder - Optimize Some Low Hanging Fruit
pull/1554/head
James Jackson-South 5 years ago
committed by GitHub
parent
commit
55f1fa7e95
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 102
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  2. 6
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  3. 2
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
  4. 93
      src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
  5. 89
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  6. 5
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Scale16X16To8X8.cs
  7. 2
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
  8. 11
      tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs
  9. 2
      tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
  10. 2
      tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs

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

@ -18,6 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <summary>
/// Represents a Jpeg block with <see cref="float"/> coefficients.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal partial struct Block8x8F : IEquatable<Block8x8F>
{
/// <summary>
@ -51,9 +52,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public Vector4 V7R;
#pragma warning restore SA1600 // ElementsMustBeDocumented
private static readonly Vector4 NegativeOne = new Vector4(-1);
private static readonly Vector4 Offset = new Vector4(.5F);
/// <summary>
/// Get/Set scalar elements at a given index
/// </summary>
@ -155,10 +153,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void Clear()
{
// The cheapest way to do this in C#:
this = default;
}
=> this = default; // The cheapest way to do this in C#:
/// <summary>
/// Load raw 32bit floating point data from source.
@ -180,9 +175,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <param name="source">Source</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static unsafe void LoadFrom(Block8x8F* blockPtr, Span<float> source)
{
blockPtr->LoadFrom(source);
}
=> blockPtr->LoadFrom(source);
/// <summary>
/// Load raw 32bit floating point data from source
@ -236,9 +229,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <param name="dest">The destination.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span<float> dest)
{
blockPtr->ScaledCopyTo(dest);
}
=> blockPtr->ScaledCopyTo(dest);
/// <summary>
/// Copy raw 32bit floating point data to dest
@ -439,7 +430,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <param name="blockPtr">The block pointer.</param>
/// <param name="qtPtr">The qt pointer.</param>
/// <param name="unzigPtr">Unzig pointer</param>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, byte* unzigPtr)
{
float* b = (float*)blockPtr;
@ -556,22 +546,60 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
[MethodImpl(InliningOptions.ShortMethod)]
private static void DivideRoundAll(ref Block8x8F a, ref Block8x8F b)
{
a.V0L = DivideRound(a.V0L, b.V0L);
a.V0R = DivideRound(a.V0R, b.V0R);
a.V1L = DivideRound(a.V1L, b.V1L);
a.V1R = DivideRound(a.V1R, b.V1R);
a.V2L = DivideRound(a.V2L, b.V2L);
a.V2R = DivideRound(a.V2R, b.V2R);
a.V3L = DivideRound(a.V3L, b.V3L);
a.V3R = DivideRound(a.V3R, b.V3R);
a.V4L = DivideRound(a.V4L, b.V4L);
a.V4R = DivideRound(a.V4R, b.V4R);
a.V5L = DivideRound(a.V5L, b.V5L);
a.V5R = DivideRound(a.V5R, b.V5R);
a.V6L = DivideRound(a.V6L, b.V6L);
a.V6R = DivideRound(a.V6R, b.V6R);
a.V7L = DivideRound(a.V7L, b.V7L);
a.V7R = DivideRound(a.V7R, b.V7R);
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx.IsSupported)
{
var vnegOne = Vector256.Create(-1f);
var vadd = Vector256.Create(.5F);
var vone = Vector256.Create(1f);
ref Vector256<float> aBase = ref Unsafe.AsRef(Unsafe.As<Vector4, Vector256<float>>(ref a.V0L));
ref Vector256<float> bBase = ref Unsafe.AsRef(Unsafe.As<Vector4, Vector256<float>>(ref b.V0L));
ref Vector256<float> aEnd = ref Unsafe.Add(ref aBase, 8);
do
{
Vector256<float> voff = Avx.Multiply(Avx.Min(Avx.Max(vnegOne, aBase), vone), vadd);
Unsafe.Add(ref aBase, 0) = Avx.Add(Avx.Divide(aBase, bBase), voff);
aBase = ref Unsafe.Add(ref aBase, 1);
bBase = ref Unsafe.Add(ref bBase, 1);
}
while (Unsafe.IsAddressLessThan(ref aBase, ref aEnd));
}
else
#endif
{
a.V0L = DivideRound(a.V0L, b.V0L);
a.V0R = DivideRound(a.V0R, b.V0R);
a.V1L = DivideRound(a.V1L, b.V1L);
a.V1R = DivideRound(a.V1R, b.V1R);
a.V2L = DivideRound(a.V2L, b.V2L);
a.V2R = DivideRound(a.V2R, b.V2R);
a.V3L = DivideRound(a.V3L, b.V3L);
a.V3R = DivideRound(a.V3R, b.V3R);
a.V4L = DivideRound(a.V4L, b.V4L);
a.V4R = DivideRound(a.V4R, b.V4R);
a.V5L = DivideRound(a.V5L, b.V5L);
a.V5R = DivideRound(a.V5R, b.V5R);
a.V6L = DivideRound(a.V6L, b.V6L);
a.V6R = DivideRound(a.V6R, b.V6R);
a.V7L = DivideRound(a.V7L, b.V7L);
a.V7R = DivideRound(a.V7R, b.V7R);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor)
{
var neg = new Vector4(-1);
var add = new Vector4(.5F);
// sign(dividend) = max(min(dividend, 1), -1)
Vector4 sign = Numerics.Clamp(dividend, neg, Vector4.One);
// AlmostRound(dividend/divisor) = dividend/divisor + 0.5*sign(dividend)
return (dividend / divisor) + (sign * add);
}
public void RoundInto(ref Block8x8 dest)
@ -673,8 +701,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <inheritdoc />
public bool Equals(Block8x8F other)
{
return this.V0L == other.V0L
=> this.V0L == other.V0L
&& this.V0R == other.V0R
&& this.V1L == other.V1L
&& this.V1R == other.V1R
@ -690,7 +717,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
&& this.V6R == other.V6R
&& this.V7L == other.V7L
&& this.V7R == other.V7R;
}
/// <inheritdoc />
public override string ToString()
@ -718,16 +744,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return row.FastRound();
}
[MethodImpl(InliningOptions.ShortMethod)]
private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor)
{
// sign(dividend) = max(min(dividend, 1), -1)
Vector4 sign = Numerics.Clamp(dividend, NegativeOne, Vector4.One);
// AlmostRound(dividend/divisor) = dividend/divisor + 0.5*sign(dividend)
return (dividend / divisor) + (sign * Offset);
}
[Conditional("DEBUG")]
private static void GuardBlockIndex(int idx)
{

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

@ -59,9 +59,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>, <see cref="Cb"/>, <see cref="Cr"/>)
/// </summary>
public void Convert(ImageFrame<TPixel> frame, int x, int y, in RowOctet<TPixel> currentRows)
public void Convert(ImageFrame<TPixel> frame, int x, int y, ref RowOctet<TPixel> currentRows)
{
this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, currentRows);
this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, ref currentRows);
Span<Rgb24> rgbSpan = this.rgbBlock.AsSpanUnsafe();
PixelOperations<TPixel>.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan);
@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
else
{
this.colorTables.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
this.colorTables.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
}
}
}

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

@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// Load a 8x8 region of an image into the block.
/// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image.
/// </summary>
public void LoadAndStretchEdges(Buffer2D<T> source, int sourceX, int sourceY, in RowOctet<T> currentRows)
public void LoadAndStretchEdges(Buffer2D<T> source, int sourceX, int sourceY, ref RowOctet<T> currentRows)
{
int width = Math.Min(8, source.Width - sourceX);
int height = Math.Min(8, source.Height - sourceY);

93
src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -12,39 +12,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// Cache 8 pixel rows on the stack, which may originate from different buffers of a <see cref="MemoryGroup{T}"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal readonly ref struct RowOctet<T>
internal ref struct RowOctet<T>
where T : struct
{
private readonly Span<T> row0;
private readonly Span<T> row1;
private readonly Span<T> row2;
private readonly Span<T> row3;
private readonly Span<T> row4;
private readonly Span<T> row5;
private readonly Span<T> row6;
private readonly Span<T> row7;
public RowOctet(Buffer2D<T> buffer, int startY)
{
int y = startY;
int height = buffer.Height;
this.row0 = y < height ? buffer.GetRowSpan(y++) : default;
this.row1 = y < height ? buffer.GetRowSpan(y++) : default;
this.row2 = y < height ? buffer.GetRowSpan(y++) : default;
this.row3 = y < height ? buffer.GetRowSpan(y++) : default;
this.row4 = y < height ? buffer.GetRowSpan(y++) : default;
this.row5 = y < height ? buffer.GetRowSpan(y++) : default;
this.row6 = y < height ? buffer.GetRowSpan(y++) : default;
this.row7 = y < height ? buffer.GetRowSpan(y) : default;
}
private Span<T> row0;
private Span<T> row1;
private Span<T> row2;
private Span<T> row3;
private Span<T> row4;
private Span<T> row5;
private Span<T> row6;
private Span<T> row7;
// No unsafe tricks, since Span<T> can't be used as a generic argument
public Span<T> this[int y]
{
[MethodImpl(InliningOptions.ShortMethod)]
get
{
// No unsafe tricks, since Span<T> can't be used as a generic argument
return y switch
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get =>
y switch
{
0 => this.row0,
1 => this.row1,
@ -56,13 +41,57 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
7 => this.row7,
_ => ThrowIndexOutOfRangeException()
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private set
{
switch (y)
{
case 0:
this.row0 = value;
break;
case 1:
this.row1 = value;
break;
case 2:
this.row2 = value;
break;
case 3:
this.row3 = value;
break;
case 4:
this.row4 = value;
break;
case 5:
this.row5 = value;
break;
case 6:
this.row6 = value;
break;
default:
this.row7 = value;
break;
}
}
}
[MethodImpl(InliningOptions.ColdPath)]
private static Span<T> ThrowIndexOutOfRangeException()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(Buffer2D<T> buffer, int startY)
{
throw new IndexOutOfRangeException();
// We don't actually have to assign values outside of the
// frame pixel buffer since they are never requested.
int y = startY;
int yEnd = Math.Min(y + 8, buffer.Height);
int i = 0;
while (y < yEnd)
{
this[i++] = buffer.GetRowSpan(y++);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static Span<T> ThrowIndexOutOfRangeException()
=> throw new IndexOutOfRangeException();
}
}

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

@ -6,6 +6,7 @@ using System.Buffers.Binary;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -313,7 +314,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <param name="bits">The packed bits.</param>
/// <param name="count">The number of bits</param>
private void Emit(uint bits, uint count)
/// <param name="emitBufferBase">The reference to the emitBuffer.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Emit(uint bits, uint count, ref byte emitBufferBase)
{
count += this.bitCount;
bits <<= (int)(32 - count);
@ -327,10 +330,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
while (count >= 8)
{
byte b = (byte)(bits >> 24);
this.emitBuffer[len++] = b;
if (b == 0xff)
Unsafe.Add(ref emitBufferBase, len++) = b;
if (b == byte.MaxValue)
{
this.emitBuffer[len++] = 0x00;
Unsafe.Add(ref emitBufferBase, len++) = byte.MinValue;
}
bits <<= 8;
@ -352,11 +355,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <param name="index">The index of the Huffman encoder</param>
/// <param name="value">The value to encode.</param>
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EmitHuff(HuffIndex index, int value)
private void EmitHuff(HuffIndex index, int value, ref byte emitBufferBase)
{
uint x = HuffmanLut.TheHuffmanLut[(int)index].Values[value];
this.Emit(x & ((1 << 24) - 1), x >> 24);
this.Emit(x & ((1 << 24) - 1), x >> 24, ref emitBufferBase);
}
/// <summary>
@ -365,8 +369,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="index">The index of the Huffman encoder</param>
/// <param name="runLength">The number of copies to encode.</param>
/// <param name="value">The value to encode.</param>
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EmitHuffRLE(HuffIndex index, int runLength, int value)
private void EmitHuffRLE(HuffIndex index, int runLength, int value, ref byte emitBufferBase)
{
int a = value;
int b = value;
@ -386,10 +391,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
bt = 8 + (uint)BitCountLut[a >> 8];
}
this.EmitHuff(index, (int)((uint)(runLength << 4) | bt));
this.EmitHuff(index, (int)((uint)(runLength << 4) | bt), ref emitBufferBase);
if (bt > 0)
{
this.Emit((uint)b & (uint)((1 << ((int)bt)) - 1), bt);
this.Emit((uint)b & (uint)((1 << ((int)bt)) - 1), bt, ref emitBufferBase);
}
}
@ -399,7 +404,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
private void Encode444<TPixel>(Image<TPixel> pixels, CancellationToken cancellationToken)
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
private void Encode444<TPixel>(Image<TPixel> pixels, CancellationToken cancellationToken, ref byte emitBufferBase)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
@ -418,15 +424,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
var pixelConverter = YCbCrForwardConverter<TPixel>.Create();
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
for (int y = 0; y < pixels.Height; y += 8)
{
cancellationToken.ThrowIfCancellationRequested();
var currentRows = new RowOctet<TPixel>(pixelBuffer, y);
currentRows.Update(pixelBuffer, y);
for (int x = 0; x < pixels.Width; x += 8)
{
pixelConverter.Convert(frame, x, y, currentRows);
pixelConverter.Convert(frame, x, y, ref currentRows);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
@ -435,7 +442,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ref temp1,
ref temp2,
ref onStackLuminanceQuantTable,
ref unzig);
ref unzig,
ref emitBufferBase);
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
@ -443,7 +452,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig);
ref unzig,
ref emitBufferBase);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
@ -451,7 +462,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig);
ref unzig,
ref emitBufferBase);
}
}
}
@ -517,9 +529,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="tempDest2">Temporal block 2</param>
/// <param name="quant">Quantization table</param>
/// <param name="unZig">The 8x8 Unzig block.</param>
/// <returns>
/// The <see cref="int"/>
/// </returns>
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
/// <returns>The <see cref="int"/>.</returns>
private int WriteBlock(
QuantIndex index,
int prevDC,
@ -527,7 +538,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ref Block8x8F tempDest1,
ref Block8x8F tempDest2,
ref Block8x8F quant,
ref ZigZag unZig)
ref ZigZag unZig,
ref byte emitBufferBase)
{
FastFloatingPointDCT.TransformFDCT(ref src, ref tempDest1, ref tempDest2);
@ -536,7 +548,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int dc = (int)tempDest2[0];
// Emit the DC delta.
this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC);
this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC, ref emitBufferBase);
// Emit the AC components.
var h = (HuffIndex)((2 * (int)index) + 1);
@ -554,18 +566,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
while (runLength > 15)
{
this.EmitHuff(h, 0xf0);
this.EmitHuff(h, 0xf0, ref emitBufferBase);
runLength -= 16;
}
this.EmitHuffRLE(h, runLength, ac);
this.EmitHuffRLE(h, runLength, ac, ref emitBufferBase);
runLength = 0;
}
}
if (runLength > 0)
{
this.EmitHuff(h, 0x00);
this.EmitHuff(h, 0x00, ref emitBufferBase);
}
return dc;
@ -747,9 +759,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <param name="app1Length">The length of the data the app1 marker contains.</param>
private void WriteApp1Header(int app1Length)
{
this.WriteAppHeader(app1Length, JpegConstants.Markers.APP1);
}
=> this.WriteAppHeader(app1Length, JpegConstants.Markers.APP1);
/// <summary>
/// Writes a AppX header.
@ -953,19 +963,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
// TODO: We should allow grayscale writing.
this.outputStream.Write(SosHeaderYCbCr);
ref byte emitBufferBase = ref MemoryMarshal.GetReference<byte>(this.emitBuffer);
switch (this.subsample)
{
case JpegSubsample.Ratio444:
this.Encode444(image, cancellationToken);
this.Encode444(image, cancellationToken, ref emitBufferBase);
break;
case JpegSubsample.Ratio420:
this.Encode420(image, cancellationToken);
this.Encode420(image, cancellationToken, ref emitBufferBase);
break;
}
// Pad the last byte with 1's.
this.Emit(0x7f, 7);
this.Emit(0x7f, 7, ref emitBufferBase);
}
/// <summary>
@ -975,7 +985,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
private void Encode420<TPixel>(Image<TPixel> pixels, CancellationToken cancellationToken)
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
private void Encode420<TPixel>(Image<TPixel> pixels, CancellationToken cancellationToken, ref byte emitBufferBase)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
@ -997,6 +1008,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
for (int y = 0; y < pixels.Height; y += 16)
{
@ -1008,10 +1020,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int xOff = (i & 1) * 8;
int yOff = (i & 2) * 4;
// TODO: Try pushing this to the outer loop!
var currentRows = new RowOctet<TPixel>(pixelBuffer, y + yOff);
pixelConverter.Convert(frame, x + xOff, y + yOff, currentRows);
currentRows.Update(pixelBuffer, y + yOff);
pixelConverter.Convert(frame, x + xOff, y + yOff, ref currentRows);
cb[i] = pixelConverter.Cb;
cr[i] = pixelConverter.Cr;
@ -1023,7 +1033,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ref temp1,
ref temp2,
ref onStackLuminanceQuantTable,
ref unzig);
ref unzig,
ref emitBufferBase);
}
Block8x8F.Scale16X16To8X8(ref b, cb);
@ -1034,7 +1045,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig);
ref unzig,
ref emitBufferBase);
Block8x8F.Scale16X16To8X8(ref b, cr);
prevDCCr = this.WriteBlock(
@ -1044,7 +1056,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig);
ref unzig,
ref emitBufferBase);
}
}
}

5
tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Scale16X16To8X8.cs

@ -1,3 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -15,7 +18,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components
{
var random = new Random();
float[] f = new float[8*8];
float[] f = new float[8 * 8];
for (int i = 0; i < f.Length; i++)
{
f[i] = (float)random.NextDouble();

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

@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
if (this.bmpStream == null)
{
const string TestImage = TestImages.Bmp.Car;
const string TestImage = TestImages.Bmp.NegHeight;
this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
this.bmpCore = Image.Load<Rgba32>(this.bmpStream);
this.bmpStream.Position = 0;

11
tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs

@ -42,8 +42,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (Image<TPixel> s = provider.GetImage())
{
var d = default(GenericBlock8x8<TPixel>);
var rowOctet = new RowOctet<TPixel>(s.GetRootFramePixelBuffer(), 0);
d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 0, 0, rowOctet);
RowOctet<TPixel> rowOctet = default;
rowOctet.Update(s.GetRootFramePixelBuffer(), 0);
d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 0, 0, ref rowOctet);
TPixel a = s.Frames.RootFrame[0, 0];
TPixel b = d[0, 0];
@ -67,8 +68,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (Image<TPixel> s = provider.GetImage())
{
var d = default(GenericBlock8x8<TPixel>);
var rowOctet = new RowOctet<TPixel>(s.GetRootFramePixelBuffer(), 7);
d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 6, 7, rowOctet);
RowOctet<TPixel> rowOctet = default;
rowOctet.Update(s.GetRootFramePixelBuffer(), 7);
d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 6, 7, ref rowOctet);
Assert.Equal(s[6, 7], d[0, 0]);
Assert.Equal(s[6, 8], d[0, 1]);

2
tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs

@ -375,7 +375,7 @@ namespace SixLabors.ImageSharp.Tests
var array = new Rgba32[size];
var memory = new Memory<Rgba32>(array);
Image.WrapMemory(memory, height, width);
Image.WrapMemory(memory, height, width);
}
private class TestMemoryOwner<T> : IMemoryOwner<T>

2
tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs

@ -263,7 +263,7 @@ namespace SixLabors.ImageSharp.Tests
private static string GetNetCoreVersion()
{
Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
string[] assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
string[] assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
{

Loading…
Cancel
Save