diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
index 8fcc63c6a..81e64b277 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
@@ -59,9 +59,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
///
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (, , )
///
- public void Convert(ImageFrame frame, int x, int y, in RowOctet currentRows)
+ public void Convert(ImageFrame frame, int x, int y, ref RowOctet currentRows)
{
- this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, currentRows);
+ this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, ref currentRows);
Span rgbSpan = this.rgbBlock.AsSpanUnsafe();
PixelOperations.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);
}
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
index 92ba1afd3..42c01d770 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
+++ b/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.
///
- public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY, in RowOctet currentRows)
+ public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY, ref RowOctet currentRows)
{
int width = Math.Min(8, source.Width - sourceX);
int height = Math.Min(8, source.Height - sourceY);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
index ae10bfba8..930d8b18c 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
+++ b/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,18 +12,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// Cache 8 pixel rows on the stack, which may originate from different buffers of a .
///
[StructLayout(LayoutKind.Sequential)]
- internal readonly ref struct RowOctet
+ internal ref struct RowOctet
where T : struct
{
- private readonly Span row0;
- private readonly Span row1;
- private readonly Span row2;
- private readonly Span row3;
- private readonly Span row4;
- private readonly Span row5;
- private readonly Span row6;
- private readonly Span row7;
+ private Span row0;
+ private Span row1;
+ private Span row2;
+ private Span row3;
+ private Span row4;
+ private Span row5;
+ private Span row6;
+ private Span row7;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public RowOctet(Buffer2D buffer, int startY)
{
int y = startY;
@@ -38,13 +39,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
this.row7 = y < height ? buffer.GetRowSpan(y) : default;
}
+ // No unsafe tricks, since Span can't be used as a generic argument
public Span this[int y]
{
- [MethodImpl(InliningOptions.ShortMethod)]
- get
- {
- // No unsafe tricks, since Span 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 +56,56 @@ 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 ThrowIndexOutOfRangeException()
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Update(Buffer2D buffer, int startY)
{
- throw new IndexOutOfRangeException();
+ int y = startY;
+ int height = buffer.Height;
+
+ // We don't actually have to assign values outside of the
+ // frame pixel buffer since they are never requested.
+ for (int i = 0; i < 8 && y < height; i++)
+ {
+ this[i] = buffer.GetRowSpan(y++);
+ }
}
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static Span ThrowIndexOutOfRangeException()
+ => throw new IndexOutOfRangeException();
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index 36766d05f..31f2cfc3f 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -418,15 +418,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
var pixelConverter = YCbCrForwardConverter.Create();
ImageFrame frame = pixels.Frames.RootFrame;
Buffer2D pixelBuffer = frame.PixelBuffer;
+ RowOctet currentRows = default;
for (int y = 0; y < pixels.Height; y += 8)
{
cancellationToken.ThrowIfCancellationRequested();
- var currentRows = new RowOctet(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,
@@ -997,6 +998,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
ImageFrame frame = pixels.Frames.RootFrame;
Buffer2D pixelBuffer = frame.PixelBuffer;
+ RowOctet currentRows = default;
for (int y = 0; y < pixels.Height; y += 16)
{
@@ -1008,10 +1010,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(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;
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
index bfbd150fe..81a5604f1 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
+++ b/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(this.bmpStream);
this.bmpStream.Position = 0;
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs
index c366e4f56..60449ba78 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs
@@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
var d = default(GenericBlock8x8);
var rowOctet = new RowOctet(s.GetRootFramePixelBuffer(), 0);
- d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 0, 0, rowOctet);
+ d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 0, 0, ref rowOctet);
TPixel a = s.Frames.RootFrame[0, 0];
TPixel b = d[0, 0];
@@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
var d = default(GenericBlock8x8);
var rowOctet = new RowOctet(s.GetRootFramePixelBuffer(), 7);
- d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 6, 7, rowOctet);
+ 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]);