diff --git a/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.cs b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.cs
new file mode 100644
index 0000000000..1bb37a7d32
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+//
+namespace SixLabors.ImageSharp.Formats.Jpeg.Common
+{
+ internal unsafe partial struct GenericBlock8x8
+ {
+ #pragma warning disable 169
+
+ // It's not allowed use fix-sized buffers with generics, need to place all the fields manually:
+ private T _y0_x0, _y0_x1, _y0_x2, _y0_x3, _y0_x4, _y0_x5, _y0_x6, _y0_x7;
+ private T _y1_x0, _y1_x1, _y1_x2, _y1_x3, _y1_x4, _y1_x5, _y1_x6, _y1_x7;
+ private T _y2_x0, _y2_x1, _y2_x2, _y2_x3, _y2_x4, _y2_x5, _y2_x6, _y2_x7;
+ private T _y3_x0, _y3_x1, _y3_x2, _y3_x3, _y3_x4, _y3_x5, _y3_x6, _y3_x7;
+ private T _y4_x0, _y4_x1, _y4_x2, _y4_x3, _y4_x4, _y4_x5, _y4_x6, _y4_x7;
+ private T _y5_x0, _y5_x1, _y5_x2, _y5_x3, _y5_x4, _y5_x5, _y5_x6, _y5_x7;
+ private T _y6_x0, _y6_x1, _y6_x2, _y6_x3, _y6_x4, _y6_x5, _y6_x6, _y6_x7;
+ private T _y7_x0, _y7_x1, _y7_x2, _y7_x3, _y7_x4, _y7_x5, _y7_x6, _y7_x7;
+
+ #pragma warning restore 169
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.tt b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.tt
new file mode 100644
index 0000000000..d9b15b34fa
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.tt
@@ -0,0 +1,43 @@
+<#
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+#>
+<#@ template debug="false" hostspecific="false" language="C#" #>
+<#@ assembly name="System.Core" #>
+<#@ import namespace="System.Linq" #>
+<#@ import namespace="System.Text" #>
+<#@ import namespace="System.Collections.Generic" #>
+<#@ output extension=".cs" #>
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+//
+namespace SixLabors.ImageSharp.Formats.Jpeg.Common
+{
+ internal unsafe partial struct GenericBlock8x8
+ {
+ #pragma warning disable 169
+
+ // It's not allowed use fix-sized buffers with generics, need to place all the fields manually:
+ <#
+ PushIndent(" ");
+ Write(" ");
+ for (int y = 0; y < 8; y++)
+ {
+ Write("private T ");
+ for (int x = 0; x < 8; x++)
+ {
+ Write($"_y{y}_x{x}");
+ if (x < 7) Write(", ");
+ }
+ WriteLine(";");
+ }
+ PopIndent();
+ #>
+
+ #pragma warning restore 169
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.cs
new file mode 100644
index 0000000000..bc5838e9b5
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Common
+{
+ ///
+ /// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data.
+ ///
+ // ReSharper disable once InconsistentNaming
+ internal unsafe partial struct GenericBlock8x8
+ where T : struct
+ {
+ public const int Size = 64;
+
+ public const int SizeInBytes = Size * 3;
+
+ public void LoadAndStretchEdges(IPixelSource source, int sourceX, int sourceY)
+ where TPixel : struct, IPixel
+ {
+ var buffer = source.PixelBuffer as Buffer2D;
+ if (buffer == null)
+ {
+ throw new InvalidOperationException("LoadAndStretchEdges() is only valid for TPixel == T !");
+ }
+
+ this.LoadAndStretchEdges(buffer, sourceX, sourceY);
+ }
+
+ ///
+ /// 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)
+ {
+ int width = Math.Min(8, source.Width - sourceX);
+ int height = Math.Min(8, source.Height - sourceY);
+
+ if (width <= 0 || height <= 0)
+ {
+ return;
+ }
+
+ uint byteWidth = (uint)width * (uint)Unsafe.SizeOf();
+ int remainderXCount = 8 - width;
+
+ ref byte blockStart = ref Unsafe.As, byte>(ref this);
+ ref byte imageStart = ref Unsafe.As(
+ ref Unsafe.Add(ref source.GetRowSpan(sourceY).DangerousGetPinnableReference(), sourceX)
+ );
+
+ int blockRowSizeInBytes = 8 * Unsafe.SizeOf();
+ int imageRowSizeInBytes = source.Width * Unsafe.SizeOf();
+
+ for (int y = 0; y < height; y++)
+ {
+ ref byte s = ref Unsafe.Add(ref imageStart, y * imageRowSizeInBytes);
+ ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes);
+
+ Unsafe.CopyBlock(ref d, ref s, byteWidth);
+
+ ref T last = ref Unsafe.Add(ref Unsafe.As(ref d), width - 1);
+
+ for (int x = 1; x <= remainderXCount; x++)
+ {
+ Unsafe.Add(ref last, x) = last;
+ }
+ }
+
+ int remainderYCount = 8 - height;
+
+ if (remainderYCount == 0)
+ {
+ return;
+ }
+
+ ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * blockRowSizeInBytes);
+
+ for (int y = 1; y <= remainderYCount; y++)
+ {
+ ref byte remStart = ref Unsafe.Add(ref lastRowStart, blockRowSizeInBytes * y);
+ Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)blockRowSizeInBytes);
+ }
+ }
+
+ ///
+ /// ONLY FOR GenericBlock instances living on the stack!
+ ///
+ public Span AsSpanUnsafe() => new Span(Unsafe.AsPointer(ref this), Size);
+
+ ///
+ /// FOR TESTING ONLY!
+ /// Gets or sets a value at the given index
+ ///
+ /// The index
+ /// The value
+ public T this[int idx]
+ {
+ get
+ {
+ ref T selfRef = ref Unsafe.As, T>(ref this);
+ return Unsafe.Add(ref selfRef, idx);
+ }
+
+ set
+ {
+ ref T selfRef = ref Unsafe.As, T>(ref this);
+ Unsafe.Add(ref selfRef, idx) = value;
+ }
+ }
+
+ ///
+ /// FOR TESTING ONLY!
+ /// Gets or sets a value in a row+coulumn of the 8x8 block
+ ///
+ /// The x position index in the row
+ /// The column index
+ /// The value
+ public T this[int x, int y]
+ {
+ get => this[(y * 8) + x];
+ set => this[(y * 8) + x] = value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs
index 02bd451b94..3c1d666850 100644
--- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs
+++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs
@@ -8,6 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
///
/// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace.
/// Methods to build the tables are based on libjpeg implementation.
+ /// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)!
///
internal unsafe struct RgbToYCbCrTables
{
@@ -91,6 +92,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
}
///
+ /// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)!
/// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values.
///
/// The The luminance block.
@@ -102,7 +104,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
/// The green value.
/// The blue value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void Allocate(ref float* yBlockRaw, ref float* cbBlockRaw, ref float* crBlockRaw, ref RgbToYCbCrTables* tables, int index, int r, int g, int b)
+ public static void Rgb2YCbCr(float* yBlockRaw, float* cbBlockRaw, float* crBlockRaw, ref RgbToYCbCrTables* tables, int index, int r, int g, int b)
{
// float y = (0.299F * r) + (0.587F * g) + (0.114F * b);
yBlockRaw[index] = (tables->YRTable[r] + tables->YGTable[g] + tables->YBTable[b]) >> ScaleBits;
diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs
index 4a9ddf3536..b03b0b5341 100644
--- a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs
@@ -331,7 +331,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
int index = j8 + i;
- RgbToYCbCrTables.Allocate(ref yBlockRaw, ref cbBlockRaw, ref crBlockRaw, ref tables, index, r, g, b);
+ RgbToYCbCrTables.Rgb2YCbCr(yBlockRaw, cbBlockRaw, crBlockRaw, ref tables, index, r, g, b);
dataIdx += 3;
}
diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OrigJpegUtils.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OrigJpegUtils.cs
index 01ed5063ba..42a7d56e3b 100644
--- a/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OrigJpegUtils.cs
+++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OrigJpegUtils.cs
@@ -8,7 +8,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils
{
///
- /// Jpeg specific utilities and extension methods
+ /// Jpeg specific utilities and extension methods
///
internal static class OrigJpegUtils
{
diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj
index b812b0b227..8b89499ea7 100644
--- a/src/ImageSharp/ImageSharp.csproj
+++ b/src/ImageSharp/ImageSharp.csproj
@@ -62,6 +62,10 @@
TextTemplatingFileGenerator
Block8x8F.Generated.cs
+
+ TextTemplatingFileGenerator
+ GenericBlock8x8.Generated.cs
+
TextTemplatingFileGenerator
Block8x8F.Generated.cs
@@ -92,6 +96,11 @@
True
Block8x8F.Generated.tt
+
+ True
+ True
+ GenericBlock8x8.Generated.tt
+
True
True
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs
new file mode 100644
index 0000000000..193e26fcbd
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs
@@ -0,0 +1,126 @@
+// ReSharper disable InconsistentNaming
+namespace SixLabors.ImageSharp.Tests.Formats.Jpg
+{
+ using System;
+ using System.Numerics;
+
+ using SixLabors.ImageSharp.Formats.Jpeg.Common;
+ using SixLabors.ImageSharp.Helpers;
+ using SixLabors.ImageSharp.PixelFormats;
+ using SixLabors.Primitives;
+
+ using Xunit;
+
+ public class GenericBlock8x8Tests
+ {
+ public static Image CreateTestImage()
+ where TPixel : struct, IPixel
+ {
+ var image = new Image(10, 10);
+ using (PixelAccessor pixels = image.Lock())
+ {
+ for (int i = 0; i < 10; i++)
+ {
+ for (int j = 0; j < 10; j++)
+ {
+ var rgba = new Rgba32((byte)(i+1), (byte)(j+1), (byte)200, (byte)255);
+ var color = default(TPixel);
+ color.PackFromRgba32(rgba);
+
+ pixels[i, j] = color;
+ }
+ }
+ }
+
+ return image;
+ }
+
+ [Theory]
+ [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32 /* | PixelTypes.Rgba32 | PixelTypes.Argb32*/)]
+ public void LoadAndStretchCorners_FromOrigo(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image s = provider.GetImage())
+ {
+ var d = default(GenericBlock8x8);
+ d.LoadAndStretchEdges(s.Frames.RootFrame, 0, 0);
+
+ TPixel a = s.Frames.RootFrame[0, 0];
+ TPixel b = d[0, 0];
+
+ Assert.Equal(s[0, 0], d[0, 0]);
+ Assert.Equal(s[1, 0], d[1, 0]);
+ Assert.Equal(s[7, 0], d[7, 0]);
+ Assert.Equal(s[0, 1], d[0, 1]);
+ Assert.Equal(s[1, 1], d[1, 1]);
+ Assert.Equal(s[7, 0], d[7, 0]);
+ Assert.Equal(s[0, 7], d[0, 7]);
+ Assert.Equal(s[7, 7], d[7, 7]);
+ }
+ }
+
+ [Theory]
+ [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32)]
+ public unsafe void LoadAndStretchCorners_WithOffset(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image s = provider.GetImage())
+ {
+ var d = default(GenericBlock8x8);
+ d.LoadAndStretchEdges(s.Frames.RootFrame, 6, 7);
+
+ Assert.Equal(s[6, 7], d[0, 0]);
+ Assert.Equal(s[6, 8], d[0, 1]);
+ Assert.Equal(s[7, 8], d[1, 1]);
+
+ Assert.Equal(s[6, 9], d[0, 2]);
+ Assert.Equal(s[6, 9], d[0, 3]);
+ Assert.Equal(s[6, 9], d[0, 7]);
+
+ Assert.Equal(s[7, 9], d[1, 2]);
+ Assert.Equal(s[7, 9], d[1, 3]);
+ Assert.Equal(s[7, 9], d[1, 7]);
+
+ Assert.Equal(s[9, 9], d[3, 2]);
+ Assert.Equal(s[9, 9], d[3, 3]);
+ Assert.Equal(s[9, 9], d[3, 7]);
+
+ Assert.Equal(s[9, 7], d[3, 0]);
+ Assert.Equal(s[9, 7], d[4, 0]);
+ Assert.Equal(s[9, 7], d[7, 0]);
+
+ Assert.Equal(s[9, 9], d[3, 2]);
+ Assert.Equal(s[9, 9], d[4, 2]);
+ Assert.Equal(s[9, 9], d[7, 2]);
+
+ Assert.Equal(s[9, 9], d[4, 3]);
+ Assert.Equal(s[9, 9], d[7, 7]);
+ }
+ }
+
+ [Fact]
+ public void Indexer()
+ {
+ var block = default(GenericBlock8x8);
+ Span span = block.AsSpanUnsafe();
+ Assert.Equal(64, span.Length);
+
+ for (int i = 0; i < 64; i++)
+ {
+ span[i] = new Rgb24((byte)i, (byte)(2 * i), (byte)(3 * i));
+ }
+
+ Rgb24 expected00 = new Rgb24(0, 0, 0);
+ Rgb24 expected07 = new Rgb24(7, 14, 21);
+ Rgb24 expected11 = new Rgb24(9, 18, 27);
+ Rgb24 expected77 = new Rgb24(63, 126, 189);
+ Rgb24 expected67 = new Rgb24(62, 124, 186);
+
+ Assert.Equal(expected00, block[0, 0]);
+ Assert.Equal(expected07, block[7, 0]);
+ Assert.Equal(expected11, block[1, 1]);
+ Assert.Equal(expected67, block[6, 7]);
+ Assert.Equal(expected77, block[7, 7]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs
index 887e9d7e95..6fe6a9bfde 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs
@@ -1,18 +1,14 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-
+using System.Numerics;
+using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils;
+using SixLabors.ImageSharp.PixelFormats;
+using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
- using System.Numerics;
-
- using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils;
- using SixLabors.ImageSharp.PixelFormats;
-
- using Xunit;
-
public class JpegUtilsTests
{
public static Image CreateTestImage()