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

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.
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}"/>.
/// </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;
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;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RowOctet(Buffer2D<T> 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<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 +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<T> ThrowIndexOutOfRangeException()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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();
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,
@ -997,6 +998,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 +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<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;

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;

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

@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
var d = default(GenericBlock8x8<TPixel>);
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 b = d[0, 0];
@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
var d = default(GenericBlock8x8<TPixel>);
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, 8], d[0, 1]);

Loading…
Cancel
Save