Browse Source

Merge branch 'master' into webp

pull/1552/head
Brian Popow 5 years ago
committed by GitHub
parent
commit
e32c3c08e1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      README.md
  2. 3
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  3. 106
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  4. 6
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  5. 2
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
  6. 93
      src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
  7. 93
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  8. 2
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
  9. 5
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Scale16X16To8X8.cs
  10. 13
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
  11. 9
      tests/ImageSharp.Tests.ProfilingSandbox/Program.cs
  12. 12
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  13. 11
      tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs
  14. 2
      tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
  15. 21
      tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs
  16. 15
      tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs
  17. 1
      tests/ImageSharp.Tests/TestImages.cs
  18. 2
      tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs
  19. 6
      tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs
  20. 2
      tests/Images/External
  21. 3
      tests/Images/Input/Gif/issues/issue1530.gif

4
README.md

@ -70,7 +70,7 @@ To clone ImageSharp locally, click the "Clone in [YOUR_OS]" button above or run
git clone https://github.com/SixLabors/ImageSharp
```
If working with Windows please ensure that you have enabled log file paths in git (run as Administrator).
If working with Windows please ensure that you have enabled long file paths in git (run as Administrator).
```bash
git config --system core.longpaths true
@ -130,4 +130,4 @@ Become a bronze sponsor with a monthly donation of $100 and get your logo (small
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/7/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/7/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/8/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/8/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/9/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/9/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/10/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/10/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/10/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/10/avatar.svg?avatarHeight=96"></a>

3
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -535,7 +535,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
return;
}
Buffer2DRegion<TPixel> pixelRegion = frame.PixelBuffer.GetRegion(this.restoreArea.Value);
var interest = Rectangle.Intersect(frame.Bounds(), this.restoreArea.Value);
Buffer2DRegion<TPixel> pixelRegion = frame.PixelBuffer.GetRegion(interest);
pixelRegion.Clear();
this.restoreArea = null;

106
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>
@ -61,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <returns>The float value at the specified index</returns>
public float this[int idx]
{
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
GuardBlockIndex(idx);
@ -69,7 +67,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return Unsafe.Add(ref selfRef, idx);
}
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
GuardBlockIndex(idx);
@ -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(InliningOptions.ShortMethod)]
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();
}
}

93
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(InliningOptions.ShortMethod)]
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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EmitHuff(HuffIndex index, int value)
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
[MethodImpl(InliningOptions.ShortMethod)]
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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EmitHuffRLE(HuffIndex index, int runLength, int value)
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
[MethodImpl(InliningOptions.ShortMethod)]
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);
}
}
}

2
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs

@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public Vector4 ConvolveCore(ref Vector4 rowStartRef)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Fma.IsSupported)
if (Avx2.IsSupported && Fma.IsSupported)
{
float* bufferStart = this.bufferPtr;
float* bufferEnd = bufferStart + (this.Length & ~3);

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();

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

@ -16,17 +16,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
private Stream bmpStream;
private SDImage bmpDrawing;
private Image<Rgba32> bmpCore;
private MemoryStream destinationStream;
[GlobalSetup]
public void ReadImages()
{
if (this.bmpStream == null)
{
const string TestImage = TestImages.Bmp.Car;
const string TestImage = TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr;
this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
this.bmpCore = Image.Load<Rgba32>(this.bmpStream);
this.bmpCore.Metadata.ExifProfile = null;
this.bmpStream.Position = 0;
this.bmpDrawing = SDImage.FromStream(this.bmpStream);
this.destinationStream = new MemoryStream();
}
}
@ -42,15 +45,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true, Description = "System.Drawing Jpeg")]
public void JpegSystemDrawing()
{
using var stream = new MemoryStream();
this.bmpDrawing.Save(stream, ImageFormat.Jpeg);
this.bmpDrawing.Save(this.destinationStream, ImageFormat.Jpeg);
this.destinationStream.Seek(0, SeekOrigin.Begin);
}
[Benchmark(Description = "ImageSharp Jpeg")]
public void JpegCore()
{
using var stream = new MemoryStream();
this.bmpCore.SaveAsJpeg(stream);
this.bmpCore.SaveAsJpeg(this.destinationStream);
this.destinationStream.Seek(0, SeekOrigin.Begin);
}
}
}

9
tests/ImageSharp.Tests.ProfilingSandbox/Program.cs

@ -31,14 +31,21 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
/// </param>
public static void Main(string[] args)
{
RunJpegEncoderProfilingTests();
// RunJpegColorProfilingTests();
RunDecodeJpegProfilingTests();
// RunDecodeJpegProfilingTests();
// RunToVector4ProfilingTest();
// RunResizeProfilingTest();
Console.ReadLine();
}
private static void RunJpegEncoderProfilingTests()
{
var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput());
benchmarks.EncodeJpeg_SingleMidSize();
}
private static void RunJpegColorProfilingTests()
{
new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(false);

12
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -171,6 +171,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
}
}
// https://github.com/SixLabors/ImageSharp/issues/1503
[Theory]
[WithFile(TestImages.Gif.Issues.Issue1530, PixelTypes.Rgba32)]
public void Issue1530_BadDescriptorDimensions<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
image.DebugSaveMultiFrame(provider);
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact);
}
// https://github.com/SixLabors/ImageSharp/issues/405
[Theory]
[WithFile(TestImages.Gif.Issues.BadAppExtLength, PixelTypes.Rgba32)]
@ -181,6 +192,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
using (Image<TPixel> image = provider.GetImage())
{
image.DebugSave(provider);
image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider);
}
}

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>

21
tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs

@ -1,8 +1,10 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Transforms
@ -85,5 +87,24 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
Assert.Equal(compand, resizeOptions.Compand);
Assert.Equal(mode, resizeOptions.Mode);
}
#if SUPPORTS_RUNTIME_INTRINSICS
[Fact]
public void HwIntrinsics_Resize()
{
static void RunTest()
{
using var image = new Image<Rgba32>(50, 50);
image.Mutate(img => img.Resize(25, 25));
Assert.Equal(25, image.Width);
Assert.Equal(25, image.Height);
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableFMA);
}
#endif
}
}

15
tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs

@ -75,6 +75,21 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks
#pragma warning restore SA1515 // Single-line comment should be preceded by blank line
}
[Fact(Skip = ProfilingSetup.SkipProfilingTests)]
public void EncodeJpeg_SingleMidSize()
{
string path = TestFile.GetInputFileFullPath(TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr);
using var image = Image.Load(path);
image.Metadata.ExifProfile = null;
using var ms = new MemoryStream();
for (int i = 0; i < 30; i++)
{
image.SaveAsJpeg(ms);
ms.Seek(0, SeekOrigin.Begin);
}
}
// Benchmark, enable manually!
[Theory(Skip = ProfilingSetup.SkipProfilingTests)]
[InlineData(1, 75, JpegSubsample.Ratio420)]

1
tests/ImageSharp.Tests/TestImages.cs

@ -416,6 +416,7 @@ namespace SixLabors.ImageSharp.Tests
public const string BadAppExtLength_2 = "Gif/issues/issue405_badappextlength252-2.gif";
public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.gif";
public const string Issue1505 = "Gif/issues/issue1505_argumentoutofrange.png";
public const string Issue1530 = "Gif/issues/issue1530.gif";
}
public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 };

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

6
tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs

@ -25,9 +25,9 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests
[Theory]
[MemberData(nameof(Intrinsics))]
public void ToFeatureCollectionReturnsExpectedResult(HwIntrinsics expectedItrinsics, string[] expectedValues)
public void ToFeatureCollectionReturnsExpectedResult(HwIntrinsics expectedIntrinsics, string[] expectedValues)
{
Dictionary<HwIntrinsics, string> features = expectedItrinsics.ToFeatureKeyValueCollection();
Dictionary<HwIntrinsics, string> features = expectedIntrinsics.ToFeatureKeyValueCollection();
HwIntrinsics[] keys = features.Keys.ToArray();
HwIntrinsics actualIntrinsics = keys[0];
@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests
actualIntrinsics |= keys[i];
}
Assert.Equal(expectedItrinsics, actualIntrinsics);
Assert.Equal(expectedIntrinsics, actualIntrinsics);
IEnumerable<string> actualValues = features.Select(x => x.Value);
Assert.Equal(expectedValues, actualValues);

2
tests/Images/External

@ -1 +1 @@
Subproject commit 346070e5ba538f1a3bbafc0ea7367404c5f8c9ab
Subproject commit 1df65162448996332449387a46da942e181043c8

3
tests/Images/Input/Gif/issues/issue1530.gif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:08b33cab34de2622a7d22edff25fe479a5018c2e0fbc65870b2f21ea7901fc61
size 1844876
Loading…
Cancel
Save