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