Browse Source

Use less expensive update for RowOctet

pull/1554/head
James Jackson-South 5 years ago
parent
commit
a58311a4aa
  1. 6
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  2. 2
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
  3. 79
      src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
  4. 12
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  5. 2
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
  6. 4
      tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs

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

79
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,18 +12,19 @@ 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;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RowOctet(Buffer2D<T> buffer, int startY) public RowOctet(Buffer2D<T> buffer, int startY)
{ {
int y = startY; int y = startY;
@ -38,13 +39,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
this.row7 = 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 +56,56 @@ 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(); 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<T> ThrowIndexOutOfRangeException()
=> throw new IndexOutOfRangeException();
} }
} }

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

@ -418,15 +418,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,
@ -997,6 +998,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 +1010,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;

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;

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

@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ {
var d = default(GenericBlock8x8<TPixel>); var d = default(GenericBlock8x8<TPixel>);
var rowOctet = new RowOctet<TPixel>(s.GetRootFramePixelBuffer(), 0); var rowOctet = new RowOctet<TPixel>(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 a = s.Frames.RootFrame[0, 0];
TPixel b = d[0, 0]; TPixel b = d[0, 0];
@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ {
var d = default(GenericBlock8x8<TPixel>); var d = default(GenericBlock8x8<TPixel>);
var rowOctet = new RowOctet<TPixel>(s.GetRootFramePixelBuffer(), 7); var rowOctet = new RowOctet<TPixel>(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, 7], d[0, 0]);
Assert.Equal(s[6, 8], d[0, 1]); Assert.Equal(s[6, 8], d[0, 1]);

Loading…
Cancel
Save