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> /// <summary>
/// Represents a Jpeg block with <see cref="float"/> coefficients. /// Represents a Jpeg block with <see cref="float"/> coefficients.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)]
internal partial struct Block8x8F : IEquatable<Block8x8F> internal partial struct Block8x8F : IEquatable<Block8x8F>
{ {
/// <summary> /// <summary>
@ -51,9 +52,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public Vector4 V7R; public Vector4 V7R;
#pragma warning restore SA1600 // ElementsMustBeDocumented #pragma warning restore SA1600 // ElementsMustBeDocumented
private static readonly Vector4 NegativeOne = new Vector4(-1);
private static readonly Vector4 Offset = new Vector4(.5F);
/// <summary> /// <summary>
/// Get/Set scalar elements at a given index /// Get/Set scalar elements at a given index
/// </summary> /// </summary>
@ -155,10 +153,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary> /// </summary>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Clear() public void Clear()
{ => this = default; // The cheapest way to do this in C#:
// The cheapest way to do this in C#:
this = default;
}
/// <summary> /// <summary>
/// Load raw 32bit floating point data from source. /// Load raw 32bit floating point data from source.
@ -180,9 +175,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <param name="source">Source</param> /// <param name="source">Source</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static unsafe void LoadFrom(Block8x8F* blockPtr, Span<float> source) public static unsafe void LoadFrom(Block8x8F* blockPtr, Span<float> source)
{ => blockPtr->LoadFrom(source);
blockPtr->LoadFrom(source);
}
/// <summary> /// <summary>
/// Load raw 32bit floating point data from source /// Load raw 32bit floating point data from source
@ -236,9 +229,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <param name="dest">The destination.</param> /// <param name="dest">The destination.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span<float> dest) public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span<float> dest)
{ => blockPtr->ScaledCopyTo(dest);
blockPtr->ScaledCopyTo(dest);
}
/// <summary> /// <summary>
/// Copy raw 32bit floating point data to dest /// 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="blockPtr">The block pointer.</param>
/// <param name="qtPtr">The qt pointer.</param> /// <param name="qtPtr">The qt pointer.</param>
/// <param name="unzigPtr">Unzig pointer</param> /// <param name="unzigPtr">Unzig pointer</param>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, byte* unzigPtr) public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, byte* unzigPtr)
{ {
float* b = (float*)blockPtr; float* b = (float*)blockPtr;
@ -556,22 +546,60 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static void DivideRoundAll(ref Block8x8F a, ref Block8x8F b) private static void DivideRoundAll(ref Block8x8F a, ref Block8x8F b)
{ {
a.V0L = DivideRound(a.V0L, b.V0L); #if SUPPORTS_RUNTIME_INTRINSICS
a.V0R = DivideRound(a.V0R, b.V0R); if (Avx.IsSupported)
a.V1L = DivideRound(a.V1L, b.V1L); {
a.V1R = DivideRound(a.V1R, b.V1R); var vnegOne = Vector256.Create(-1f);
a.V2L = DivideRound(a.V2L, b.V2L); var vadd = Vector256.Create(.5F);
a.V2R = DivideRound(a.V2R, b.V2R); var vone = Vector256.Create(1f);
a.V3L = DivideRound(a.V3L, b.V3L);
a.V3R = DivideRound(a.V3R, b.V3R); ref Vector256<float> aBase = ref Unsafe.AsRef(Unsafe.As<Vector4, Vector256<float>>(ref a.V0L));
a.V4L = DivideRound(a.V4L, b.V4L); ref Vector256<float> bBase = ref Unsafe.AsRef(Unsafe.As<Vector4, Vector256<float>>(ref b.V0L));
a.V4R = DivideRound(a.V4R, b.V4R); ref Vector256<float> aEnd = ref Unsafe.Add(ref aBase, 8);
a.V5L = DivideRound(a.V5L, b.V5L);
a.V5R = DivideRound(a.V5R, b.V5R); do
a.V6L = DivideRound(a.V6L, b.V6L); {
a.V6R = DivideRound(a.V6R, b.V6R); Vector256<float> voff = Avx.Multiply(Avx.Min(Avx.Max(vnegOne, aBase), vone), vadd);
a.V7L = DivideRound(a.V7L, b.V7L); Unsafe.Add(ref aBase, 0) = Avx.Add(Avx.Divide(aBase, bBase), voff);
a.V7R = DivideRound(a.V7R, b.V7R);
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) public void RoundInto(ref Block8x8 dest)
@ -673,8 +701,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(Block8x8F other) public bool Equals(Block8x8F other)
{ => this.V0L == other.V0L
return this.V0L == other.V0L
&& this.V0R == other.V0R && this.V0R == other.V0R
&& this.V1L == other.V1L && this.V1L == other.V1L
&& this.V1R == other.V1R && this.V1R == other.V1R
@ -690,7 +717,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
&& this.V6R == other.V6R && this.V6R == other.V6R
&& this.V7L == other.V7L && this.V7L == other.V7L
&& this.V7R == other.V7R; && this.V7R == other.V7R;
}
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
@ -718,16 +744,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return row.FastRound(); 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")] [Conditional("DEBUG")]
private static void GuardBlockIndex(int idx) 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> /// <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"/>) /// 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> /// </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(); Span<Rgb24> rgbSpan = this.rgbBlock.AsSpanUnsafe();
PixelOperations<TPixel>.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan); PixelOperations<TPixel>.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan);
@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
} }
else 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. /// 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. /// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image.
/// </summary> /// </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 width = Math.Min(8, source.Width - sourceX);
int height = Math.Min(8, source.Height - sourceY); 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. // Licensed under the Apache License, Version 2.0.
using System; 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}"/>. /// Cache 8 pixel rows on the stack, which may originate from different buffers of a <see cref="MemoryGroup{T}"/>.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal readonly ref struct RowOctet<T> internal ref struct RowOctet<T>
where T : struct where T : struct
{ {
private readonly Span<T> row0; private Span<T> row0;
private readonly Span<T> row1; private Span<T> row1;
private readonly Span<T> row2; private Span<T> row2;
private readonly Span<T> row3; private Span<T> row3;
private readonly Span<T> row4; private Span<T> row4;
private readonly Span<T> row5; private Span<T> row5;
private readonly Span<T> row6; private Span<T> row6;
private readonly Span<T> row7; private 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;
}
// No unsafe tricks, since Span<T> can't be used as a generic argument
public Span<T> this[int y] public Span<T> this[int y]
{ {
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get =>
{ y switch
// No unsafe tricks, since Span<T> can't be used as a generic argument
return y switch
{ {
0 => this.row0, 0 => this.row0,
1 => this.row1, 1 => this.row1,
@ -56,13 +41,57 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
7 => this.row7, 7 => this.row7,
_ => ThrowIndexOutOfRangeException() _ => 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Span<T> ThrowIndexOutOfRangeException() 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.IO;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -313,7 +314,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
/// <param name="bits">The packed bits.</param> /// <param name="bits">The packed bits.</param>
/// <param name="count">The number of 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; count += this.bitCount;
bits <<= (int)(32 - count); bits <<= (int)(32 - count);
@ -327,10 +330,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
while (count >= 8) while (count >= 8)
{ {
byte b = (byte)(bits >> 24); byte b = (byte)(bits >> 24);
this.emitBuffer[len++] = b; Unsafe.Add(ref emitBufferBase, len++) = b;
if (b == 0xff) if (b == byte.MaxValue)
{ {
this.emitBuffer[len++] = 0x00; Unsafe.Add(ref emitBufferBase, len++) = byte.MinValue;
} }
bits <<= 8; bits <<= 8;
@ -352,11 +355,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
/// <param name="index">The index of the Huffman encoder</param> /// <param name="index">The index of the Huffman encoder</param>
/// <param name="value">The value to encode.</param> /// <param name="value">The value to encode.</param>
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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]; 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> /// <summary>
@ -365,8 +369,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="index">The index of the Huffman encoder</param> /// <param name="index">The index of the Huffman encoder</param>
/// <param name="runLength">The number of copies to encode.</param> /// <param name="runLength">The number of copies to encode.</param>
/// <param name="value">The value to encode.</param> /// <param name="value">The value to encode.</param>
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 a = value;
int b = value; int b = value;
@ -386,10 +391,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
bt = 8 + (uint)BitCountLut[a >> 8]; 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) 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> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param> /// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</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> where TPixel : unmanaged, IPixel<TPixel>
{ {
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // 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(); var pixelConverter = YCbCrForwardConverter<TPixel>.Create();
ImageFrame<TPixel> frame = pixels.Frames.RootFrame; ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer; Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
for (int y = 0; y < pixels.Height; y += 8) for (int y = 0; y < pixels.Height; y += 8)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
var currentRows = new RowOctet<TPixel>(pixelBuffer, y); currentRows.Update(pixelBuffer, y);
for (int x = 0; x < pixels.Width; x += 8) 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( prevDCY = this.WriteBlock(
QuantIndex.Luminance, QuantIndex.Luminance,
@ -435,7 +442,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ref temp1, ref temp1,
ref temp2, ref temp2,
ref onStackLuminanceQuantTable, ref onStackLuminanceQuantTable,
ref unzig); ref unzig,
ref emitBufferBase);
prevDCCb = this.WriteBlock( prevDCCb = this.WriteBlock(
QuantIndex.Chrominance, QuantIndex.Chrominance,
prevDCCb, prevDCCb,
@ -443,7 +452,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ref temp1, ref temp1,
ref temp2, ref temp2,
ref onStackChrominanceQuantTable, ref onStackChrominanceQuantTable,
ref unzig); ref unzig,
ref emitBufferBase);
prevDCCr = this.WriteBlock( prevDCCr = this.WriteBlock(
QuantIndex.Chrominance, QuantIndex.Chrominance,
prevDCCr, prevDCCr,
@ -451,7 +462,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ref temp1, ref temp1,
ref temp2, ref temp2,
ref onStackChrominanceQuantTable, 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="tempDest2">Temporal block 2</param>
/// <param name="quant">Quantization table</param> /// <param name="quant">Quantization table</param>
/// <param name="unZig">The 8x8 Unzig block.</param> /// <param name="unZig">The 8x8 Unzig block.</param>
/// <returns> /// <param name="emitBufferBase">The reference to the emit buffer.</param>
/// The <see cref="int"/> /// <returns>The <see cref="int"/>.</returns>
/// </returns>
private int WriteBlock( private int WriteBlock(
QuantIndex index, QuantIndex index,
int prevDC, int prevDC,
@ -527,7 +538,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ref Block8x8F tempDest1, ref Block8x8F tempDest1,
ref Block8x8F tempDest2, ref Block8x8F tempDest2,
ref Block8x8F quant, ref Block8x8F quant,
ref ZigZag unZig) ref ZigZag unZig,
ref byte emitBufferBase)
{ {
FastFloatingPointDCT.TransformFDCT(ref src, ref tempDest1, ref tempDest2); FastFloatingPointDCT.TransformFDCT(ref src, ref tempDest1, ref tempDest2);
@ -536,7 +548,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int dc = (int)tempDest2[0]; int dc = (int)tempDest2[0];
// Emit the DC delta. // 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. // Emit the AC components.
var h = (HuffIndex)((2 * (int)index) + 1); var h = (HuffIndex)((2 * (int)index) + 1);
@ -554,18 +566,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
while (runLength > 15) while (runLength > 15)
{ {
this.EmitHuff(h, 0xf0); this.EmitHuff(h, 0xf0, ref emitBufferBase);
runLength -= 16; runLength -= 16;
} }
this.EmitHuffRLE(h, runLength, ac); this.EmitHuffRLE(h, runLength, ac, ref emitBufferBase);
runLength = 0; runLength = 0;
} }
} }
if (runLength > 0) if (runLength > 0)
{ {
this.EmitHuff(h, 0x00); this.EmitHuff(h, 0x00, ref emitBufferBase);
} }
return dc; return dc;
@ -747,9 +759,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
/// <param name="app1Length">The length of the data the app1 marker contains.</param> /// <param name="app1Length">The length of the data the app1 marker contains.</param>
private void WriteApp1Header(int app1Length) private void WriteApp1Header(int app1Length)
{ => this.WriteAppHeader(app1Length, JpegConstants.Markers.APP1);
this.WriteAppHeader(app1Length, JpegConstants.Markers.APP1);
}
/// <summary> /// <summary>
/// Writes a AppX header. /// 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: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
// TODO: We should allow grayscale writing. // TODO: We should allow grayscale writing.
this.outputStream.Write(SosHeaderYCbCr); this.outputStream.Write(SosHeaderYCbCr);
ref byte emitBufferBase = ref MemoryMarshal.GetReference<byte>(this.emitBuffer);
switch (this.subsample) switch (this.subsample)
{ {
case JpegSubsample.Ratio444: case JpegSubsample.Ratio444:
this.Encode444(image, cancellationToken); this.Encode444(image, cancellationToken, ref emitBufferBase);
break; break;
case JpegSubsample.Ratio420: case JpegSubsample.Ratio420:
this.Encode420(image, cancellationToken); this.Encode420(image, cancellationToken, ref emitBufferBase);
break; break;
} }
// Pad the last byte with 1's. // Pad the last byte with 1's.
this.Emit(0x7f, 7); this.Emit(0x7f, 7, ref emitBufferBase);
} }
/// <summary> /// <summary>
@ -975,7 +985,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param> /// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</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> where TPixel : unmanaged, IPixel<TPixel>
{ {
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // 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; int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
ImageFrame<TPixel> frame = pixels.Frames.RootFrame; ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer; Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
for (int y = 0; y < pixels.Height; y += 16) for (int y = 0; y < pixels.Height; y += 16)
{ {
@ -1008,10 +1020,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int xOff = (i & 1) * 8; int xOff = (i & 1) * 8;
int yOff = (i & 2) * 4; int yOff = (i & 2) * 4;
// TODO: Try pushing this to the outer loop! currentRows.Update(pixelBuffer, y + yOff);
var currentRows = new RowOctet<TPixel>(pixelBuffer, y + yOff); pixelConverter.Convert(frame, x + xOff, y + yOff, ref currentRows);
pixelConverter.Convert(frame, x + xOff, y + yOff, currentRows);
cb[i] = pixelConverter.Cb; cb[i] = pixelConverter.Cb;
cr[i] = pixelConverter.Cr; cr[i] = pixelConverter.Cr;
@ -1023,7 +1033,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ref temp1, ref temp1,
ref temp2, ref temp2,
ref onStackLuminanceQuantTable, ref onStackLuminanceQuantTable,
ref unzig); ref unzig,
ref emitBufferBase);
} }
Block8x8F.Scale16X16To8X8(ref b, cb); Block8x8F.Scale16X16To8X8(ref b, cb);
@ -1034,7 +1045,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ref temp1, ref temp1,
ref temp2, ref temp2,
ref onStackChrominanceQuantTable, ref onStackChrominanceQuantTable,
ref unzig); ref unzig,
ref emitBufferBase);
Block8x8F.Scale16X16To8X8(ref b, cr); Block8x8F.Scale16X16To8X8(ref b, cr);
prevDCCr = this.WriteBlock( prevDCCr = this.WriteBlock(
@ -1044,7 +1056,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ref temp1, ref temp1,
ref temp2, ref temp2,
ref onStackChrominanceQuantTable, 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 System;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -15,7 +18,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components
{ {
var random = new Random(); var random = new Random();
float[] f = new float[8*8]; float[] f = new float[8 * 8];
for (int i = 0; i < f.Length; i++) for (int i = 0; i < f.Length; i++)
{ {
f[i] = (float)random.NextDouble(); 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) 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.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
this.bmpCore = Image.Load<Rgba32>(this.bmpStream); this.bmpCore = Image.Load<Rgba32>(this.bmpStream);
this.bmpStream.Position = 0; 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()) using (Image<TPixel> s = provider.GetImage())
{ {
var d = default(GenericBlock8x8<TPixel>); var d = default(GenericBlock8x8<TPixel>);
var rowOctet = new RowOctet<TPixel>(s.GetRootFramePixelBuffer(), 0); RowOctet<TPixel> rowOctet = default;
d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 0, 0, rowOctet); rowOctet.Update(s.GetRootFramePixelBuffer(), 0);
d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 0, 0, ref rowOctet);
TPixel a = s.Frames.RootFrame[0, 0]; TPixel a = s.Frames.RootFrame[0, 0];
TPixel b = d[0, 0]; TPixel b = d[0, 0];
@ -67,8 +68,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (Image<TPixel> s = provider.GetImage()) using (Image<TPixel> s = provider.GetImage())
{ {
var d = default(GenericBlock8x8<TPixel>); var d = default(GenericBlock8x8<TPixel>);
var rowOctet = new RowOctet<TPixel>(s.GetRootFramePixelBuffer(), 7); RowOctet<TPixel> rowOctet = default;
d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 6, 7, rowOctet); 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, 7], d[0, 0]);
Assert.Equal(s[6, 8], d[0, 1]); 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 array = new Rgba32[size];
var memory = new Memory<Rgba32>(array); var memory = new Memory<Rgba32>(array);
Image.WrapMemory(memory, height, width); Image.WrapMemory(memory, height, width);
} }
private class TestMemoryOwner<T> : IMemoryOwner<T> 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() private static string GetNetCoreVersion()
{ {
Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; 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"); int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
{ {

Loading…
Cancel
Save